1// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
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 "submissioncontext_p.h"
5
6#include <Qt3DCore/private/qbuffer_p.h>
7#include <Qt3DRender/qgraphicsapifilter.h>
8#include <Qt3DRender/qparameter.h>
9#include <Qt3DRender/qcullface.h>
10#include <Qt3DRender/private/renderlogging_p.h>
11#include <Qt3DRender/private/shader_p.h>
12#include <Qt3DRender/private/material_p.h>
13#include <Qt3DRender/private/buffer_p.h>
14#include <Qt3DRender/private/attribute_p.h>
15#include <Qt3DRender/private/renderstates_p.h>
16#include <Qt3DRender/private/renderstateset_p.h>
17#include <Qt3DRender/private/rendertarget_p.h>
18#include <Qt3DRender/private/nodemanagers_p.h>
19#include <Qt3DRender/private/buffermanager_p.h>
20#include <Qt3DRender/private/managers_p.h>
21#include <Qt3DRender/private/attachmentpack_p.h>
22#include <Qt3DRender/private/stringtoint_p.h>
23#include <gltexture_p.h>
24#include <rendercommand_p.h>
25#include <graphicshelperinterface_p.h>
26#include <renderer_p.h>
27#include <glresourcemanagers_p.h>
28#include <renderbuffer_p.h>
29#include <glshader_p.h>
30#include <openglvertexarrayobject_p.h>
31#include <QOpenGLShaderProgram>
32
33#if !QT_CONFIG(opengles2)
34#include <QOpenGLFunctions_2_0>
35#include <QOpenGLFunctions_3_2_Core>
36#include <QOpenGLFunctions_3_3_Core>
37#include <QOpenGLFunctions_4_3_Core>
38#include <graphicshelpergl2_p.h>
39#include <graphicshelpergl3_2_p.h>
40#include <graphicshelpergl3_3_p.h>
41#include <graphicshelpergl4_p.h>
42#endif
43#include <graphicshelperes2_p.h>
44#include <graphicshelperes3_p.h>
45
46#include <private/qdebug_p.h>
47#include <QSurface>
48#include <QWindow>
49#include <QOpenGLTexture>
50#ifdef QT_OPENGL_LIB
51#include <QtOpenGL/QOpenGLDebugLogger>
52#endif
53
54QT_BEGIN_NAMESPACE
55
56#ifndef GL_READ_FRAMEBUFFER
57#define GL_READ_FRAMEBUFFER 0x8CA8
58#endif
59
60#ifndef GL_DRAW_FRAMEBUFFER
61#define GL_DRAW_FRAMEBUFFER 0x8CA9
62#endif
63
64namespace Qt3DRender {
65namespace Render {
66namespace OpenGL {
67
68
69static QHash<unsigned int, SubmissionContext*> static_contexts;
70
71unsigned int nextFreeContextId()
72{
73 for (unsigned int i=0; i < 0xffff; ++i) {
74 if (!static_contexts.contains(key: i))
75 return i;
76 }
77
78 qFatal(msg: "Couldn't find free context ID");
79 return 0;
80}
81
82namespace {
83
84GLBuffer::Type attributeTypeToGLBufferType(Qt3DCore::QAttribute::AttributeType type)
85{
86 switch (type) {
87 case Qt3DCore::QAttribute::VertexAttribute:
88 return GLBuffer::ArrayBuffer;
89 case Qt3DCore::QAttribute::IndexAttribute:
90 return GLBuffer::IndexBuffer;
91 case Qt3DCore::QAttribute::DrawIndirectAttribute:
92 return GLBuffer::DrawIndirectBuffer;
93 default:
94 Q_UNREACHABLE();
95 }
96}
97
98void copyGLFramebufferDataToImage(QImage &img, const uchar *srcData, uint stride, uint width, uint height, QAbstractTexture::TextureFormat format)
99{
100 switch (format) {
101 case QAbstractTexture::RGBA32F:
102 {
103 uchar *srcScanline = (uchar *)srcData + stride * (height - 1);
104 for (uint i = 0; i < height; ++i) {
105 uchar *dstScanline = img.scanLine(i);
106 float *pSrc = (float*)srcScanline;
107 for (uint j = 0; j < width; j++) {
108 *dstScanline++ = (uchar)(255.0f * qBound(min: 0.0f, val: pSrc[4*j+2], max: 1.0f));
109 *dstScanline++ = (uchar)(255.0f * qBound(min: 0.0f, val: pSrc[4*j+1], max: 1.0f));
110 *dstScanline++ = (uchar)(255.0f * qBound(min: 0.0f, val: pSrc[4*j+0], max: 1.0f));
111 *dstScanline++ = (uchar)(255.0f * qBound(min: 0.0f, val: pSrc[4*j+3], max: 1.0f));
112 }
113 srcScanline -= stride;
114 }
115 } break;
116 default:
117 {
118 uchar* srcScanline = (uchar *)srcData + stride * (height - 1);
119 for (uint i = 0; i < height; ++i) {
120 memcpy(dest: img.scanLine(i), src: srcScanline, n: stride);
121 srcScanline -= stride;
122 }
123 } break;
124 }
125}
126
127// Render States Helpers
128template<typename GenericState>
129void applyStateHelper(const GenericState *state, SubmissionContext *gc)
130{
131 Q_UNUSED(state);
132 Q_UNUSED(gc);
133}
134
135template<>
136void applyStateHelper<AlphaFunc>(const AlphaFunc *state, SubmissionContext *gc)
137{
138 const auto values = state->values();
139 gc->alphaTest(mode1: std::get<0>(t: values), mode2: std::get<1>(t: values));
140}
141
142template<>
143void applyStateHelper<BlendEquationArguments>(const BlendEquationArguments *state, SubmissionContext *gc)
144{
145 const auto values = state->values();
146 // Un-indexed BlendEquationArguments -> Use normal GL1.0 functions
147 if (std::get<5>(t: values) < 0) {
148 if (std::get<4>(t: values)) {
149 gc->openGLContext()->functions()->glEnable(GL_BLEND);
150 gc->openGLContext()->functions()->glBlendFuncSeparate(srcRGB: std::get<0>(t: values), dstRGB: std::get<1>(t: values), srcAlpha: std::get<2>(t: values), dstAlpha: std::get<3>(t: values));
151 } else {
152 gc->openGLContext()->functions()->glDisable(GL_BLEND);
153 }
154 }
155 // BlendEquationArguments for a particular Draw Buffer. Different behaviours for
156 // (1) 3.0-3.3: only enablei/disablei supported.
157 // (2) 4.0+: all operations supported.
158 // We just ignore blend func parameter for (1), so no warnings get
159 // printed.
160 else {
161 if (std::get<4>(t: values)) {
162 gc->enablei(GL_BLEND, index: std::get<5>(t: values));
163 if (gc->supportsDrawBuffersBlend()) {
164 gc->blendFuncSeparatei(buf: std::get<5>(t: values), sRGB: std::get<0>(t: values), dRGB: std::get<1>(t: values), sAlpha: std::get<2>(t: values), dAlpha: std::get<3>(t: values));
165 }
166 } else {
167 gc->disablei(GL_BLEND, index: std::get<5>(t: values));
168 }
169 }
170}
171
172template<>
173void applyStateHelper<BlendEquation>(const BlendEquation *state, SubmissionContext *gc)
174{
175 gc->blendEquation(mode: std::get<0>(t: state->values()));
176}
177
178template<>
179void applyStateHelper<MSAAEnabled>(const MSAAEnabled *state, SubmissionContext *gc)
180{
181 gc->setMSAAEnabled(std::get<0>(t: state->values()));
182}
183
184template<>
185void applyStateHelper<DepthRange>(const DepthRange *state, SubmissionContext *gc)
186{
187 const auto values = state->values();
188 gc->depthRange(nearValue: std::get<0>(t: values), farValue: std::get<1>(t: values));
189}
190
191template<>
192void applyStateHelper<DepthTest>(const DepthTest *state, SubmissionContext *gc)
193{
194 gc->depthTest(mode: std::get<0>(t: state->values()));
195}
196
197template<>
198void applyStateHelper<RasterMode>(const RasterMode *state, SubmissionContext *gc)
199{
200 gc->rasterMode(faceMode: std::get<0>(t: state->values()), rasterMode: std::get<1>(t: state->values()));
201}
202
203template<>
204void applyStateHelper<NoDepthMask>(const NoDepthMask *state, SubmissionContext *gc)
205{
206 gc->depthMask(mode: std::get<0>(t: state->values()));
207}
208
209template<>
210void applyStateHelper<CullFace>(const CullFace *state, SubmissionContext *gc)
211{
212 const auto values = state->values();
213 if (std::get<0>(t: values) == QCullFace::NoCulling) {
214 gc->openGLContext()->functions()->glDisable(GL_CULL_FACE);
215 } else {
216 gc->openGLContext()->functions()->glEnable(GL_CULL_FACE);
217 gc->openGLContext()->functions()->glCullFace(mode: std::get<0>(t: values));
218 }
219}
220
221template<>
222void applyStateHelper<FrontFace>(const FrontFace *state, SubmissionContext *gc)
223{
224 gc->frontFace(mode: std::get<0>(t: state->values()));
225}
226
227template<>
228void applyStateHelper<ScissorTest>(const ScissorTest *state, SubmissionContext *gc)
229{
230 const auto values = state->values();
231 gc->openGLContext()->functions()->glEnable(GL_SCISSOR_TEST);
232 gc->openGLContext()->functions()->glScissor(x: std::get<0>(t: values), y: std::get<1>(t: values), width: std::get<2>(t: values), height: std::get<3>(t: values));
233}
234
235template<>
236void applyStateHelper<StencilTest>(const StencilTest *state, SubmissionContext *gc)
237{
238 const auto values = state->values();
239 gc->openGLContext()->functions()->glEnable(GL_STENCIL_TEST);
240 gc->openGLContext()->functions()->glStencilFuncSeparate(GL_FRONT, func: std::get<0>(t: values), ref: std::get<1>(t: values), mask: std::get<2>(t: values));
241 gc->openGLContext()->functions()->glStencilFuncSeparate(GL_BACK, func: std::get<3>(t: values), ref: std::get<4>(t: values), mask: std::get<5>(t: values));
242}
243
244template<>
245void applyStateHelper<AlphaCoverage>(const AlphaCoverage *, SubmissionContext *gc)
246{
247 gc->setAlphaCoverageEnabled(true);
248}
249
250template<>
251void applyStateHelper<PointSize>(const PointSize *state, SubmissionContext *gc)
252{
253 const auto values = state->values();
254 gc->pointSize(programmable: std::get<0>(t: values), value: std::get<1>(t: values));
255}
256
257
258template<>
259void applyStateHelper<PolygonOffset>(const PolygonOffset *state, SubmissionContext *gc)
260{
261 const auto values = state->values();
262 gc->openGLContext()->functions()->glEnable(GL_POLYGON_OFFSET_FILL);
263 gc->openGLContext()->functions()->glPolygonOffset(factor: std::get<0>(t: values), units: std::get<1>(t: values));
264}
265
266template<>
267void applyStateHelper<ColorMask>(const ColorMask *state, SubmissionContext *gc)
268{
269 const auto values = state->values();
270 gc->openGLContext()->functions()->glColorMask(red: std::get<0>(t: values), green: std::get<1>(t: values), blue: std::get<2>(t: values), alpha: std::get<3>(t: values));
271}
272
273template<>
274void applyStateHelper<ClipPlane>(const ClipPlane *state, SubmissionContext *gc)
275{
276 const auto values = state->values();
277 gc->enableClipPlane(clipPlane: std::get<0>(t: values));
278 gc->setClipPlane(clipPlane: std::get<0>(t: values), normal: std::get<1>(t: values), distance: std::get<2>(t: values));
279}
280
281template<>
282void applyStateHelper<SeamlessCubemap>(const SeamlessCubemap *, SubmissionContext *gc)
283{
284 gc->setSeamlessCubemap(true);
285}
286
287template<>
288void applyStateHelper<StencilOp>(const StencilOp *state, SubmissionContext *gc)
289{
290 const auto values = state->values();
291 gc->openGLContext()->functions()->glStencilOpSeparate(GL_FRONT, fail: std::get<0>(t: values), zfail: std::get<1>(t: values), zpass: std::get<2>(t: values));
292 gc->openGLContext()->functions()->glStencilOpSeparate(GL_BACK, fail: std::get<3>(t: values), zfail: std::get<4>(t: values), zpass: std::get<5>(t: values));
293}
294
295template<>
296void applyStateHelper<StencilMask>(const StencilMask *state, SubmissionContext *gc)
297{
298 const auto values = state->values();
299 gc->openGLContext()->functions()->glStencilMaskSeparate(GL_FRONT, mask: std::get<0>(t: values));
300 gc->openGLContext()->functions()->glStencilMaskSeparate(GL_BACK, mask: std::get<1>(t: values));
301}
302
303template<>
304void applyStateHelper<Dithering>(const Dithering *, SubmissionContext *gc)
305{
306 gc->openGLContext()->functions()->glEnable(GL_DITHER);
307}
308
309#ifndef GL_LINE_SMOOTH
310#define GL_LINE_SMOOTH 0x0B20
311#endif
312
313template<>
314void applyStateHelper<LineWidth>(const LineWidth *state, SubmissionContext *gc)
315{
316 const auto values = state->values();
317 if (std::get<1>(t: values))
318 gc->openGLContext()->functions()->glEnable(GL_LINE_SMOOTH);
319 else
320 gc->openGLContext()->functions()->glDisable(GL_LINE_SMOOTH);
321
322 gc->openGLContext()->functions()->glLineWidth(width: std::get<0>(t: values));
323}
324
325GLint glAttachmentPoint(const QRenderTargetOutput::AttachmentPoint &attachmentPoint)
326{
327 if (attachmentPoint <= QRenderTargetOutput::Color15)
328 return GL_COLOR_ATTACHMENT0 + attachmentPoint;
329
330 switch (attachmentPoint) {
331 case QRenderTargetOutput::Depth:
332 case QRenderTargetOutput::DepthStencil:
333 return GL_DEPTH_ATTACHMENT;
334 case QRenderTargetOutput::Stencil:
335 return GL_STENCIL_ATTACHMENT;
336 case QRenderTargetOutput::Left:
337#ifndef GL_BACK_LEFT
338# define GL_BACK_LEFT 0x0402
339#endif
340 return GL_BACK_LEFT;
341 case QRenderTargetOutput::Right:
342#ifndef GL_BACK_RIGHT
343# define GL_BACK_RIGHT 0x0403
344#endif
345 return GL_BACK_RIGHT;
346 default:
347 Q_UNREACHABLE_RETURN(GL_NONE);
348 }
349}
350
351} // anonymous
352
353
354SubmissionContext::SubmissionContext()
355 : GraphicsContext()
356 , m_ownCurrent(true)
357 , m_id(nextFreeContextId())
358 , m_surface(nullptr)
359 , m_activeShader(nullptr)
360 , m_renderTargetFormat(QAbstractTexture::NoFormat)
361 , m_currClearStencilValue(0)
362 , m_currClearDepthValue(1.f)
363 , m_currClearColorValue(0,0,0,0)
364 , m_material(nullptr)
365 , m_activeFBO(0)
366 , m_boundArrayBuffer(nullptr)
367 , m_stateSet(nullptr)
368 , m_renderer(nullptr)
369 , m_uboTempArray(QByteArray(1024, 0))
370{
371 static_contexts[m_id] = this;
372}
373
374SubmissionContext::~SubmissionContext()
375{
376 releaseOpenGL();
377
378 Q_ASSERT(static_contexts[m_id] == this);
379 static_contexts.remove(key: m_id);
380}
381
382void SubmissionContext::initialize()
383{
384 GraphicsContext::initialize();
385 m_textureContext.initialize(context: this);
386 m_imageContext.initialize(context: this);
387}
388
389void SubmissionContext::resolveRenderTargetFormat()
390{
391 const QSurfaceFormat format = m_gl->format();
392 const uint a = (format.alphaBufferSize() == -1) ? 0 : format.alphaBufferSize();
393 const uint r = format.redBufferSize();
394 const uint g = format.greenBufferSize();
395 const uint b = format.blueBufferSize();
396
397#define RGBA_BITS(r,g,b,a) (r | (g << 6) | (b << 12) | (a << 18))
398
399 const uint bits = RGBA_BITS(r,g,b,a);
400 switch (bits) {
401 case RGBA_BITS(8,8,8,8):
402 m_renderTargetFormat = QAbstractTexture::RGBA8_UNorm;
403 break;
404 case RGBA_BITS(8,8,8,0):
405 m_renderTargetFormat = QAbstractTexture::RGB8_UNorm;
406 break;
407 case RGBA_BITS(5,6,5,0):
408 m_renderTargetFormat = QAbstractTexture::R5G6B5;
409 break;
410 }
411#undef RGBA_BITS
412}
413
414bool SubmissionContext::beginDrawing(QSurface *surface)
415{
416 Q_ASSERT(surface);
417 Q_ASSERT(m_gl);
418
419 m_surface = surface;
420
421 // TO DO: Find a way to make to pause work if the window is not exposed
422 // if (m_surface && m_surface->surfaceClass() == QSurface::Window) {
423 // qDebug() << Q_FUNC_INFO << 1;
424 // if (!static_cast<QWindow *>(m_surface)->isExposed())
425 // return false;
426 // qDebug() << Q_FUNC_INFO << 2;
427 // }
428
429 // Makes the surface current on the OpenGLContext
430 // and sets the right glHelper
431 m_ownCurrent = !(m_gl->surface() == m_surface);
432 if (m_ownCurrent && !makeCurrent(surface: m_surface))
433 return false;
434
435 // TODO: cache surface format somewhere rather than doing this every time render surface changes
436 resolveRenderTargetFormat();
437
438#if defined(QT3D_RENDER_ASPECT_OPENGL_DEBUG)
439 GLint err = m_gl->functions()->glGetError();
440 if (err != 0) {
441 qCWarning(Backend) << Q_FUNC_INFO << "glGetError:" << err;
442 }
443#endif
444
445 if (!isInitialized())
446 initialize();
447 initializeHelpers(surface: m_surface);
448
449 // need to reset these values every frame, may get overwritten elsewhere
450 resetState();
451
452 if (m_activeShader) {
453 m_activeShader = nullptr;
454 }
455
456 m_boundArrayBuffer = nullptr;
457
458 // Record the default FBO value as there's no guarantee it remains constant over time
459 m_defaultFBO = m_gl->defaultFramebufferObject();
460
461 return true;
462}
463
464void SubmissionContext::endDrawing(bool swapBuffers)
465{
466 if (swapBuffers)
467 m_gl->swapBuffers(surface: m_surface);
468 if (m_ownCurrent)
469 m_gl->doneCurrent();
470 m_textureContext.endDrawing();
471 m_imageContext.endDrawing();
472}
473
474void SubmissionContext::activateRenderTarget(Qt3DCore::QNodeId renderTargetNodeId, const AttachmentPack &attachments, GLuint defaultFboId)
475{
476 GLuint fboId = defaultFboId; // Default FBO
477 resolveRenderTargetFormat(); // Reset m_renderTargetFormat based on the default FBO
478
479 // check if target buffer is GL_BACK_LEFT or GL_BACK_RIGHT, as they don't need an
480 // own fbo
481 auto allLeftOrRight = std::all_of(first: attachments.attachments().begin(), last: attachments.attachments().end(),
482 pred: [](const Attachment& att){
483 return att.m_point == QRenderTargetOutput::Left || att.m_point == QRenderTargetOutput::Right;
484 });
485
486 const bool allAttachmentsAreLeftOrRight = attachments.attachments().size() > 0 && allLeftOrRight;
487 const bool shouldCreateRenderTarget = !allAttachmentsAreLeftOrRight && renderTargetNodeId;
488
489 if (shouldCreateRenderTarget) {
490 // New RenderTarget
491 if (!m_renderTargets.contains(key: renderTargetNodeId)) {
492 if (m_defaultFBO && fboId == m_defaultFBO) {
493 // this is the default fbo that some platforms create (iOS), we never
494 // register it
495 } else {
496 fboId = createRenderTarget(renderTargetNodeId, attachments);
497 }
498 } else {
499 fboId = updateRenderTarget(renderTargetNodeId, attachments, isActiveRenderTarget: true); // Overwrites m_renderTargetFormat based on custom FBO
500 }
501 }
502
503 m_activeFBO = fboId;
504 m_activeFBONodeId = renderTargetNodeId;
505 m_glHelper->bindFrameBufferObject(frameBufferId: m_activeFBO, mode: GraphicsHelperInterface::FBODraw);
506 // Set active drawBuffers
507 activateDrawBuffers(attachments);
508}
509
510void SubmissionContext::releaseRenderTarget(const Qt3DCore::QNodeId id)
511{
512 if (m_renderTargets.contains(key: id)) {
513 const RenderTargetInfo targetInfo = m_renderTargets.take(key: id);
514 const GLuint fboId = targetInfo.fboId;
515 m_glHelper->releaseFrameBufferObject(frameBufferId: fboId);
516 }
517}
518
519GLuint SubmissionContext::createRenderTarget(Qt3DCore::QNodeId renderTargetNodeId, const AttachmentPack &attachments)
520{
521 const GLuint fboId = m_glHelper->createFrameBufferObject();
522 if (fboId) {
523 // The FBO is created and its attachments are set once
524 // Bind FBO
525 m_glHelper->bindFrameBufferObject(frameBufferId: fboId, mode: GraphicsHelperInterface::FBODraw);
526 // Insert FBO into hash
527 const RenderTargetInfo info = bindFrameBufferAttachmentHelper(fboId, attachments);
528 m_renderTargets.insert(key: renderTargetNodeId, value: info);
529 } else {
530 qCritical(msg: "Failed to create FBO");
531 }
532 return fboId;
533}
534
535GLuint SubmissionContext::updateRenderTarget(Qt3DCore::QNodeId renderTargetNodeId, const AttachmentPack &attachments, bool isActiveRenderTarget)
536{
537 const RenderTargetInfo fboInfo = m_renderTargets.value(key: renderTargetNodeId);
538 const GLuint fboId =fboInfo.fboId;
539
540 // We need to check if one of the attachnent have changed QTBUG-64757
541 bool needsRebuild = attachments != fboInfo.attachments;
542
543 // Even if the attachment packs are the same, one of the inner texture might have
544 // been resized or recreated, we need to check for that
545 if (!needsRebuild) {
546 // render target exists, has attachment been resized?
547 GLTextureManager *glTextureManager = m_renderer->glResourceManagers()->glTextureManager();
548 const QSize s = fboInfo.size;
549
550 const auto attachments_ = attachments.attachments();
551 for (const Attachment &attachment : attachments_) {
552 const bool textureWasUpdated = m_updateTextureIds.contains(t: attachment.m_textureUuid);
553 GLTexture *rTex = glTextureManager->lookupResource(id: attachment.m_textureUuid);
554 if (rTex) {
555 const bool sizeHasChanged = rTex->size() != s;
556 needsRebuild |= sizeHasChanged;
557 if (isActiveRenderTarget && attachment.m_point == QRenderTargetOutput::Color0)
558 m_renderTargetFormat = rTex->properties().format;
559 }
560 needsRebuild |= textureWasUpdated;
561 }
562 }
563
564 if (needsRebuild) {
565 m_glHelper->bindFrameBufferObject(frameBufferId: fboId, mode: GraphicsHelperInterface::FBODraw);
566 const RenderTargetInfo updatedInfo = bindFrameBufferAttachmentHelper(fboId, attachments);
567 // Update our stored Render Target Info
568 m_renderTargets.insert(key: renderTargetNodeId, value: updatedInfo);
569 }
570
571 return fboId;
572}
573
574void SubmissionContext::releaseRenderTargets()
575{
576 const auto keys = m_renderTargets.keys();
577 for (Qt3DCore::QNodeId renderTargetId : keys)
578 releaseRenderTarget(id: renderTargetId);
579}
580
581QSize SubmissionContext::renderTargetSize(const QSize &surfaceSize) const
582{
583 QSize renderTargetSize;
584 if (m_activeFBO != m_defaultFBO) {
585 // For external FBOs we may not have a m_renderTargets entry.
586 if (m_renderTargets.contains(key: m_activeFBONodeId)) {
587 renderTargetSize = m_renderTargets[m_activeFBONodeId].size;
588 } else if (surfaceSize.isValid()) {
589 renderTargetSize = surfaceSize;
590 } else {
591 // External FBO (when used with QtQuick2 Scene3D)
592
593 // Query FBO color attachment 0 size
594 GLint attachmentObjectType = GL_NONE;
595 GLint attachment0Name = 0;
596 m_gl->functions()->glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER,
597 GL_COLOR_ATTACHMENT0,
598 GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,
599 params: &attachmentObjectType);
600 m_gl->functions()->glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER,
601 GL_COLOR_ATTACHMENT0,
602 GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
603 params: &attachment0Name);
604
605 if (attachmentObjectType == GL_RENDERBUFFER && m_glHelper->supportsFeature(feature: GraphicsHelperInterface::RenderBufferDimensionRetrieval))
606 renderTargetSize = m_glHelper->getRenderBufferDimensions(renderBufferId: attachment0Name);
607 else if (attachmentObjectType == GL_TEXTURE && m_glHelper->supportsFeature(feature: GraphicsHelperInterface::TextureDimensionRetrieval))
608 // Assumes texture level 0 and GL_TEXTURE_2D target
609 renderTargetSize = m_glHelper->getTextureDimensions(textureId: attachment0Name, GL_TEXTURE_2D);
610 else
611 return renderTargetSize;
612 }
613 } else {
614 renderTargetSize = m_surface->size().isValid() ? m_surface->size() : surfaceSize;
615 if (m_surface->surfaceClass() == QSurface::Window) {
616 const float dpr = static_cast<QWindow *>(m_surface)->devicePixelRatio();
617 renderTargetSize *= dpr;
618 }
619 }
620 return renderTargetSize;
621}
622
623QImage SubmissionContext::readFramebuffer(const QRect &rect)
624{
625 QImage img;
626 const unsigned int area = rect.width() * rect.height();
627 unsigned int bytes;
628 GLenum format, type;
629 QImage::Format imageFormat;
630 uint stride;
631
632 // m_renderTargetFormat is set when the current RV FBO is set in activateRenderTarget
633 /* format value should match GL internalFormat */
634 GLenum internalFormat = m_renderTargetFormat;
635
636 switch (m_renderTargetFormat) {
637 case QAbstractTexture::RGBAFormat:
638 case QAbstractTexture::RGBA8_SNorm:
639 case QAbstractTexture::RGBA8_UNorm:
640 case QAbstractTexture::RGBA8U:
641 case QAbstractTexture::SRGB8_Alpha8:
642#if QT_CONFIG(opengles2)
643 format = GL_RGBA;
644 imageFormat = QImage::Format_RGBA8888_Premultiplied;
645#else
646 format = GL_BGRA;
647 imageFormat = QImage::Format_ARGB32_Premultiplied;
648 internalFormat = GL_RGBA8;
649#endif
650 type = GL_UNSIGNED_BYTE;
651 bytes = area * 4;
652 stride = rect.width() * 4;
653 break;
654 case QAbstractTexture::SRGB8:
655 case QAbstractTexture::RGBFormat:
656 case QAbstractTexture::RGB8U:
657 case QAbstractTexture::RGB8_UNorm:
658#if QT_CONFIG(opengles2)
659 format = GL_RGBA;
660 imageFormat = QImage::Format_RGBX8888;
661#else
662 format = GL_BGRA;
663 imageFormat = QImage::Format_RGB32;
664 internalFormat = GL_RGB8;
665#endif
666 type = GL_UNSIGNED_BYTE;
667 bytes = area * 4;
668 stride = rect.width() * 4;
669 break;
670#if !QT_CONFIG(opengles2)
671 case QAbstractTexture::RG11B10F:
672 bytes = area * 4;
673 format = GL_RGB;
674 type = GL_UNSIGNED_INT_10F_11F_11F_REV;
675 imageFormat = QImage::Format_RGB30;
676 stride = rect.width() * 4;
677 break;
678 case QAbstractTexture::RGB10A2:
679 bytes = area * 4;
680 format = GL_RGBA;
681 type = GL_UNSIGNED_INT_2_10_10_10_REV;
682 imageFormat = QImage::Format_A2BGR30_Premultiplied;
683 stride = rect.width() * 4;
684 break;
685 case QAbstractTexture::R5G6B5:
686 bytes = area * 2;
687 format = GL_RGB;
688 type = GL_UNSIGNED_SHORT;
689 internalFormat = GL_UNSIGNED_SHORT_5_6_5_REV;
690 imageFormat = QImage::Format_RGB16;
691 stride = rect.width() * 2;
692 break;
693 case QAbstractTexture::RGBA16F:
694 case QAbstractTexture::RGBA16U:
695 case QAbstractTexture::RGBA32F:
696 case QAbstractTexture::RGBA32U:
697 bytes = area * 16;
698 format = GL_RGBA;
699 type = GL_FLOAT;
700 imageFormat = QImage::Format_ARGB32_Premultiplied;
701 stride = rect.width() * 16;
702 break;
703#endif
704 default:
705 auto warning = qWarning();
706 warning << "Unable to convert";
707 QtDebugUtils::formatQEnum(debug&: warning, value: m_renderTargetFormat);
708 warning << "render target texture format to QImage.";
709 return img;
710 }
711
712 GLint samples = 0;
713 m_gl->functions()->glGetIntegerv(GL_SAMPLES, params: &samples);
714 if (samples > 0 && !m_glHelper->supportsFeature(feature: GraphicsHelperInterface::BlitFramebuffer)) {
715 qCWarning(Backend) << Q_FUNC_INFO << "Unable to capture multisampled framebuffer; "
716 "Required feature BlitFramebuffer is missing.";
717 return img;
718 }
719
720 img = QImage(rect.width(), rect.height(), imageFormat);
721
722 QScopedArrayPointer<uchar> data(new uchar [bytes]);
723
724 if (samples > 0) {
725 // resolve multisample-framebuffer to renderbuffer and read pixels from it
726 GLuint fbo, rb;
727 QOpenGLFunctions *gl = m_gl->functions();
728 gl->glGenFramebuffers(n: 1, framebuffers: &fbo);
729 gl->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer: fbo);
730 gl->glGenRenderbuffers(n: 1, renderbuffers: &rb);
731 gl->glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer: rb);
732 gl->glRenderbufferStorage(GL_RENDERBUFFER, internalformat: internalFormat, width: rect.width(), height: rect.height());
733 gl->glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer: rb);
734
735 const GLenum status = gl->glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
736 if (status != GL_FRAMEBUFFER_COMPLETE) {
737 gl->glDeleteRenderbuffers(n: 1, renderbuffers: &rb);
738 gl->glDeleteFramebuffers(n: 1, framebuffers: &fbo);
739 qCWarning(Backend) << Q_FUNC_INFO << "Copy-framebuffer not complete: " << status;
740 return img;
741 }
742
743 m_glHelper->blitFramebuffer(srcX0: rect.x(), srcY0: rect.y(), srcX1: rect.x() + rect.width(), srcY1: rect.y() + rect.height(),
744 dstX0: 0, dstY0: 0, dstX1: rect.width(), dstY1: rect.height(),
745 GL_COLOR_BUFFER_BIT, GL_NEAREST);
746 gl->glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer: fbo);
747 gl->glReadPixels(x: 0,y: 0,width: rect.width(), height: rect.height(), format, type, pixels: data.data());
748
749 copyGLFramebufferDataToImage(img, srcData: data.data(), stride, width: rect.width(), height: rect.height(), format: m_renderTargetFormat);
750
751 gl->glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer: rb);
752 gl->glDeleteRenderbuffers(n: 1, renderbuffers: &rb);
753 gl->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: m_activeFBO);
754 gl->glDeleteFramebuffers(n: 1, framebuffers: &fbo);
755 } else {
756 // read pixels directly from framebuffer
757 m_gl->functions()->glReadPixels(x: rect.x(), y: rect.y(), width: rect.width(), height: rect.height(), format, type, pixels: data.data());
758 copyGLFramebufferDataToImage(img, srcData: data.data(), stride, width: rect.width(), height: rect.height(), format: m_renderTargetFormat);
759 }
760
761 return img;
762}
763
764void SubmissionContext::setViewport(const QRectF &viewport, const QSize &surfaceSize)
765{
766 // // save for later use; this has nothing to do with the viewport but it is
767 // // here that we get to know the surfaceSize from the RenderView.
768 m_surfaceSize = surfaceSize;
769
770 m_viewport = viewport;
771 QSize size = renderTargetSize(surfaceSize);
772
773 // Check that the returned size is before calling glViewport
774 if (size.isEmpty())
775 return;
776
777 // Qt3D 0------------------> 1 OpenGL 1^
778 // | |
779 // | |
780 // | |
781 // V |
782 // 1 0---------------------> 1
783 // The Viewport is defined between 0 and 1 which allows us to automatically
784 // scale to the size of the provided window surface
785 m_gl->functions()->glViewport(x: m_viewport.x() * size.width(),
786 y: (1.0 - m_viewport.y() - m_viewport.height()) * size.height(),
787 width: m_viewport.width() * size.width(),
788 height: m_viewport.height() * size.height());
789}
790
791void SubmissionContext::releaseOpenGL()
792{
793 m_renderBufferHash.clear();
794
795 // Stop and destroy the OpenGL logger
796#ifdef QT_OPENGL_LIB
797 if (m_debugLogger) {
798 m_debugLogger->stopLogging();
799 m_debugLogger.reset(other: nullptr);
800 }
801#endif
802}
803
804// The OpenGLContext is not current on any surface at this point
805void SubmissionContext::setOpenGLContext(QOpenGLContext* ctx)
806{
807 Q_ASSERT(ctx);
808
809 releaseOpenGL();
810 m_gl = ctx;
811}
812
813// Called only from RenderThread
814bool SubmissionContext::activateShader(GLShader *shader)
815{
816 if (shader->shaderProgram() != m_activeShader) {
817 // Ensure material uniforms are re-applied
818 m_material = nullptr;
819
820 m_activeShader = shader->shaderProgram();
821 if (Q_LIKELY(m_activeShader != nullptr)) {
822 m_activeShader->bind();
823 } else {
824 m_glHelper->useProgram(programId: 0);
825 qWarning() << "No shader program found";
826 return false;
827 }
828 }
829 return true;
830}
831
832SubmissionContext::RenderTargetInfo SubmissionContext::bindFrameBufferAttachmentHelper(GLuint fboId, const AttachmentPack &attachments)
833{
834 // Set FBO attachments. These are normally textures, except that on Open GL
835 // ES <= 3.1 we must use a renderbuffer if a combined depth+stencil is
836 // desired since this cannot be achieved neither with a single texture (not
837 // before GLES 3.2) nor with separate textures (no suitable format for
838 // stencil before 3.1 with the appropriate extension).
839
840 QSize fboSize;
841 GLTextureManager *glTextureManager = m_renderer->glResourceManagers()->glTextureManager();
842 const auto attachments_ = attachments.attachments();
843 for (const Attachment &attachment : attachments_) {
844 GLTexture *rTex = glTextureManager->lookupResource(id: attachment.m_textureUuid);
845 if (!m_glHelper->frameBufferNeedsRenderBuffer(attachment)) {
846 QOpenGLTexture *glTex = rTex ? rTex->getGLTexture() : nullptr;
847 if (glTex != nullptr) {
848 // The texture can not be rendered simultaniously by another renderer
849 Q_ASSERT(!rTex->isExternalRenderingEnabled());
850 if (fboSize.isEmpty())
851 fboSize = QSize(glTex->width(), glTex->height());
852 else
853 fboSize = QSize(qMin(a: fboSize.width(), b: glTex->width()), qMin(a: fboSize.height(), b: glTex->height()));
854 m_glHelper->bindFrameBufferAttachment(texture: glTex, attachment);
855 }
856 } else {
857 RenderBuffer *renderBuffer = rTex ? rTex->getOrCreateRenderBuffer() : nullptr;
858 if (renderBuffer) {
859 if (fboSize.isEmpty())
860 fboSize = QSize(renderBuffer->width(), renderBuffer->height());
861 else
862 fboSize = QSize(qMin(a: fboSize.width(), b: renderBuffer->width()), qMin(a: fboSize.height(), b: renderBuffer->height()));
863 m_glHelper->bindFrameBufferAttachment(renderBuffer, attachment);
864 }
865 }
866 }
867 return {.fboId: fboId, .size: fboSize, .attachments: attachments};
868}
869
870void SubmissionContext::activateDrawBuffers(const AttachmentPack &attachments)
871{
872 const std::vector<QRenderTargetOutput::AttachmentPoint> &activeDrawBuffers = attachments.getDrawBuffers();
873
874 std::vector<GLenum> activeGlDrawBuffers;
875 activeGlDrawBuffers.reserve(n: activeDrawBuffers.size());
876 for (const auto &attachmentPoint : activeDrawBuffers)
877 activeGlDrawBuffers.push_back(x: glAttachmentPoint(attachmentPoint));
878
879 if (m_glHelper->checkFrameBufferComplete()) {
880 if (activeDrawBuffers.size() > 1) {// We need MRT
881 if (m_glHelper->supportsFeature(feature: GraphicsHelperInterface::MRT)) {
882 // Set up MRT, glDrawBuffers...
883 m_glHelper->drawBuffers(n: GLsizei(activeGlDrawBuffers.size()), bufs: activeGlDrawBuffers.data());
884 }
885 }
886 else if (activeDrawBuffers.size() == 1){
887 m_glHelper->drawBuffer(mode: activeGlDrawBuffers.at(n: 0));
888 }
889 } else {
890 qCWarning(Backend) << "FBO incomplete";
891 }
892}
893
894
895void SubmissionContext::setActiveMaterial(Material *rmat)
896{
897 if (m_material == rmat)
898 return;
899
900 m_textureContext.deactivateTexturesWithScope(ts: TextureSubmissionContext::TextureScopeMaterial);
901 m_imageContext.deactivateImages();
902 m_material = rmat;
903}
904
905void SubmissionContext::setCurrentStateSet(RenderStateSet *ss)
906{
907 if (ss == m_stateSet)
908 return;
909 if (ss)
910 applyStateSet(ss);
911 m_stateSet = ss;
912}
913
914RenderStateSet *SubmissionContext::currentStateSet() const
915{
916 return m_stateSet;
917}
918
919void SubmissionContext::applyState(const StateVariant &stateVariant)
920{
921 switch (stateVariant.type) {
922
923 case AlphaCoverageStateMask: {
924 applyStateHelper<AlphaCoverage>(static_cast<const AlphaCoverage *>(stateVariant.constState()), gc: this);
925 break;
926 }
927 case AlphaTestMask: {
928 applyStateHelper<AlphaFunc>(state: static_cast<const AlphaFunc *>(stateVariant.constState()), gc: this);
929 break;
930 }
931 case BlendStateMask: {
932 applyStateHelper<BlendEquation>(state: static_cast<const BlendEquation *>(stateVariant.constState()), gc: this);
933 break;
934 }
935 case BlendEquationArgumentsMask: {
936 applyStateHelper<BlendEquationArguments>(state: static_cast<const BlendEquationArguments *>(stateVariant.constState()), gc: this);
937 break;
938 }
939 case MSAAEnabledStateMask: {
940 applyStateHelper<MSAAEnabled>(state: static_cast<const MSAAEnabled *>(stateVariant.constState()), gc: this);
941 break;
942 }
943
944 case CullFaceStateMask: {
945 applyStateHelper<CullFace>(state: static_cast<const CullFace *>(stateVariant.constState()), gc: this);
946 break;
947 }
948
949 case DepthWriteStateMask: {
950 applyStateHelper<NoDepthMask>(state: static_cast<const NoDepthMask *>(stateVariant.constState()), gc: this);
951 break;
952 }
953
954 case DepthTestStateMask: {
955 applyStateHelper<DepthTest>(state: static_cast<const DepthTest *>(stateVariant.constState()), gc: this);
956 break;
957 }
958
959 case DepthRangeMask: {
960 applyStateHelper<DepthRange>(state: static_cast<const DepthRange *>(stateVariant.constState()), gc: this);
961 break;
962 }
963
964 case RasterModeMask: {
965 applyStateHelper<RasterMode>(state: static_cast<const RasterMode *>(stateVariant.constState()), gc: this);
966 break;
967 }
968
969 case FrontFaceStateMask: {
970 applyStateHelper<FrontFace>(state: static_cast<const FrontFace *>(stateVariant.constState()), gc: this);
971 break;
972 }
973
974 case ScissorStateMask: {
975 applyStateHelper<ScissorTest>(state: static_cast<const ScissorTest *>(stateVariant.constState()), gc: this);
976 break;
977 }
978
979 case StencilTestStateMask: {
980 applyStateHelper<StencilTest>(state: static_cast<const StencilTest *>(stateVariant.constState()), gc: this);
981 break;
982 }
983
984 case PointSizeMask: {
985 applyStateHelper<PointSize>(state: static_cast<const PointSize *>(stateVariant.constState()), gc: this);
986 break;
987 }
988
989 case PolygonOffsetStateMask: {
990 applyStateHelper<PolygonOffset>(state: static_cast<const PolygonOffset *>(stateVariant.constState()), gc: this);
991 break;
992 }
993
994 case ColorStateMask: {
995 applyStateHelper<ColorMask>(state: static_cast<const ColorMask *>(stateVariant.constState()), gc: this);
996 break;
997 }
998
999 case ClipPlaneMask: {
1000 applyStateHelper<ClipPlane>(state: static_cast<const ClipPlane *>(stateVariant.constState()), gc: this);
1001 break;
1002 }
1003
1004 case SeamlessCubemapMask: {
1005 applyStateHelper<SeamlessCubemap>(static_cast<const SeamlessCubemap *>(stateVariant.constState()), gc: this);
1006 break;
1007 }
1008
1009 case StencilOpMask: {
1010 applyStateHelper<StencilOp>(state: static_cast<const StencilOp *>(stateVariant.constState()), gc: this);
1011 break;
1012 }
1013
1014 case StencilWriteStateMask: {
1015 applyStateHelper<StencilMask>(state: static_cast<const StencilMask *>(stateVariant.constState()), gc: this);
1016 break;
1017 }
1018
1019 case DitheringStateMask: {
1020 applyStateHelper<Dithering>(static_cast<const Dithering *>(stateVariant.constState()), gc: this);
1021 break;
1022 }
1023
1024 case LineWidthMask: {
1025 applyStateHelper<LineWidth>(state: static_cast<const LineWidth *>(stateVariant.constState()), gc: this);
1026 break;
1027 }
1028 default:
1029 Q_UNREACHABLE();
1030 }
1031}
1032
1033void SubmissionContext::resetState()
1034{
1035 QOpenGLFunctions *f = m_gl->functions();
1036 f->glActiveTexture(GL_TEXTURE0);
1037 f->glBindTexture(GL_TEXTURE_2D, texture: 0);
1038
1039 f->glDisable(GL_SCISSOR_TEST);
1040
1041 f->glColorMask(red: true, green: true, blue: true, alpha: true);
1042 f->glClearColor(red: m_currClearColorValue.redF(), green: m_currClearColorValue.greenF(), blue: m_currClearColorValue.blueF(), alpha: m_currClearColorValue.alphaF());
1043
1044 f->glEnable(GL_DEPTH_TEST);
1045 f->glDepthMask(flag: true);
1046 f->glDepthFunc(GL_LESS);
1047 f->glClearDepthf(depth: m_currClearDepthValue);
1048
1049 f->glDisable(GL_STENCIL_TEST);
1050 f->glStencilMask(mask: 0xff);
1051 f->glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
1052 f->glStencilFunc(GL_ALWAYS, ref: 0, mask: 0xff);
1053 f->glClearStencil(s: m_currClearStencilValue);
1054
1055 f->glDisable(GL_BLEND);
1056 f->glBlendFunc(GL_ONE, GL_ZERO);
1057
1058 f->glUseProgram(program: 0);
1059}
1060
1061void SubmissionContext::resetMasked(qint64 maskOfStatesToReset)
1062{
1063 // TO DO -> Call gcHelper methods instead of raw GL
1064 // QOpenGLFunctions shouldn't be used here directly
1065 QOpenGLFunctions *funcs = m_gl->functions();
1066
1067 if (maskOfStatesToReset & ScissorStateMask)
1068 funcs->glDisable(GL_SCISSOR_TEST);
1069
1070 if (maskOfStatesToReset & BlendStateMask)
1071 funcs->glDisable(GL_BLEND);
1072
1073 if (maskOfStatesToReset & StencilWriteStateMask)
1074 funcs->glStencilMask(mask: 0);
1075
1076 if (maskOfStatesToReset & StencilTestStateMask)
1077 funcs->glDisable(GL_STENCIL_TEST);
1078
1079 if (maskOfStatesToReset & DepthRangeMask)
1080 depthRange(nearValue: 0.0f, farValue: 1.0f);
1081
1082 if (maskOfStatesToReset & DepthTestStateMask)
1083 funcs->glDisable(GL_DEPTH_TEST);
1084
1085 if (maskOfStatesToReset & DepthWriteStateMask)
1086 funcs->glDepthMask(GL_TRUE); // reset to default
1087
1088 if (maskOfStatesToReset & FrontFaceStateMask)
1089 funcs->glFrontFace(GL_CCW); // reset to default
1090
1091 if (maskOfStatesToReset & CullFaceStateMask)
1092 funcs->glDisable(GL_CULL_FACE);
1093
1094 if (maskOfStatesToReset & DitheringStateMask)
1095 funcs->glDisable(GL_DITHER);
1096
1097 if (maskOfStatesToReset & AlphaCoverageStateMask)
1098 setAlphaCoverageEnabled(false);
1099
1100 if (maskOfStatesToReset & PointSizeMask)
1101 pointSize(programmable: false, value: 1.0f); // reset to default
1102
1103 if (maskOfStatesToReset & PolygonOffsetStateMask)
1104 funcs->glDisable(GL_POLYGON_OFFSET_FILL);
1105
1106 if (maskOfStatesToReset & ColorStateMask)
1107 funcs->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1108
1109 if (maskOfStatesToReset & ClipPlaneMask) {
1110 GLint max = maxClipPlaneCount();
1111 for (GLint i = 0; i < max; ++i)
1112 disableClipPlane(clipPlane: i);
1113 }
1114
1115 if (maskOfStatesToReset & SeamlessCubemapMask)
1116 setSeamlessCubemap(false);
1117
1118 if (maskOfStatesToReset & StencilOpMask)
1119 funcs->glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
1120
1121 if (maskOfStatesToReset & LineWidthMask)
1122 funcs->glLineWidth(width: 1.0f);
1123
1124#if !QT_CONFIG(opengles2)
1125 if (maskOfStatesToReset & RasterModeMask)
1126 m_glHelper->rasterMode(GL_FRONT_AND_BACK, GL_FILL);
1127#endif
1128}
1129
1130void SubmissionContext::applyStateSet(RenderStateSet *ss)
1131{
1132 RenderStateSet* previousStates = currentStateSet();
1133
1134 const StateMaskSet invOurState = ~ss->stateMask();
1135 // generate a mask for each set bit in previous, where we do not have
1136 // the corresponding bit set.
1137
1138 StateMaskSet stateToReset = 0;
1139 if (previousStates) {
1140 stateToReset = previousStates->stateMask() & invOurState;
1141 qCDebug(RenderStates) << "previous states " << QString::number(previousStates->stateMask(), base: 2);
1142 }
1143 qCDebug(RenderStates) << " current states " << QString::number(ss->stateMask(), base: 2) << "inverse " << QString::number(invOurState, base: 2) << " -> states to change: " << QString::number(stateToReset, base: 2);
1144
1145 // Reset states that aren't active in the current state set
1146 resetMasked(maskOfStatesToReset: stateToReset);
1147
1148 // Apply states that weren't in the previous state or that have
1149 // different values
1150 const std::vector<StateVariant> statesToSet = ss->states();
1151 for (const StateVariant &ds : statesToSet) {
1152 if (previousStates && previousStates->contains(ds))
1153 continue;
1154 applyState(stateVariant: ds);
1155 }
1156}
1157
1158void SubmissionContext::clearColor(const QColor &color)
1159{
1160 if (m_currClearColorValue != color) {
1161 m_currClearColorValue = color;
1162 m_gl->functions()->glClearColor(red: color.redF(), green: color.greenF(), blue: color.blueF(), alpha: color.alphaF());
1163 }
1164}
1165
1166void SubmissionContext::clearDepthValue(float depth)
1167{
1168 if (m_currClearDepthValue != depth) {
1169 m_currClearDepthValue = depth;
1170 m_gl->functions()->glClearDepthf(depth);
1171 }
1172}
1173
1174void SubmissionContext::clearStencilValue(int stencil)
1175{
1176 if (m_currClearStencilValue != stencil) {
1177 m_currClearStencilValue = stencil;
1178 m_gl->functions()->glClearStencil(s: stencil);
1179 }
1180}
1181
1182GLFence SubmissionContext::fenceSync()
1183{
1184 return m_glHelper->fenceSync();
1185}
1186
1187void SubmissionContext::clientWaitSync(GLFence sync, GLuint64 nanoSecTimeout)
1188{
1189 qDebug() << Q_FUNC_INFO << sync;
1190 m_glHelper->clientWaitSync(sync, nanoSecTimeout);
1191}
1192
1193void SubmissionContext::waitSync(GLFence sync)
1194{
1195 qDebug() << Q_FUNC_INFO << sync;
1196 m_glHelper->waitSync(sync);
1197}
1198
1199bool SubmissionContext::wasSyncSignaled(GLFence sync)
1200{
1201 return m_glHelper->wasSyncSignaled(sync);
1202}
1203
1204void SubmissionContext::deleteSync(GLFence sync)
1205{
1206 m_glHelper->deleteSync(sync);
1207}
1208
1209void SubmissionContext::setUpdatedTexture(const Qt3DCore::QNodeIdVector &updatedTextureIds)
1210{
1211 m_updateTextureIds = updatedTextureIds;
1212}
1213
1214// It will be easier if the QGraphicContext applies the QUniformPack
1215// than the other way around
1216bool SubmissionContext::setParameters(ShaderParameterPack &parameterPack, GLShader *shader)
1217{
1218 static const int irradianceStructId = StringToInt::lookupId(str: QLatin1String("envLight.irradiance"));
1219 static const int specularStructId = StringToInt::lookupId(str: QLatin1String("envLight.specular"));
1220 static const int irradianceId = StringToInt::lookupId(str: QLatin1String("envLightIrradiance"));
1221 static const int specularId = StringToInt::lookupId(str: QLatin1String("envLightSpecular"));
1222 // Activate textures and update TextureUniform in the pack
1223 // with the correct textureUnit
1224
1225 // Set the pinned texture of the previous material texture
1226 // to pinable so that we should easily find an available texture unit
1227 m_textureContext.deactivateTexturesWithScope(ts: TextureSubmissionContext::TextureScopeMaterial);
1228 // Update the uniforms with the correct texture unit id's
1229 PackUniformHash &uniformValues = parameterPack.uniforms();
1230
1231 // Fill Texture Uniform Value with proper texture units
1232 // so that they can be applied as regular uniforms in a second step
1233 for (size_t i = 0; i < parameterPack.textures().size(); ++i) {
1234 const ShaderParameterPack::NamedResource &namedTex = parameterPack.textures().at(n: i);
1235 // Given a Texture QNodeId, we retrieve the associated shared GLTexture
1236 if (uniformValues.contains(key: namedTex.glslNameId)) {
1237 GLTexture *t = m_renderer->glResourceManagers()->glTextureManager()->lookupResource(id: namedTex.nodeId);
1238 if (t != nullptr) {
1239 UniformValue &texUniform = uniformValues.value(key: namedTex.glslNameId);
1240 if (texUniform.valueType() == UniformValue::TextureValue) {
1241 const int texUnit = m_textureContext.activateTexture(scope: TextureSubmissionContext::TextureScopeMaterial, gl: m_gl, tex: t);
1242 texUniform.data<int>()[namedTex.uniformArrayIndex] = texUnit;
1243 if (texUnit == -1) {
1244 if (namedTex.glslNameId != irradianceId &&
1245 namedTex.glslNameId != specularId &&
1246 namedTex.glslNameId != irradianceStructId &&
1247 namedTex.glslNameId != specularStructId) {
1248 // Only return false if we are not dealing with env light textures
1249 qCWarning(Backend) << "Unable to find suitable Texture Unit for" << StringToInt::lookupString(idx: namedTex.glslNameId);
1250 return false;
1251 }
1252 }
1253 }
1254 }
1255 }
1256 }
1257
1258 // Set the pinned images of the previous material
1259 // to pinable so that we should easily find an available image unit
1260 m_imageContext.deactivateImages();
1261
1262 // Fill Image Uniform Value with proper image units
1263 // so that they can be applied as regular uniforms in a second step
1264 for (size_t i = 0; i < parameterPack.images().size(); ++i) {
1265 const ShaderParameterPack::NamedResource &namedTex = parameterPack.images().at(n: i);
1266 // Given a Texture QNodeId, we retrieve the associated shared GLTexture
1267 if (uniformValues.contains(key: namedTex.glslNameId)) {
1268 ShaderImage *img = m_renderer->nodeManagers()->shaderImageManager()->lookupResource(id: namedTex.nodeId);
1269 if (img != nullptr) {
1270 GLTexture *t = m_renderer->glResourceManagers()->glTextureManager()->lookupResource(id: img->textureId());
1271 if (t == nullptr) {
1272 qCWarning(Backend) << "Shader Image referencing invalid texture";
1273 continue;
1274 } else {
1275 UniformValue &imgUniform = uniformValues.value(key: namedTex.glslNameId);
1276 if (imgUniform.valueType() == UniformValue::ShaderImageValue) {
1277 const int imgUnit = m_imageContext.activateImage(image: img, tex: t);
1278 imgUniform.data<int>()[namedTex.uniformArrayIndex] = imgUnit;
1279 if (imgUnit == -1) {
1280 qCWarning(Backend) << "Unable to bind Image to Texture";
1281 return false;
1282 }
1283 }
1284 }
1285 }
1286 }
1287 }
1288
1289 QOpenGLShaderProgram *glShader = activeShader();
1290
1291 // TO DO: We could cache the binding points somehow and only do the binding when necessary
1292 // for SSBO and UBO
1293
1294 // Bind Shader Storage block to SSBO and update SSBO
1295 const std::vector<BlockToSSBO> &blockToSSBOs = parameterPack.shaderStorageBuffers();
1296 for (const BlockToSSBO &b : blockToSSBOs) {
1297 Buffer *cpuBuffer = m_renderer->nodeManagers()->bufferManager()->lookupResource(id: b.m_bufferID);
1298 GLBuffer *ssbo = glBufferForRenderBuffer(buf: cpuBuffer);
1299 // bindShaderStorageBlock
1300 // This is currently not required as we are introspecting the bindingIndex
1301 // value from the shaders and not replacing them, making such a call useless
1302 // bindShaderStorageBlock(shader->programId(), b.m_blockIndex, b.m_bindingIndex);
1303 bindShaderStorageBlock(programId: glShader->programId(), shaderStorageBlockIndex: b.m_blockIndex, shaderStorageBlockBinding: b.m_bindingIndex);
1304 // Needed to avoid conflict where the buffer would already
1305 // be bound as a VertexArray
1306 bindGLBuffer(buffer: ssbo, type: GLBuffer::ShaderStorageBuffer);
1307 ssbo->bindBufferBase(ctx: this, bindingPoint: b.m_bindingIndex, t: GLBuffer::ShaderStorageBuffer);
1308 // TO DO: Make sure that there's enough binding points
1309 }
1310
1311 // Bind UniformBlocks to UBO and update UBO from Buffer
1312 // TO DO: Convert ShaderData to Buffer so that we can use that generic process
1313 const std::vector<BlockToUBO> &blockToUBOs = parameterPack.uniformBuffers();
1314 int uboIndex = 0;
1315 for (const BlockToUBO &b : blockToUBOs) {
1316 Buffer *cpuBuffer = m_renderer->nodeManagers()->bufferManager()->lookupResource(id: b.m_bufferID);
1317 GLBuffer *ubo = glBufferForRenderBuffer(buf: cpuBuffer);
1318 bindUniformBlock(programId: glShader->programId(), uniformBlockIndex: b.m_blockIndex, uniformBlockBinding: uboIndex);
1319 // Needed to avoid conflict where the buffer would already
1320 // be bound as a VertexArray
1321 bindGLBuffer(buffer: ubo, type: GLBuffer::UniformBuffer);
1322 ubo->bindBufferBase(ctx: this, bindingPoint: uboIndex++, t: GLBuffer::UniformBuffer);
1323 // TO DO: Make sure that there's enough binding points
1324 }
1325
1326 // Update uniforms in the Default Uniform Block
1327 const PackUniformHash& values = parameterPack.uniforms();
1328 const auto &activeUniformsIndices = parameterPack.submissionUniformIndices();
1329 const std::vector<ShaderUniform> &shaderUniforms = shader->uniforms();
1330
1331 for (const int shaderUniformIndex : activeUniformsIndices) {
1332 const ShaderUniform &uniform = shaderUniforms[shaderUniformIndex];
1333 values.apply(key: uniform.m_nameId, func: [&] (const UniformValue& v) {
1334 // skip invalid textures/images
1335 if (!((v.valueType() == UniformValue::TextureValue ||
1336 v.valueType() == UniformValue::ShaderImageValue) &&
1337 *v.constData<int>() == -1))
1338 applyUniform(description: uniform, v);
1339 });
1340
1341 }
1342 // if not all data is valid, the next frame will be rendered immediately
1343 return true;
1344}
1345
1346void SubmissionContext::enableAttribute(const VAOVertexAttribute &attr)
1347{
1348 // Bind buffer within the current VAO
1349 GLBuffer *buf = m_renderer->glResourceManagers()->glBufferManager()->data(handle: attr.bufferHandle);
1350 Q_ASSERT(buf);
1351 bindGLBuffer(buffer: buf, type: attr.attributeType);
1352
1353 // Don't use QOpenGLShaderProgram::setAttributeBuffer() because of QTBUG-43199.
1354 // Use the introspection data and set the attribute explicitly
1355 m_glHelper->enableVertexAttributeArray(location: attr.location);
1356 m_glHelper->vertexAttributePointer(shaderDataType: attr.shaderDataType,
1357 index: attr.location,
1358 size: attr.vertexSize,
1359 type: attr.dataType,
1360 GL_TRUE, // TODO: Support normalization property on QAttribute
1361 stride: attr.byteStride,
1362 pointer: reinterpret_cast<const void *>(qintptr(attr.byteOffset)));
1363
1364
1365 // Done by the helper if it supports it
1366 if (attr.divisor != 0)
1367 m_glHelper->vertexAttribDivisor(index: attr.location, divisor: attr.divisor);
1368}
1369
1370void SubmissionContext::disableAttribute(const SubmissionContext::VAOVertexAttribute &attr)
1371{
1372 QOpenGLShaderProgram *prog = activeShader();
1373 prog->disableAttributeArray(location: attr.location);
1374}
1375
1376// Note: needs to be called while VAO is bound
1377void SubmissionContext::specifyAttribute(const Attribute *attribute,
1378 Buffer *buffer,
1379 const ShaderAttribute *attributeDescription)
1380{
1381 const int location = attributeDescription->m_location;
1382 if (location < 0) {
1383 qCWarning(Backend) << "failed to resolve location for attribute:" << attribute->name();
1384 return;
1385 }
1386
1387 const GLint attributeDataType = glDataTypeFromAttributeDataType(dataType: attribute->vertexBaseType());
1388 const HGLBuffer glBufferHandle = m_renderer->glResourceManagers()->glBufferManager()->lookupHandle(id: buffer->peerId());
1389 Q_ASSERT(!glBufferHandle.isNull());
1390 const GLBuffer::Type attributeType = attributeTypeToGLBufferType(type: attribute->attributeType());
1391
1392 int typeSize = 0;
1393 int attrCount = 0;
1394
1395 if (attribute->vertexSize() >= 1 && attribute->vertexSize() <= 4) {
1396 attrCount = 1;
1397 } else if (attribute->vertexSize() == 9) {
1398 typeSize = byteSizeFromType(type: attributeDataType);
1399 attrCount = 3;
1400 } else if (attribute->vertexSize() == 16) {
1401 typeSize = byteSizeFromType(type: attributeDataType);
1402 attrCount = 4;
1403 } else {
1404 Q_UNREACHABLE();
1405 }
1406
1407 Q_ASSERT(!glBufferHandle.isNull());
1408 VAOVertexAttribute attr;
1409 attr.bufferHandle = glBufferHandle;
1410 attr.attributeType = attributeType;
1411 attr.dataType = attributeDataType;
1412 attr.divisor = attribute->divisor();
1413 attr.vertexSize = attribute->vertexSize() / attrCount;
1414 attr.byteStride = (attribute->byteStride() != 0) ? attribute->byteStride() : (attrCount * attrCount * typeSize);
1415 attr.shaderDataType = attributeDescription->m_type;
1416
1417 for (int i = 0; i < attrCount; i++) {
1418 attr.location = location + i;
1419 attr.byteOffset = attribute->byteOffset() + (i * attrCount * typeSize);
1420
1421 enableAttribute(attr);
1422
1423 // Save this in the current emulated VAO
1424 if (m_currentVAO)
1425 m_currentVAO->saveVertexAttribute(attr);
1426 }
1427}
1428
1429void SubmissionContext::specifyIndices(Buffer *buffer)
1430{
1431 GLBuffer *buf = glBufferForRenderBuffer(buf: buffer);
1432 if (!bindGLBuffer(buffer: buf, type: GLBuffer::IndexBuffer))
1433 qCWarning(Backend) << Q_FUNC_INFO << "binding index buffer failed";
1434
1435 // bound within the current VAO
1436 // Save this in the current emulated VAO
1437 if (m_currentVAO)
1438 m_currentVAO->saveIndexAttribute(glBufferHandle: m_renderer->glResourceManagers()->glBufferManager()->lookupHandle(id: buffer->peerId()));
1439}
1440
1441void SubmissionContext::updateBuffer(Buffer *buffer)
1442{
1443 const QHash<Qt3DCore::QNodeId, HGLBuffer>::iterator it = m_renderBufferHash.find(key: buffer->peerId());
1444 if (it != m_renderBufferHash.end())
1445 uploadDataToGLBuffer(buffer, b: m_renderer->glResourceManagers()->glBufferManager()->data(handle: it.value()));
1446}
1447
1448QByteArray SubmissionContext::downloadBufferContent(Buffer *buffer)
1449{
1450 const QHash<Qt3DCore::QNodeId, HGLBuffer>::iterator it = m_renderBufferHash.find(key: buffer->peerId());
1451 if (it != m_renderBufferHash.end())
1452 return downloadDataFromGLBuffer(buffer, b: m_renderer->glResourceManagers()->glBufferManager()->data(handle: it.value()));
1453 return QByteArray();
1454}
1455
1456void SubmissionContext::releaseBuffer(Qt3DCore::QNodeId bufferId)
1457{
1458 auto it = m_renderBufferHash.find(key: bufferId);
1459 if (it != m_renderBufferHash.end()) {
1460 HGLBuffer glBuffHandle = it.value();
1461 GLBuffer *glBuff = m_renderer->glResourceManagers()->glBufferManager()->data(handle: glBuffHandle);
1462
1463 Q_ASSERT(glBuff);
1464 // Destroy the GPU resource
1465 glBuff->destroy(ctx: this);
1466 // Destroy the GLBuffer instance
1467 m_renderer->glResourceManagers()->glBufferManager()->releaseResource(id: bufferId);
1468 // Remove Id - HGLBuffer entry
1469 m_renderBufferHash.erase(it);
1470 }
1471}
1472
1473bool SubmissionContext::hasGLBufferForBuffer(Buffer *buffer)
1474{
1475 const QHash<Qt3DCore::QNodeId, HGLBuffer>::iterator it = m_renderBufferHash.find(key: buffer->peerId());
1476 return (it != m_renderBufferHash.end());
1477}
1478
1479GLBuffer *SubmissionContext::glBufferForRenderBuffer(Buffer *buf)
1480{
1481 if (!m_renderBufferHash.contains(key: buf->peerId()))
1482 m_renderBufferHash.insert(key: buf->peerId(), value: createGLBufferFor(buffer: buf));
1483 return m_renderer->glResourceManagers()->glBufferManager()->data(handle: m_renderBufferHash.value(key: buf->peerId()));
1484}
1485
1486HGLBuffer SubmissionContext::createGLBufferFor(Buffer *buffer)
1487{
1488 GLBuffer *b = m_renderer->glResourceManagers()->glBufferManager()->getOrCreateResource(id: buffer->peerId());
1489 // b.setUsagePattern(static_cast<QOpenGLBuffer::UsagePattern>(buffer->usage()));
1490 Q_ASSERT(b);
1491 if (!b->create(ctx: this))
1492 qCWarning(Io) << Q_FUNC_INFO << "buffer creation failed";
1493
1494 return m_renderer->glResourceManagers()->glBufferManager()->lookupHandle(id: buffer->peerId());
1495}
1496
1497bool SubmissionContext::bindGLBuffer(GLBuffer *buffer, GLBuffer::Type type)
1498{
1499 if (type == GLBuffer::ArrayBuffer && buffer == m_boundArrayBuffer)
1500 return true;
1501
1502 if (buffer->bind(ctx: this, t: type)) {
1503 if (type == GLBuffer::ArrayBuffer)
1504 m_boundArrayBuffer = buffer;
1505 return true;
1506 }
1507 return false;
1508}
1509
1510void SubmissionContext::uploadDataToGLBuffer(Buffer *buffer, GLBuffer *b, bool releaseBuffer)
1511{
1512 if (!bindGLBuffer(buffer: b, type: GLBuffer::ArrayBuffer)) // We're uploading, the type doesn't matter here
1513 qCWarning(Io) << Q_FUNC_INFO << "buffer bind failed";
1514 // If the buffer is dirty (hence being called here)
1515 // there are two possible cases
1516 // * setData was called changing the whole data or functor (or the usage pattern)
1517 // * partial buffer updates where received
1518
1519 // TO DO: Handle usage pattern
1520 std::vector<Qt3DCore::QBufferUpdate> updates = Qt3DCore::moveAndClear(data&: buffer->pendingBufferUpdates());
1521 for (auto it = updates.begin(); it != updates.end(); ++it) {
1522 auto update = it;
1523 // We have a partial update
1524 if (update->offset >= 0) {
1525 //accumulate sequential updates as single one
1526 qsizetype bufferSize = update->data.size();
1527 auto it2 = it + 1;
1528 while ((it2 != updates.end())
1529 && (it2->offset - update->offset == bufferSize)) {
1530 bufferSize += it2->data.size();
1531 ++it2;
1532 }
1533 update->data.resize(size: bufferSize);
1534 while (it + 1 != it2) {
1535 ++it;
1536 update->data.replace(index: it->offset - update->offset, len: it->data.size(), s: it->data);
1537 it->data.clear();
1538 }
1539 // TO DO: based on the number of updates .., it might make sense to
1540 // sometime use glMapBuffer rather than glBufferSubData
1541 b->update(ctx: this, data: update->data.constData(), size: update->data.size(), offset: update->offset);
1542 } else {
1543 // We have an update that was done by calling QBuffer::setData
1544 // which is used to resize or entirely clear the buffer
1545 // Note: we use the buffer data directly in that case
1546 const qsizetype bufferSize = buffer->data().size();
1547 b->allocate(ctx: this, size: bufferSize, dynamic: false); // orphan the buffer
1548 b->allocate(ctx: this, data: buffer->data().constData(), size: bufferSize, dynamic: false);
1549 }
1550 }
1551
1552 if (releaseBuffer) {
1553 b->release(ctx: this);
1554 m_boundArrayBuffer = nullptr;
1555 }
1556 qCDebug(Io) << "uploaded buffer size=" << buffer->data().size();
1557}
1558
1559QByteArray SubmissionContext::downloadDataFromGLBuffer(Buffer *buffer, GLBuffer *b)
1560{
1561 if (!bindGLBuffer(buffer: b, type: GLBuffer::ArrayBuffer)) // We're downloading, the type doesn't matter here
1562 qCWarning(Io) << Q_FUNC_INFO << "buffer bind failed";
1563
1564 QByteArray data = b->download(ctx: this, size: buffer->data().size());
1565 return data;
1566}
1567
1568void SubmissionContext::blitFramebuffer(Qt3DCore::QNodeId inputRenderTargetId,
1569 Qt3DCore::QNodeId outputRenderTargetId,
1570 QRect inputRect, QRect outputRect,
1571 uint defaultFboId,
1572 QRenderTargetOutput::AttachmentPoint inputAttachmentPoint,
1573 QRenderTargetOutput::AttachmentPoint outputAttachmentPoint,
1574 QBlitFramebuffer::InterpolationMethod interpolationMethod)
1575{
1576 GLuint inputFboId = defaultFboId;
1577 bool inputBufferIsDefault = true;
1578 if (!inputRenderTargetId.isNull()) {
1579 RenderTarget *renderTarget = m_renderer->nodeManagers()->renderTargetManager()->lookupResource(id: inputRenderTargetId);
1580 if (renderTarget) {
1581 AttachmentPack attachments(renderTarget, m_renderer->nodeManagers()->attachmentManager());
1582 if (m_renderTargets.contains(key: inputRenderTargetId))
1583 inputFboId = updateRenderTarget(renderTargetNodeId: inputRenderTargetId, attachments, isActiveRenderTarget: false);
1584 else
1585 inputFboId = createRenderTarget(renderTargetNodeId: inputRenderTargetId, attachments);
1586 }
1587 inputBufferIsDefault = false;
1588 }
1589
1590 GLuint outputFboId = defaultFboId;
1591 bool outputBufferIsDefault = true;
1592 if (!outputRenderTargetId.isNull()) {
1593 RenderTarget *renderTarget = m_renderer->nodeManagers()->renderTargetManager()->lookupResource(id: outputRenderTargetId);
1594 if (renderTarget) {
1595 AttachmentPack attachments(renderTarget, m_renderer->nodeManagers()->attachmentManager());
1596 if (m_renderTargets.contains(key: outputRenderTargetId))
1597 outputFboId = updateRenderTarget(renderTargetNodeId: outputRenderTargetId, attachments, isActiveRenderTarget: false);
1598 else
1599 outputFboId = createRenderTarget(renderTargetNodeId: outputRenderTargetId, attachments);
1600 }
1601 outputBufferIsDefault = false;
1602 }
1603
1604 // Up until this point the input and output rects are normal Qt rectangles.
1605 // Convert them to GL rectangles (Y at bottom).
1606 const int inputFboHeight = inputFboId == defaultFboId ? m_surfaceSize.height() : m_renderTargets[inputRenderTargetId].size.height();
1607 const GLint srcX0 = inputRect.left();
1608 const GLint srcY0 = inputFboHeight - (inputRect.top() + inputRect.height());
1609 const GLint srcX1 = srcX0 + inputRect.width();
1610 const GLint srcY1 = srcY0 + inputRect.height();
1611
1612 const int outputFboHeight = outputFboId == defaultFboId ? m_surfaceSize.height() : m_renderTargets[outputRenderTargetId].size.height();
1613 const GLint dstX0 = outputRect.left();
1614 const GLint dstY0 = outputFboHeight - (outputRect.top() + outputRect.height());
1615 const GLint dstX1 = dstX0 + outputRect.width();
1616 const GLint dstY1 = dstY0 + outputRect.height();
1617
1618 //Get the last bounded framebuffers
1619 const GLuint lastDrawFboId = boundFrameBufferObject();
1620
1621 // Activate input framebuffer for reading
1622 bindFramebuffer(fbo: inputFboId, mode: GraphicsHelperInterface::FBORead);
1623
1624 // Activate output framebuffer for writing
1625 bindFramebuffer(fbo: outputFboId, mode: GraphicsHelperInterface::FBODraw);
1626
1627 //Bind texture
1628 if (!inputBufferIsDefault)
1629 readBuffer(mode: glAttachmentPoint(attachmentPoint: inputAttachmentPoint));
1630
1631 if (!outputBufferIsDefault) {
1632 // Note that we use glDrawBuffers, not glDrawBuffer. The
1633 // latter is not available with GLES.
1634 const GLenum buf = glAttachmentPoint(attachmentPoint: outputAttachmentPoint);
1635 drawBuffers(n: 1, bufs: &buf);
1636 }
1637
1638 // Blit framebuffer
1639 const GLenum mode = interpolationMethod ? GL_NEAREST : GL_LINEAR;
1640 m_glHelper->blitFramebuffer(srcX0, srcY0, srcX1, srcY1,
1641 dstX0, dstY0, dstX1, dstY1,
1642 GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT,
1643 filter: mode);
1644
1645 // Reset draw buffer
1646 bindFramebuffer(fbo: lastDrawFboId, mode: GraphicsHelperInterface::FBOReadAndDraw);
1647 if (outputAttachmentPoint != QRenderTargetOutput::Color0) {
1648 const GLenum buf = GL_COLOR_ATTACHMENT0;
1649 drawBuffers(n: 1, bufs: &buf);
1650 }
1651}
1652
1653} // namespace OpenGL
1654} // namespace Render
1655} // namespace Qt3DRender of namespace
1656
1657QT_END_NAMESPACE
1658

source code of qt3d/src/plugins/renderers/opengl/graphicshelpers/submissioncontext.cpp