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 GLuint fbo = cmd.args.bindFramebuffer.fbo;
3317 if (!fbo)
3318 fbo = ctx->defaultFramebufferObject();
3319 f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: fbo);
3320 if (fbo) {
3321 const int colorAttCount = cmd.args.bindFramebuffer.colorAttCount;
3322 bufs.append(t: colorAttCount > 0 ? GL_COLOR_ATTACHMENT0 : GL_NONE);
3323 if (caps.maxDrawBuffers > 1) {
3324 for (int i = 1; i < colorAttCount; ++i)
3325 bufs.append(GL_COLOR_ATTACHMENT0 + uint(i));
3326 }
3327 } else {
3328 if (cmd.args.bindFramebuffer.stereo && cmd.args.bindFramebuffer.stereoTarget == QRhiSwapChain::RightBuffer)
3329 bufs.append(GL_BACK_RIGHT);
3330 else
3331 bufs.append(t: caps.gles ? GL_BACK : GL_BACK_LEFT);
3332 }
3333 if (caps.hasDrawBuffersFunc)
3334 f->glDrawBuffers(n: bufs.count(), bufs: bufs.constData());
3335 if (caps.srgbWriteControl) {
3336 if (cmd.args.bindFramebuffer.srgb)
3337 f->glEnable(GL_FRAMEBUFFER_SRGB);
3338 else
3339 f->glDisable(GL_FRAMEBUFFER_SRGB);
3340 }
3341 }
3342 break;
3343 case QGles2CommandBuffer::Command::Clear:
3344 f->glDisable(GL_SCISSOR_TEST);
3345 if (cmd.args.clear.mask & GL_COLOR_BUFFER_BIT) {
3346 f->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
3347 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]);
3348 }
3349 if (cmd.args.clear.mask & GL_DEPTH_BUFFER_BIT) {
3350 f->glDepthMask(GL_TRUE);
3351 f->glClearDepthf(depth: cmd.args.clear.d);
3352 }
3353 if (cmd.args.clear.mask & GL_STENCIL_BUFFER_BIT) {
3354 f->glStencilMask(mask: 0xFF);
3355 f->glClearStencil(s: GLint(cmd.args.clear.s));
3356 }
3357 f->glClear(mask: cmd.args.clear.mask);
3358 cbD->graphicsPassState.reset(); // altered depth/color write, invalidate in order to avoid confusing the state tracking
3359 break;
3360 case QGles2CommandBuffer::Command::BufferSubData:
3361 bindVertexIndexBufferWithStateReset(state: &state, f, target: cmd.args.bufferSubData.target, buffer: cmd.args.bufferSubData.buffer);
3362 f->glBufferSubData(target: cmd.args.bufferSubData.target, offset: cmd.args.bufferSubData.offset, size: cmd.args.bufferSubData.size,
3363 data: cmd.args.bufferSubData.data);
3364 break;
3365 case QGles2CommandBuffer::Command::GetBufferSubData:
3366 {
3367 QRhiReadbackResult *result = cmd.args.getBufferSubData.result;
3368 bindVertexIndexBufferWithStateReset(state: &state, f, target: cmd.args.getBufferSubData.target, buffer: cmd.args.getBufferSubData.buffer);
3369 if (caps.gles) {
3370 if (caps.properMapBuffer) {
3371 void *p = f->glMapBufferRange(target: cmd.args.getBufferSubData.target,
3372 offset: cmd.args.getBufferSubData.offset,
3373 length: cmd.args.getBufferSubData.size,
3374 GL_MAP_READ_BIT);
3375 if (p) {
3376 result->data.resize(size: cmd.args.getBufferSubData.size);
3377 memcpy(dest: result->data.data(), src: p, n: size_t(cmd.args.getBufferSubData.size));
3378 f->glUnmapBuffer(target: cmd.args.getBufferSubData.target);
3379 }
3380 }
3381 } else {
3382 result->data.resize(size: cmd.args.getBufferSubData.size);
3383 f->glGetBufferSubData(target: cmd.args.getBufferSubData.target,
3384 offset: cmd.args.getBufferSubData.offset,
3385 size: cmd.args.getBufferSubData.size,
3386 data: result->data.data());
3387 }
3388 if (result->completed)
3389 result->completed();
3390 }
3391 break;
3392 case QGles2CommandBuffer::Command::CopyTex:
3393 {
3394 GLuint fbo;
3395 f->glGenFramebuffers(n: 1, framebuffers: &fbo);
3396 f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: fbo);
3397 if (cmd.args.copyTex.srcTarget == GL_TEXTURE_3D
3398 || cmd.args.copyTex.srcTarget == GL_TEXTURE_2D_ARRAY
3399 || cmd.args.copyTex.srcTarget == GL_TEXTURE_1D_ARRAY) {
3400 f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture: cmd.args.copyTex.srcTexture,
3401 level: cmd.args.copyTex.srcLevel, layer: cmd.args.copyTex.srcZ);
3402 } else if (cmd.args.copyTex.srcTarget == GL_TEXTURE_1D) {
3403 glFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
3404 cmd.args.copyTex.srcTarget, cmd.args.copyTex.srcTexture,
3405 cmd.args.copyTex.srcLevel);
3406 } else {
3407 f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
3408 textarget: cmd.args.copyTex.srcFaceTarget, texture: cmd.args.copyTex.srcTexture, level: cmd.args.copyTex.srcLevel);
3409 }
3410 f->glBindTexture(target: cmd.args.copyTex.dstTarget, texture: cmd.args.copyTex.dstTexture);
3411 if (cmd.args.copyTex.dstTarget == GL_TEXTURE_3D || cmd.args.copyTex.dstTarget == GL_TEXTURE_2D_ARRAY) {
3412 f->glCopyTexSubImage3D(target: cmd.args.copyTex.dstTarget, level: cmd.args.copyTex.dstLevel,
3413 xoffset: cmd.args.copyTex.dstX, yoffset: cmd.args.copyTex.dstY, zoffset: cmd.args.copyTex.dstZ,
3414 x: cmd.args.copyTex.srcX, y: cmd.args.copyTex.srcY,
3415 width: cmd.args.copyTex.w, height: cmd.args.copyTex.h);
3416 } else if (cmd.args.copyTex.dstTarget == GL_TEXTURE_1D) {
3417 glCopyTexSubImage1D(cmd.args.copyTex.dstTarget, cmd.args.copyTex.dstLevel,
3418 cmd.args.copyTex.dstX, cmd.args.copyTex.srcX,
3419 cmd.args.copyTex.srcY, cmd.args.copyTex.w);
3420 } else {
3421 f->glCopyTexSubImage2D(target: cmd.args.copyTex.dstFaceTarget, level: cmd.args.copyTex.dstLevel,
3422 xoffset: cmd.args.copyTex.dstX, yoffset: cmd.args.copyTex.dstY,
3423 x: cmd.args.copyTex.srcX, y: cmd.args.copyTex.srcY,
3424 width: cmd.args.copyTex.w, height: cmd.args.copyTex.h);
3425 }
3426 f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: ctx->defaultFramebufferObject());
3427 f->glDeleteFramebuffers(n: 1, framebuffers: &fbo);
3428 }
3429 break;
3430 case QGles2CommandBuffer::Command::ReadPixels:
3431 {
3432 QRhiReadbackResult *result = cmd.args.readPixels.result;
3433 GLuint tex = cmd.args.readPixels.texture;
3434 GLuint fbo = 0;
3435 int mipLevel = 0;
3436 if (tex) {
3437 result->pixelSize = QSize(cmd.args.readPixels.w, cmd.args.readPixels.h);
3438 result->format = cmd.args.readPixels.format;
3439 mipLevel = cmd.args.readPixels.level;
3440 if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) {
3441 f->glGenFramebuffers(n: 1, framebuffers: &fbo);
3442 f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: fbo);
3443 if (cmd.args.readPixels.slice3D >= 0) {
3444 f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
3445 texture: tex, level: mipLevel, layer: cmd.args.readPixels.slice3D);
3446 } else if (cmd.args.readPixels.readTarget == GL_TEXTURE_1D) {
3447 glFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
3448 cmd.args.readPixels.readTarget, tex, mipLevel);
3449 } else {
3450 f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
3451 textarget: cmd.args.readPixels.readTarget, texture: tex, level: mipLevel);
3452 }
3453 }
3454 } else {
3455 result->pixelSize = currentSwapChain->pixelSize;
3456 result->format = QRhiTexture::RGBA8;
3457 // readPixels handles multisample resolving implicitly
3458 }
3459 const int w = result->pixelSize.width();
3460 const int h = result->pixelSize.height();
3461 if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) {
3462 // With GLES, GL_RGBA is the only mandated readback format, so stick with it.
3463 // (and that's why we return false for the ReadBackAnyTextureFormat feature)
3464 if (result->format == QRhiTexture::R8 || result->format == QRhiTexture::RED_OR_ALPHA8) {
3465 result->data.resize(size: w * h);
3466 QByteArray tmpBuf;
3467 tmpBuf.resize(size: w * h * 4);
3468 f->glReadPixels(x: 0, y: 0, width: w, height: h, GL_RGBA, GL_UNSIGNED_BYTE, pixels: tmpBuf.data());
3469 const quint8 *srcBase = reinterpret_cast<const quint8 *>(tmpBuf.constData());
3470 quint8 *dstBase = reinterpret_cast<quint8 *>(result->data.data());
3471 const int componentIndex = isFeatureSupported(feature: QRhi::RedOrAlpha8IsRed) ? 0 : 3;
3472 for (int y = 0; y < h; ++y) {
3473 const quint8 *src = srcBase + y * w * 4;
3474 quint8 *dst = dstBase + y * w;
3475 int count = w;
3476 while (count-- > 0) {
3477 *dst++ = src[componentIndex];
3478 src += 4;
3479 }
3480 }
3481 } else {
3482 switch (result->format) {
3483 // For floating point formats try it because this can be
3484 // relevant for some use cases; if it works, then fine, if
3485 // not, there's nothing we can do.
3486 case QRhiTexture::RGBA16F:
3487 result->data.resize(size: w * h * 8);
3488 f->glReadPixels(x: 0, y: 0, width: w, height: h, GL_RGBA, GL_HALF_FLOAT, pixels: result->data.data());
3489 break;
3490 case QRhiTexture::R16F:
3491 result->data.resize(size: w * h * 2);
3492 f->glReadPixels(x: 0, y: 0, width: w, height: h, GL_RED, GL_HALF_FLOAT, pixels: result->data.data());
3493 break;
3494 case QRhiTexture::R32F:
3495 result->data.resize(size: w * h * 4);
3496 f->glReadPixels(x: 0, y: 0, width: w, height: h, GL_RED, GL_FLOAT, pixels: result->data.data());
3497 break;
3498 case QRhiTexture::RGBA32F:
3499 result->data.resize(size: w * h * 16);
3500 f->glReadPixels(x: 0, y: 0, width: w, height: h, GL_RGBA, GL_FLOAT, pixels: result->data.data());
3501 break;
3502 case QRhiTexture::RGB10A2:
3503 result->data.resize(size: w * h * 4);
3504 f->glReadPixels(x: 0, y: 0, width: w, height: h, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, pixels: result->data.data());
3505 break;
3506 default:
3507 result->data.resize(size: w * h * 4);
3508 f->glReadPixels(x: 0, y: 0, width: w, height: h, GL_RGBA, GL_UNSIGNED_BYTE, pixels: result->data.data());
3509 break;
3510 }
3511 }
3512 } else {
3513 result->data.resize(size: w * h * 4);
3514 result->data.fill(c: '\0');
3515 }
3516 if (fbo) {
3517 f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: ctx->defaultFramebufferObject());
3518 f->glDeleteFramebuffers(n: 1, framebuffers: &fbo);
3519 }
3520 if (result->completed)
3521 result->completed();
3522 }
3523 break;
3524 case QGles2CommandBuffer::Command::SubImage:
3525 f->glBindTexture(target: cmd.args.subImage.target, texture: cmd.args.subImage.texture);
3526 if (cmd.args.subImage.rowStartAlign != 4)
3527 f->glPixelStorei(GL_UNPACK_ALIGNMENT, param: cmd.args.subImage.rowStartAlign);
3528 if (cmd.args.subImage.rowLength != 0)
3529 f->glPixelStorei(GL_UNPACK_ROW_LENGTH, param: cmd.args.subImage.rowLength);
3530 if (cmd.args.subImage.target == GL_TEXTURE_3D || cmd.args.subImage.target == GL_TEXTURE_2D_ARRAY) {
3531 f->glTexSubImage3D(target: cmd.args.subImage.target, level: cmd.args.subImage.level,
3532 xoffset: cmd.args.subImage.dx, yoffset: cmd.args.subImage.dy, zoffset: cmd.args.subImage.dz,
3533 width: cmd.args.subImage.w, height: cmd.args.subImage.h, depth: 1,
3534 format: cmd.args.subImage.glformat, type: cmd.args.subImage.gltype,
3535 pixels: cmd.args.subImage.data);
3536 } else if (cmd.args.subImage.target == GL_TEXTURE_1D) {
3537 glTexSubImage1D(cmd.args.subImage.target, cmd.args.subImage.level,
3538 cmd.args.subImage.dx, cmd.args.subImage.w,
3539 cmd.args.subImage.glformat, cmd.args.subImage.gltype,
3540 cmd.args.subImage.data);
3541 } else {
3542 f->glTexSubImage2D(target: cmd.args.subImage.faceTarget, level: cmd.args.subImage.level,
3543 xoffset: cmd.args.subImage.dx, yoffset: cmd.args.subImage.dy,
3544 width: cmd.args.subImage.w, height: cmd.args.subImage.h,
3545 format: cmd.args.subImage.glformat, type: cmd.args.subImage.gltype,
3546 pixels: cmd.args.subImage.data);
3547 }
3548 if (cmd.args.subImage.rowStartAlign != 4)
3549 f->glPixelStorei(GL_UNPACK_ALIGNMENT, param: 4);
3550 if (cmd.args.subImage.rowLength != 0)
3551 f->glPixelStorei(GL_UNPACK_ROW_LENGTH, param: 0);
3552 break;
3553 case QGles2CommandBuffer::Command::CompressedImage:
3554 f->glBindTexture(target: cmd.args.compressedImage.target, texture: cmd.args.compressedImage.texture);
3555 if (cmd.args.compressedImage.target == GL_TEXTURE_3D || cmd.args.compressedImage.target == GL_TEXTURE_2D_ARRAY) {
3556 f->glCompressedTexImage3D(target: cmd.args.compressedImage.target, level: cmd.args.compressedImage.level,
3557 internalformat: cmd.args.compressedImage.glintformat,
3558 width: cmd.args.compressedImage.w, height: cmd.args.compressedImage.h, depth: cmd.args.compressedImage.depth,
3559 border: 0, imageSize: cmd.args.compressedImage.size, data: cmd.args.compressedImage.data);
3560 } else if (cmd.args.compressedImage.target == GL_TEXTURE_1D) {
3561 glCompressedTexImage1D(
3562 cmd.args.compressedImage.target, cmd.args.compressedImage.level,
3563 cmd.args.compressedImage.glintformat, cmd.args.compressedImage.w, 0,
3564 cmd.args.compressedImage.size, cmd.args.compressedImage.data);
3565 } else {
3566 f->glCompressedTexImage2D(target: cmd.args.compressedImage.faceTarget, level: cmd.args.compressedImage.level,
3567 internalformat: cmd.args.compressedImage.glintformat,
3568 width: cmd.args.compressedImage.w, height: cmd.args.compressedImage.h,
3569 border: 0, imageSize: cmd.args.compressedImage.size, data: cmd.args.compressedImage.data);
3570 }
3571 break;
3572 case QGles2CommandBuffer::Command::CompressedSubImage:
3573 f->glBindTexture(target: cmd.args.compressedSubImage.target, texture: cmd.args.compressedSubImage.texture);
3574 if (cmd.args.compressedSubImage.target == GL_TEXTURE_3D || cmd.args.compressedSubImage.target == GL_TEXTURE_2D_ARRAY) {
3575 f->glCompressedTexSubImage3D(target: cmd.args.compressedSubImage.target, level: cmd.args.compressedSubImage.level,
3576 xoffset: cmd.args.compressedSubImage.dx, yoffset: cmd.args.compressedSubImage.dy, zoffset: cmd.args.compressedSubImage.dz,
3577 width: cmd.args.compressedSubImage.w, height: cmd.args.compressedSubImage.h, depth: 1,
3578 format: cmd.args.compressedSubImage.glintformat,
3579 imageSize: cmd.args.compressedSubImage.size, data: cmd.args.compressedSubImage.data);
3580 } else if (cmd.args.compressedImage.target == GL_TEXTURE_1D) {
3581 glCompressedTexSubImage1D(
3582 cmd.args.compressedSubImage.target, cmd.args.compressedSubImage.level,
3583 cmd.args.compressedSubImage.dx, cmd.args.compressedSubImage.w,
3584 cmd.args.compressedSubImage.glintformat, cmd.args.compressedSubImage.size,
3585 cmd.args.compressedSubImage.data);
3586 } else {
3587 f->glCompressedTexSubImage2D(target: cmd.args.compressedSubImage.faceTarget, level: cmd.args.compressedSubImage.level,
3588 xoffset: cmd.args.compressedSubImage.dx, yoffset: cmd.args.compressedSubImage.dy,
3589 width: cmd.args.compressedSubImage.w, height: cmd.args.compressedSubImage.h,
3590 format: cmd.args.compressedSubImage.glintformat,
3591 imageSize: cmd.args.compressedSubImage.size, data: cmd.args.compressedSubImage.data);
3592 }
3593 break;
3594 case QGles2CommandBuffer::Command::BlitFromRenderbuffer:
3595 {
3596 // Altering the scissor state, so reset the stored state, although
3597 // not strictly required as long as blit is done in endPass() only.
3598 cbD->graphicsPassState.reset();
3599 f->glDisable(GL_SCISSOR_TEST);
3600 GLuint fbo[2];
3601 f->glGenFramebuffers(n: 2, framebuffers: fbo);
3602 f->glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer: fbo[0]);
3603 const bool ds = cmd.args.blitFromRenderbuffer.isDepthStencil;
3604 if (ds) {
3605 f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
3606 GL_RENDERBUFFER, renderbuffer: cmd.args.blitFromRenderbuffer.renderbuffer);
3607 f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
3608 GL_RENDERBUFFER, renderbuffer: cmd.args.blitFromRenderbuffer.renderbuffer);
3609 } else {
3610 f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
3611 GL_RENDERBUFFER, renderbuffer: cmd.args.blitFromRenderbuffer.renderbuffer);
3612 }
3613 f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer: fbo[1]);
3614 if (cmd.args.blitFromRenderbuffer.target == GL_TEXTURE_3D || cmd.args.blitFromRenderbuffer.target == GL_TEXTURE_2D_ARRAY) {
3615 if (ds) {
3616 f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
3617 texture: cmd.args.blitFromRenderbuffer.dstTexture,
3618 level: cmd.args.blitFromRenderbuffer.dstLevel,
3619 layer: cmd.args.blitFromRenderbuffer.dstLayer);
3620 f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
3621 texture: cmd.args.blitFromRenderbuffer.dstTexture,
3622 level: cmd.args.blitFromRenderbuffer.dstLevel,
3623 layer: cmd.args.blitFromRenderbuffer.dstLayer);
3624 } else {
3625 f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
3626 texture: cmd.args.blitFromRenderbuffer.dstTexture,
3627 level: cmd.args.blitFromRenderbuffer.dstLevel,
3628 layer: cmd.args.blitFromRenderbuffer.dstLayer);
3629 }
3630 } else {
3631 if (ds) {
3632 f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, textarget: cmd.args.blitFromRenderbuffer.target,
3633 texture: cmd.args.blitFromRenderbuffer.dstTexture, level: cmd.args.blitFromRenderbuffer.dstLevel);
3634 f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, textarget: cmd.args.blitFromRenderbuffer.target,
3635 texture: cmd.args.blitFromRenderbuffer.dstTexture, level: cmd.args.blitFromRenderbuffer.dstLevel);
3636 } else {
3637 f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textarget: cmd.args.blitFromRenderbuffer.target,
3638 texture: cmd.args.blitFromRenderbuffer.dstTexture, level: cmd.args.blitFromRenderbuffer.dstLevel);
3639 }
3640 }
3641 f->glBlitFramebuffer(srcX0: 0, srcY0: 0, srcX1: cmd.args.blitFromRenderbuffer.w, srcY1: cmd.args.blitFromRenderbuffer.h,
3642 dstX0: 0, dstY0: 0, dstX1: cmd.args.blitFromRenderbuffer.w, dstY1: cmd.args.blitFromRenderbuffer.h,
3643 mask: ds ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT,
3644 GL_NEAREST); // Qt 5 used Nearest when resolving samples, stick to that
3645 f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: ctx->defaultFramebufferObject());
3646 f->glDeleteFramebuffers(n: 2, framebuffers: fbo);
3647 }
3648 break;
3649 case QGles2CommandBuffer::Command::BlitFromTexture:
3650 {
3651 // Altering the scissor state, so reset the stored state, although
3652 // not strictly required as long as blit is done in endPass() only.
3653 cbD->graphicsPassState.reset();
3654 f->glDisable(GL_SCISSOR_TEST);
3655 GLuint fbo[2];
3656 f->glGenFramebuffers(n: 2, framebuffers: fbo);
3657 f->glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer: fbo[0]);
3658 const bool ds = cmd.args.blitFromTexture.isDepthStencil;
3659 if (cmd.args.blitFromTexture.srcTarget == GL_TEXTURE_2D_MULTISAMPLE_ARRAY) {
3660 if (ds) {
3661 f->glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
3662 texture: cmd.args.blitFromTexture.srcTexture,
3663 level: cmd.args.blitFromTexture.srcLevel,
3664 layer: cmd.args.blitFromTexture.srcLayer);
3665 f->glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
3666 texture: cmd.args.blitFromTexture.srcTexture,
3667 level: cmd.args.blitFromTexture.srcLevel,
3668 layer: cmd.args.blitFromTexture.srcLayer);
3669 } else {
3670 f->glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
3671 texture: cmd.args.blitFromTexture.srcTexture,
3672 level: cmd.args.blitFromTexture.srcLevel,
3673 layer: cmd.args.blitFromTexture.srcLayer);
3674 }
3675 } else {
3676 if (ds) {
3677 f->glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, textarget: cmd.args.blitFromTexture.srcTarget,
3678 texture: cmd.args.blitFromTexture.srcTexture, level: cmd.args.blitFromTexture.srcLevel);
3679 f->glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, textarget: cmd.args.blitFromTexture.srcTarget,
3680 texture: cmd.args.blitFromTexture.srcTexture, level: cmd.args.blitFromTexture.srcLevel);
3681 } else {
3682 f->glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textarget: cmd.args.blitFromTexture.srcTarget,
3683 texture: cmd.args.blitFromTexture.srcTexture, level: cmd.args.blitFromTexture.srcLevel);
3684 }
3685 }
3686 f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer: fbo[1]);
3687 if (cmd.args.blitFromTexture.dstTarget == GL_TEXTURE_3D || cmd.args.blitFromTexture.dstTarget == GL_TEXTURE_2D_ARRAY) {
3688 if (ds) {
3689 f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
3690 texture: cmd.args.blitFromTexture.dstTexture,
3691 level: cmd.args.blitFromTexture.dstLevel,
3692 layer: cmd.args.blitFromTexture.dstLayer);
3693 f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
3694 texture: cmd.args.blitFromTexture.dstTexture,
3695 level: cmd.args.blitFromTexture.dstLevel,
3696 layer: cmd.args.blitFromTexture.dstLayer);
3697 } else {
3698 f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
3699 texture: cmd.args.blitFromTexture.dstTexture,
3700 level: cmd.args.blitFromTexture.dstLevel,
3701 layer: cmd.args.blitFromTexture.dstLayer);
3702 }
3703 } else {
3704 if (ds) {
3705 f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, textarget: cmd.args.blitFromTexture.dstTarget,
3706 texture: cmd.args.blitFromTexture.dstTexture, level: cmd.args.blitFromTexture.dstLevel);
3707 f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, textarget: cmd.args.blitFromTexture.dstTarget,
3708 texture: cmd.args.blitFromTexture.dstTexture, level: cmd.args.blitFromTexture.dstLevel);
3709 } else {
3710 f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textarget: cmd.args.blitFromTexture.dstTarget,
3711 texture: cmd.args.blitFromTexture.dstTexture, level: cmd.args.blitFromTexture.dstLevel);
3712 }
3713 }
3714 f->glBlitFramebuffer(srcX0: 0, srcY0: 0, srcX1: cmd.args.blitFromTexture.w, srcY1: cmd.args.blitFromTexture.h,
3715 dstX0: 0, dstY0: 0, dstX1: cmd.args.blitFromTexture.w, dstY1: cmd.args.blitFromTexture.h,
3716 mask: ds ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT,
3717 GL_NEAREST); // Qt 5 used Nearest when resolving samples, stick to that
3718 f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: ctx->defaultFramebufferObject());
3719 f->glDeleteFramebuffers(n: 2, framebuffers: fbo);
3720 }
3721 break;
3722 case QGles2CommandBuffer::Command::GenMip:
3723 f->glBindTexture(target: cmd.args.genMip.target, texture: cmd.args.genMip.texture);
3724 f->glGenerateMipmap(target: cmd.args.genMip.target);
3725 break;
3726 case QGles2CommandBuffer::Command::BindComputePipeline:
3727 {
3728 QGles2ComputePipeline *psD = QRHI_RES(QGles2ComputePipeline, cmd.args.bindComputePipeline.ps);
3729 f->glUseProgram(program: psD->program);
3730 }
3731 break;
3732 case QGles2CommandBuffer::Command::Dispatch:
3733 f->glDispatchCompute(num_groups_x: cmd.args.dispatch.x, num_groups_y: cmd.args.dispatch.y, num_groups_z: cmd.args.dispatch.z);
3734 break;
3735 case QGles2CommandBuffer::Command::BarriersForPass:
3736 {
3737 if (!caps.compute)
3738 break;
3739 GLbitfield barriers = 0;
3740 QRhiPassResourceTracker &tracker(cbD->passResTrackers[cmd.args.barriersForPass.trackerIndex]);
3741 // we only care about after-write, not any other accesses, and
3742 // cannot tell if something was written in a shader several passes
3743 // ago: now the previously written resource may be used with an
3744 // access that was not in the previous passes, result in a missing
3745 // barrier in theory. Hence setting all barrier bits whenever
3746 // something previously written is used for the first time in a
3747 // subsequent pass.
3748 for (auto it = tracker.cbeginBuffers(), itEnd = tracker.cendBuffers(); it != itEnd; ++it) {
3749 QGles2Buffer::Access accessBeforePass = QGles2Buffer::Access(it->stateAtPassBegin.access);
3750 if (bufferAccessIsWrite(access: accessBeforePass))
3751 barriers |= barriersForBuffer();
3752 }
3753 for (auto it = tracker.cbeginTextures(), itEnd = tracker.cendTextures(); it != itEnd; ++it) {
3754 QGles2Texture::Access accessBeforePass = QGles2Texture::Access(it->stateAtPassBegin.access);
3755 if (textureAccessIsWrite(access: accessBeforePass))
3756 barriers |= barriersForTexture();
3757 }
3758 if (barriers)
3759 f->glMemoryBarrier(barriers);
3760 }
3761 break;
3762 case QGles2CommandBuffer::Command::Barrier:
3763 if (caps.compute)
3764 f->glMemoryBarrier(barriers: cmd.args.barrier.barriers);
3765 break;
3766 case QGles2CommandBuffer::Command::InvalidateFramebuffer:
3767 if (caps.gles && caps.ctxMajor >= 3) {
3768 f->glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER,
3769 numAttachments: cmd.args.invalidateFramebuffer.attCount,
3770 attachments: cmd.args.invalidateFramebuffer.att);
3771 }
3772 break;
3773 default:
3774 break;
3775 }
3776 }
3777 if (state.instancedAttributesUsed) {
3778 for (int i = 0; i < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT; ++i) {
3779 if (state.nonzeroAttribDivisor[i])
3780 f->glVertexAttribDivisor(index: GLuint(i), divisor: 0);
3781 }
3782 for (int i = CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT; i <= state.maxUntrackedInstancedAttribute; ++i)
3783 f->glVertexAttribDivisor(index: GLuint(i), divisor: 0);
3784 }
3785}
3786
3787void QRhiGles2::executeBindGraphicsPipeline(QGles2CommandBuffer *cbD, QGles2GraphicsPipeline *psD)
3788{
3789 QGles2CommandBuffer::GraphicsPassState &state(cbD->graphicsPassState);
3790 const bool forceUpdate = !state.valid;
3791 state.valid = true;
3792
3793 const bool scissor = psD->m_flags.testFlag(flag: QRhiGraphicsPipeline::UsesScissor);
3794 if (forceUpdate || scissor != state.scissor) {
3795 state.scissor = scissor;
3796 if (scissor)
3797 f->glEnable(GL_SCISSOR_TEST);
3798 else
3799 f->glDisable(GL_SCISSOR_TEST);
3800 }
3801
3802 const bool cullFace = psD->m_cullMode != QRhiGraphicsPipeline::None;
3803 const GLenum cullMode = cullFace ? toGlCullMode(c: psD->m_cullMode) : GL_NONE;
3804 if (forceUpdate || cullFace != state.cullFace || cullMode != state.cullMode) {
3805 state.cullFace = cullFace;
3806 state.cullMode = cullMode;
3807 if (cullFace) {
3808 f->glEnable(GL_CULL_FACE);
3809 f->glCullFace(mode: cullMode);
3810 } else {
3811 f->glDisable(GL_CULL_FACE);
3812 }
3813 }
3814
3815 const GLenum frontFace = toGlFrontFace(f: psD->m_frontFace);
3816 if (forceUpdate || frontFace != state.frontFace) {
3817 state.frontFace = frontFace;
3818 f->glFrontFace(mode: frontFace);
3819 }
3820
3821 const GLenum polygonMode = toGlPolygonMode(mode: psD->m_polygonMode);
3822 if (glPolygonMode && (forceUpdate || polygonMode != state.polygonMode)) {
3823 state.polygonMode = polygonMode;
3824 glPolygonMode(GL_FRONT_AND_BACK, polygonMode);
3825 }
3826
3827 if (!psD->m_targetBlends.isEmpty()) {
3828 // We do not have MRT support here, meaning all targets use the blend
3829 // params from the first one. This is technically incorrect, even if
3830 // nothing in Qt relies on it. However, considering that
3831 // glBlendFuncSeparatei is only available in GL 4.0+ and GLES 3.2+, we
3832 // may just live with this for now because no point in bothering if it
3833 // won't be usable on many GLES (3.1 or 3.0) systems.
3834 const QRhiGraphicsPipeline::TargetBlend &targetBlend(psD->m_targetBlends.first());
3835
3836 const QGles2CommandBuffer::GraphicsPassState::ColorMask colorMask = {
3837 .r: targetBlend.colorWrite.testFlag(flag: QRhiGraphicsPipeline::R),
3838 .g: targetBlend.colorWrite.testFlag(flag: QRhiGraphicsPipeline::G),
3839 .b: targetBlend.colorWrite.testFlag(flag: QRhiGraphicsPipeline::B),
3840 .a: targetBlend.colorWrite.testFlag(flag: QRhiGraphicsPipeline::A)
3841 };
3842 if (forceUpdate || colorMask != state.colorMask) {
3843 state.colorMask = colorMask;
3844 f->glColorMask(red: colorMask.r, green: colorMask.g, blue: colorMask.b, alpha: colorMask.a);
3845 }
3846
3847 const bool blendEnabled = targetBlend.enable;
3848 const QGles2CommandBuffer::GraphicsPassState::Blend blend = {
3849 .srcColor: toGlBlendFactor(f: targetBlend.srcColor),
3850 .dstColor: toGlBlendFactor(f: targetBlend.dstColor),
3851 .srcAlpha: toGlBlendFactor(f: targetBlend.srcAlpha),
3852 .dstAlpha: toGlBlendFactor(f: targetBlend.dstAlpha),
3853 .opColor: toGlBlendOp(op: targetBlend.opColor),
3854 .opAlpha: toGlBlendOp(op: targetBlend.opAlpha)
3855 };
3856 if (forceUpdate || blendEnabled != state.blendEnabled || (blendEnabled && blend != state.blend)) {
3857 state.blendEnabled = blendEnabled;
3858 if (blendEnabled) {
3859 state.blend = blend;
3860 f->glEnable(GL_BLEND);
3861 f->glBlendFuncSeparate(srcRGB: blend.srcColor, dstRGB: blend.dstColor, srcAlpha: blend.srcAlpha, dstAlpha: blend.dstAlpha);
3862 f->glBlendEquationSeparate(modeRGB: blend.opColor, modeAlpha: blend.opAlpha);
3863 } else {
3864 f->glDisable(GL_BLEND);
3865 }
3866 }
3867 } else {
3868 const QGles2CommandBuffer::GraphicsPassState::ColorMask colorMask = { .r: true, .g: true, .b: true, .a: true };
3869 if (forceUpdate || colorMask != state.colorMask) {
3870 state.colorMask = colorMask;
3871 f->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
3872 }
3873 const bool blendEnabled = false;
3874 if (forceUpdate || blendEnabled != state.blendEnabled) {
3875 state.blendEnabled = blendEnabled;
3876 f->glDisable(GL_BLEND);
3877 }
3878 }
3879
3880 const bool depthTest = psD->m_depthTest;
3881 if (forceUpdate || depthTest != state.depthTest) {
3882 state.depthTest = depthTest;
3883 if (depthTest)
3884 f->glEnable(GL_DEPTH_TEST);
3885 else
3886 f->glDisable(GL_DEPTH_TEST);
3887 }
3888
3889 const bool depthWrite = psD->m_depthWrite;
3890 if (forceUpdate || depthWrite != state.depthWrite) {
3891 state.depthWrite = depthWrite;
3892 f->glDepthMask(flag: depthWrite);
3893 }
3894
3895 const GLenum depthFunc = toGlCompareOp(op: psD->m_depthOp);
3896 if (forceUpdate || depthFunc != state.depthFunc) {
3897 state.depthFunc = depthFunc;
3898 f->glDepthFunc(func: depthFunc);
3899 }
3900
3901 const bool stencilTest = psD->m_stencilTest;
3902 const GLuint stencilReadMask = psD->m_stencilReadMask;
3903 const GLuint stencilWriteMask = psD->m_stencilWriteMask;
3904 const QGles2CommandBuffer::GraphicsPassState::StencilFace stencilFront = {
3905 .func: toGlCompareOp(op: psD->m_stencilFront.compareOp),
3906 .failOp: toGlStencilOp(op: psD->m_stencilFront.failOp),
3907 .zfailOp: toGlStencilOp(op: psD->m_stencilFront.depthFailOp),
3908 .zpassOp: toGlStencilOp(op: psD->m_stencilFront.passOp)
3909 };
3910 const QGles2CommandBuffer::GraphicsPassState::StencilFace stencilBack = {
3911 .func: toGlCompareOp(op: psD->m_stencilBack.compareOp),
3912 .failOp: toGlStencilOp(op: psD->m_stencilBack.failOp),
3913 .zfailOp: toGlStencilOp(op: psD->m_stencilBack.depthFailOp),
3914 .zpassOp: toGlStencilOp(op: psD->m_stencilBack.passOp)
3915 };
3916 if (forceUpdate || stencilTest != state.stencilTest
3917 || (stencilTest
3918 && (stencilReadMask != state.stencilReadMask || stencilWriteMask != state.stencilWriteMask
3919 || stencilFront != state.stencil[0] || stencilBack != state.stencil[1])))
3920 {
3921 state.stencilTest = stencilTest;
3922 if (stencilTest) {
3923 state.stencilReadMask = stencilReadMask;
3924 state.stencilWriteMask = stencilWriteMask;
3925 state.stencil[0] = stencilFront;
3926 state.stencil[1] = stencilBack;
3927
3928 f->glEnable(GL_STENCIL_TEST);
3929
3930 f->glStencilFuncSeparate(GL_FRONT, func: stencilFront.func, ref: state.dynamic.stencilRef, mask: stencilReadMask);
3931 f->glStencilOpSeparate(GL_FRONT, fail: stencilFront.failOp, zfail: stencilFront.zfailOp, zpass: stencilFront.zpassOp);
3932 f->glStencilMaskSeparate(GL_FRONT, mask: stencilWriteMask);
3933
3934 f->glStencilFuncSeparate(GL_BACK, func: stencilBack.func, ref: state.dynamic.stencilRef, mask: stencilReadMask);
3935 f->glStencilOpSeparate(GL_BACK, fail: stencilBack.failOp, zfail: stencilBack.zfailOp, zpass: stencilBack.zpassOp);
3936 f->glStencilMaskSeparate(GL_BACK, mask: stencilWriteMask);
3937 } else {
3938 f->glDisable(GL_STENCIL_TEST);
3939 }
3940 }
3941
3942 const bool polyOffsetFill = psD->m_depthBias != 0 || !qFuzzyIsNull(f: psD->m_slopeScaledDepthBias);
3943 const float polyOffsetFactor = psD->m_slopeScaledDepthBias;
3944 const float polyOffsetUnits = psD->m_depthBias;
3945 if (forceUpdate || state.polyOffsetFill != polyOffsetFill
3946 || polyOffsetFactor != state.polyOffsetFactor || polyOffsetUnits != state.polyOffsetUnits)
3947 {
3948 state.polyOffsetFill = polyOffsetFill;
3949 state.polyOffsetFactor = polyOffsetFactor;
3950 state.polyOffsetUnits = polyOffsetUnits;
3951 if (polyOffsetFill) {
3952 f->glPolygonOffset(factor: polyOffsetFactor, units: polyOffsetUnits);
3953 f->glEnable(GL_POLYGON_OFFSET_FILL);
3954 } else {
3955 f->glDisable(GL_POLYGON_OFFSET_FILL);
3956 }
3957 }
3958
3959 if (psD->m_topology == QRhiGraphicsPipeline::Lines || psD->m_topology == QRhiGraphicsPipeline::LineStrip) {
3960 const float lineWidth = psD->m_lineWidth;
3961 if (forceUpdate || lineWidth != state.lineWidth) {
3962 state.lineWidth = lineWidth;
3963 f->glLineWidth(width: lineWidth);
3964 }
3965 }
3966
3967 if (psD->m_topology == QRhiGraphicsPipeline::Patches) {
3968 const int cpCount = psD->m_patchControlPointCount;
3969 if (forceUpdate || cpCount != state.cpCount) {
3970 state.cpCount = cpCount;
3971 f->glPatchParameteri(GL_PATCH_VERTICES, value: qMax(a: 1, b: cpCount));
3972 }
3973 }
3974
3975 f->glUseProgram(program: psD->program);
3976}
3977
3978template <typename T>
3979static inline void qrhi_std140_to_packed(T *dst, int vecSize, int elemCount, const void *src)
3980{
3981 const T *p = reinterpret_cast<const T *>(src);
3982 for (int i = 0; i < elemCount; ++i) {
3983 for (int j = 0; j < vecSize; ++j)
3984 dst[vecSize * i + j] = *p++;
3985 p += 4 - vecSize;
3986 }
3987}
3988
3989void QRhiGles2::bindCombinedSampler(QGles2CommandBuffer *cbD, QGles2Texture *texD, QGles2Sampler *samplerD,
3990 void *ps, uint psGeneration, int glslLocation,
3991 int *texUnit, bool *activeTexUnitAltered)
3992{
3993 const bool samplerStateValid = texD->samplerState == samplerD->d;
3994 const bool cachedStateInRange = *texUnit < 16;
3995 bool updateTextureBinding = true;
3996 if (samplerStateValid && cachedStateInRange) {
3997 // If we already encountered the same texture with
3998 // the same pipeline for this texture unit in the
3999 // current pass, then the shader program already
4000 // has the uniform set. As in a 3D scene one model
4001 // often has more than one associated texture map,
4002 // the savings here can become significant,
4003 // depending on the scene.
4004 if (cbD->textureUnitState[*texUnit].ps == ps
4005 && cbD->textureUnitState[*texUnit].psGeneration == psGeneration
4006 && cbD->textureUnitState[*texUnit].texture == texD->texture)
4007 {
4008 updateTextureBinding = false;
4009 }
4010 }
4011 if (updateTextureBinding) {
4012 f->glActiveTexture(GL_TEXTURE0 + uint(*texUnit));
4013 *activeTexUnitAltered = true;
4014 f->glBindTexture(target: texD->target, texture: texD->texture);
4015 f->glUniform1i(location: glslLocation, x: *texUnit);
4016 if (cachedStateInRange) {
4017 cbD->textureUnitState[*texUnit].ps = ps;
4018 cbD->textureUnitState[*texUnit].psGeneration = psGeneration;
4019 cbD->textureUnitState[*texUnit].texture = texD->texture;
4020 }
4021 }
4022 ++(*texUnit);
4023 if (!samplerStateValid) {
4024 f->glTexParameteri(target: texD->target, GL_TEXTURE_MIN_FILTER, param: GLint(samplerD->d.glminfilter));
4025 f->glTexParameteri(target: texD->target, GL_TEXTURE_MAG_FILTER, param: GLint(samplerD->d.glmagfilter));
4026 f->glTexParameteri(target: texD->target, GL_TEXTURE_WRAP_S, param: GLint(samplerD->d.glwraps));
4027 f->glTexParameteri(target: texD->target, GL_TEXTURE_WRAP_T, param: GLint(samplerD->d.glwrapt));
4028 if (caps.texture3D && texD->target == GL_TEXTURE_3D)
4029 f->glTexParameteri(target: texD->target, GL_TEXTURE_WRAP_R, param: GLint(samplerD->d.glwrapr));
4030 if (caps.textureCompareMode) {
4031 if (samplerD->d.gltexcomparefunc != GL_NEVER) {
4032 f->glTexParameteri(target: texD->target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
4033 f->glTexParameteri(target: texD->target, GL_TEXTURE_COMPARE_FUNC, param: GLint(samplerD->d.gltexcomparefunc));
4034 } else {
4035 f->glTexParameteri(target: texD->target, GL_TEXTURE_COMPARE_MODE, GL_NONE);
4036 }
4037 }
4038 texD->samplerState = samplerD->d;
4039 }
4040}
4041
4042void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD,
4043 QRhiGraphicsPipeline *maybeGraphicsPs, QRhiComputePipeline *maybeComputePs,
4044 QRhiShaderResourceBindings *srb,
4045 const uint *dynOfsPairs, int dynOfsCount)
4046{
4047 QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, srb);
4048 int texUnit = 1; // start from unit 1, keep 0 for resource mgmt stuff to avoid clashes
4049 bool activeTexUnitAltered = false;
4050 QGles2UniformDescriptionVector &uniforms(maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->uniforms
4051 : QRHI_RES(QGles2ComputePipeline, maybeComputePs)->uniforms);
4052 QGles2UniformState *uniformState = maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->uniformState
4053 : QRHI_RES(QGles2ComputePipeline, maybeComputePs)->uniformState;
4054 m_scratch.separateTextureBindings.clear();
4055 m_scratch.separateSamplerBindings.clear();
4056
4057 for (int i = 0, ie = srbD->m_bindings.size(); i != ie; ++i) {
4058 const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(binding: srbD->m_bindings.at(idx: i));
4059
4060 switch (b->type) {
4061 case QRhiShaderResourceBinding::UniformBuffer:
4062 {
4063 int viewOffset = b->u.ubuf.offset;
4064 for (int j = 0; j < dynOfsCount; ++j) {
4065 if (dynOfsPairs[2 * j] == uint(b->binding)) {
4066 viewOffset = int(dynOfsPairs[2 * j + 1]);
4067 break;
4068 }
4069 }
4070 QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, b->u.ubuf.buf);
4071 const char *bufView = bufD->data.constData() + viewOffset;
4072 for (const QGles2UniformDescription &uniform : std::as_const(t&: uniforms)) {
4073 if (uniform.binding == b->binding) {
4074 // in a uniform buffer everything is at least 4 byte aligned
4075 // so this should not cause unaligned reads
4076 const void *src = bufView + uniform.offset;
4077
4078#ifndef QT_NO_DEBUG
4079 if (uniform.arrayDim > 0
4080 && uniform.type != QShaderDescription::Float
4081 && uniform.type != QShaderDescription::Vec2
4082 && uniform.type != QShaderDescription::Vec3
4083 && uniform.type != QShaderDescription::Vec4
4084 && uniform.type != QShaderDescription::Int
4085 && uniform.type != QShaderDescription::Int2
4086 && uniform.type != QShaderDescription::Int3
4087 && uniform.type != QShaderDescription::Int4
4088 && uniform.type != QShaderDescription::Mat3
4089 && uniform.type != QShaderDescription::Mat4)
4090 {
4091 qWarning(msg: "Uniform with buffer binding %d, buffer offset %d, type %d is an array, "
4092 "but arrays are only supported for float, vec2, vec3, vec4, int, "
4093 "ivec2, ivec3, ivec4, mat3 and mat4. "
4094 "Only the first element will be set.",
4095 uniform.binding, uniform.offset, uniform.type);
4096 }
4097#endif
4098
4099 // Our input is an std140 layout uniform block. See
4100 // "Standard Uniform Block Layout" in section 7.6.2.2 of
4101 // the OpenGL spec. This has some peculiar alignment
4102 // requirements, which is not what glUniform* wants. Hence
4103 // the unpacking/repacking for arrays and certain types.
4104
4105 switch (uniform.type) {
4106 case QShaderDescription::Float:
4107 {
4108 const int elemCount = uniform.arrayDim;
4109 if (elemCount < 1) {
4110 const float v = *reinterpret_cast<const float *>(src);
4111 if (uniform.glslLocation <= QGles2UniformState::MAX_TRACKED_LOCATION) {
4112 QGles2UniformState &thisUniformState(uniformState[uniform.glslLocation]);
4113 if (thisUniformState.componentCount != 1 || thisUniformState.v[0] != v) {
4114 thisUniformState.componentCount = 1;
4115 thisUniformState.v[0] = v;
4116 f->glUniform1f(location: uniform.glslLocation, x: v);
4117 }
4118 } else {
4119 f->glUniform1f(location: uniform.glslLocation, x: v);
4120 }
4121 } else {
4122 // input is 16 bytes per element as per std140, have to convert to packed
4123 m_scratch.packedArray.resize(sz: elemCount);
4124 qrhi_std140_to_packed(dst: &m_scratch.packedArray.data()->f, vecSize: 1, elemCount, src);
4125 f->glUniform1fv(location: uniform.glslLocation, count: elemCount, v: &m_scratch.packedArray.constData()->f);
4126 }
4127 }
4128 break;
4129 case QShaderDescription::Vec2:
4130 {
4131 const int elemCount = uniform.arrayDim;
4132 if (elemCount < 1) {
4133 const float *v = reinterpret_cast<const float *>(src);
4134 if (uniform.glslLocation <= QGles2UniformState::MAX_TRACKED_LOCATION) {
4135 QGles2UniformState &thisUniformState(uniformState[uniform.glslLocation]);
4136 if (thisUniformState.componentCount != 2
4137 || thisUniformState.v[0] != v[0]
4138 || thisUniformState.v[1] != v[1])
4139 {
4140 thisUniformState.componentCount = 2;
4141 thisUniformState.v[0] = v[0];
4142 thisUniformState.v[1] = v[1];
4143 f->glUniform2fv(location: uniform.glslLocation, count: 1, v);
4144 }
4145 } else {
4146 f->glUniform2fv(location: uniform.glslLocation, count: 1, v);
4147 }
4148 } else {
4149 m_scratch.packedArray.resize(sz: elemCount * 2);
4150 qrhi_std140_to_packed(dst: &m_scratch.packedArray.data()->f, vecSize: 2, elemCount, src);
4151 f->glUniform2fv(location: uniform.glslLocation, count: elemCount, v: &m_scratch.packedArray.constData()->f);
4152 }
4153 }
4154 break;
4155 case QShaderDescription::Vec3:
4156 {
4157 const int elemCount = uniform.arrayDim;
4158 if (elemCount < 1) {
4159 const float *v = reinterpret_cast<const float *>(src);
4160 if (uniform.glslLocation <= QGles2UniformState::MAX_TRACKED_LOCATION) {
4161 QGles2UniformState &thisUniformState(uniformState[uniform.glslLocation]);
4162 if (thisUniformState.componentCount != 3
4163 || thisUniformState.v[0] != v[0]
4164 || thisUniformState.v[1] != v[1]
4165 || thisUniformState.v[2] != v[2])
4166 {
4167 thisUniformState.componentCount = 3;
4168 thisUniformState.v[0] = v[0];
4169 thisUniformState.v[1] = v[1];
4170 thisUniformState.v[2] = v[2];
4171 f->glUniform3fv(location: uniform.glslLocation, count: 1, v);
4172 }
4173 } else {
4174 f->glUniform3fv(location: uniform.glslLocation, count: 1, v);
4175 }
4176 } else {
4177 m_scratch.packedArray.resize(sz: elemCount * 3);
4178 qrhi_std140_to_packed(dst: &m_scratch.packedArray.data()->f, vecSize: 3, elemCount, src);
4179 f->glUniform3fv(location: uniform.glslLocation, count: elemCount, v: &m_scratch.packedArray.constData()->f);
4180 }
4181 }
4182 break;
4183 case QShaderDescription::Vec4:
4184 {
4185 const int elemCount = uniform.arrayDim;
4186 if (elemCount < 1) {
4187 const float *v = reinterpret_cast<const float *>(src);
4188 if (uniform.glslLocation <= QGles2UniformState::MAX_TRACKED_LOCATION) {
4189 QGles2UniformState &thisUniformState(uniformState[uniform.glslLocation]);
4190 if (thisUniformState.componentCount != 4
4191 || thisUniformState.v[0] != v[0]
4192 || thisUniformState.v[1] != v[1]
4193 || thisUniformState.v[2] != v[2]
4194 || thisUniformState.v[3] != v[3])
4195 {
4196 thisUniformState.componentCount = 4;
4197 thisUniformState.v[0] = v[0];
4198 thisUniformState.v[1] = v[1];
4199 thisUniformState.v[2] = v[2];
4200 thisUniformState.v[3] = v[3];
4201 f->glUniform4fv(location: uniform.glslLocation, count: 1, v);
4202 }
4203 } else {
4204 f->glUniform4fv(location: uniform.glslLocation, count: 1, v);
4205 }
4206 } else {
4207 f->glUniform4fv(location: uniform.glslLocation, count: elemCount, v: reinterpret_cast<const float *>(src));
4208 }
4209 }
4210 break;
4211 case QShaderDescription::Mat2:
4212 f->glUniformMatrix2fv(location: uniform.glslLocation, count: 1, GL_FALSE, value: reinterpret_cast<const float *>(src));
4213 break;
4214 case QShaderDescription::Mat3:
4215 {
4216 const int elemCount = uniform.arrayDim;
4217 if (elemCount < 1) {
4218 // 4 floats per column (or row, if row-major)
4219 float mat[9];
4220 const float *srcMat = reinterpret_cast<const float *>(src);
4221 memcpy(dest: mat, src: srcMat, n: 3 * sizeof(float));
4222 memcpy(dest: mat + 3, src: srcMat + 4, n: 3 * sizeof(float));
4223 memcpy(dest: mat + 6, src: srcMat + 8, n: 3 * sizeof(float));
4224 f->glUniformMatrix3fv(location: uniform.glslLocation, count: 1, GL_FALSE, value: mat);
4225 } else {
4226 m_scratch.packedArray.resize(sz: elemCount * 9);
4227 qrhi_std140_to_packed(dst: &m_scratch.packedArray.data()->f, vecSize: 3, elemCount: elemCount * 3, src);
4228 f->glUniformMatrix3fv(location: uniform.glslLocation, count: elemCount, GL_FALSE, value: &m_scratch.packedArray.constData()->f);
4229 }
4230 }
4231 break;
4232 case QShaderDescription::Mat4:
4233 f->glUniformMatrix4fv(location: uniform.glslLocation, count: qMax(a: 1, b: uniform.arrayDim), GL_FALSE, value: reinterpret_cast<const float *>(src));
4234 break;
4235 case QShaderDescription::Int:
4236 {
4237 const int elemCount = uniform.arrayDim;
4238 if (elemCount < 1) {
4239 f->glUniform1i(location: uniform.glslLocation, x: *reinterpret_cast<const qint32 *>(src));
4240 } else {
4241 m_scratch.packedArray.resize(sz: elemCount);
4242 qrhi_std140_to_packed(dst: &m_scratch.packedArray.data()->i, vecSize: 1, elemCount, src);
4243 f->glUniform1iv(location: uniform.glslLocation, count: elemCount, v: &m_scratch.packedArray.constData()->i);
4244 }
4245 }
4246 break;
4247 case QShaderDescription::Int2:
4248 {
4249 const int elemCount = uniform.arrayDim;
4250 if (elemCount < 1) {
4251 f->glUniform2iv(location: uniform.glslLocation, count: 1, v: reinterpret_cast<const qint32 *>(src));
4252 } else {
4253 m_scratch.packedArray.resize(sz: elemCount * 2);
4254 qrhi_std140_to_packed(dst: &m_scratch.packedArray.data()->i, vecSize: 2, elemCount, src);
4255 f->glUniform2iv(location: uniform.glslLocation, count: elemCount, v: &m_scratch.packedArray.constData()->i);
4256 }
4257 }
4258 break;
4259 case QShaderDescription::Int3:
4260 {
4261 const int elemCount = uniform.arrayDim;
4262 if (elemCount < 1) {
4263 f->glUniform3iv(location: uniform.glslLocation, count: 1, v: reinterpret_cast<const qint32 *>(src));
4264 } else {
4265 m_scratch.packedArray.resize(sz: elemCount * 3);
4266 qrhi_std140_to_packed(dst: &m_scratch.packedArray.data()->i, vecSize: 3, elemCount, src);
4267 f->glUniform3iv(location: uniform.glslLocation, count: elemCount, v: &m_scratch.packedArray.constData()->i);
4268 }
4269 }
4270 break;
4271 case QShaderDescription::Int4:
4272 f->glUniform4iv(location: uniform.glslLocation, count: qMax(a: 1, b: uniform.arrayDim), v: reinterpret_cast<const qint32 *>(src));
4273 break;
4274 case QShaderDescription::Uint:
4275 f->glUniform1ui(location: uniform.glslLocation, v0: *reinterpret_cast<const quint32 *>(src));
4276 break;
4277 case QShaderDescription::Uint2:
4278 f->glUniform2uiv(location: uniform.glslLocation, count: 1, value: reinterpret_cast<const quint32 *>(src));
4279 break;
4280 case QShaderDescription::Uint3:
4281 f->glUniform3uiv(location: uniform.glslLocation, count: 1, value: reinterpret_cast<const quint32 *>(src));
4282 break;
4283 case QShaderDescription::Uint4:
4284 f->glUniform4uiv(location: uniform.glslLocation, count: 1, value: reinterpret_cast<const quint32 *>(src));
4285 break;
4286 case QShaderDescription::Bool: // a glsl bool is 4 bytes, like (u)int
4287 f->glUniform1i(location: uniform.glslLocation, x: *reinterpret_cast<const qint32 *>(src));
4288 break;
4289 case QShaderDescription::Bool2:
4290 f->glUniform2iv(location: uniform.glslLocation, count: 1, v: reinterpret_cast<const qint32 *>(src));
4291 break;
4292 case QShaderDescription::Bool3:
4293 f->glUniform3iv(location: uniform.glslLocation, count: 1, v: reinterpret_cast<const qint32 *>(src));
4294 break;
4295 case QShaderDescription::Bool4:
4296 f->glUniform4iv(location: uniform.glslLocation, count: 1, v: reinterpret_cast<const qint32 *>(src));
4297 break;
4298 default:
4299 qWarning(msg: "Uniform with buffer binding %d, buffer offset %d has unsupported type %d",
4300 uniform.binding, uniform.offset, uniform.type);
4301 break;
4302 }
4303 }
4304 }
4305 }
4306 break;
4307 case QRhiShaderResourceBinding::SampledTexture:
4308 {
4309 const QGles2SamplerDescriptionVector &samplers(maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->samplers
4310 : QRHI_RES(QGles2ComputePipeline, maybeComputePs)->samplers);
4311 void *ps;
4312 uint psGeneration;
4313 if (maybeGraphicsPs) {
4314 ps = maybeGraphicsPs;
4315 psGeneration = QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->generation;
4316 } else {
4317 ps = maybeComputePs;
4318 psGeneration = QRHI_RES(QGles2ComputePipeline, maybeComputePs)->generation;
4319 }
4320 for (int elem = 0; elem < b->u.stex.count; ++elem) {
4321 QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.stex.texSamplers[elem].tex);
4322 QGles2Sampler *samplerD = QRHI_RES(QGles2Sampler, b->u.stex.texSamplers[elem].sampler);
4323 for (const QGles2SamplerDescription &shaderSampler : samplers) {
4324 if (shaderSampler.combinedBinding == b->binding) {
4325 const int loc = shaderSampler.glslLocation + elem;
4326 bindCombinedSampler(cbD, texD, samplerD, ps, psGeneration, glslLocation: loc, texUnit: &texUnit, activeTexUnitAltered: &activeTexUnitAltered);
4327 break;
4328 }
4329 }
4330 }
4331 }
4332 break;
4333 case QRhiShaderResourceBinding::Texture:
4334 for (int elem = 0; elem < b->u.stex.count; ++elem) {
4335 QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.stex.texSamplers[elem].tex);
4336 m_scratch.separateTextureBindings.append(t: { .texture: texD, .binding: b->binding, .elem: elem });
4337 }
4338 break;
4339 case QRhiShaderResourceBinding::Sampler:
4340 {
4341 QGles2Sampler *samplerD = QRHI_RES(QGles2Sampler, b->u.stex.texSamplers[0].sampler);
4342 m_scratch.separateSamplerBindings.append(t: { .sampler: samplerD, .binding: b->binding });
4343 }
4344 break;
4345 case QRhiShaderResourceBinding::ImageLoad:
4346 case QRhiShaderResourceBinding::ImageStore:
4347 case QRhiShaderResourceBinding::ImageLoadStore:
4348 {
4349 QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.simage.tex);
4350 Q_ASSERT(texD->m_flags.testFlag(QRhiTexture::UsedWithLoadStore));
4351 const bool layered = texD->m_flags.testFlag(flag: QRhiTexture::CubeMap);
4352 GLenum access = GL_READ_WRITE;
4353 if (b->type == QRhiShaderResourceBinding::ImageLoad)
4354 access = GL_READ_ONLY;
4355 else if (b->type == QRhiShaderResourceBinding::ImageStore)
4356 access = GL_WRITE_ONLY;
4357 f->glBindImageTexture(unit: GLuint(b->binding), texture: texD->texture,
4358 level: b->u.simage.level, layered, layer: 0,
4359 access, format: texD->glsizedintformat);
4360 }
4361 break;
4362 case QRhiShaderResourceBinding::BufferLoad:
4363 case QRhiShaderResourceBinding::BufferStore:
4364 case QRhiShaderResourceBinding::BufferLoadStore:
4365 {
4366 QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, b->u.sbuf.buf);
4367 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer));
4368 if (b->u.sbuf.offset == 0 && b->u.sbuf.maybeSize == 0)
4369 f->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, index: GLuint(b->binding), buffer: bufD->buffer);
4370 else
4371 f->glBindBufferRange(GL_SHADER_STORAGE_BUFFER, index: GLuint(b->binding), buffer: bufD->buffer,
4372 offset: b->u.sbuf.offset, size: b->u.sbuf.maybeSize ? b->u.sbuf.maybeSize : bufD->m_size);
4373 }
4374 break;
4375 default:
4376 Q_UNREACHABLE();
4377 break;
4378 }
4379 }
4380
4381 if (!m_scratch.separateTextureBindings.isEmpty() || !m_scratch.separateSamplerBindings.isEmpty()) {
4382 const QGles2SamplerDescriptionVector &samplers(maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->samplers
4383 : QRHI_RES(QGles2ComputePipeline, maybeComputePs)->samplers);
4384 void *ps;
4385 uint psGeneration;
4386 if (maybeGraphicsPs) {
4387 ps = maybeGraphicsPs;
4388 psGeneration = QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->generation;
4389 } else {
4390 ps = maybeComputePs;
4391 psGeneration = QRHI_RES(QGles2ComputePipeline, maybeComputePs)->generation;
4392 }
4393 for (const QGles2SamplerDescription &shaderSampler : samplers) {
4394 if (shaderSampler.combinedBinding >= 0)
4395 continue;
4396 for (const Scratch::SeparateSampler &sepSampler : std::as_const(t&: m_scratch.separateSamplerBindings)) {
4397 if (sepSampler.binding != shaderSampler.sbinding)
4398 continue;
4399 for (const Scratch::SeparateTexture &sepTex : std::as_const(t&: m_scratch.separateTextureBindings)) {
4400 if (sepTex.binding != shaderSampler.tbinding)
4401 continue;
4402 const int loc = shaderSampler.glslLocation + sepTex.elem;
4403 bindCombinedSampler(cbD, texD: sepTex.texture, samplerD: sepSampler.sampler, ps, psGeneration,
4404 glslLocation: loc, texUnit: &texUnit, activeTexUnitAltered: &activeTexUnitAltered);
4405 }
4406 }
4407 }
4408 }
4409
4410 if (activeTexUnitAltered)
4411 f->glActiveTexture(GL_TEXTURE0);
4412}
4413
4414void QRhiGles2::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
4415{
4416 Q_ASSERT(QRHI_RES(QGles2CommandBuffer, cb)->recordingPass == QGles2CommandBuffer::NoPass);
4417
4418 enqueueResourceUpdates(cb, resourceUpdates);
4419}
4420
4421QGles2RenderTargetData *QRhiGles2::enqueueBindFramebuffer(QRhiRenderTarget *rt, QGles2CommandBuffer *cbD,
4422 bool *wantsColorClear, bool *wantsDsClear)
4423{
4424 QGles2RenderTargetData *rtD = nullptr;
4425 QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
4426
4427 QGles2CommandBuffer::Command &fbCmd(cbD->commands.get());
4428 fbCmd.cmd = QGles2CommandBuffer::Command::BindFramebuffer;
4429
4430 static const bool doClearBuffers = qEnvironmentVariableIntValue(varName: "QT_GL_NO_CLEAR_BUFFERS") == 0;
4431 static const bool doClearColorBuffer = qEnvironmentVariableIntValue(varName: "QT_GL_NO_CLEAR_COLOR_BUFFER") == 0;
4432
4433 switch (rt->resourceType()) {
4434 case QRhiResource::SwapChainRenderTarget:
4435 rtD = &QRHI_RES(QGles2SwapChainRenderTarget, rt)->d;
4436 if (wantsColorClear)
4437 *wantsColorClear = doClearBuffers && doClearColorBuffer;
4438 if (wantsDsClear)
4439 *wantsDsClear = doClearBuffers;
4440 fbCmd.args.bindFramebuffer.fbo = 0;
4441 fbCmd.args.bindFramebuffer.colorAttCount = 1;
4442 fbCmd.args.bindFramebuffer.stereo = rtD->stereoTarget.has_value();
4443 if (fbCmd.args.bindFramebuffer.stereo)
4444 fbCmd.args.bindFramebuffer.stereoTarget = rtD->stereoTarget.value();
4445 break;
4446 case QRhiResource::TextureRenderTarget:
4447 {
4448 QGles2TextureRenderTarget *rtTex = QRHI_RES(QGles2TextureRenderTarget, rt);
4449 rtD = &rtTex->d;
4450 if (wantsColorClear)
4451 *wantsColorClear = !rtTex->m_flags.testFlag(flag: QRhiTextureRenderTarget::PreserveColorContents);
4452 if (wantsDsClear)
4453 *wantsDsClear = !rtTex->m_flags.testFlag(flag: QRhiTextureRenderTarget::PreserveDepthStencilContents);
4454 fbCmd.args.bindFramebuffer.fbo = rtTex->framebuffer;
4455 fbCmd.args.bindFramebuffer.colorAttCount = rtD->colorAttCount;
4456 fbCmd.args.bindFramebuffer.stereo = false;
4457
4458 for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments();
4459 it != itEnd; ++it)
4460 {
4461 const QRhiColorAttachment &colorAtt(*it);
4462 QGles2Texture *texD = QRHI_RES(QGles2Texture, colorAtt.texture());
4463 QGles2Texture *resolveTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture());
4464 if (texD && cbD->passNeedsResourceTracking) {
4465 trackedRegisterTexture(passResTracker: &passResTracker, texD,
4466 access: QRhiPassResourceTracker::TexColorOutput,
4467 stage: QRhiPassResourceTracker::TexColorOutputStage);
4468 }
4469 if (resolveTexD && cbD->passNeedsResourceTracking) {
4470 trackedRegisterTexture(passResTracker: &passResTracker, texD: resolveTexD,
4471 access: QRhiPassResourceTracker::TexColorOutput,
4472 stage: QRhiPassResourceTracker::TexColorOutputStage);
4473 }
4474 // renderbuffers cannot be written in shaders (no image store) so
4475 // they do not matter here
4476 }
4477 if (rtTex->m_desc.depthTexture() && cbD->passNeedsResourceTracking) {
4478 trackedRegisterTexture(passResTracker: &passResTracker, QRHI_RES(QGles2Texture, rtTex->m_desc.depthTexture()),
4479 access: QRhiPassResourceTracker::TexDepthOutput,
4480 stage: QRhiPassResourceTracker::TexDepthOutputStage);
4481 }
4482 }
4483 break;
4484 default:
4485 Q_UNREACHABLE();
4486 break;
4487 }
4488
4489 fbCmd.args.bindFramebuffer.srgb = rtD->srgbUpdateAndBlend;
4490
4491 return rtD;
4492}
4493
4494void QRhiGles2::enqueueBarriersForPass(QGles2CommandBuffer *cbD)
4495{
4496 cbD->passResTrackers.append(t: QRhiPassResourceTracker());
4497 cbD->currentPassResTrackerIndex = cbD->passResTrackers.size() - 1;
4498 QGles2CommandBuffer::Command &cmd(cbD->commands.get());
4499 cmd.cmd = QGles2CommandBuffer::Command::BarriersForPass;
4500 cmd.args.barriersForPass.trackerIndex = cbD->currentPassResTrackerIndex;
4501}
4502
4503void QRhiGles2::beginPass(QRhiCommandBuffer *cb,
4504 QRhiRenderTarget *rt,
4505 const QColor &colorClearValue,
4506 const QRhiDepthStencilClearValue &depthStencilClearValue,
4507 QRhiResourceUpdateBatch *resourceUpdates,
4508 QRhiCommandBuffer::BeginPassFlags flags)
4509{
4510 QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
4511 Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass);
4512
4513 if (resourceUpdates)
4514 enqueueResourceUpdates(cb, resourceUpdates);
4515
4516 // Get a new resource tracker. Then add a command that will generate
4517 // glMemoryBarrier() calls based on that tracker when submitted.
4518 enqueueBarriersForPass(cbD);
4519
4520 if (rt->resourceType() == QRhiRenderTarget::TextureRenderTarget) {
4521 QGles2TextureRenderTarget *rtTex = QRHI_RES(QGles2TextureRenderTarget, rt);
4522 if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QGles2Texture, QGles2RenderBuffer>(desc: rtTex->description(), currentResIdList: rtTex->d.currentResIdList))
4523 rtTex->create();
4524 }
4525
4526 bool wantsColorClear, wantsDsClear;
4527 QGles2RenderTargetData *rtD = enqueueBindFramebuffer(rt, cbD, wantsColorClear: &wantsColorClear, wantsDsClear: &wantsDsClear);
4528
4529 QGles2CommandBuffer::Command &clearCmd(cbD->commands.get());
4530 clearCmd.cmd = QGles2CommandBuffer::Command::Clear;
4531 clearCmd.args.clear.mask = 0;
4532 if (rtD->colorAttCount && wantsColorClear)
4533 clearCmd.args.clear.mask |= GL_COLOR_BUFFER_BIT;
4534 if (rtD->dsAttCount && wantsDsClear)
4535 clearCmd.args.clear.mask |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
4536 clearCmd.args.clear.c[0] = float(colorClearValue.redF());
4537 clearCmd.args.clear.c[1] = float(colorClearValue.greenF());
4538 clearCmd.args.clear.c[2] = float(colorClearValue.blueF());
4539 clearCmd.args.clear.c[3] = float(colorClearValue.alphaF());
4540 clearCmd.args.clear.d = depthStencilClearValue.depthClearValue();
4541 clearCmd.args.clear.s = depthStencilClearValue.stencilClearValue();
4542
4543 cbD->recordingPass = QGles2CommandBuffer::RenderPass;
4544 cbD->passNeedsResourceTracking = !flags.testFlag(flag: QRhiCommandBuffer::DoNotTrackResourcesForCompute);
4545 cbD->currentTarget = rt;
4546
4547 cbD->resetCachedState();
4548}
4549
4550void QRhiGles2::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
4551{
4552 QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
4553 Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
4554
4555 if (cbD->currentTarget->resourceType() == QRhiResource::TextureRenderTarget) {
4556 QGles2TextureRenderTarget *rtTex = QRHI_RES(QGles2TextureRenderTarget, cbD->currentTarget);
4557 for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments();
4558 it != itEnd; ++it)
4559 {
4560 const QRhiColorAttachment &colorAtt(*it);
4561 if (!colorAtt.resolveTexture())
4562 continue;
4563
4564 QGles2Texture *resolveTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture());
4565 const QSize size = resolveTexD->pixelSize();
4566 if (colorAtt.renderBuffer()) {
4567 QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, colorAtt.renderBuffer());
4568 if (rbD->pixelSize() != size) {
4569 qWarning(msg: "Resolve source (%dx%d) and target (%dx%d) size does not match",
4570 rbD->pixelSize().width(), rbD->pixelSize().height(), size.width(), size.height());
4571 }
4572 QGles2CommandBuffer::Command &cmd(cbD->commands.get());
4573 cmd.cmd = QGles2CommandBuffer::Command::BlitFromRenderbuffer;
4574 cmd.args.blitFromRenderbuffer.renderbuffer = rbD->renderbuffer;
4575 cmd.args.blitFromRenderbuffer.w = size.width();
4576 cmd.args.blitFromRenderbuffer.h = size.height();
4577 if (resolveTexD->m_flags.testFlag(flag: QRhiTexture::CubeMap))
4578 cmd.args.blitFromRenderbuffer.target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + uint(colorAtt.resolveLayer());
4579 else
4580 cmd.args.blitFromRenderbuffer.target = resolveTexD->target;
4581 cmd.args.blitFromRenderbuffer.dstTexture = resolveTexD->texture;
4582 cmd.args.blitFromRenderbuffer.dstLevel = colorAtt.resolveLevel();
4583 const bool hasZ = resolveTexD->m_flags.testFlag(flag: QRhiTexture::ThreeDimensional)
4584 || resolveTexD->m_flags.testFlag(flag: QRhiTexture::TextureArray);
4585 cmd.args.blitFromRenderbuffer.dstLayer = hasZ ? colorAtt.resolveLayer() : 0;
4586 cmd.args.blitFromRenderbuffer.isDepthStencil = false;
4587 } else if (caps.glesMultisampleRenderToTexture) {
4588 // Nothing to do, resolving into colorAtt.resolveTexture() is automatic,
4589 // colorAtt.texture() is in fact not used for anything.
4590 } else {
4591 Q_ASSERT(colorAtt.texture());
4592 QGles2Texture *texD = QRHI_RES(QGles2Texture, colorAtt.texture());
4593 if (texD->pixelSize() != size) {
4594 qWarning(msg: "Resolve source (%dx%d) and target (%dx%d) size does not match",
4595 texD->pixelSize().width(), texD->pixelSize().height(), size.width(), size.height());
4596 }
4597 const int resolveCount = colorAtt.multiViewCount() >= 2 ? colorAtt.multiViewCount() : 1;
4598 for (int resolveIdx = 0; resolveIdx < resolveCount; ++resolveIdx) {
4599 const int srcLayer = colorAtt.layer() + resolveIdx;
4600 const int dstLayer = colorAtt.resolveLayer() + resolveIdx;
4601 QGles2CommandBuffer::Command &cmd(cbD->commands.get());
4602 cmd.cmd = QGles2CommandBuffer::Command::BlitFromTexture;
4603 if (texD->m_flags.testFlag(flag: QRhiTexture::CubeMap))
4604 cmd.args.blitFromTexture.srcTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + uint(srcLayer);
4605 else
4606 cmd.args.blitFromTexture.srcTarget = texD->target;
4607 cmd.args.blitFromTexture.srcTexture = texD->texture;
4608 cmd.args.blitFromTexture.srcLevel = colorAtt.level();
4609 cmd.args.blitFromTexture.srcLayer = 0;
4610 if (texD->m_flags.testFlag(flag: QRhiTexture::ThreeDimensional) || texD->m_flags.testFlag(flag: QRhiTexture::TextureArray))
4611 cmd.args.blitFromTexture.srcLayer = srcLayer;
4612 cmd.args.blitFromTexture.w = size.width();
4613 cmd.args.blitFromTexture.h = size.height();
4614 if (resolveTexD->m_flags.testFlag(flag: QRhiTexture::CubeMap))
4615 cmd.args.blitFromTexture.dstTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + uint(dstLayer);
4616 else
4617 cmd.args.blitFromTexture.dstTarget = resolveTexD->target;
4618 cmd.args.blitFromTexture.dstTexture = resolveTexD->texture;
4619 cmd.args.blitFromTexture.dstLevel = colorAtt.resolveLevel();
4620 cmd.args.blitFromTexture.dstLayer = 0;
4621 if (resolveTexD->m_flags.testFlag(flag: QRhiTexture::ThreeDimensional) || resolveTexD->m_flags.testFlag(flag: QRhiTexture::TextureArray))
4622 cmd.args.blitFromTexture.dstLayer = dstLayer;
4623 cmd.args.blitFromTexture.isDepthStencil = false;
4624 }
4625 }
4626 }
4627
4628 if (rtTex->m_desc.depthResolveTexture()) {
4629 QGles2Texture *depthResolveTexD = QRHI_RES(QGles2Texture, rtTex->m_desc.depthResolveTexture());
4630 const QSize size = depthResolveTexD->pixelSize();
4631 if (rtTex->m_desc.depthStencilBuffer()) {
4632 QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, rtTex->m_desc.depthStencilBuffer());
4633 QGles2CommandBuffer::Command &cmd(cbD->commands.get());
4634 cmd.cmd = QGles2CommandBuffer::Command::BlitFromRenderbuffer;
4635 cmd.args.blitFromRenderbuffer.renderbuffer = rbD->renderbuffer;
4636 cmd.args.blitFromRenderbuffer.w = size.width();
4637 cmd.args.blitFromRenderbuffer.h = size.height();
4638 cmd.args.blitFromRenderbuffer.target = depthResolveTexD->target;
4639 cmd.args.blitFromRenderbuffer.dstTexture = depthResolveTexD->texture;
4640 cmd.args.blitFromRenderbuffer.dstLevel = 0;
4641 cmd.args.blitFromRenderbuffer.dstLayer = 0;
4642 cmd.args.blitFromRenderbuffer.isDepthStencil = true;
4643 } else if (caps.glesMultisampleRenderToTexture) {
4644 // Nothing to do, resolving into depthResolveTexture() is automatic.
4645 } else {
4646 QGles2Texture *depthTexD = QRHI_RES(QGles2Texture, rtTex->m_desc.depthTexture());
4647 const int resolveCount = depthTexD->arraySize() >= 2 ? depthTexD->arraySize() : 1;
4648 for (int resolveIdx = 0; resolveIdx < resolveCount; ++resolveIdx) {
4649 QGles2CommandBuffer::Command &cmd(cbD->commands.get());
4650 cmd.cmd = QGles2CommandBuffer::Command::BlitFromTexture;
4651 cmd.args.blitFromTexture.srcTarget = depthTexD->target;
4652 cmd.args.blitFromTexture.srcTexture = depthTexD->texture;
4653 cmd.args.blitFromTexture.srcLevel = 0;
4654 cmd.args.blitFromTexture.srcLayer = resolveIdx;
4655 cmd.args.blitFromTexture.w = size.width();
4656 cmd.args.blitFromTexture.h = size.height();
4657 cmd.args.blitFromTexture.dstTarget = depthResolveTexD->target;
4658 cmd.args.blitFromTexture.dstTexture = depthResolveTexD->texture;
4659 cmd.args.blitFromTexture.dstLevel = 0;
4660 cmd.args.blitFromTexture.dstLayer = resolveIdx;
4661 cmd.args.blitFromTexture.isDepthStencil = true;
4662 }
4663 }
4664 }
4665
4666 const bool mayDiscardDepthStencil =
4667 (rtTex->m_desc.depthStencilBuffer()
4668 || (rtTex->m_desc.depthTexture() && rtTex->m_flags.testFlag(flag: QRhiTextureRenderTarget::DoNotStoreDepthStencilContents)))
4669 && !rtTex->m_desc.depthResolveTexture();
4670 if (mayDiscardDepthStencil) {
4671 QGles2CommandBuffer::Command &cmd(cbD->commands.get());
4672 cmd.cmd = QGles2CommandBuffer::Command::InvalidateFramebuffer;
4673 if (caps.needsDepthStencilCombinedAttach) {
4674 cmd.args.invalidateFramebuffer.attCount = 1;
4675 cmd.args.invalidateFramebuffer.att[0] = GL_DEPTH_STENCIL_ATTACHMENT;
4676 } else {
4677 cmd.args.invalidateFramebuffer.attCount = 2;
4678 cmd.args.invalidateFramebuffer.att[0] = GL_DEPTH_ATTACHMENT;
4679 cmd.args.invalidateFramebuffer.att[1] = GL_STENCIL_ATTACHMENT;
4680 }
4681 }
4682 }
4683
4684 cbD->recordingPass = QGles2CommandBuffer::NoPass;
4685 cbD->currentTarget = nullptr;
4686
4687 if (resourceUpdates)
4688 enqueueResourceUpdates(cb, resourceUpdates);
4689}
4690
4691void QRhiGles2::beginComputePass(QRhiCommandBuffer *cb,
4692 QRhiResourceUpdateBatch *resourceUpdates,
4693 QRhiCommandBuffer::BeginPassFlags)
4694{
4695 QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
4696 Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass);
4697
4698 if (resourceUpdates)
4699 enqueueResourceUpdates(cb, resourceUpdates);
4700
4701 enqueueBarriersForPass(cbD);
4702
4703 cbD->recordingPass = QGles2CommandBuffer::ComputePass;
4704
4705 cbD->resetCachedState();
4706}
4707
4708void QRhiGles2::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
4709{
4710 QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
4711 Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::ComputePass);
4712
4713 cbD->recordingPass = QGles2CommandBuffer::NoPass;
4714
4715 if (resourceUpdates)
4716 enqueueResourceUpdates(cb, resourceUpdates);
4717}
4718
4719void QRhiGles2::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
4720{
4721 QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
4722 Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::ComputePass);
4723 QGles2ComputePipeline *psD = QRHI_RES(QGles2ComputePipeline, ps);
4724 const bool pipelineChanged = cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation;
4725
4726 if (pipelineChanged) {
4727 cbD->currentGraphicsPipeline = nullptr;
4728 cbD->currentComputePipeline = ps;
4729 cbD->currentPipelineGeneration = psD->generation;
4730
4731 QGles2CommandBuffer::Command &cmd(cbD->commands.get());
4732 cmd.cmd = QGles2CommandBuffer::Command::BindComputePipeline;
4733 cmd.args.bindComputePipeline.ps = ps;
4734 }
4735}
4736
4737template<typename T>
4738inline void qrhigl_accumulateComputeResource(T *writtenResources, QRhiResource *resource,
4739 QRhiShaderResourceBinding::Type bindingType,
4740 int loadTypeVal, int storeTypeVal, int loadStoreTypeVal)
4741{
4742 int access = 0;
4743 if (bindingType == loadTypeVal) {
4744 access = QGles2CommandBuffer::ComputePassState::Read;
4745 } else {
4746 access = QGles2CommandBuffer::ComputePassState::Write;
4747 if (bindingType == loadStoreTypeVal)
4748 access |= QGles2CommandBuffer::ComputePassState::Read;
4749 }
4750 auto it = writtenResources->find(resource);
4751 if (it != writtenResources->end())
4752 it->first |= access;
4753 else if (bindingType == storeTypeVal || bindingType == loadStoreTypeVal)
4754 writtenResources->insert(resource, { access, true });
4755}
4756
4757void QRhiGles2::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
4758{
4759 QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
4760 Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::ComputePass);
4761
4762 if (cbD->currentComputeSrb) {
4763 GLbitfield barriers = 0;
4764
4765 // The key in the writtenResources map indicates that the resource was
4766 // written in a previous dispatch, whereas the value accumulates the
4767 // access mask in the current one.
4768 for (auto &accessAndIsNewFlag : cbD->computePassState.writtenResources)
4769 accessAndIsNewFlag = { 0, false };
4770
4771 QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, cbD->currentComputeSrb);
4772 const int bindingCount = srbD->m_bindings.size();
4773 for (int i = 0; i < bindingCount; ++i) {
4774 const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(binding: srbD->m_bindings.at(idx: i));
4775 switch (b->type) {
4776 case QRhiShaderResourceBinding::ImageLoad:
4777 case QRhiShaderResourceBinding::ImageStore:
4778 case QRhiShaderResourceBinding::ImageLoadStore:
4779 qrhigl_accumulateComputeResource(writtenResources: &cbD->computePassState.writtenResources,
4780 resource: b->u.simage.tex,
4781 bindingType: b->type,
4782 loadTypeVal: QRhiShaderResourceBinding::ImageLoad,
4783 storeTypeVal: QRhiShaderResourceBinding::ImageStore,
4784 loadStoreTypeVal: QRhiShaderResourceBinding::ImageLoadStore);
4785 break;
4786 case QRhiShaderResourceBinding::BufferLoad:
4787 case QRhiShaderResourceBinding::BufferStore:
4788 case QRhiShaderResourceBinding::BufferLoadStore:
4789 qrhigl_accumulateComputeResource(writtenResources: &cbD->computePassState.writtenResources,
4790 resource: b->u.sbuf.buf,
4791 bindingType: b->type,
4792 loadTypeVal: QRhiShaderResourceBinding::BufferLoad,
4793 storeTypeVal: QRhiShaderResourceBinding::BufferStore,
4794 loadStoreTypeVal: QRhiShaderResourceBinding::BufferLoadStore);
4795 break;
4796 default:
4797 break;
4798 }
4799 }
4800
4801 for (auto it = cbD->computePassState.writtenResources.begin(); it != cbD->computePassState.writtenResources.end(); ) {
4802 const int accessInThisDispatch = it->first;
4803 const bool isNewInThisDispatch = it->second;
4804 if (accessInThisDispatch && !isNewInThisDispatch) {
4805 if (it.key()->resourceType() == QRhiResource::Texture)
4806 barriers |= GL_SHADER_IMAGE_ACCESS_BARRIER_BIT;
4807 else
4808 barriers |= GL_SHADER_STORAGE_BARRIER_BIT;
4809 }
4810 // Anything that was previously written, but is only read now, can be
4811 // removed from the written list (because that previous write got a
4812 // corresponding barrier now).
4813 if (accessInThisDispatch == QGles2CommandBuffer::ComputePassState::Read)
4814 it = cbD->computePassState.writtenResources.erase(it);
4815 else
4816 ++it;
4817 }
4818
4819 if (barriers) {
4820 QGles2CommandBuffer::Command &cmd(cbD->commands.get());
4821 cmd.cmd = QGles2CommandBuffer::Command::Barrier;
4822 cmd.args.barrier.barriers = barriers;
4823 }
4824 }
4825
4826 QGles2CommandBuffer::Command &cmd(cbD->commands.get());
4827 cmd.cmd = QGles2CommandBuffer::Command::Dispatch;
4828 cmd.args.dispatch.x = GLuint(x);
4829 cmd.args.dispatch.y = GLuint(y);
4830 cmd.args.dispatch.z = GLuint(z);
4831}
4832
4833static inline GLenum toGlShaderType(QRhiShaderStage::Type type)
4834{
4835 switch (type) {
4836 case QRhiShaderStage::Vertex:
4837 return GL_VERTEX_SHADER;
4838 case QRhiShaderStage::TessellationControl:
4839 return GL_TESS_CONTROL_SHADER;
4840 case QRhiShaderStage::TessellationEvaluation:
4841 return GL_TESS_EVALUATION_SHADER;
4842 case QRhiShaderStage::Geometry:
4843 return GL_GEOMETRY_SHADER;
4844 case QRhiShaderStage::Fragment:
4845 return GL_FRAGMENT_SHADER;
4846 case QRhiShaderStage::Compute:
4847 return GL_COMPUTE_SHADER;
4848 default:
4849 Q_UNREACHABLE_RETURN(GL_VERTEX_SHADER);
4850 }
4851}
4852
4853QByteArray QRhiGles2::shaderSource(const QRhiShaderStage &shaderStage, QShaderVersion *shaderVersion)
4854{
4855 const QShader bakedShader = shaderStage.shader();
4856 QList<int> versionsToTry;
4857 QByteArray source;
4858 if (caps.gles) {
4859 if (caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2)) {
4860 versionsToTry << 320 << 310 << 300 << 100;
4861 } else if (caps.ctxMajor == 3 && caps.ctxMinor == 1) {
4862 versionsToTry << 310 << 300 << 100;
4863 } else if (caps.ctxMajor == 3 && caps.ctxMinor == 0) {
4864 versionsToTry << 300 << 100;
4865 } else {
4866 versionsToTry << 100;
4867 }
4868 for (int v : versionsToTry) {
4869 QShaderVersion ver(v, QShaderVersion::GlslEs);
4870 source = bakedShader.shader(key: { QShader::GlslShader, ver, shaderStage.shaderVariant() }).shader();
4871 if (!source.isEmpty()) {
4872 if (shaderVersion)
4873 *shaderVersion = ver;
4874 break;
4875 }
4876 }
4877 } else {
4878 if (caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 6)) {
4879 versionsToTry << 460 << 450 << 440 << 430 << 420 << 410 << 400 << 330 << 150 << 140 << 130;
4880 } else if (caps.ctxMajor == 4 && caps.ctxMinor == 5) {
4881 versionsToTry << 450 << 440 << 430 << 420 << 410 << 400 << 330 << 150 << 140 << 130;
4882 } else if (caps.ctxMajor == 4 && caps.ctxMinor == 4) {
4883 versionsToTry << 440 << 430 << 420 << 410 << 400 << 330 << 150 << 140 << 130;
4884 } else if (caps.ctxMajor == 4 && caps.ctxMinor == 3) {
4885 versionsToTry << 430 << 420 << 410 << 400 << 330 << 150 << 140 << 130;
4886 } else if (caps.ctxMajor == 4 && caps.ctxMinor == 2) {
4887 versionsToTry << 420 << 410 << 400 << 330 << 150 << 140 << 130;
4888 } else if (caps.ctxMajor == 4 && caps.ctxMinor == 1) {
4889 versionsToTry << 410 << 400 << 330 << 150 << 140 << 130;
4890 } else if (caps.ctxMajor == 4 && caps.ctxMinor == 0) {
4891 versionsToTry << 400 << 330 << 150 << 140 << 130;
4892 } else if (caps.ctxMajor == 3 && caps.ctxMinor == 3) {
4893 versionsToTry << 330 << 150 << 140 << 130;
4894 } else if (caps.ctxMajor == 3 && caps.ctxMinor == 2) {
4895 versionsToTry << 150 << 140 << 130;
4896 } else if (caps.ctxMajor == 3 && caps.ctxMinor == 1) {
4897 versionsToTry << 140 << 130;
4898 } else if (caps.ctxMajor == 3 && caps.ctxMinor == 0) {
4899 versionsToTry << 130;
4900 }
4901 if (!caps.coreProfile)
4902 versionsToTry << 120;
4903 for (int v : versionsToTry) {
4904 source = bakedShader.shader(key: { QShader::GlslShader, v, shaderStage.shaderVariant() }).shader();
4905 if (!source.isEmpty()) {
4906 if (shaderVersion)
4907 *shaderVersion = v;
4908 break;
4909 }
4910 }
4911 }
4912 if (source.isEmpty()) {
4913 qWarning() << "No GLSL shader code found (versions tried: " << versionsToTry
4914 << ") in baked shader" << bakedShader;
4915 }
4916 return source;
4917}
4918
4919bool QRhiGles2::compileShader(GLuint program, const QRhiShaderStage &shaderStage, QShaderVersion *shaderVersion)
4920{
4921 const QByteArray source = shaderSource(shaderStage, shaderVersion);
4922 if (source.isEmpty())
4923 return false;
4924
4925 GLuint shader;
4926 auto cacheIt = m_shaderCache.constFind(key: shaderStage);
4927 if (cacheIt != m_shaderCache.constEnd()) {
4928 shader = *cacheIt;
4929 } else {
4930 shader = f->glCreateShader(type: toGlShaderType(type: shaderStage.type()));
4931 const char *srcStr = source.constData();
4932 const GLint srcLength = source.size();
4933 f->glShaderSource(shader, count: 1, string: &srcStr, length: &srcLength);
4934 f->glCompileShader(shader);
4935 GLint compiled = 0;
4936 f->glGetShaderiv(shader, GL_COMPILE_STATUS, params: &compiled);
4937 if (!compiled) {
4938 GLint infoLogLength = 0;
4939 f->glGetShaderiv(shader, GL_INFO_LOG_LENGTH, params: &infoLogLength);
4940 QByteArray log;
4941 if (infoLogLength > 1) {
4942 GLsizei length = 0;
4943 log.resize(size: infoLogLength);
4944 f->glGetShaderInfoLog(shader, bufsize: infoLogLength, length: &length, infolog: log.data());
4945 }
4946 qWarning(msg: "Failed to compile shader: %s\nSource was:\n%s", log.constData(), source.constData());
4947 return false;
4948 }
4949 if (m_shaderCache.size() >= MAX_SHADER_CACHE_ENTRIES) {
4950 // Use the simplest strategy: too many cached shaders -> drop them all.
4951 for (uint shader : m_shaderCache)
4952 f->glDeleteShader(shader); // does not actually get released yet when attached to a not-yet-released program
4953 m_shaderCache.clear();
4954 }
4955 m_shaderCache.insert(key: shaderStage, value: shader);
4956 }
4957
4958 f->glAttachShader(program, shader);
4959
4960 return true;
4961}
4962
4963bool QRhiGles2::linkProgram(GLuint program)
4964{
4965 f->glLinkProgram(program);
4966 GLint linked = 0;
4967 f->glGetProgramiv(program, GL_LINK_STATUS, params: &linked);
4968 if (!linked) {
4969 GLint infoLogLength = 0;
4970 f->glGetProgramiv(program, GL_INFO_LOG_LENGTH, params: &infoLogLength);
4971 QByteArray log;
4972 if (infoLogLength > 1) {
4973 GLsizei length = 0;
4974 log.resize(size: infoLogLength);
4975 f->glGetProgramInfoLog(program, bufsize: infoLogLength, length: &length, infolog: log.data());
4976 }
4977 qWarning(msg: "Failed to link shader program: %s", log.constData());
4978 return false;
4979 }
4980 return true;
4981}
4982
4983void QRhiGles2::registerUniformIfActive(const QShaderDescription::BlockVariable &var,
4984 const QByteArray &namePrefix,
4985 int binding,
4986 int baseOffset,
4987 GLuint program,
4988 ActiveUniformLocationTracker *activeUniformLocations,
4989 QGles2UniformDescriptionVector *dst)
4990{
4991 if (var.type == QShaderDescription::Struct) {
4992 qWarning(msg: "Nested structs are not supported at the moment. '%s' ignored.",
4993 var.name.constData());
4994 return;
4995 }
4996 QGles2UniformDescription uniform;
4997 uniform.type = var.type;
4998 const QByteArray name = namePrefix + var.name;
4999 // Here we expect that the OpenGL implementation has proper active uniform
5000 // handling, meaning that a uniform that is declared but not accessed
5001 // elsewhere in the code is reported as -1 when querying the location. If
5002 // that is not the case, it won't break anything, but we'll generate
5003 // unnecessary glUniform* calls then.
5004 uniform.glslLocation = f->glGetUniformLocation(program, name: name.constData());
5005 if (uniform.glslLocation >= 0 && !activeUniformLocations->hasSeen(s: uniform.glslLocation)) {
5006 if (var.arrayDims.size() > 1) {
5007 qWarning(msg: "Array '%s' has more than one dimension. This is not supported.",
5008 var.name.constData());
5009 return;
5010 }
5011 uniform.binding = binding;
5012 uniform.offset = uint(baseOffset + var.offset);
5013 uniform.size = var.size;
5014 uniform.arrayDim = var.arrayDims.isEmpty() ? 0 : var.arrayDims.first();
5015 dst->append(t: uniform);
5016 }
5017}
5018
5019void QRhiGles2::gatherUniforms(GLuint program,
5020 const QShaderDescription::UniformBlock &ub,
5021 ActiveUniformLocationTracker *activeUniformLocations,
5022 QGles2UniformDescriptionVector *dst)
5023{
5024 QByteArray prefix = ub.structName + '.';
5025 for (const QShaderDescription::BlockVariable &blockMember : ub.members) {
5026 if (blockMember.type == QShaderDescription::Struct) {
5027 QByteArray structPrefix = prefix + blockMember.name;
5028
5029 const int baseOffset = blockMember.offset;
5030 if (blockMember.arrayDims.isEmpty()) {
5031 for (const QShaderDescription::BlockVariable &structMember : blockMember.structMembers)
5032 registerUniformIfActive(var: structMember, namePrefix: structPrefix + ".", binding: ub.binding,
5033 baseOffset, program, activeUniformLocations, dst);
5034 } else {
5035 if (blockMember.arrayDims.size() > 1) {
5036 qWarning(msg: "Array of struct '%s' has more than one dimension. Only the first "
5037 "dimension is used.",
5038 blockMember.name.constData());
5039 }
5040 const int dim = blockMember.arrayDims.first();
5041 const int elemSize = blockMember.size / dim;
5042 int elemOffset = baseOffset;
5043 for (int di = 0; di < dim; ++di) {
5044 const QByteArray arrayPrefix = structPrefix + '[' + QByteArray::number(di) + ']' + '.';
5045 for (const QShaderDescription::BlockVariable &structMember : blockMember.structMembers)
5046 registerUniformIfActive(var: structMember, namePrefix: arrayPrefix, binding: ub.binding, baseOffset: elemOffset, program, activeUniformLocations, dst);
5047 elemOffset += elemSize;
5048 }
5049 }
5050 } else {
5051 registerUniformIfActive(var: blockMember, namePrefix: prefix, binding: ub.binding, baseOffset: 0, program, activeUniformLocations, dst);
5052 }
5053 }
5054}
5055
5056void QRhiGles2::gatherSamplers(GLuint program,
5057 const QShaderDescription::InOutVariable &v,
5058 QGles2SamplerDescriptionVector *dst)
5059{
5060 QGles2SamplerDescription sampler;
5061 sampler.glslLocation = f->glGetUniformLocation(program, name: v.name.constData());
5062 if (sampler.glslLocation >= 0) {
5063 sampler.combinedBinding = v.binding;
5064 sampler.tbinding = -1;
5065 sampler.sbinding = -1;
5066 dst->append(t: sampler);
5067 }
5068}
5069
5070void QRhiGles2::gatherGeneratedSamplers(GLuint program,
5071 const QShader::SeparateToCombinedImageSamplerMapping &mapping,
5072 QGles2SamplerDescriptionVector *dst)
5073{
5074 QGles2SamplerDescription sampler;
5075 sampler.glslLocation = f->glGetUniformLocation(program, name: mapping.combinedSamplerName.constData());
5076 if (sampler.glslLocation >= 0) {
5077 sampler.combinedBinding = -1;
5078 sampler.tbinding = mapping.textureBinding;
5079 sampler.sbinding = mapping.samplerBinding;
5080 dst->append(t: sampler);
5081 }
5082}
5083
5084void QRhiGles2::sanityCheckVertexFragmentInterface(const QShaderDescription &vsDesc, const QShaderDescription &fsDesc)
5085{
5086 if (!vsDesc.isValid() || !fsDesc.isValid())
5087 return;
5088
5089 // Print a warning if the fragment shader input for a given location uses a
5090 // name that does not match the vertex shader output at the same location.
5091 // This is not an error with any other API and not with GLSL >= 330 either,
5092 // but matters for older GLSL code that has no location qualifiers.
5093 for (const QShaderDescription::InOutVariable &outVar : vsDesc.outputVariables()) {
5094 for (const QShaderDescription::InOutVariable &inVar : fsDesc.inputVariables()) {
5095 if (inVar.location == outVar.location) {
5096 if (inVar.name != outVar.name) {
5097 qWarning(msg: "Vertex output name '%s' does not match fragment input '%s'. "
5098 "This should be avoided because it causes problems with older GLSL versions.",
5099 outVar.name.constData(), inVar.name.constData());
5100 }
5101 break;
5102 }
5103 }
5104 }
5105}
5106
5107bool QRhiGles2::isProgramBinaryDiskCacheEnabled() const
5108{
5109 static QOpenGLProgramBinarySupportCheckWrapper checker;
5110 return checker.get(context: ctx)->isSupported();
5111}
5112
5113Q_GLOBAL_STATIC(QOpenGLProgramBinaryCache, qrhi_programBinaryCache);
5114
5115static inline QShader::Stage toShaderStage(QRhiShaderStage::Type type)
5116{
5117 switch (type) {
5118 case QRhiShaderStage::Vertex:
5119 return QShader::VertexStage;
5120 case QRhiShaderStage::TessellationControl:
5121 return QShader::TessellationControlStage;
5122 case QRhiShaderStage::TessellationEvaluation:
5123 return QShader::TessellationEvaluationStage;
5124 case QRhiShaderStage::Geometry:
5125 return QShader::GeometryStage;
5126 case QRhiShaderStage::Fragment:
5127 return QShader::FragmentStage;
5128 case QRhiShaderStage::Compute:
5129 return QShader::ComputeStage;
5130 default:
5131 Q_UNREACHABLE_RETURN(QShader::VertexStage);
5132 }
5133}
5134
5135QRhiGles2::ProgramCacheResult QRhiGles2::tryLoadFromDiskOrPipelineCache(const QRhiShaderStage *stages,
5136 int stageCount,
5137 GLuint program,
5138 const QVector<QShaderDescription::InOutVariable> &inputVars,
5139 QByteArray *cacheKey)
5140{
5141 Q_ASSERT(cacheKey);
5142
5143 // the traditional QOpenGL disk cache since Qt 5.9
5144 const bool legacyDiskCacheEnabled = isProgramBinaryDiskCacheEnabled();
5145
5146 // QRhi's own (set)PipelineCacheData()
5147 const bool pipelineCacheEnabled = caps.programBinary && !m_pipelineCache.isEmpty();
5148
5149 // calculating the cache key based on the source code is common for both types of caches
5150 if (legacyDiskCacheEnabled || pipelineCacheEnabled) {
5151 QOpenGLProgramBinaryCache::ProgramDesc binaryProgram;
5152 for (int i = 0; i < stageCount; ++i) {
5153 const QRhiShaderStage &stage(stages[i]);
5154 QByteArray source = shaderSource(shaderStage: stage, shaderVersion: nullptr);
5155 if (source.isEmpty())
5156 return QRhiGles2::ProgramCacheError;
5157
5158 if (stage.type() == QRhiShaderStage::Vertex) {
5159 // Now add something to the key that indicates the vertex input locations.
5160 // A GLSL shader lower than 330 (150, 140, ...) will not have location
5161 // qualifiers. This means that the shader source code is the same
5162 // regardless of what locations inputVars contains. This becomes a problem
5163 // because we'll glBindAttribLocation the shader based on inputVars, but
5164 // that's only when compiling/linking when there was no cache hit. Picking
5165 // from the cache afterwards should take the input locations into account
5166 // since if inputVars has now different locations for the attributes, then
5167 // it is not ok to reuse a program binary that used different attribute
5168 // locations. For a lot of clients this would not be an issue since they
5169 // typically hardcode and use the same vertex locations on every run. Some
5170 // systems that dynamically generate shaders may end up with a non-stable
5171 // order (and so location numbers), however. This is sub-optimal because
5172 // it makes caching inefficient, and said clients should be fixed, but in
5173 // any case this should not break rendering. Hence including the locations
5174 // in the cache key.
5175 QMap<QByteArray, int> inputLocations; // sorted by key when iterating
5176 for (const QShaderDescription::InOutVariable &var : inputVars)
5177 inputLocations.insert(key: var.name, value: var.location);
5178 source += QByteArrayLiteral("\n // "); // just to be nice; treated as an arbitrary string regardless
5179 for (auto it = inputLocations.cbegin(), end = inputLocations.cend(); it != end; ++it) {
5180 source += it.key();
5181 source += QByteArray::number(it.value());
5182 }
5183 source += QByteArrayLiteral("\n");
5184 }
5185
5186 binaryProgram.shaders.append(t: QOpenGLProgramBinaryCache::ShaderDesc(toShaderStage(type: stage.type()), source));
5187 }
5188
5189 *cacheKey = binaryProgram.cacheKey();
5190
5191 // Try our pipeline cache simulation first, if it got seeded with
5192 // setPipelineCacheData and there's a hit, then no need to go to the
5193 // filesystem at all.
5194 if (pipelineCacheEnabled) {
5195 auto it = m_pipelineCache.constFind(key: *cacheKey);
5196 if (it != m_pipelineCache.constEnd()) {
5197 GLenum err;
5198 for ( ; ; ) {
5199 err = f->glGetError();
5200 if (err == GL_NO_ERROR || err == GL_CONTEXT_LOST)
5201 break;
5202 }
5203 f->glProgramBinary(program, binaryFormat: it->format, binary: it->data.constData(), length: it->data.size());
5204 err = f->glGetError();
5205 if (err == GL_NO_ERROR) {
5206 GLint linkStatus = 0;
5207 f->glGetProgramiv(program, GL_LINK_STATUS, params: &linkStatus);
5208 if (linkStatus == GL_TRUE)
5209 return QRhiGles2::ProgramCacheHit;
5210 }
5211 }
5212 }
5213
5214 if (legacyDiskCacheEnabled && qrhi_programBinaryCache()->load(cacheKey: *cacheKey, programId: program)) {
5215 // use the logging category QOpenGLShaderProgram would
5216 qCDebug(lcOpenGLProgramDiskCache, "Program binary received from cache, program %u, key %s",
5217 program, cacheKey->constData());
5218 return QRhiGles2::ProgramCacheHit;
5219 }
5220 }
5221
5222 return QRhiGles2::ProgramCacheMiss;
5223}
5224
5225void QRhiGles2::trySaveToDiskCache(GLuint program, const QByteArray &cacheKey)
5226{
5227 // This is only for the traditional QOpenGL disk cache since Qt 5.9.
5228
5229 if (isProgramBinaryDiskCacheEnabled()) {
5230 // use the logging category QOpenGLShaderProgram would
5231 qCDebug(lcOpenGLProgramDiskCache, "Saving program binary, program %u, key %s",
5232 program, cacheKey.constData());
5233 qrhi_programBinaryCache()->save(cacheKey, programId: program);
5234 }
5235}
5236
5237void QRhiGles2::trySaveToPipelineCache(GLuint program, const QByteArray &cacheKey, bool force)
5238{
5239 // This handles our own simulated "pipeline cache". (specific to QRhi, not
5240 // shared with legacy QOpenGL* stuff)
5241
5242 if (caps.programBinary && (force || !m_pipelineCache.contains(key: cacheKey))) {
5243 GLint blobSize = 0;
5244 f->glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, params: &blobSize);
5245 QByteArray blob(blobSize, Qt::Uninitialized);
5246 GLint outSize = 0;
5247 GLenum binaryFormat = 0;
5248 f->glGetProgramBinary(program, bufSize: blobSize, length: &outSize, binaryFormat: &binaryFormat, binary: blob.data());
5249 if (blobSize == outSize)
5250 m_pipelineCache.insert(key: cacheKey, value: { .format: binaryFormat, .data: blob });
5251 }
5252}
5253
5254QGles2Buffer::QGles2Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size)
5255 : QRhiBuffer(rhi, type, usage, size)
5256{
5257}
5258
5259QGles2Buffer::~QGles2Buffer()
5260{
5261 destroy();
5262}
5263
5264void QGles2Buffer::destroy()
5265{
5266 data.clear();
5267 if (!buffer)
5268 return;
5269
5270 QRhiGles2::DeferredReleaseEntry e;
5271 e.type = QRhiGles2::DeferredReleaseEntry::Buffer;
5272
5273 e.buffer.buffer = buffer;
5274 buffer = 0;
5275
5276 QRHI_RES_RHI(QRhiGles2);
5277 if (rhiD) {
5278 rhiD->releaseQueue.append(t: e);
5279 rhiD->unregisterResource(res: this);
5280 }
5281}
5282
5283bool QGles2Buffer::create()
5284{
5285 if (buffer)
5286 destroy();
5287
5288 QRHI_RES_RHI(QRhiGles2);
5289
5290 nonZeroSize = m_size <= 0 ? 256 : m_size;
5291
5292 if (m_usage.testFlag(flag: QRhiBuffer::UniformBuffer)) {
5293 if (int(m_usage) != QRhiBuffer::UniformBuffer) {
5294 qWarning(msg: "Uniform buffer: multiple usages specified, this is not supported by the OpenGL backend");
5295 return false;
5296 }
5297 data.resize(size: nonZeroSize);
5298 return true;
5299 }
5300
5301 if (!rhiD->ensureContext())
5302 return false;
5303
5304 targetForDataOps = GL_ARRAY_BUFFER;
5305 if (m_usage.testFlag(flag: QRhiBuffer::IndexBuffer))
5306 targetForDataOps = GL_ELEMENT_ARRAY_BUFFER;
5307 else if (m_usage.testFlag(flag: QRhiBuffer::StorageBuffer))
5308 targetForDataOps = GL_SHADER_STORAGE_BUFFER;
5309
5310 rhiD->f->glGenBuffers(n: 1, buffers: &buffer);
5311 rhiD->f->glBindBuffer(target: targetForDataOps, buffer);
5312 rhiD->f->glBufferData(target: targetForDataOps, size: nonZeroSize, data: nullptr, usage: m_type == Dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
5313
5314 if (rhiD->glObjectLabel)
5315 rhiD->glObjectLabel(GL_BUFFER, buffer, -1, m_objectName.constData());
5316
5317 usageState.access = AccessNone;
5318
5319 rhiD->registerResource(res: this);
5320 return true;
5321}
5322
5323QRhiBuffer::NativeBuffer QGles2Buffer::nativeBuffer()
5324{
5325 if (m_usage.testFlag(flag: QRhiBuffer::UniformBuffer))
5326 return { .objects: {}, .slotCount: 0 };
5327
5328 return { .objects: { &buffer }, .slotCount: 1 };
5329}
5330
5331char *QGles2Buffer::beginFullDynamicBufferUpdateForCurrentFrame()
5332{
5333 Q_ASSERT(m_type == Dynamic);
5334 if (!m_usage.testFlag(flag: UniformBuffer)) {
5335 QRHI_RES_RHI(QRhiGles2);
5336 rhiD->f->glBindBuffer(target: targetForDataOps, buffer);
5337 if (rhiD->caps.properMapBuffer) {
5338 return static_cast<char *>(rhiD->f->glMapBufferRange(target: targetForDataOps, offset: 0, length: nonZeroSize,
5339 GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT));
5340 } else {
5341 // Need some storage for the data, use the otherwise unused 'data' member.
5342 if (data.isEmpty())
5343 data.resize(size: nonZeroSize);
5344 }
5345 }
5346 return data.data();
5347}
5348
5349void QGles2Buffer::endFullDynamicBufferUpdateForCurrentFrame()
5350{
5351 if (!m_usage.testFlag(flag: UniformBuffer)) {
5352 QRHI_RES_RHI(QRhiGles2);
5353 rhiD->f->glBindBuffer(target: targetForDataOps, buffer);
5354 if (rhiD->caps.properMapBuffer)
5355 rhiD->f->glUnmapBuffer(target: targetForDataOps);
5356 else
5357 rhiD->f->glBufferSubData(target: targetForDataOps, offset: 0, size: nonZeroSize, data: data.data());
5358 }
5359}
5360
5361void QGles2Buffer::fullDynamicBufferUpdateForCurrentFrame(const void *bufferData, quint32 size)
5362{
5363 const quint32 copySize = size > 0 ? size : m_size;
5364 if (!m_usage.testFlag(flag: UniformBuffer)) {
5365 QRHI_RES_RHI(QRhiGles2);
5366 rhiD->f->glBindBuffer(target: targetForDataOps, buffer);
5367 rhiD->f->glBufferSubData(target: targetForDataOps, offset: 0, size: copySize, data: bufferData);
5368 } else {
5369 memcpy(dest: data.data(), src: bufferData, n: copySize);
5370 }
5371}
5372
5373QGles2RenderBuffer::QGles2RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
5374 int sampleCount, QRhiRenderBuffer::Flags flags,
5375 QRhiTexture::Format backingFormatHint)
5376 : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags, backingFormatHint)
5377{
5378}
5379
5380QGles2RenderBuffer::~QGles2RenderBuffer()
5381{
5382 destroy();
5383}
5384
5385void QGles2RenderBuffer::destroy()
5386{
5387 if (!renderbuffer)
5388 return;
5389
5390 QRhiGles2::DeferredReleaseEntry e;
5391 e.type = QRhiGles2::DeferredReleaseEntry::RenderBuffer;
5392
5393 e.renderbuffer.renderbuffer = renderbuffer;
5394 e.renderbuffer.renderbuffer2 = stencilRenderbuffer;
5395
5396 renderbuffer = 0;
5397 stencilRenderbuffer = 0;
5398
5399 QRHI_RES_RHI(QRhiGles2);
5400 if (rhiD) {
5401 if (owns)
5402 rhiD->releaseQueue.append(t: e);
5403 rhiD->unregisterResource(res: this);
5404 }
5405}
5406
5407bool QGles2RenderBuffer::create()
5408{
5409 if (renderbuffer)
5410 destroy();
5411
5412 QRHI_RES_RHI(QRhiGles2);
5413 samples = rhiD->effectiveSampleCount(sampleCount: m_sampleCount);
5414
5415 if (m_flags.testFlag(flag: UsedWithSwapChainOnly)) {
5416 if (m_type == DepthStencil)
5417 return true;
5418
5419 qWarning(msg: "RenderBuffer: UsedWithSwapChainOnly is meaningless in combination with Color");
5420 }
5421
5422 if (!rhiD->ensureContext())
5423 return false;
5424
5425 rhiD->f->glGenRenderbuffers(n: 1, renderbuffers: &renderbuffer);
5426 rhiD->f->glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
5427
5428 const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
5429
5430 switch (m_type) {
5431 case QRhiRenderBuffer::DepthStencil:
5432 if (rhiD->caps.msaaRenderBuffer && samples > 1) {
5433 rhiD->f->glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH24_STENCIL8,
5434 width: size.width(), height: size.height());
5435 stencilRenderbuffer = 0;
5436 } else if (rhiD->caps.packedDepthStencil || rhiD->caps.needsDepthStencilCombinedAttach) {
5437 const GLenum storage = rhiD->caps.needsDepthStencilCombinedAttach ? GL_DEPTH_STENCIL : GL_DEPTH24_STENCIL8;
5438 rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, internalformat: storage,
5439 width: size.width(), height: size.height());
5440 stencilRenderbuffer = 0;
5441 } else {
5442 GLenum depthStorage = GL_DEPTH_COMPONENT;
5443 if (rhiD->caps.gles) {
5444 if (rhiD->caps.depth24)
5445 depthStorage = GL_DEPTH_COMPONENT24;
5446 else
5447 depthStorage = GL_DEPTH_COMPONENT16; // plain ES 2.0 only has this
5448 }
5449 const GLenum stencilStorage = rhiD->caps.gles ? GL_STENCIL_INDEX8 : GL_STENCIL_INDEX;
5450 rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, internalformat: depthStorage,
5451 width: size.width(), height: size.height());
5452 rhiD->f->glGenRenderbuffers(n: 1, renderbuffers: &stencilRenderbuffer);
5453 rhiD->f->glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer: stencilRenderbuffer);
5454 rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, internalformat: stencilStorage,
5455 width: size.width(), height: size.height());
5456 }
5457 break;
5458 case QRhiRenderBuffer::Color:
5459 {
5460 GLenum internalFormat = GL_RGBA4; // ES 2.0
5461 if (rhiD->caps.rgba8Format) {
5462 internalFormat = GL_RGBA8;
5463 if (m_backingFormatHint != QRhiTexture::UnknownFormat) {
5464 GLenum glintformat, glformat, gltype;
5465 // only care about the sized internal format, the rest is not used here
5466 toGlTextureFormat(format: m_backingFormatHint, caps: rhiD->caps,
5467 glintformat: &glintformat, glsizedintformat: &internalFormat, glformat: &glformat, gltype: &gltype);
5468 }
5469 }
5470 if (rhiD->caps.msaaRenderBuffer && samples > 1) {
5471 rhiD->f->glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, internalformat: internalFormat,
5472 width: size.width(), height: size.height());
5473 } else {
5474 rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, internalformat: internalFormat,
5475 width: size.width(), height: size.height());
5476 }
5477 }
5478 break;
5479 default:
5480 Q_UNREACHABLE();
5481 break;
5482 }
5483
5484 if (rhiD->glObjectLabel)
5485 rhiD->glObjectLabel(GL_RENDERBUFFER, renderbuffer, -1, m_objectName.constData());
5486
5487 owns = true;
5488 generation += 1;
5489 rhiD->registerResource(res: this);
5490 return true;
5491}
5492
5493bool QGles2RenderBuffer::createFrom(NativeRenderBuffer src)
5494{
5495 if (!src.object)
5496 return false;
5497
5498 if (renderbuffer)
5499 destroy();
5500
5501 QRHI_RES_RHI(QRhiGles2);
5502 samples = rhiD->effectiveSampleCount(sampleCount: m_sampleCount);
5503
5504 if (m_flags.testFlag(flag: UsedWithSwapChainOnly))
5505 qWarning(msg: "RenderBuffer: UsedWithSwapChainOnly is meaningless when importing an existing native object");
5506
5507 if (!rhiD->ensureContext())
5508 return false;
5509
5510 renderbuffer = src.object;
5511
5512 owns = false;
5513 generation += 1;
5514 rhiD->registerResource(res: this);
5515 return true;
5516}
5517
5518QRhiTexture::Format QGles2RenderBuffer::backingFormat() const
5519{
5520 if (m_backingFormatHint != QRhiTexture::UnknownFormat)
5521 return m_backingFormatHint;
5522 else
5523 return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
5524}
5525
5526QGles2Texture::QGles2Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
5527 int arraySize, int sampleCount, Flags flags)
5528 : QRhiTexture(rhi, format, pixelSize, depth, arraySize, sampleCount, flags)
5529{
5530}
5531
5532QGles2Texture::~QGles2Texture()
5533{
5534 destroy();
5535}
5536
5537void QGles2Texture::destroy()
5538{
5539 if (!texture)
5540 return;
5541
5542 QRhiGles2::DeferredReleaseEntry e;
5543 e.type = QRhiGles2::DeferredReleaseEntry::Texture;
5544
5545 e.texture.texture = texture;
5546
5547 texture = 0;
5548 specified = false;
5549 zeroInitialized = false;
5550
5551 QRHI_RES_RHI(QRhiGles2);
5552 if (rhiD) {
5553 if (owns)
5554 rhiD->releaseQueue.append(t: e);
5555 rhiD->unregisterResource(res: this);
5556 }
5557}
5558
5559bool QGles2Texture::prepareCreate(QSize *adjustedSize)
5560{
5561 if (texture)
5562 destroy();
5563
5564 QRHI_RES_RHI(QRhiGles2);
5565 if (!rhiD->ensureContext())
5566 return false;
5567
5568 const bool isCube = m_flags.testFlag(flag: CubeMap);
5569 const bool isArray = m_flags.testFlag(flag: QRhiTexture::TextureArray);
5570 const bool is3D = m_flags.testFlag(flag: ThreeDimensional);
5571 const bool hasMipMaps = m_flags.testFlag(flag: MipMapped);
5572 const bool isCompressed = rhiD->isCompressedFormat(format: m_format);
5573 const bool is1D = m_flags.testFlag(flag: OneDimensional);
5574
5575 const QSize size = is1D ? QSize(qMax(a: 1, b: m_pixelSize.width()), 1)
5576 : (m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize);
5577
5578 if (is3D && !rhiD->caps.texture3D) {
5579 qWarning(msg: "3D textures are not supported");
5580 return false;
5581 }
5582 if (isCube && is3D) {
5583 qWarning(msg: "Texture cannot be both cube and 3D");
5584 return false;
5585 }
5586 if (isArray && is3D) {
5587 qWarning(msg: "Texture cannot be both array and 3D");
5588 return false;
5589 }
5590 if (is1D && !rhiD->caps.texture1D) {
5591 qWarning(msg: "1D textures are not supported");
5592 return false;
5593 }
5594 if (is1D && is3D) {
5595 qWarning(msg: "Texture cannot be both 1D and 3D");
5596 return false;
5597 }
5598 if (is1D && isCube) {
5599 qWarning(msg: "Texture cannot be both 1D and cube");
5600 return false;
5601 }
5602
5603 if (m_depth > 1 && !is3D) {
5604 qWarning(msg: "Texture cannot have a depth of %d when it is not 3D", m_depth);
5605 return false;
5606 }
5607 if (m_arraySize > 0 && !isArray) {
5608 qWarning(msg: "Texture cannot have an array size of %d when it is not an array", m_arraySize);
5609 return false;
5610 }
5611 if (m_arraySize < 1 && isArray) {
5612 qWarning(msg: "Texture is an array but array size is %d", m_arraySize);
5613 return false;
5614 }
5615
5616 target = isCube ? GL_TEXTURE_CUBE_MAP
5617 : m_sampleCount > 1 ? (isArray ? GL_TEXTURE_2D_MULTISAMPLE_ARRAY : GL_TEXTURE_2D_MULTISAMPLE)
5618 : (is3D ? GL_TEXTURE_3D
5619 : (is1D ? (isArray ? GL_TEXTURE_1D_ARRAY : GL_TEXTURE_1D)
5620 : (isArray ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D)));
5621
5622 if (m_flags.testFlag(flag: ExternalOES))
5623 target = GL_TEXTURE_EXTERNAL_OES;
5624 else if (m_flags.testFlag(flag: TextureRectangleGL))
5625 target = GL_TEXTURE_RECTANGLE;
5626
5627 mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
5628 gltype = GL_UNSIGNED_BYTE;
5629
5630 if (isCompressed) {
5631 if (m_flags.testFlag(flag: UsedWithLoadStore)) {
5632 qWarning(msg: "Compressed texture cannot be used with image load/store");
5633 return false;
5634 }
5635 glintformat = toGlCompressedTextureFormat(format: m_format, flags: m_flags);
5636 if (!glintformat) {
5637 qWarning(msg: "Compressed format %d not mappable to GL compressed format", m_format);
5638 return false;
5639 }
5640 glsizedintformat = glintformat;
5641 glformat = GL_RGBA;
5642 } else {
5643 toGlTextureFormat(format: m_format, caps: rhiD->caps,
5644 glintformat: &glintformat, glsizedintformat: &glsizedintformat, glformat: &glformat, gltype: &gltype);
5645 }
5646
5647 samplerState = QGles2SamplerData();
5648
5649 usageState.access = AccessNone;
5650
5651 if (adjustedSize)
5652 *adjustedSize = size;
5653
5654 return true;
5655}
5656
5657bool QGles2Texture::create()
5658{
5659 QSize size;
5660 if (!prepareCreate(adjustedSize: &size))
5661 return false;
5662
5663 QRHI_RES_RHI(QRhiGles2);
5664 rhiD->f->glGenTextures(n: 1, textures: &texture);
5665
5666 const bool isCube = m_flags.testFlag(flag: CubeMap);
5667 const bool isArray = m_flags.testFlag(flag: QRhiTexture::TextureArray);
5668 const bool is3D = m_flags.testFlag(flag: ThreeDimensional);
5669 const bool hasMipMaps = m_flags.testFlag(flag: MipMapped);
5670 const bool isCompressed = rhiD->isCompressedFormat(format: m_format);
5671 const bool is1D = m_flags.testFlag(flag: OneDimensional);
5672
5673 if (!isCompressed) {
5674 rhiD->f->glBindTexture(target, texture);
5675 if (!m_flags.testFlag(flag: UsedWithLoadStore)) {
5676 if (is1D) {
5677 for (int level = 0; level < mipLevelCount; ++level) {
5678 const QSize mipSize = rhiD->q->sizeForMipLevel(mipLevel: level, baseLevelSize: size);
5679 if (isArray)
5680 rhiD->f->glTexImage2D(target, level, internalformat: GLint(glintformat), width: mipSize.width(),
5681 height: qMax(a: 0, b: m_arraySize), border: 0, format: glformat, type: gltype, pixels: nullptr);
5682 else
5683 rhiD->glTexImage1D(target, level, GLint(glintformat), mipSize.width(), 0,
5684 glformat, gltype, nullptr);
5685 }
5686 } else if (is3D || isArray) {
5687 const int layerCount = is3D ? qMax(a: 1, b: m_depth) : qMax(a: 0, b: m_arraySize);
5688 if (hasMipMaps) {
5689 for (int level = 0; level != mipLevelCount; ++level) {
5690 const QSize mipSize = rhiD->q->sizeForMipLevel(mipLevel: level, baseLevelSize: size);
5691 rhiD->f->glTexImage3D(target, level, internalformat: GLint(glintformat), width: mipSize.width(), height: mipSize.height(), depth: layerCount,
5692 border: 0, format: glformat, type: gltype, pixels: nullptr);
5693 }
5694 } else {
5695 rhiD->f->glTexImage3D(target, level: 0, internalformat: GLint(glintformat), width: size.width(), height: size.height(), depth: layerCount,
5696 border: 0, format: glformat, type: gltype, pixels: nullptr);
5697 }
5698 } else if (hasMipMaps || isCube) {
5699 const GLenum faceTargetBase = isCube ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : target;
5700 for (int layer = 0, layerCount = isCube ? 6 : 1; layer != layerCount; ++layer) {
5701 for (int level = 0; level != mipLevelCount; ++level) {
5702 const QSize mipSize = rhiD->q->sizeForMipLevel(mipLevel: level, baseLevelSize: size);
5703 rhiD->f->glTexImage2D(target: faceTargetBase + uint(layer), level, internalformat: GLint(glintformat),
5704 width: mipSize.width(), height: mipSize.height(), border: 0,
5705 format: glformat, type: gltype, pixels: nullptr);
5706 }
5707 }
5708 } else {
5709 // 2D texture. For multisample textures the GLES 3.1
5710 // glStorage2DMultisample must be used for portability.
5711 if (m_sampleCount > 1 && rhiD->caps.multisampledTexture) {
5712 // internal format must be sized
5713 rhiD->f->glTexStorage2DMultisample(target, samples: m_sampleCount, internalformat: glsizedintformat,
5714 width: size.width(), height: size.height(), GL_TRUE);
5715 } else {
5716 rhiD->f->glTexImage2D(target, level: 0, internalformat: GLint(glintformat), width: size.width(), height: size.height(),
5717 border: 0, format: glformat, type: gltype, pixels: nullptr);
5718 }
5719 }
5720 } else {
5721 // Must be specified with immutable storage functions otherwise
5722 // bindImageTexture may fail. Also, the internal format must be a
5723 // sized format here.
5724 if (is1D && !isArray)
5725 rhiD->glTexStorage1D(target, mipLevelCount, glsizedintformat, size.width());
5726 else if (!is1D && (is3D || isArray))
5727 rhiD->f->glTexStorage3D(target, levels: mipLevelCount, internalformat: glsizedintformat, width: size.width(), height: size.height(),
5728 depth: is3D ? qMax(a: 1, b: m_depth) : qMax(a: 0, b: m_arraySize));
5729 else if (m_sampleCount > 1)
5730 rhiD->f->glTexStorage2DMultisample(target, samples: m_sampleCount, internalformat: glsizedintformat,
5731 width: size.width(), height: size.height(), GL_TRUE);
5732 else
5733 rhiD->f->glTexStorage2D(target, levels: mipLevelCount, internalformat: glsizedintformat, width: size.width(),
5734 height: is1D ? qMax(a: 0, b: m_arraySize) : size.height());
5735 }
5736 // Make sure the min filter is set to something non-mipmap-based already
5737 // here, given the ridiculous default of GL. It is changed based on
5738 // the sampler later, but there could be cases when one pulls the native
5739 // object out via nativeTexture() right away.
5740 rhiD->f->glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
5741 specified = true;
5742 } else {
5743 // Cannot use glCompressedTexImage2D without valid data, so defer.
5744 // Compressed textures will not be used as render targets so this is
5745 // not an issue.
5746 specified = false;
5747 }
5748
5749 if (rhiD->glObjectLabel)
5750 rhiD->glObjectLabel(GL_TEXTURE, texture, -1, m_objectName.constData());
5751
5752 owns = true;
5753
5754 generation += 1;
5755 rhiD->registerResource(res: this);
5756 return true;
5757}
5758
5759bool QGles2Texture::createFrom(QRhiTexture::NativeTexture src)
5760{
5761 const uint textureId = uint(src.object);
5762 if (textureId == 0)
5763 return false;
5764
5765 if (!prepareCreate())
5766 return false;
5767
5768 texture = textureId;
5769 specified = true;
5770 zeroInitialized = true;
5771
5772 owns = false;
5773
5774 generation += 1;
5775 QRHI_RES_RHI(QRhiGles2);
5776 rhiD->registerResource(res: this);
5777 return true;
5778}
5779
5780QRhiTexture::NativeTexture QGles2Texture::nativeTexture()
5781{
5782 return {.object: texture, .layout: 0};
5783}
5784
5785QGles2Sampler::QGles2Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
5786 AddressMode u, AddressMode v, AddressMode w)
5787 : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w)
5788{
5789}
5790
5791QGles2Sampler::~QGles2Sampler()
5792{
5793 destroy();
5794}
5795
5796void QGles2Sampler::destroy()
5797{
5798 QRHI_RES_RHI(QRhiGles2);
5799 if (rhiD)
5800 rhiD->unregisterResource(res: this);
5801}
5802
5803bool QGles2Sampler::create()
5804{
5805 d.glminfilter = toGlMinFilter(f: m_minFilter, m: m_mipmapMode);
5806 d.glmagfilter = toGlMagFilter(f: m_magFilter);
5807 d.glwraps = toGlWrapMode(m: m_addressU);
5808 d.glwrapt = toGlWrapMode(m: m_addressV);
5809 d.glwrapr = toGlWrapMode(m: m_addressW);
5810 d.gltexcomparefunc = toGlTextureCompareFunc(op: m_compareOp);
5811
5812 generation += 1;
5813 QRHI_RES_RHI(QRhiGles2);
5814 rhiD->registerResource(res: this, ownsNativeResources: false);
5815 return true;
5816}
5817
5818// dummy, no Vulkan-style RenderPass+Framebuffer concept here
5819QGles2RenderPassDescriptor::QGles2RenderPassDescriptor(QRhiImplementation *rhi)
5820 : QRhiRenderPassDescriptor(rhi)
5821{
5822}
5823
5824QGles2RenderPassDescriptor::~QGles2RenderPassDescriptor()
5825{
5826 destroy();
5827}
5828
5829void QGles2RenderPassDescriptor::destroy()
5830{
5831 QRHI_RES_RHI(QRhiGles2);
5832 if (rhiD)
5833 rhiD->unregisterResource(res: this);
5834}
5835
5836bool QGles2RenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const
5837{
5838 Q_UNUSED(other);
5839 return true;
5840}
5841
5842QRhiRenderPassDescriptor *QGles2RenderPassDescriptor::newCompatibleRenderPassDescriptor() const
5843{
5844 QGles2RenderPassDescriptor *rpD = new QGles2RenderPassDescriptor(m_rhi);
5845 QRHI_RES_RHI(QRhiGles2);
5846 rhiD->registerResource(res: rpD, ownsNativeResources: false);
5847 return rpD;
5848}
5849
5850QVector<quint32> QGles2RenderPassDescriptor::serializedFormat() const
5851{
5852 return {};
5853}
5854
5855QGles2SwapChainRenderTarget::QGles2SwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain)
5856 : QRhiSwapChainRenderTarget(rhi, swapchain),
5857 d(rhi)
5858{
5859}
5860
5861QGles2SwapChainRenderTarget::~QGles2SwapChainRenderTarget()
5862{
5863 destroy();
5864}
5865
5866void QGles2SwapChainRenderTarget::destroy()
5867{
5868 // nothing to do here
5869}
5870
5871QSize QGles2SwapChainRenderTarget::pixelSize() const
5872{
5873 return d.pixelSize;
5874}
5875
5876float QGles2SwapChainRenderTarget::devicePixelRatio() const
5877{
5878 return d.dpr;
5879}
5880
5881int QGles2SwapChainRenderTarget::sampleCount() const
5882{
5883 return d.sampleCount;
5884}
5885
5886QGles2TextureRenderTarget::QGles2TextureRenderTarget(QRhiImplementation *rhi,
5887 const QRhiTextureRenderTargetDescription &desc,
5888 Flags flags)
5889 : QRhiTextureRenderTarget(rhi, desc, flags),
5890 d(rhi)
5891{
5892}
5893
5894QGles2TextureRenderTarget::~QGles2TextureRenderTarget()
5895{
5896 destroy();
5897}
5898
5899void QGles2TextureRenderTarget::destroy()
5900{
5901 if (!framebuffer)
5902 return;
5903
5904 QRhiGles2::DeferredReleaseEntry e;
5905 e.type = QRhiGles2::DeferredReleaseEntry::TextureRenderTarget;
5906
5907 e.textureRenderTarget.framebuffer = framebuffer;
5908 e.textureRenderTarget.nonMsaaThrowawayDepthTexture = nonMsaaThrowawayDepthTexture;
5909
5910 framebuffer = 0;
5911 nonMsaaThrowawayDepthTexture = 0;
5912
5913 QRHI_RES_RHI(QRhiGles2);
5914 if (rhiD) {
5915 rhiD->releaseQueue.append(t: e);
5916 rhiD->unregisterResource(res: this);
5917 }
5918}
5919
5920QRhiRenderPassDescriptor *QGles2TextureRenderTarget::newCompatibleRenderPassDescriptor()
5921{
5922 QGles2RenderPassDescriptor *rpD = new QGles2RenderPassDescriptor(m_rhi);
5923 QRHI_RES_RHI(QRhiGles2);
5924 rhiD->registerResource(res: rpD, ownsNativeResources: false);
5925 return rpD;
5926}
5927
5928bool QGles2TextureRenderTarget::create()
5929{
5930 QRHI_RES_RHI(QRhiGles2);
5931
5932 if (framebuffer)
5933 destroy();
5934
5935 const bool hasColorAttachments = m_desc.colorAttachmentCount() > 0;
5936 Q_ASSERT(hasColorAttachments || m_desc.depthTexture());
5937 Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
5938 const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
5939
5940 if (hasColorAttachments) {
5941 const int count = int(m_desc.colorAttachmentCount());
5942 if (count > rhiD->caps.maxDrawBuffers) {
5943 qWarning(msg: "QGles2TextureRenderTarget: Too many color attachments (%d, max is %d)",
5944 count, rhiD->caps.maxDrawBuffers);
5945 }
5946 }
5947 if (m_desc.depthTexture() && !rhiD->caps.depthTexture)
5948 qWarning(msg: "QGles2TextureRenderTarget: Depth texture is not supported and will be ignored");
5949
5950 if (!rhiD->ensureContext())
5951 return false;
5952
5953 rhiD->f->glGenFramebuffers(n: 1, framebuffers: &framebuffer);
5954 rhiD->f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
5955
5956 d.colorAttCount = 0;
5957 int attIndex = 0;
5958 int multiViewCount = 0;
5959 for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) {
5960 d.colorAttCount += 1;
5961 const QRhiColorAttachment &colorAtt(*it);
5962 QRhiTexture *texture = colorAtt.texture();
5963 QRhiRenderBuffer *renderBuffer = colorAtt.renderBuffer();
5964 Q_ASSERT(texture || renderBuffer);
5965 if (texture) {
5966 QGles2Texture *texD = QRHI_RES(QGles2Texture, texture);
5967 Q_ASSERT(texD->texture && texD->specified);
5968 if (texD->flags().testFlag(flag: QRhiTexture::ThreeDimensional) || texD->flags().testFlag(flag: QRhiTexture::TextureArray)) {
5969 if (colorAtt.multiViewCount() < 2) {
5970 rhiD->f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), texture: texD->texture,
5971 level: colorAtt.level(), layer: colorAtt.layer());
5972 } else {
5973 multiViewCount = colorAtt.multiViewCount();
5974 if (texD->sampleCount() > 1 && rhiD->caps.glesMultiviewMultisampleRenderToTexture && colorAtt.resolveTexture()) {
5975 // Special path for GLES and GL_OVR_multiview_multisampled_render_to_texture:
5976 // ignore the color attachment's (multisample) texture
5977 // array and give the resolve texture array to GL. (no
5978 // explicit resolving is needed by us later on)
5979 QGles2Texture *resolveTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture());
5980 rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER,
5981 GL_COLOR_ATTACHMENT0 + uint(attIndex),
5982 resolveTexD->texture,
5983 colorAtt.resolveLevel(),
5984 texD->sampleCount(),
5985 colorAtt.resolveLayer(),
5986 multiViewCount);
5987 } else {
5988 rhiD->glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER,
5989 GL_COLOR_ATTACHMENT0 + uint(attIndex),
5990 texD->texture,
5991 colorAtt.level(),
5992 colorAtt.layer(),
5993 multiViewCount);
5994 }
5995 }
5996 } else if (texD->flags().testFlag(flag: QRhiTexture::OneDimensional)) {
5997 rhiD->glFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex),
5998 texD->target + uint(colorAtt.layer()), texD->texture,
5999 colorAtt.level());
6000 } else {
6001 if (texD->sampleCount() > 1 && rhiD->caps.glesMultisampleRenderToTexture && colorAtt.resolveTexture()) {
6002 // Special path for GLES and GL_EXT_multisampled_render_to_texture:
6003 // ignore the color attachment's (multisample) texture and
6004 // give the resolve texture to GL. (no explicit resolving is
6005 // needed by us later on)
6006 QGles2Texture *resolveTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture());
6007 const GLenum faceTargetBase = resolveTexD->flags().testFlag(flag: QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : resolveTexD->target;
6008 rhiD->glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), faceTargetBase + uint(colorAtt.resolveLayer()),
6009 resolveTexD->texture, colorAtt.level(), texD->sampleCount());
6010 } else {
6011 const GLenum faceTargetBase = texD->flags().testFlag(flag: QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target;
6012 rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), textarget: faceTargetBase + uint(colorAtt.layer()),
6013 texture: texD->texture, level: colorAtt.level());
6014 }
6015 }
6016 if (attIndex == 0) {
6017 d.pixelSize = rhiD->q->sizeForMipLevel(mipLevel: colorAtt.level(), baseLevelSize: texD->pixelSize());
6018 d.sampleCount = texD->sampleCount();
6019 }
6020 } else if (renderBuffer) {
6021 QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, renderBuffer);
6022 rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), GL_RENDERBUFFER, renderbuffer: rbD->renderbuffer);
6023 if (attIndex == 0) {
6024 d.pixelSize = rbD->pixelSize();
6025 d.sampleCount = rbD->samples;
6026 }
6027 }
6028 }
6029
6030 if (hasDepthStencil) {
6031 if (m_desc.depthStencilBuffer()) {
6032 QGles2RenderBuffer *depthRbD = QRHI_RES(QGles2RenderBuffer, m_desc.depthStencilBuffer());
6033 if (rhiD->caps.needsDepthStencilCombinedAttach) {
6034 rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
6035 renderbuffer: depthRbD->renderbuffer);
6036 } else {
6037 rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
6038 renderbuffer: depthRbD->renderbuffer);
6039 if (depthRbD->stencilRenderbuffer) {
6040 rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
6041 renderbuffer: depthRbD->stencilRenderbuffer);
6042 } else {
6043 // packed depth-stencil
6044 rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
6045 renderbuffer: depthRbD->renderbuffer);
6046 }
6047 }
6048 if (d.colorAttCount == 0) {
6049 d.pixelSize = depthRbD->pixelSize();
6050 d.sampleCount = depthRbD->samples;
6051 }
6052 } else {
6053 QGles2Texture *depthTexD = QRHI_RES(QGles2Texture, m_desc.depthTexture());
6054 if (multiViewCount < 2) {
6055 if (depthTexD->sampleCount() > 1 && rhiD->caps.glesMultisampleRenderToTexture && m_desc.depthResolveTexture()) {
6056 // Special path for GLES and
6057 // GL_EXT_multisampled_render_to_texture, for depth-stencil.
6058 // Relevant only when depthResolveTexture is set.
6059 QGles2Texture *depthResolveTexD = QRHI_RES(QGles2Texture, m_desc.depthResolveTexture());
6060 rhiD->glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthResolveTexD->target,
6061 depthResolveTexD->texture, 0, depthTexD->sampleCount());
6062 if (rhiD->isStencilSupportingFormat(format: depthResolveTexD->format())) {
6063 rhiD->glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthResolveTexD->target,
6064 depthResolveTexD->texture, 0, depthTexD->sampleCount());
6065 }
6066 } else {
6067 rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, textarget: depthTexD->target,
6068 texture: depthTexD->texture, level: 0);
6069 if (rhiD->isStencilSupportingFormat(format: depthTexD->format())) {
6070 rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, textarget: depthTexD->target,
6071 texture: depthTexD->texture, level: 0);
6072 }
6073 }
6074 } else {
6075 if (depthTexD->sampleCount() > 1 && rhiD->caps.glesMultiviewMultisampleRenderToTexture) {
6076 // And so it turns out
6077 // https://registry.khronos.org/OpenGL/extensions/OVR/OVR_multiview.txt
6078 // does not work with multisample 2D texture arrays. (at least
6079 // that's what Issue 30 in the extension spec seems to imply)
6080 //
6081 // There is https://registry.khronos.org/OpenGL/extensions/EXT/EXT_multiview_texture_multisample.txt
6082 // that seems to resolve that, but that does not seem to
6083 // work (or not available) on GLES devices such as the Quest 3.
6084 //
6085 // So instead, on GLES we can use the
6086 // multisample-multiview-auto-resolving version (which in
6087 // turn is not supported on desktop GL e.g. by NVIDIA), too
6088 // bad we have a multisample depth texture array here as
6089 // every other API out there requires that. So, in absence
6090 // of a depthResolveTexture, create a temporary one ignoring
6091 // what the user has already created.
6092 //
6093 if (!m_flags.testFlag(flag: DoNotStoreDepthStencilContents) && !m_desc.depthResolveTexture()) {
6094 qWarning(msg: "Attempted to create a multiview+multisample QRhiTextureRenderTarget, but DoNotStoreDepthStencilContents was not set."
6095 " This path has no choice but to behave as if DoNotStoreDepthStencilContents was set, because QRhi is forced to create"
6096 " a throwaway non-multisample depth texture here. Set the flag to silence this warning, or set a depthResolveTexture.");
6097 }
6098 if (m_desc.depthResolveTexture()) {
6099 QGles2Texture *depthResolveTexD = QRHI_RES(QGles2Texture, m_desc.depthResolveTexture());
6100 rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER,
6101 GL_DEPTH_ATTACHMENT,
6102 depthResolveTexD->texture,
6103 0,
6104 depthTexD->sampleCount(),
6105 0,
6106 multiViewCount);
6107 if (rhiD->isStencilSupportingFormat(format: depthResolveTexD->format())) {
6108 rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER,
6109 GL_STENCIL_ATTACHMENT,
6110 depthResolveTexD->texture,
6111 0,
6112 depthTexD->sampleCount(),
6113 0,
6114 multiViewCount);
6115 }
6116 } else {
6117 if (!nonMsaaThrowawayDepthTexture) {
6118 rhiD->f->glGenTextures(n: 1, textures: &nonMsaaThrowawayDepthTexture);
6119 rhiD->f->glBindTexture(GL_TEXTURE_2D_ARRAY, texture: nonMsaaThrowawayDepthTexture);
6120 rhiD->f->glTexStorage3D(GL_TEXTURE_2D_ARRAY, levels: 1, GL_DEPTH24_STENCIL8,
6121 width: depthTexD->pixelSize().width(), height: depthTexD->pixelSize().height(), depth: multiViewCount);
6122 }
6123 rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER,
6124 GL_DEPTH_ATTACHMENT,
6125 nonMsaaThrowawayDepthTexture,
6126 0,
6127 depthTexD->sampleCount(),
6128 0,
6129 multiViewCount);
6130 rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER,
6131 GL_STENCIL_ATTACHMENT,
6132 nonMsaaThrowawayDepthTexture,
6133 0,
6134 depthTexD->sampleCount(),
6135 0,
6136 multiViewCount);
6137 }
6138 } else {
6139 // The depth texture here must be an array with at least
6140 // multiViewCount elements, and the format should be D24 or D32F
6141 // for depth only, or D24S8 for depth and stencil.
6142 rhiD->glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexD->texture,
6143 0, 0, multiViewCount);
6144 if (rhiD->isStencilSupportingFormat(format: depthTexD->format())) {
6145 rhiD->glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthTexD->texture,
6146 0, 0, multiViewCount);
6147 }
6148 }
6149 }
6150 if (d.colorAttCount == 0) {
6151 d.pixelSize = depthTexD->pixelSize();
6152 d.sampleCount = depthTexD->sampleCount();
6153 }
6154 }
6155 d.dsAttCount = 1;
6156 } else {
6157 d.dsAttCount = 0;
6158 }
6159
6160 d.dpr = 1;
6161 d.rp = QRHI_RES(QGles2RenderPassDescriptor, m_renderPassDesc);
6162
6163 GLenum status = rhiD->f->glCheckFramebufferStatus(GL_FRAMEBUFFER);
6164 if (status != GL_NO_ERROR && status != GL_FRAMEBUFFER_COMPLETE) {
6165 qWarning(msg: "Framebuffer incomplete: 0x%x", status);
6166 return false;
6167 }
6168
6169 if (rhiD->glObjectLabel)
6170 rhiD->glObjectLabel(GL_FRAMEBUFFER, framebuffer, -1, m_objectName.constData());
6171
6172 QRhiRenderTargetAttachmentTracker::updateResIdList<QGles2Texture, QGles2RenderBuffer>(desc: m_desc, dst: &d.currentResIdList);
6173
6174 rhiD->registerResource(res: this);
6175 return true;
6176}
6177
6178QSize QGles2TextureRenderTarget::pixelSize() const
6179{
6180 if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QGles2Texture, QGles2RenderBuffer>(desc: m_desc, currentResIdList: d.currentResIdList))
6181 const_cast<QGles2TextureRenderTarget *>(this)->create();
6182
6183 return d.pixelSize;
6184}
6185
6186float QGles2TextureRenderTarget::devicePixelRatio() const
6187{
6188 return d.dpr;
6189}
6190
6191int QGles2TextureRenderTarget::sampleCount() const
6192{
6193 return d.sampleCount;
6194}
6195
6196QGles2ShaderResourceBindings::QGles2ShaderResourceBindings(QRhiImplementation *rhi)
6197 : QRhiShaderResourceBindings(rhi)
6198{
6199}
6200
6201QGles2ShaderResourceBindings::~QGles2ShaderResourceBindings()
6202{
6203 destroy();
6204}
6205
6206void QGles2ShaderResourceBindings::destroy()
6207{
6208 QRHI_RES_RHI(QRhiGles2);
6209 if (rhiD)
6210 rhiD->unregisterResource(res: this);
6211}
6212
6213bool QGles2ShaderResourceBindings::create()
6214{
6215 QRHI_RES_RHI(QRhiGles2);
6216 if (!rhiD->sanityCheckShaderResourceBindings(srb: this))
6217 return false;
6218
6219 hasDynamicOffset = false;
6220 for (int i = 0, ie = m_bindings.size(); i != ie; ++i) {
6221 const QRhiShaderResourceBinding::Data *b = QRhiImplementation::shaderResourceBindingData(binding: m_bindings.at(idx: i));
6222 if (b->type == QRhiShaderResourceBinding::UniformBuffer) {
6223 if (b->u.ubuf.hasDynamicOffset) {
6224 hasDynamicOffset = true;
6225 break;
6226 }
6227 }
6228 }
6229
6230 rhiD->updateLayoutDesc(srb: this);
6231
6232 generation += 1;
6233 rhiD->registerResource(res: this, ownsNativeResources: false);
6234 return true;
6235}
6236
6237void QGles2ShaderResourceBindings::updateResources(UpdateFlags flags)
6238{
6239 Q_UNUSED(flags);
6240 generation += 1;
6241}
6242
6243QGles2GraphicsPipeline::QGles2GraphicsPipeline(QRhiImplementation *rhi)
6244 : QRhiGraphicsPipeline(rhi)
6245{
6246}
6247
6248QGles2GraphicsPipeline::~QGles2GraphicsPipeline()
6249{
6250 destroy();
6251}
6252
6253void QGles2GraphicsPipeline::destroy()
6254{
6255 if (!program)
6256 return;
6257
6258 QRhiGles2::DeferredReleaseEntry e;
6259 e.type = QRhiGles2::DeferredReleaseEntry::Pipeline;
6260
6261 e.pipeline.program = program;
6262
6263 program = 0;
6264 uniforms.clear();
6265 samplers.clear();
6266
6267 QRHI_RES_RHI(QRhiGles2);
6268 if (rhiD) {
6269 rhiD->releaseQueue.append(t: e);
6270 rhiD->unregisterResource(res: this);
6271 }
6272}
6273
6274static inline bool isGraphicsStage(const QRhiShaderStage &shaderStage)
6275{
6276 const QRhiShaderStage::Type t = shaderStage.type();
6277 return t == QRhiShaderStage::Vertex
6278 || t == QRhiShaderStage::TessellationControl
6279 || t == QRhiShaderStage::TessellationEvaluation
6280 || t == QRhiShaderStage::Geometry
6281 || t == QRhiShaderStage::Fragment;
6282}
6283
6284bool QGles2GraphicsPipeline::create()
6285{
6286 QRHI_RES_RHI(QRhiGles2);
6287
6288 if (program)
6289 destroy();
6290
6291 if (!rhiD->ensureContext())
6292 return false;
6293
6294 rhiD->pipelineCreationStart();
6295 if (!rhiD->sanityCheckGraphicsPipeline(ps: this))
6296 return false;
6297
6298 drawMode = toGlTopology(t: m_topology);
6299
6300 program = rhiD->f->glCreateProgram();
6301
6302 enum {
6303 VtxIdx = 0,
6304 TCIdx,
6305 TEIdx,
6306 GeomIdx,
6307 FragIdx,
6308 LastIdx
6309 };
6310 const auto descIdxForStage = [](const QRhiShaderStage &shaderStage) {
6311 switch (shaderStage.type()) {
6312 case QRhiShaderStage::Vertex:
6313 return VtxIdx;
6314 case QRhiShaderStage::TessellationControl:
6315 return TCIdx;
6316 case QRhiShaderStage::TessellationEvaluation:
6317 return TEIdx;
6318 case QRhiShaderStage::Geometry:
6319 return GeomIdx;
6320 case QRhiShaderStage::Fragment:
6321 return FragIdx;
6322 default:
6323 break;
6324 }
6325 Q_UNREACHABLE_RETURN(VtxIdx);
6326 };
6327 QShaderDescription desc[LastIdx];
6328 QShader::SeparateToCombinedImageSamplerMappingList samplerMappingList[LastIdx];
6329 bool vertexFragmentOnly = true;
6330 for (const QRhiShaderStage &shaderStage : std::as_const(t&: m_shaderStages)) {
6331 if (isGraphicsStage(shaderStage)) {
6332 const int idx = descIdxForStage(shaderStage);
6333 if (idx != VtxIdx && idx != FragIdx)
6334 vertexFragmentOnly = false;
6335 QShader shader = shaderStage.shader();
6336 QShaderVersion shaderVersion;
6337 desc[idx] = shader.description();
6338 if (!rhiD->shaderSource(shaderStage, shaderVersion: &shaderVersion).isEmpty()) {
6339 samplerMappingList[idx] = shader.separateToCombinedImageSamplerMappingList(
6340 key: { QShader::GlslShader, shaderVersion, shaderStage.shaderVariant() });
6341 }
6342 }
6343 }
6344
6345 QByteArray cacheKey;
6346 QRhiGles2::ProgramCacheResult cacheResult = rhiD->tryLoadFromDiskOrPipelineCache(stages: m_shaderStages.constData(),
6347 stageCount: m_shaderStages.size(),
6348 program,
6349 inputVars: desc[VtxIdx].inputVariables(),
6350 cacheKey: &cacheKey);
6351 if (cacheResult == QRhiGles2::ProgramCacheError)
6352 return false;
6353
6354 if (cacheResult == QRhiGles2::ProgramCacheMiss) {
6355 for (const QRhiShaderStage &shaderStage : std::as_const(t&: m_shaderStages)) {
6356 if (isGraphicsStage(shaderStage)) {
6357 if (!rhiD->compileShader(program, shaderStage, shaderVersion: nullptr))
6358 return false;
6359 }
6360 }
6361
6362 // important when GLSL <= 150 is used that does not have location qualifiers
6363 for (const QShaderDescription::InOutVariable &inVar : desc[VtxIdx].inputVariables())
6364 rhiD->f->glBindAttribLocation(program, index: GLuint(inVar.location), name: inVar.name);
6365
6366 if (vertexFragmentOnly)
6367 rhiD->sanityCheckVertexFragmentInterface(vsDesc: desc[VtxIdx], fsDesc: desc[FragIdx]);
6368
6369 if (!rhiD->linkProgram(program))
6370 return false;
6371
6372 if (rhiD->rhiFlags.testFlag(flag: QRhi::EnablePipelineCacheDataSave)) {
6373 // force replacing existing cache entry (if there is one, then
6374 // something is wrong with it, as there was no hit)
6375 rhiD->trySaveToPipelineCache(program, cacheKey, force: true);
6376 }
6377 // legacy QOpenGLShaderProgram style behavior: do this always, even
6378 // though it is superfluous with the "pipeline cache" enabled. Continue
6379 // storing to the Qt 5 style individual-file disk cache, because there
6380 // is no guarantee one retrieves the "pipeline cache" blob and writes it
6381 // out. Classic example: if Qt Quick only retrieves and stores the
6382 // combined cache contents when exiting, applications that never exit
6383 // cleanly (because they are killed, Ctrl+C'd, etc.) never store any
6384 // program binaries! Therefore, to maintain Qt 5 behavioral
6385 // compatibility, continue writing out the individual files no matter
6386 // what.
6387 rhiD->trySaveToDiskCache(program, cacheKey);
6388 } else {
6389 Q_ASSERT(cacheResult == QRhiGles2::ProgramCacheHit);
6390 if (rhiD->rhiFlags.testFlag(flag: QRhi::EnablePipelineCacheDataSave)) {
6391 // just so that it ends up in the pipeline cache also when the hit was
6392 // from the disk cache
6393 rhiD->trySaveToPipelineCache(program, cacheKey);
6394 }
6395 }
6396
6397 // Use the same work area for the vertex & fragment stages, thus ensuring
6398 // that we will not do superfluous glUniform calls for uniforms that are
6399 // present in both shaders.
6400 QRhiGles2::ActiveUniformLocationTracker activeUniformLocations;
6401
6402 for (const QRhiShaderStage &shaderStage : std::as_const(t&: m_shaderStages)) {
6403 if (isGraphicsStage(shaderStage)) {
6404 const int idx = descIdxForStage(shaderStage);
6405 for (const QShaderDescription::UniformBlock &ub : desc[idx].uniformBlocks())
6406 rhiD->gatherUniforms(program, ub, activeUniformLocations: &activeUniformLocations, dst: &uniforms);
6407 for (const QShaderDescription::InOutVariable &v : desc[idx].combinedImageSamplers())
6408 rhiD->gatherSamplers(program, v, dst: &samplers);
6409 for (const QShader::SeparateToCombinedImageSamplerMapping &mapping : samplerMappingList[idx])
6410 rhiD->gatherGeneratedSamplers(program, mapping, dst: &samplers);
6411 }
6412 }
6413
6414 std::sort(first: uniforms.begin(), last: uniforms.end(),
6415 comp: [](const QGles2UniformDescription &a, const QGles2UniformDescription &b)
6416 {
6417 return a.offset < b.offset;
6418 });
6419
6420 memset(s: uniformState, c: 0, n: sizeof(uniformState));
6421
6422 currentSrb = nullptr;
6423 currentSrbGeneration = 0;
6424
6425 if (rhiD->glObjectLabel)
6426 rhiD->glObjectLabel(GL_PROGRAM, program, -1, m_objectName.constData());
6427
6428 rhiD->pipelineCreationEnd();
6429 generation += 1;
6430 rhiD->registerResource(res: this);
6431 return true;
6432}
6433
6434QGles2ComputePipeline::QGles2ComputePipeline(QRhiImplementation *rhi)
6435 : QRhiComputePipeline(rhi)
6436{
6437}
6438
6439QGles2ComputePipeline::~QGles2ComputePipeline()
6440{
6441 destroy();
6442}
6443
6444void QGles2ComputePipeline::destroy()
6445{
6446 if (!program)
6447 return;
6448
6449 QRhiGles2::DeferredReleaseEntry e;
6450 e.type = QRhiGles2::DeferredReleaseEntry::Pipeline;
6451
6452 e.pipeline.program = program;
6453
6454 program = 0;
6455 uniforms.clear();
6456 samplers.clear();
6457
6458 QRHI_RES_RHI(QRhiGles2);
6459 if (rhiD) {
6460 rhiD->releaseQueue.append(t: e);
6461 rhiD->unregisterResource(res: this);
6462 }
6463}
6464
6465bool QGles2ComputePipeline::create()
6466{
6467 QRHI_RES_RHI(QRhiGles2);
6468
6469 if (program)
6470 destroy();
6471
6472 if (!rhiD->ensureContext())
6473 return false;
6474
6475 rhiD->pipelineCreationStart();
6476
6477 const QShaderDescription csDesc = m_shaderStage.shader().description();
6478 QShader::SeparateToCombinedImageSamplerMappingList csSamplerMappingList;
6479 QShaderVersion shaderVersion;
6480 if (!rhiD->shaderSource(shaderStage: m_shaderStage, shaderVersion: &shaderVersion).isEmpty()) {
6481 csSamplerMappingList = m_shaderStage.shader().separateToCombinedImageSamplerMappingList(
6482 key: { QShader::GlslShader, shaderVersion, m_shaderStage.shaderVariant() });
6483 }
6484
6485 program = rhiD->f->glCreateProgram();
6486
6487 QByteArray cacheKey;
6488 QRhiGles2::ProgramCacheResult cacheResult = rhiD->tryLoadFromDiskOrPipelineCache(stages: &m_shaderStage, stageCount: 1, program, inputVars: {}, cacheKey: &cacheKey);
6489 if (cacheResult == QRhiGles2::ProgramCacheError)
6490 return false;
6491
6492 if (cacheResult == QRhiGles2::ProgramCacheMiss) {
6493 if (!rhiD->compileShader(program, shaderStage: m_shaderStage, shaderVersion: nullptr))
6494 return false;
6495
6496 if (!rhiD->linkProgram(program))
6497 return false;
6498
6499 if (rhiD->rhiFlags.testFlag(flag: QRhi::EnablePipelineCacheDataSave)) {
6500 // force replacing existing cache entry (if there is one, then
6501 // something is wrong with it, as there was no hit)
6502 rhiD->trySaveToPipelineCache(program, cacheKey, force: true);
6503 }
6504 // legacy QOpenGLShaderProgram style behavior
6505 rhiD->trySaveToDiskCache(program, cacheKey);
6506 } else {
6507 Q_ASSERT(cacheResult == QRhiGles2::ProgramCacheHit);
6508 if (rhiD->rhiFlags.testFlag(flag: QRhi::EnablePipelineCacheDataSave)) {
6509 // just so that it ends up in the pipeline cache also when the hit was
6510 // from the disk cache
6511 rhiD->trySaveToPipelineCache(program, cacheKey);
6512 }
6513 }
6514
6515 QRhiGles2::ActiveUniformLocationTracker activeUniformLocations;
6516 for (const QShaderDescription::UniformBlock &ub : csDesc.uniformBlocks())
6517 rhiD->gatherUniforms(program, ub, activeUniformLocations: &activeUniformLocations, dst: &uniforms);
6518 for (const QShaderDescription::InOutVariable &v : csDesc.combinedImageSamplers())
6519 rhiD->gatherSamplers(program, v, dst: &samplers);
6520 for (const QShader::SeparateToCombinedImageSamplerMapping &mapping : csSamplerMappingList)
6521 rhiD->gatherGeneratedSamplers(program, mapping, dst: &samplers);
6522
6523 // storage images and buffers need no special steps here
6524
6525 memset(s: uniformState, c: 0, n: sizeof(uniformState));
6526
6527 currentSrb = nullptr;
6528 currentSrbGeneration = 0;
6529
6530 rhiD->pipelineCreationEnd();
6531 generation += 1;
6532 rhiD->registerResource(res: this);
6533 return true;
6534}
6535
6536QGles2CommandBuffer::QGles2CommandBuffer(QRhiImplementation *rhi)
6537 : QRhiCommandBuffer(rhi)
6538{
6539 resetState();
6540}
6541
6542QGles2CommandBuffer::~QGles2CommandBuffer()
6543{
6544 destroy();
6545}
6546
6547void QGles2CommandBuffer::destroy()
6548{
6549 // nothing to do here
6550}
6551
6552QGles2SwapChain::QGles2SwapChain(QRhiImplementation *rhi)
6553 : QRhiSwapChain(rhi),
6554 rt(rhi, this),
6555 rtLeft(rhi, this),
6556 rtRight(rhi, this),
6557 cb(rhi)
6558{
6559}
6560
6561QGles2SwapChain::~QGles2SwapChain()
6562{
6563 destroy();
6564}
6565
6566void QGles2SwapChain::destroy()
6567{
6568 QRHI_RES_RHI(QRhiGles2);
6569 if (rhiD)
6570 rhiD->unregisterResource(res: this);
6571}
6572
6573QRhiCommandBuffer *QGles2SwapChain::currentFrameCommandBuffer()
6574{
6575 return &cb;
6576}
6577
6578QRhiRenderTarget *QGles2SwapChain::currentFrameRenderTarget()
6579{
6580 return &rt;
6581}
6582
6583QRhiRenderTarget *QGles2SwapChain::currentFrameRenderTarget(StereoTargetBuffer targetBuffer)
6584{
6585 if (targetBuffer == LeftBuffer)
6586 return rtLeft.d.isValid() ? &rtLeft : &rt;
6587 else if (targetBuffer == RightBuffer)
6588 return rtRight.d.isValid() ? &rtRight : &rt;
6589 else
6590 Q_UNREACHABLE_RETURN(nullptr);
6591}
6592
6593QSize QGles2SwapChain::surfacePixelSize()
6594{
6595 Q_ASSERT(m_window);
6596 if (QPlatformWindow *platformWindow = m_window->handle())
6597 // Prefer using QPlatformWindow geometry and DPR in order to avoid
6598 // errors due to rounded QWindow geometry.
6599 return platformWindow->geometry().size() * platformWindow->devicePixelRatio();
6600 else
6601 return m_window->size() * m_window->devicePixelRatio();
6602}
6603
6604bool QGles2SwapChain::isFormatSupported(Format f)
6605{
6606 return f == SDR;
6607}
6608
6609QRhiRenderPassDescriptor *QGles2SwapChain::newCompatibleRenderPassDescriptor()
6610{
6611 QGles2RenderPassDescriptor *rpD = new QGles2RenderPassDescriptor(m_rhi);
6612 QRHI_RES_RHI(QRhiGles2);
6613 rhiD->registerResource(res: rpD, ownsNativeResources: false);
6614 return rpD;
6615}
6616
6617void QGles2SwapChain::initSwapChainRenderTarget(QGles2SwapChainRenderTarget *rt)
6618{
6619 rt->setRenderPassDescriptor(m_renderPassDesc); // for the public getter in QRhiRenderTarget
6620 rt->d.rp = QRHI_RES(QGles2RenderPassDescriptor, m_renderPassDesc);
6621 rt->d.pixelSize = pixelSize;
6622 rt->d.dpr = float(m_window->devicePixelRatio());
6623 rt->d.sampleCount = qBound(min: 1, val: m_sampleCount, max: 64);
6624 rt->d.colorAttCount = 1;
6625 rt->d.dsAttCount = m_depthStencil ? 1 : 0;
6626 rt->d.srgbUpdateAndBlend = m_flags.testFlag(flag: QRhiSwapChain::sRGB);
6627}
6628
6629bool QGles2SwapChain::createOrResize()
6630{
6631 // can be called multiple times due to window resizes
6632 const bool needsRegistration = !surface || surface != m_window;
6633 if (surface && surface != m_window)
6634 destroy();
6635
6636 surface = m_window;
6637 m_currentPixelSize = surfacePixelSize();
6638 pixelSize = m_currentPixelSize;
6639
6640 if (m_depthStencil && m_depthStencil->flags().testFlag(flag: QRhiRenderBuffer::UsedWithSwapChainOnly)
6641 && m_depthStencil->pixelSize() != pixelSize)
6642 {
6643 m_depthStencil->setPixelSize(pixelSize);
6644 m_depthStencil->create();
6645 }
6646
6647 initSwapChainRenderTarget(rt: &rt);
6648
6649 if (m_window->format().stereo()) {
6650 initSwapChainRenderTarget(rt: &rtLeft);
6651 rtLeft.d.stereoTarget = QRhiSwapChain::LeftBuffer;
6652 initSwapChainRenderTarget(rt: &rtRight);
6653 rtRight.d.stereoTarget = QRhiSwapChain::RightBuffer;
6654 }
6655
6656 frameCount = 0;
6657
6658 QRHI_RES_RHI(QRhiGles2);
6659 if (rhiD->rhiFlags.testFlag(flag: QRhi::EnableTimestamps) && rhiD->caps.timestamps)
6660 timestamps.prepare(rhiD);
6661
6662 // The only reason to register this fairly fake gl swapchain
6663 // object with no native resources underneath is to be able to
6664 // implement a safe destroy().
6665 if (needsRegistration)
6666 rhiD->registerResource(res: this, ownsNativeResources: false);
6667
6668 return true;
6669}
6670
6671void QGles2SwapChainTimestamps::prepare(QRhiGles2 *rhiD)
6672{
6673 if (!query[0])
6674 rhiD->f->glGenQueries(n: TIMESTAMP_PAIRS * 2, ids: query);
6675}
6676
6677void QGles2SwapChainTimestamps::destroy(QRhiGles2 *rhiD)
6678{
6679 rhiD->f->glDeleteQueries(n: TIMESTAMP_PAIRS * 2, ids: query);
6680 memset(s: active, c: 0, n: sizeof(active));
6681 memset(s: query, c: 0, n: sizeof(query));
6682}
6683
6684bool QGles2SwapChainTimestamps::tryQueryTimestamps(int pairIndex, QRhiGles2 *rhiD, double *elapsedSec)
6685{
6686 if (!active[pairIndex])
6687 return false;
6688
6689 GLuint tsStart = query[pairIndex * 2];
6690 GLuint tsEnd = query[pairIndex * 2 + 1];
6691
6692 GLuint ready = GL_FALSE;
6693 rhiD->f->glGetQueryObjectuiv(id: tsEnd, GL_QUERY_RESULT_AVAILABLE, params: &ready);
6694
6695 if (!ready)
6696 return false;
6697
6698 bool result = false;
6699 quint64 timestamps[2];
6700 rhiD->glGetQueryObjectui64v(tsStart, GL_QUERY_RESULT, &timestamps[0]);
6701 rhiD->glGetQueryObjectui64v(tsEnd, GL_QUERY_RESULT, &timestamps[1]);
6702
6703 if (timestamps[1] >= timestamps[0]) {
6704 const quint64 nanoseconds = timestamps[1] - timestamps[0];
6705 *elapsedSec = nanoseconds / 1000000000.0;
6706 result = true;
6707 }
6708
6709 active[pairIndex] = false;
6710 return result;
6711}
6712
6713QT_END_NAMESPACE
6714

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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