1 /****************************************************************************
2 **
3 ** Copyright (C) 2008-2012 NVIDIA Corporation.
4 ** Copyright (C) 2019 The Qt Company Ltd.
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of Qt Quick 3D.
8 **
9 ** $QT_BEGIN_LICENSE:GPL$
10 ** Commercial License Usage
11 ** Licensees holding valid commercial Qt licenses may use this file in
12 ** accordance with the commercial license agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and The Qt Company. For licensing terms
15 ** and conditions see https://www.qt.io/terms-conditions. For further
16 ** information use the contact form at https://www.qt.io/contact-us.
17 **
18 ** GNU General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU
20 ** General Public License version 3 or (at your option) any later version
21 ** approved by the KDE Free Qt Foundation. The licenses are as published by
22 ** the Free Software Foundation and appearing in the file LICENSE.GPL3
23 ** included in the packaging of this file. Please review the following
24 ** information to ensure the GNU General Public License requirements will
25 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
26 **
27 ** $QT_END_LICENSE$
28 **
29 ****************************************************************************/
30 
31 #include <QtQuick3DRender/private/qssgrenderbackendgl3_p.h>
32 #include <QtQuick3DRender/private/qssgrenderbackendinputassemblergl_p.h>
33 #include <QtQuick3DRender/private/qssgrenderbackendrenderstatesgl_p.h>
34 #include <QtQuick3DRender/private/qssgrenderbackendshaderprogramgl_p.h>
35 #include <QtQuick3DRender/private/qssgopenglextensions_p.h>
36 
37 QT_BEGIN_NAMESPACE
38 
39 #ifdef RENDER_BACKEND_LOG_GL_ERRORS
40 #define RENDER_LOG_ERROR_PARAMS(x) checkGLError(#x, __FILE__, __LINE__)
41 #else
42 #define RENDER_LOG_ERROR_PARAMS(x) checkGLError()
43 #endif
44 
45 #define GL_CALL_EXTRA_FUNCTION(x)                                                                                      \
46     m_glExtraFunctions->x;                                                                                             \
47     RENDER_LOG_ERROR_PARAMS(x);
48 
49 #if defined(QT_OPENGL_ES)
50 #define GL_CALL_TIMER_EXT(x)                                                                                           \
51     m_QSSGExtensions->x;                                                                                             \
52     RENDER_LOG_ERROR_PARAMS(x);
53 #define GL_CALL_TESSELATION_EXT(x)                                                                                     \
54     m_QSSGExtensions->x;                                                                                             \
55     RENDER_LOG_ERROR_PARAMS(x);
56 #else
57 #define GL_CALL_TIMER_EXT(x)                                                                                           \
58     m_timerExtension->x;                                                                                               \
59     RENDER_LOG_ERROR_PARAMS(x);
60 #define GL_CALL_TESSELATION_EXT(x)                                                                                     \
61     m_tessellationShader->x;                                                                                           \
62     RENDER_LOG_ERROR_PARAMS(x);
63 #define GL_CALL_MULTISAMPLE_EXT(x)                                                                                     \
64     m_multiSample->x;                                                                                                  \
65     RENDER_LOG_ERROR_PARAMS(x);
66 #endif
67 
68 #ifndef GL_PATCH_VERTICES
69 #define GL_PATCH_VERTICES 0x8E72
70 #endif
71 
72 namespace QSSGGlExtStrings {
extsAstcHDR()73 QByteArray extsAstcHDR()
74 {
75     return QByteArrayLiteral("GL_KHR_texture_compression_astc_hdr");
76 }
extsAstcLDR()77 QByteArray extsAstcLDR()
78 {
79     return QByteArrayLiteral("GL_KHR_texture_compression_astc_ldr");
80 }
81 }
82 
83 /// constructor
QSSGRenderBackendGL3Impl(const QSurfaceFormat & format)84 QSSGRenderBackendGL3Impl::QSSGRenderBackendGL3Impl(const QSurfaceFormat &format) : QSSGRenderBackendGLBase(format)
85 {
86     // clear support bits
87     m_backendSupport.caps.u32Values = 0;
88 
89     // get extension count
90     GLint numExtensions = 0;
91     GL_CALL_EXTRA_FUNCTION(glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions));
92 
93     QByteArray extensionBuffer;
94 
95     for (qint32 i = 0; i < numExtensions; i++) {
96         const GLubyte *glExt = GL_CALL_EXTRA_FUNCTION(glGetStringi(GL_EXTENSIONS, GLuint(i)));
97         const QByteArray extensionString(reinterpret_cast<const char *>(glExt));
98 
99         m_extensions.push_back(extensionString);
100 
101         if (extensionBuffer.size())
102             extensionBuffer.append(" ");
103         extensionBuffer.append(extensionString);
104 
105         // search for extension
106         if (!m_backendSupport.caps.bits.bDXTImagesSupported
107             && (QSSGGlExtStrings::exts3tc().compare(extensionString) == 0
108                 || QSSGGlExtStrings::extsdxt().compare(extensionString) == 0)) {
109             m_backendSupport.caps.bits.bDXTImagesSupported = true;
110         } else if (!m_backendSupport.caps.bits.bAnistropySupported && QSSGGlExtStrings::extsAniso().compare(extensionString) == 0) {
111             m_backendSupport.caps.bits.bAnistropySupported = true;
112         } else if (!m_backendSupport.caps.bits.bFPRenderTargetsSupported
113                    && QSSGGlExtStrings::extsFPRenderTarget().compare(extensionString) == 0) {
114             m_backendSupport.caps.bits.bFPRenderTargetsSupported = true;
115         } else if (!m_backendSupport.caps.bits.bTimerQuerySupported
116                    && QSSGGlExtStrings::extsTimerQuery().compare(extensionString) == 0) {
117             m_backendSupport.caps.bits.bTimerQuerySupported = true;
118         } else if (!m_backendSupport.caps.bits.bGPUShader5ExtensionSupported
119                    && QSSGGlExtStrings::extsGpuShader5().compare(extensionString) == 0) {
120             m_backendSupport.caps.bits.bGPUShader5ExtensionSupported = true;
121         }
122     }
123 
124     qCInfo(RENDER_TRACE_INFO, "OpenGL extensions: %s", extensionBuffer.constData());
125 
126     // texture swizzle is always true
127     m_backendSupport.caps.bits.bTextureSwizzleSupported = true;
128     // depthstencil renderbuffer support is always true
129     m_backendSupport.caps.bits.bDepthStencilSupported = true;
130     // constant buffers support is always true
131     m_backendSupport.caps.bits.bConstantBufferSupported = true;
132     m_backendSupport.caps.bits.bStandardDerivativesSupported = true;
133     m_backendSupport.caps.bits.bVertexArrayObjectSupported = true;
134     m_backendSupport.caps.bits.bTextureLodSupported = true;
135 
136     if (!isESCompatible()) {
137         // render to float textures is always supported on none ES systems which support >=GL3
138         m_backendSupport.caps.bits.bFPRenderTargetsSupported = true;
139         // multisampled texture is always supported on none ES systems which support >=GL3
140         m_backendSupport.caps.bits.bMsTextureSupported = true;
141         // timer queries are always supported on none ES systems which support >=GL3
142         m_backendSupport.caps.bits.bTimerQuerySupported = true;
143     }
144 
145     // query hardware
146     GL_CALL_EXTRA_FUNCTION(glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &m_maxAttribCount));
147 
148     // internal state tracker
149     m_currentMiscState = new QSSGRenderBackendMiscStateGL();
150 
151     // finally setup caps based on device
152     setAndInspectHardwareCaps();
153 
154     // Initialize extensions
155 #if defined(QT_OPENGL_ES_2)
156     m_QSSGExtensions = new QSSGOpenGLES2Extensions;
157     m_QSSGExtensions->initializeOpenGLFunctions();
158 #else
159     m_timerExtension = new QOpenGLExtension_ARB_timer_query;
160     m_timerExtension->initializeOpenGLFunctions();
161     m_tessellationShader = new QOpenGLExtension_ARB_tessellation_shader;
162     m_tessellationShader->initializeOpenGLFunctions();
163     m_multiSample = new QOpenGLExtension_ARB_texture_multisample;
164     m_multiSample->initializeOpenGLFunctions();
165     m_QSSGExtensions = new QSSGOpenGLExtensions;
166     m_QSSGExtensions->initializeOpenGLFunctions();
167 #endif
168 }
169 /// destructor
~QSSGRenderBackendGL3Impl()170 QSSGRenderBackendGL3Impl::~QSSGRenderBackendGL3Impl()
171 {
172     delete m_currentMiscState;
173 #if !defined(QT_OPENGL_ES_2)
174     delete m_timerExtension;
175     delete m_tessellationShader;
176     delete m_multiSample;
177 #endif
178     delete m_QSSGExtensions;
179 }
180 
setMultisampledTextureData2D(QSSGRenderBackendTextureObject to,QSSGRenderTextureTargetType target,qint32 samples,QSSGRenderTextureFormat internalFormat,qint32 width,qint32 height,bool fixedsamplelocations)181 void QSSGRenderBackendGL3Impl::setMultisampledTextureData2D(QSSGRenderBackendTextureObject to,
182                                                               QSSGRenderTextureTargetType target,
183                                                               qint32 samples,
184                                                               QSSGRenderTextureFormat internalFormat,
185                                                               qint32 width,
186                                                               qint32 height,
187                                                               bool fixedsamplelocations)
188 {
189     // Not supported by ES 3 yet
190 #if defined(QT_OPENGL_ES)
191     Q_UNUSED(to)
192     Q_UNUSED(target)
193     Q_UNUSED(samples)
194     Q_UNUSED(internalFormat)
195     Q_UNUSED(width)
196     Q_UNUSED(height)
197     Q_UNUSED(fixedsamplelocations)
198 #else
199     GLuint texID = HandleToID_cast(GLuint, quintptr, to);
200     GLenum glTarget = GLConversion::fromTextureTargetToGL(target);
201     setActiveTexture(GL_TEXTURE0);
202     GL_CALL_EXTRA_FUNCTION(glBindTexture(glTarget, texID));
203 
204     QSSGRenderTextureSwizzleMode swizzleMode = QSSGRenderTextureSwizzleMode::NoSwizzle;
205     internalFormat = GLConversion::replaceDeprecatedTextureFormat(getRenderContextType(), internalFormat, swizzleMode);
206 
207     GLenum glformat = 0, glInternalFormat = 0, gltype = GL_UNSIGNED_BYTE;
208 
209     if (internalFormat.isUncompressedTextureFormat())
210         GLConversion::fromUncompressedTextureFormatToGL(getRenderContextType(), internalFormat, glformat, gltype, glInternalFormat);
211     else if (internalFormat.isDepthTextureFormat())
212         GLConversion::fromDepthTextureFormatToGL(getRenderContextType(), internalFormat, glformat, gltype, glInternalFormat);
213 
214     GL_CALL_MULTISAMPLE_EXT(
215             glTexImage2DMultisample(glTarget, GLsizei(samples), GLint(glInternalFormat), GLsizei(width), GLsizei(height), fixedsamplelocations));
216 
217     GL_CALL_EXTRA_FUNCTION(glBindTexture(glTarget, 0));
218 #endif
219 }
220 
setTextureData3D(QSSGRenderBackendTextureObject to,QSSGRenderTextureTargetType target,qint32 level,QSSGRenderTextureFormat internalFormat,qint32 width,qint32 height,qint32 depth,qint32 border,QSSGRenderTextureFormat format,QSSGByteView hostData)221 void QSSGRenderBackendGL3Impl::setTextureData3D(QSSGRenderBackendTextureObject to,
222                                                   QSSGRenderTextureTargetType target,
223                                                   qint32 level,
224                                                   QSSGRenderTextureFormat internalFormat,
225                                                   qint32 width,
226                                                   qint32 height,
227                                                   qint32 depth,
228                                                   qint32 border,
229                                                   QSSGRenderTextureFormat format,
230                                                   QSSGByteView hostData)
231 {
232     GLuint texID = HandleToID_cast(GLuint, quintptr, to);
233     GLenum glTarget = GLConversion::fromTextureTargetToGL(target);
234     setActiveTexture(GL_TEXTURE0);
235     GL_CALL_EXTRA_FUNCTION(glBindTexture(glTarget, texID));
236     bool conversionRequired = format != internalFormat;
237 
238     QSSGRenderTextureSwizzleMode swizzleMode = QSSGRenderTextureSwizzleMode::NoSwizzle;
239     internalFormat = GLConversion::replaceDeprecatedTextureFormat(getRenderContextType(), internalFormat, swizzleMode);
240 
241     GLenum glformat = 0, glInternalFormat = 0, gltype = GL_UNSIGNED_BYTE;
242 
243     if (internalFormat.isUncompressedTextureFormat())
244         GLConversion::fromUncompressedTextureFormatToGL(getRenderContextType(), internalFormat, glformat, gltype, glInternalFormat);
245 
246     if (conversionRequired) {
247         GLenum dummy;
248         GLConversion::fromUncompressedTextureFormatToGL(getRenderContextType(), format, glformat, gltype, dummy);
249     } else if (internalFormat.isCompressedTextureFormat()) {
250         GLConversion::fromUncompressedTextureFormatToGL(getRenderContextType(), format, glformat, gltype, glInternalFormat);
251         glInternalFormat = GLConversion::fromCompressedTextureFormatToGL(internalFormat);
252     } else if (format.isDepthTextureFormat()) {
253         GLConversion::fromDepthTextureFormatToGL(getRenderContextType(), format, glformat, gltype, glInternalFormat);
254     }
255 
256     GL_CALL_EXTRA_FUNCTION(
257             glTexImage3D(glTarget, level, GLint(glInternalFormat), GLsizei(width), GLsizei(height), GLsizei(depth), border, glformat, gltype, hostData));
258 
259     GL_CALL_EXTRA_FUNCTION(glBindTexture(glTarget, 0));
260 }
261 
updateSampler(QSSGRenderBackendSamplerObject,QSSGRenderTextureTargetType target,QSSGRenderTextureMinifyingOp minFilter,QSSGRenderTextureMagnifyingOp magFilter,QSSGRenderTextureCoordOp wrapS,QSSGRenderTextureCoordOp wrapT,QSSGRenderTextureCoordOp wrapR,float minLod,float maxLod,float lodBias,QSSGRenderTextureCompareMode compareMode,QSSGRenderTextureCompareOp compareFunc,float anisotropy,float * borderColor)262 void QSSGRenderBackendGL3Impl::updateSampler(QSSGRenderBackendSamplerObject /* so */,
263                                                QSSGRenderTextureTargetType target,
264                                                QSSGRenderTextureMinifyingOp minFilter,
265                                                QSSGRenderTextureMagnifyingOp magFilter,
266                                                QSSGRenderTextureCoordOp wrapS,
267                                                QSSGRenderTextureCoordOp wrapT,
268                                                QSSGRenderTextureCoordOp wrapR,
269                                                float minLod,
270                                                float maxLod,
271                                                float lodBias,
272                                                QSSGRenderTextureCompareMode compareMode,
273                                                QSSGRenderTextureCompareOp compareFunc,
274                                                float anisotropy,
275                                                float *borderColor)
276 {
277 
278     // Satisfy the compiler
279     // These are not available in GLES 3 and we don't use them right now
280     Q_ASSERT(qFuzzyIsNull(lodBias));
281     Q_ASSERT(!borderColor);
282     Q_UNUSED(lodBias)
283     Q_UNUSED(borderColor)
284 
285     GLenum glTarget = GLConversion::fromTextureTargetToGL(target);
286 
287     GL_CALL_EXTRA_FUNCTION(glTexParameteri(glTarget, GL_TEXTURE_MIN_FILTER, GLint(m_conversion.fromTextureMinifyingOpToGL(minFilter))));
288     GL_CALL_EXTRA_FUNCTION(glTexParameteri(glTarget, GL_TEXTURE_MAG_FILTER, GLint(m_conversion.fromTextureMagnifyingOpToGL(magFilter))));
289     GL_CALL_EXTRA_FUNCTION(glTexParameteri(glTarget, GL_TEXTURE_WRAP_S, GLint(m_conversion.fromTextureCoordOpToGL(wrapS))));
290     GL_CALL_EXTRA_FUNCTION(glTexParameteri(glTarget, GL_TEXTURE_WRAP_T, GLint(m_conversion.fromTextureCoordOpToGL(wrapT))));
291     GL_CALL_EXTRA_FUNCTION(glTexParameteri(glTarget, GL_TEXTURE_WRAP_R, GLint(m_conversion.fromTextureCoordOpToGL(wrapR))));
292     GL_CALL_EXTRA_FUNCTION(glTexParameterf(glTarget, GL_TEXTURE_MIN_LOD, minLod));
293     GL_CALL_EXTRA_FUNCTION(glTexParameterf(glTarget, GL_TEXTURE_MAX_LOD, maxLod));
294     GL_CALL_EXTRA_FUNCTION(glTexParameteri(glTarget, GL_TEXTURE_COMPARE_MODE, GLint(m_conversion.fromTextureCompareModeToGL(compareMode))));
295     GL_CALL_EXTRA_FUNCTION(glTexParameteri(glTarget, GL_TEXTURE_COMPARE_FUNC, GLint(m_conversion.fromTextureCompareFuncToGL(compareFunc))));
296 
297     if (m_backendSupport.caps.bits.bAnistropySupported) {
298         GL_CALL_EXTRA_FUNCTION(glTexParameterf(glTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy));
299     }
300 }
301 
updateTextureObject(QSSGRenderBackendTextureObject to,QSSGRenderTextureTargetType target,qint32 baseLevel,qint32 maxLevel)302 void QSSGRenderBackendGL3Impl::updateTextureObject(QSSGRenderBackendTextureObject to,
303                                                      QSSGRenderTextureTargetType target,
304                                                      qint32 baseLevel,
305                                                      qint32 maxLevel)
306 {
307     Q_UNUSED(to)
308 
309     GLenum glTarget = GLConversion::fromTextureTargetToGL(target);
310 
311     GL_CALL_EXTRA_FUNCTION(glTexParameteri(glTarget, GL_TEXTURE_BASE_LEVEL, baseLevel));
312     GL_CALL_EXTRA_FUNCTION(glTexParameteri(glTarget, GL_TEXTURE_MAX_LEVEL, maxLevel));
313 }
314 
updateTextureSwizzle(QSSGRenderBackendTextureObject to,QSSGRenderTextureTargetType target,QSSGRenderTextureSwizzleMode swizzleMode)315 void QSSGRenderBackendGL3Impl::updateTextureSwizzle(QSSGRenderBackendTextureObject to,
316                                                       QSSGRenderTextureTargetType target,
317                                                       QSSGRenderTextureSwizzleMode swizzleMode)
318 {
319     Q_UNUSED(to)
320     if (m_backendSupport.caps.bits.bTextureSwizzleSupported) {
321         GLint glSwizzle[4];
322         GLenum glTarget = GLConversion::fromTextureTargetToGL(target);
323         GLConversion::NVRenderConvertSwizzleModeToGL(swizzleMode, glSwizzle);
324 #if defined(QT_OPENGL_ES)
325         // since ES3 spec has no GL_TEXTURE_SWIZZLE_RGBA set it separately
326         GL_CALL_EXTRA_FUNCTION(glTexParameteri(glTarget, GL_TEXTURE_SWIZZLE_R, glSwizzle[0]));
327         GL_CALL_EXTRA_FUNCTION(glTexParameteri(glTarget, GL_TEXTURE_SWIZZLE_G, glSwizzle[1]));
328         GL_CALL_EXTRA_FUNCTION(glTexParameteri(glTarget, GL_TEXTURE_SWIZZLE_B, glSwizzle[2]));
329         GL_CALL_EXTRA_FUNCTION(glTexParameteri(glTarget, GL_TEXTURE_SWIZZLE_A, glSwizzle[3]));
330 #else
331         GL_CALL_EXTRA_FUNCTION(glTexParameteriv(glTarget, GL_TEXTURE_SWIZZLE_RGBA, glSwizzle));
332 #endif
333     }
334 }
335 
getDepthBits() const336 qint32 QSSGRenderBackendGL3Impl::getDepthBits() const
337 {
338     qint32 depthBits;
339     GL_CALL_EXTRA_FUNCTION(
340             glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, &depthBits));
341 
342     return depthBits;
343 }
344 
getStencilBits() const345 qint32 QSSGRenderBackendGL3Impl::getStencilBits() const
346 {
347     qint32 stencilBits;
348     GL_CALL_EXTRA_FUNCTION(
349             glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE, &stencilBits));
350 
351     return stencilBits;
352 }
353 
generateMipMaps(QSSGRenderBackendTextureObject to,QSSGRenderTextureTargetType target,QSSGRenderHint)354 void QSSGRenderBackendGL3Impl::generateMipMaps(QSSGRenderBackendTextureObject to,
355                                                  QSSGRenderTextureTargetType target,
356                                                  QSSGRenderHint /*genType*/)
357 {
358     GLuint texID = HandleToID_cast(GLuint, quintptr, to);
359     GLenum glTarget = GLConversion::fromTextureTargetToGL(target);
360     setActiveTexture(GL_TEXTURE0);
361     GL_CALL_EXTRA_FUNCTION(glBindTexture(glTarget, texID));
362     GL_CALL_EXTRA_FUNCTION(glGenerateMipmap(glTarget));
363     GL_CALL_EXTRA_FUNCTION(glBindTexture(glTarget, 0));
364 }
365 
getShadingLanguageVersion()366 QByteArray QSSGRenderBackendGL3Impl::getShadingLanguageVersion()
367 {
368     Q_ASSERT(m_format.majorVersion() >= 3);
369 
370     QByteArray ver("#version 300");
371     if (m_format.majorVersion() == 3)
372         ver[10] = '0' + char(m_format.minorVersion());
373     else if (m_format.majorVersion() > 3)
374         ver[10] = '3';
375 
376     if (m_format.renderableType() == QSurfaceFormat::OpenGLES)
377         ver.append(" es");
378 
379     return ver.append("\n");
380 }
381 
getRenderContextType() const382 QSSGRenderContextType QSSGRenderBackendGL3Impl::getRenderContextType() const
383 {
384     Q_ASSERT(m_format.majorVersion() >= 3);
385 
386     if (m_format.renderableType() == QSurfaceFormat::OpenGLES) {
387         if (m_format.minorVersion() >= 1)
388             return QSSGRenderContextType::GLES3PLUS;
389 
390         return QSSGRenderContextType::GLES3;
391     }
392 
393     return QSSGRenderContextType::GL3;
394 }
395 
setInputAssembler(QSSGRenderBackendInputAssemblerObject iao,QSSGRenderBackendShaderProgramObject po)396 bool QSSGRenderBackendGL3Impl::setInputAssembler(QSSGRenderBackendInputAssemblerObject iao, QSSGRenderBackendShaderProgramObject po)
397 {
398     if (iao == nullptr) {
399         // unbind and return;
400         GL_CALL_EXTRA_FUNCTION(glBindVertexArray(0));
401         return true;
402     }
403 
404     QSSGRenderBackendInputAssemblerGL *inputAssembler = reinterpret_cast<QSSGRenderBackendInputAssemblerGL *>(iao);
405     QSSGRenderBackendAttributeLayoutGL *attribLayout = inputAssembler->m_attribLayout;
406     QSSGRenderBackendShaderProgramGL *pProgram = reinterpret_cast<QSSGRenderBackendShaderProgramGL *>(po);
407     GLuint programID = static_cast<GLuint>(pProgram->m_programID);
408     QSSGDataRef<QSSGRenderBackendShaderInputEntryGL> shaderAttribBuffer;
409     if (pProgram->m_shaderInput)
410         shaderAttribBuffer = pProgram->m_shaderInput->m_shaderInputEntries;
411 
412     // Need to be careful with the attributes. shaderAttribBuffer contains
413     // whatever glGetActiveAttrib() returns. There can however be differences
414     // between OpenGL implementations: some will optimize out unused
415     // attributes, while others could report all attributes as active,
416     // regardless of them being used in practice or not.
417     //
418     // In addition, not binding any data to an attribute is not an error with
419     // OpenGL, and in fact is unavoidable when a model, for example, has no UV
420     // coordinates (and associated data, such as tangetst and binormals), but
421     // is then used with a shader with lighting, shadows, and such. This needs
422     // to be handled gracefully. It can lead to incorrect rendering but the
423     // object still needs to be there, without bailing out or flooding the
424     // output with warnings.
425 
426     // if (attribLayout->m_layoutAttribEntries.size() < shaderAttribBuffer.size())
427 
428     if (inputAssembler->m_vertexbufferHandles.size() <= attribLayout->m_maxInputSlot)
429         return false;
430 
431     if (inputAssembler->m_vaoID == 0) {
432         // generate vao
433         GL_CALL_EXTRA_FUNCTION(glGenVertexArrays(1, &inputAssembler->m_vaoID));
434         Q_ASSERT(inputAssembler->m_vaoID);
435     }
436 
437     // set patch parameter count if changed
438     if (m_backendSupport.caps.bits.bTessellationSupported && m_currentMiscState->m_patchVertexCount != inputAssembler->m_patchVertexCount) {
439         m_currentMiscState->m_patchVertexCount = inputAssembler->m_patchVertexCount;
440 #if defined(QT_OPENGL_ES)
441         GL_CALL_TESSELATION_EXT(glPatchParameteriEXT(GL_PATCH_VERTICES, inputAssembler->m_patchVertexCount));
442 #else
443         GL_CALL_TESSELATION_EXT(glPatchParameteri(GL_PATCH_VERTICES, GLint(inputAssembler->m_patchVertexCount)));
444 #endif
445     }
446 
447     if (inputAssembler->m_cachedShaderHandle != programID) {
448         GL_CALL_EXTRA_FUNCTION(glBindVertexArray(inputAssembler->m_vaoID));
449         inputAssembler->m_cachedShaderHandle = programID;
450 
451         for (const auto &attrib : qAsConst(shaderAttribBuffer)) {
452             QSSGRenderBackendLayoutEntryGL *entry = attribLayout->getEntryByName(attrib.m_attribName);
453 
454             if (entry) {
455                 QSSGRenderBackendLayoutEntryGL &entryData(*entry);
456                 if (Q_UNLIKELY(entryData.m_type != attrib.m_type || entryData.m_numComponents != attrib.m_numComponents)) {
457                     qCCritical(RENDER_INVALID_OPERATION, "Attrib %s doesn't match vertex layout", attrib.m_attribName.constData());
458                     Q_ASSERT(false);
459                     return false;
460                 }
461                 entryData.m_attribIndex = attrib.m_attribLocation;
462             } else {
463                 qCWarning(RENDER_WARNING, "Failed to bind attribute %s", attrib.m_attribName.constData());
464             }
465         }
466 
467         // disable max possible used first
468         // this is currently sufficient since we always re-arrange input attributes from 0
469         for (int i = 0; i < attribLayout->m_layoutAttribEntries.size(); i++)
470             GL_CALL_EXTRA_FUNCTION(glDisableVertexAttribArray(GLuint(i)));
471 
472         // setup all attribs
473         GLuint boundArrayBufferId = 0; // 0 means unbound
474         for (int idx = 0; idx != shaderAttribBuffer.size(); ++idx) {
475             QSSGRenderBackendLayoutEntryGL *entry = attribLayout->getEntryByName(shaderAttribBuffer[idx].m_attribName);
476             if (entry) {
477                 const QSSGRenderBackendLayoutEntryGL &entryData(*entry);
478                 GLuint id = HandleToID_cast(GLuint, quintptr, inputAssembler->m_vertexbufferHandles.mData[entryData.m_inputSlot]);
479                 if (boundArrayBufferId != id) {
480                     GL_CALL_EXTRA_FUNCTION(glBindBuffer(GL_ARRAY_BUFFER, id));
481                     boundArrayBufferId = id;
482                 }
483                 GL_CALL_EXTRA_FUNCTION(glEnableVertexAttribArray(entryData.m_attribIndex));
484                 GLuint offset = inputAssembler->m_offsets.at(int(entryData.m_inputSlot));
485                 GLuint stride = inputAssembler->m_strides.at(int(entryData.m_inputSlot));
486                 GL_CALL_EXTRA_FUNCTION(glVertexAttribPointer(entryData.m_attribIndex,
487                                                              GLint(entryData.m_numComponents),
488                                                              GL_FLOAT,
489                                                              GL_FALSE,
490                                                              GLsizei(stride),
491                                                              reinterpret_cast<const void *>(quintptr(entryData.m_offset + offset))));
492 
493             } else {
494                 GL_CALL_EXTRA_FUNCTION(glDisableVertexAttribArray(GLuint(idx)));
495             }
496         }
497 
498         // setup index buffer.
499         if (inputAssembler->m_indexbufferHandle) {
500             GL_CALL_EXTRA_FUNCTION(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
501                                                 HandleToID_cast(GLuint, quintptr, inputAssembler->m_indexbufferHandle)));
502         } else {
503             GL_CALL_EXTRA_FUNCTION(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
504         }
505     } else {
506         GL_CALL_EXTRA_FUNCTION(glBindVertexArray(inputAssembler->m_vaoID));
507     }
508 #ifdef _DEBUG
509     if (inputAssembler->m_vaoID) {
510         for (const auto &attrib : qAsConst(shaderAttribBuffer)) {
511             QSSGRenderBackendLayoutEntryGL *entry = attribLayout->getEntryByName(attrib.m_attribName);
512 
513             if (entry) {
514                 QSSGRenderBackendLayoutEntryGL &entryData(*entry);
515                 if (entryData.m_type != attrib.m_type || entryData.m_numComponents != attrib.m_numComponents
516                     || entryData.m_attribIndex != attrib.m_attribLocation) {
517                     qCCritical(RENDER_INVALID_OPERATION, "Attrib %s doesn't match vertex layout", qPrintable(attrib.m_attribName));
518                     Q_ASSERT(false);
519                 }
520             } else {
521                 qCWarning(RENDER_WARNING, "Failed to bind attribute %s", qPrintable(attrib.m_attribName));
522             }
523         }
524     }
525 #endif // _DEBUG
526 
527     return true;
528 }
529 
setDrawBuffers(QSSGRenderBackendRenderTargetObject rto,QSSGDataView<qint32> inDrawBufferSet)530 void QSSGRenderBackendGL3Impl::setDrawBuffers(QSSGRenderBackendRenderTargetObject rto, QSSGDataView<qint32> inDrawBufferSet)
531 {
532     Q_UNUSED(rto)
533 
534     m_drawBuffersArray.clear();
535 
536     for (int idx = 0, end = inDrawBufferSet.size(); idx < end; ++idx) {
537         if (inDrawBufferSet[idx] < 0)
538             m_drawBuffersArray.push_back(GL_NONE);
539         else
540             m_drawBuffersArray.push_back(GL_COLOR_ATTACHMENT0 + GLuint(inDrawBufferSet[idx]));
541     }
542 
543     GL_CALL_EXTRA_FUNCTION(glDrawBuffers(m_drawBuffersArray.size(), m_drawBuffersArray.data()));
544 }
545 
setReadBuffer(QSSGRenderBackendRenderTargetObject rto,QSSGReadFace inReadFace)546 void QSSGRenderBackendGL3Impl::setReadBuffer(QSSGRenderBackendRenderTargetObject rto, QSSGReadFace inReadFace)
547 {
548     Q_UNUSED(rto)
549 
550     GL_CALL_EXTRA_FUNCTION(glReadBuffer(m_conversion.fromReadFacesToGL(inReadFace)));
551 }
552 
renderTargetAttach(QSSGRenderBackendRenderTargetObject,QSSGRenderFrameBufferAttachment attachment,QSSGRenderBackendTextureObject to,qint32 level,qint32 layer)553 void QSSGRenderBackendGL3Impl::renderTargetAttach(QSSGRenderBackendRenderTargetObject,
554                                                     QSSGRenderFrameBufferAttachment attachment,
555                                                     QSSGRenderBackendTextureObject to,
556                                                     qint32 level,
557                                                     qint32 layer)
558 {
559     // rto must be the current render target
560     GLuint texID = HandleToID_cast(GLuint, quintptr, to);
561 
562     GLenum glAttach = GLConversion::fromFramebufferAttachmentsToGL(attachment);
563 
564     GL_CALL_EXTRA_FUNCTION(glFramebufferTextureLayer(GL_FRAMEBUFFER, glAttach, texID, level, layer))
565 }
566 
setReadTarget(QSSGRenderBackendRenderTargetObject rto)567 void QSSGRenderBackendGL3Impl::setReadTarget(QSSGRenderBackendRenderTargetObject rto)
568 {
569     GLuint fboID = HandleToID_cast(GLuint, quintptr, rto);
570 
571     GL_CALL_EXTRA_FUNCTION(glBindFramebuffer(GL_READ_FRAMEBUFFER, fboID));
572 }
573 
blitFramebuffer(qint32 srcX0,qint32 srcY0,qint32 srcX1,qint32 srcY1,qint32 dstX0,qint32 dstY0,qint32 dstX1,qint32 dstY1,QSSGRenderClearFlags flags,QSSGRenderTextureMagnifyingOp filter)574 void QSSGRenderBackendGL3Impl::blitFramebuffer(qint32 srcX0,
575                                                  qint32 srcY0,
576                                                  qint32 srcX1,
577                                                  qint32 srcY1,
578                                                  qint32 dstX0,
579                                                  qint32 dstY0,
580                                                  qint32 dstX1,
581                                                  qint32 dstY1,
582                                                  QSSGRenderClearFlags flags,
583                                                  QSSGRenderTextureMagnifyingOp filter)
584 {
585     GL_CALL_EXTRA_FUNCTION(glBlitFramebuffer(srcX0,
586                                              srcY0,
587                                              srcX1,
588                                              srcY1,
589                                              dstX0,
590                                              dstY0,
591                                              dstX1,
592                                              dstY1,
593                                              m_conversion.fromClearFlagsToGL(flags),
594                                              m_conversion.fromTextureMagnifyingOpToGL(filter)));
595 }
596 
copyFramebufferTexture(qint32 srcX0,qint32 srcY0,qint32 width,qint32 height,qint32 dstX0,qint32 dstY0,QSSGRenderBackendTextureObject texture,QSSGRenderTextureTargetType target)597 void QSSGRenderBackendGL3Impl::copyFramebufferTexture(qint32 srcX0,
598                                                       qint32 srcY0,
599                                                       qint32 width,
600                                                       qint32 height,
601                                                       qint32 dstX0,
602                                                       qint32 dstY0,
603                                                       QSSGRenderBackendTextureObject texture,
604                                                       QSSGRenderTextureTargetType target)
605 {
606     GLuint texID = HandleToID_cast(GLuint, quintptr, texture);
607     GLenum glTarget = GLConversion::fromTextureTargetToGL(target);
608     setActiveTexture(GL_TEXTURE0);
609     GL_CALL_EXTRA_FUNCTION(glBindTexture(glTarget, texID))
610     GL_CALL_EXTRA_FUNCTION(glCopyTexSubImage2D(GL_TEXTURE_2D, 0, srcX0, srcY0, dstX0, dstY0,
611                                                width, height))
612 }
613 
mapBuffer(QSSGRenderBackendBufferObject,QSSGRenderBufferType bindFlags,size_t offset,size_t length,QSSGRenderBufferAccessFlags accessFlags)614 void *QSSGRenderBackendGL3Impl::mapBuffer(QSSGRenderBackendBufferObject,
615                                             QSSGRenderBufferType bindFlags,
616                                             size_t offset,
617                                             size_t length,
618                                             QSSGRenderBufferAccessFlags accessFlags)
619 {
620     void *ret = nullptr;
621     ret = GL_CALL_EXTRA_FUNCTION(glMapBufferRange(m_conversion.fromBindBufferFlagsToGL(bindFlags),
622                                                   GLintptr(offset),
623                                                   GLintptr(length),
624                                                   m_conversion.fromBufferAccessBitToGL(accessFlags)));
625 
626     return ret;
627 }
628 
unmapBuffer(QSSGRenderBackendBufferObject,QSSGRenderBufferType bindFlags)629 bool QSSGRenderBackendGL3Impl::unmapBuffer(QSSGRenderBackendBufferObject, QSSGRenderBufferType bindFlags)
630 {
631     const GLboolean ret = GL_CALL_EXTRA_FUNCTION(glUnmapBuffer(m_conversion.fromBindBufferFlagsToGL(bindFlags)));
632     return (ret != 0);
633 }
634 
getConstantBufferCount(QSSGRenderBackendShaderProgramObject po)635 qint32 QSSGRenderBackendGL3Impl::getConstantBufferCount(QSSGRenderBackendShaderProgramObject po)
636 {
637     Q_ASSERT(po);
638     QSSGRenderBackendShaderProgramGL *pProgram = reinterpret_cast<QSSGRenderBackendShaderProgramGL *>(po);
639     GLuint programID = static_cast<GLuint>(pProgram->m_programID);
640 
641     GLint numUniformBuffers;
642     GL_CALL_EXTRA_FUNCTION(glGetProgramiv(programID, GL_ACTIVE_UNIFORM_BLOCKS, &numUniformBuffers));
643 
644     return numUniformBuffers;
645 }
646 
getConstantBufferInfoByID(QSSGRenderBackendShaderProgramObject po,quint32 id,quint32 nameBufSize,qint32 * paramCount,qint32 * bufferSize,qint32 * length,char * nameBuf)647 qint32 QSSGRenderBackendGL3Impl::getConstantBufferInfoByID(QSSGRenderBackendShaderProgramObject po,
648                                                              quint32 id,
649                                                              quint32 nameBufSize,
650                                                              qint32 *paramCount,
651                                                              qint32 *bufferSize,
652                                                              qint32 *length,
653                                                              char *nameBuf)
654 {
655     Q_ASSERT(po);
656     Q_ASSERT(length);
657     Q_ASSERT(nameBuf);
658 
659     QSSGRenderBackendShaderProgramGL *pProgram = reinterpret_cast<QSSGRenderBackendShaderProgramGL *>(po);
660     GLuint programID = static_cast<GLuint>(pProgram->m_programID);
661     GLuint blockIndex = GL_INVALID_INDEX;
662 
663     GL_CALL_EXTRA_FUNCTION(glGetActiveUniformBlockName(programID, id, GLsizei(nameBufSize), length, nameBuf));
664 
665     if (*length > 0) {
666         blockIndex = GL_CALL_EXTRA_FUNCTION(glGetUniformBlockIndex(programID, nameBuf));
667         if (blockIndex != GL_INVALID_INDEX) {
668             GL_CALL_EXTRA_FUNCTION(glGetActiveUniformBlockiv(programID, blockIndex, GL_UNIFORM_BLOCK_DATA_SIZE, bufferSize));
669             GL_CALL_EXTRA_FUNCTION(glGetActiveUniformBlockiv(programID, blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, paramCount));
670         }
671     }
672 
673     return qint32(blockIndex);
674 }
675 
getConstantBufferParamIndices(QSSGRenderBackendShaderProgramObject po,quint32 id,qint32 * indices)676 void QSSGRenderBackendGL3Impl::getConstantBufferParamIndices(QSSGRenderBackendShaderProgramObject po, quint32 id, qint32 *indices)
677 {
678     Q_ASSERT(po);
679     Q_ASSERT(indices);
680 
681     QSSGRenderBackendShaderProgramGL *pProgram = reinterpret_cast<QSSGRenderBackendShaderProgramGL *>(po);
682     GLuint programID = static_cast<GLuint>(pProgram->m_programID);
683 
684     if (indices) {
685         GL_CALL_EXTRA_FUNCTION(glGetActiveUniformBlockiv(programID, id, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, indices));
686     }
687 }
688 
getConstantBufferParamInfoByIndices(QSSGRenderBackendShaderProgramObject po,quint32 count,quint32 * indices,QSSGRenderShaderDataType * type,qint32 * size,qint32 * offset)689 void QSSGRenderBackendGL3Impl::getConstantBufferParamInfoByIndices(QSSGRenderBackendShaderProgramObject po,
690                                                                    quint32 count,
691                                                                    quint32 *indices,
692                                                                    QSSGRenderShaderDataType *type,
693                                                                    qint32 *size,
694                                                                    qint32 *offset)
695 {
696     Q_ASSERT(po);
697     Q_ASSERT(count && count <= INT32_MAX);
698     Q_ASSERT(indices);
699 
700     QSSGRenderBackendShaderProgramGL *pProgram = reinterpret_cast<QSSGRenderBackendShaderProgramGL *>(po);
701     GLuint programID = static_cast<GLuint>(pProgram->m_programID);
702 
703     if (count && indices) {
704         if (type) {
705             QVarLengthArray<qint32, 1024> glTypes(count);
706             GL_CALL_EXTRA_FUNCTION(glGetActiveUniformsiv(programID, GLsizei(count), indices, GL_UNIFORM_TYPE, glTypes.data()));
707             // convert to UIC types
708             for (qint32 idx = 0; idx != qint32(count); ++idx)
709                 type[idx] = GLConversion::fromShaderGLToPropertyDataTypes(GLenum(glTypes[idx]));
710         }
711         if (size)
712             GL_CALL_EXTRA_FUNCTION(glGetActiveUniformsiv(programID, GLsizei(count), indices, GL_UNIFORM_SIZE, size));
713         if (offset)
714             GL_CALL_EXTRA_FUNCTION(glGetActiveUniformsiv(programID, GLsizei(count), indices, GL_UNIFORM_OFFSET, offset));
715     }
716 }
717 
programSetConstantBlock(QSSGRenderBackendShaderProgramObject po,quint32 blockIndex,quint32 binding)718 void QSSGRenderBackendGL3Impl::programSetConstantBlock(QSSGRenderBackendShaderProgramObject po, quint32 blockIndex, quint32 binding)
719 {
720     QSSGRenderBackendShaderProgramGL *pProgram = reinterpret_cast<QSSGRenderBackendShaderProgramGL *>(po);
721     GLuint programID = static_cast<GLuint>(pProgram->m_programID);
722 
723     GL_CALL_EXTRA_FUNCTION(glUniformBlockBinding(programID, blockIndex, binding));
724 }
725 
programSetConstantBuffer(quint32 index,QSSGRenderBackendBufferObject bo)726 void QSSGRenderBackendGL3Impl::programSetConstantBuffer(quint32 index, QSSGRenderBackendBufferObject bo)
727 {
728     Q_ASSERT(bo);
729 
730     GLuint bufID = HandleToID_cast(GLuint, quintptr, bo);
731     GL_CALL_EXTRA_FUNCTION(glBindBufferBase(GL_UNIFORM_BUFFER, index, bufID));
732 }
733 
createQuery()734 QSSGRenderBackend::QSSGRenderBackendQueryObject QSSGRenderBackendGL3Impl::createQuery()
735 {
736     quint32 glQueryID = 0;
737 
738     GL_CALL_EXTRA_FUNCTION(glGenQueries(1, &glQueryID));
739 
740     return reinterpret_cast<QSSGRenderBackendQueryObject>(quintptr(glQueryID));
741 }
742 
releaseQuery(QSSGRenderBackendQueryObject qo)743 void QSSGRenderBackendGL3Impl::releaseQuery(QSSGRenderBackendQueryObject qo)
744 {
745     GLuint queryID = HandleToID_cast(GLuint, quintptr, qo);
746 
747     GL_CALL_EXTRA_FUNCTION(glDeleteQueries(1, &queryID));
748 }
749 
beginQuery(QSSGRenderBackendQueryObject qo,QSSGRenderQueryType type)750 void QSSGRenderBackendGL3Impl::beginQuery(QSSGRenderBackendQueryObject qo, QSSGRenderQueryType type)
751 {
752     GLuint queryID = HandleToID_cast(GLuint, quintptr, qo);
753 
754     GL_CALL_EXTRA_FUNCTION(glBeginQuery(m_conversion.fromQueryTypeToGL(type), queryID));
755 }
756 
endQuery(QSSGRenderBackendQueryObject,QSSGRenderQueryType type)757 void QSSGRenderBackendGL3Impl::endQuery(QSSGRenderBackendQueryObject, QSSGRenderQueryType type)
758 {
759     GL_CALL_EXTRA_FUNCTION(glEndQuery(m_conversion.fromQueryTypeToGL(type)));
760 }
761 
getQueryResult(QSSGRenderBackendQueryObject qo,QSSGRenderQueryResultType resultType,quint32 * params)762 void QSSGRenderBackendGL3Impl::getQueryResult(QSSGRenderBackendQueryObject qo,
763                                                 QSSGRenderQueryResultType resultType,
764                                                 quint32 *params)
765 {
766     GLuint queryID = HandleToID_cast(GLuint, quintptr, qo);
767 
768     if (params)
769         GL_CALL_EXTRA_FUNCTION(glGetQueryObjectuiv(queryID, m_conversion.fromQueryResultTypeToGL(resultType), params));
770 }
771 
getQueryResult(QSSGRenderBackendQueryObject qo,QSSGRenderQueryResultType resultType,quint64 * params)772 void QSSGRenderBackendGL3Impl::getQueryResult(QSSGRenderBackendQueryObject qo,
773                                                 QSSGRenderQueryResultType resultType,
774                                                 quint64 *params)
775 {
776     // TODO: params type!
777     if (m_backendSupport.caps.bits.bTimerQuerySupported) {
778         GLuint queryID = HandleToID_cast(GLuint, quintptr, qo);
779 
780         if (params)
781 #if defined(QT_OPENGL_ES)
782             GL_CALL_TIMER_EXT(glGetQueryObjectui64vEXT(queryID, m_conversion.fromQueryResultTypeToGL(resultType), reinterpret_cast<GLuint64 *>(params)));
783 #else
784             GL_CALL_TIMER_EXT(glGetQueryObjectui64v(queryID, m_conversion.fromQueryResultTypeToGL(resultType), reinterpret_cast<GLuint64 *>(params)));
785 #endif
786     }
787 }
788 
setQueryTimer(QSSGRenderBackendQueryObject qo)789 void QSSGRenderBackendGL3Impl::setQueryTimer(QSSGRenderBackendQueryObject qo)
790 {
791     if (m_backendSupport.caps.bits.bTimerQuerySupported) {
792         GLuint queryID = HandleToID_cast(GLuint, quintptr, qo);
793 #if defined(QT_OPENGL_ES)
794         GL_CALL_TIMER_EXT(glQueryCounterEXT(queryID, GL_TIMESTAMP));
795 #else
796         GL_CALL_TIMER_EXT(glQueryCounter(queryID, GL_TIMESTAMP));
797 #endif
798     }
799 }
800 
createSync(QSSGRenderSyncType syncType,QSSGRenderSyncFlags)801 QSSGRenderBackend::QSSGRenderBackendSyncObject QSSGRenderBackendGL3Impl::createSync(QSSGRenderSyncType syncType,
802                                                                                           QSSGRenderSyncFlags)
803 {
804     GLsync syncID = nullptr;
805 
806     syncID = GL_CALL_EXTRA_FUNCTION(glFenceSync(m_conversion.fromSyncTypeToGL(syncType), 0));
807 
808     return QSSGRenderBackendSyncObject(syncID);
809 }
810 
releaseSync(QSSGRenderBackendSyncObject so)811 void QSSGRenderBackendGL3Impl::releaseSync(QSSGRenderBackendSyncObject so)
812 {
813     GLsync syncID = GLsync(so);
814 
815     GL_CALL_EXTRA_FUNCTION(glDeleteSync(syncID));
816 }
817 
waitSync(QSSGRenderBackendSyncObject so,QSSGRenderCommandFlushFlags,quint64)818 void QSSGRenderBackendGL3Impl::waitSync(QSSGRenderBackendSyncObject so, QSSGRenderCommandFlushFlags, quint64)
819 {
820     GLsync syncID = GLsync(so);
821 
822     GL_CALL_EXTRA_FUNCTION(glWaitSync(syncID, 0, GL_TIMEOUT_IGNORED));
823 }
824 
releaseInputAssembler(QSSGRenderBackendInputAssemblerObject iao)825 void QSSGRenderBackendGL3Impl::releaseInputAssembler(QSSGRenderBackendInputAssemblerObject iao)
826 {
827     QSSGRenderBackendInputAssemblerGL *inputAssembler = reinterpret_cast<QSSGRenderBackendInputAssemblerGL *>(iao);
828     GL_CALL_EXTRA_FUNCTION(glDeleteVertexArrays(1, &inputAssembler->m_vaoID));
829     delete inputAssembler;
830 }
831 
832 QT_END_NAMESPACE
833 
834 
835