4729 lines
185 KiB
C++
4729 lines
185 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2019 The Qt Company Ltd.
|
|
** Contact: http://www.qt.io/licensing/
|
|
**
|
|
** This file is part of the Qt Gui module
|
|
**
|
|
** $QT_BEGIN_LICENSE:LGPL3$
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see http://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at http://www.qt.io/contact-us.
|
|
**
|
|
** GNU Lesser General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU Lesser General Public License version 3 requirements
|
|
** will be met: https://www.gnu.org/licenses/lgpl.html.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 2.0 or later as published by the Free
|
|
** Software Foundation and appearing in the file LICENSE.GPL included in
|
|
** the packaging of this file. Please review the following information to
|
|
** ensure the GNU General Public License version 2.0 requirements will be
|
|
** met: http://www.gnu.org/licenses/gpl-2.0.html.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "qrhigles2_p_p.h"
|
|
#include <QWindow>
|
|
#include <QOffscreenSurface>
|
|
#include <QOpenGLContext>
|
|
#include <QtGui/private/qopenglextensions_p.h>
|
|
#include <QtGui/private/qopenglprogrambinarycache_p.h>
|
|
#include <qmath.h>
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
/*
|
|
OpenGL backend. Binding vertex attribute locations and decomposing uniform
|
|
buffers into uniforms are handled transparently to the application via the
|
|
reflection data (QShaderDescription). Real uniform buffers are never used,
|
|
regardless of the GLSL version. Textures and buffers feature no special
|
|
logic, it's all just glTexSubImage2D and glBufferSubData (with "dynamic"
|
|
buffers set to GL_DYNAMIC_DRAW). The swapchain and the associated
|
|
renderbuffer for depth-stencil will be dummies since we have no control over
|
|
the underlying buffers here. While the baseline here is plain GLES 2.0, some
|
|
modern GL(ES) features like multisample renderbuffers, blits, and compute are
|
|
used when available. Also functional with core profile contexts.
|
|
*/
|
|
|
|
/*!
|
|
\class QRhiGles2InitParams
|
|
\internal
|
|
\inmodule QtGui
|
|
\brief OpenGL specific initialization parameters.
|
|
|
|
An OpenGL-based QRhi needs an already created QOffscreenSurface at minimum.
|
|
Additionally, while optional, it is recommended that the QWindow the first
|
|
QRhiSwapChain will target is passed in as well.
|
|
|
|
\badcode
|
|
QOffscreenSurface *fallbackSurface = QRhiGles2InitParams::newFallbackSurface();
|
|
QRhiGles2InitParams params;
|
|
params.fallbackSurface = fallbackSurface;
|
|
params.window = window;
|
|
rhi = QRhi::create(QRhi::OpenGLES2, ¶ms);
|
|
\endcode
|
|
|
|
By default QRhi creates a QOpenGLContext on its own. This approach works
|
|
well in most cases, included threaded scenarios, where there is a dedicated
|
|
QRhi for each rendering thread. As there will be a QOpenGLContext for each
|
|
QRhi, the OpenGL context requirements (a context can only be current on one
|
|
thread) are satisfied. The implicitly created context is destroyed
|
|
automatically together with the QRhi.
|
|
|
|
The QSurfaceFormat for the context is specified in \l format. The
|
|
constructor sets this to QSurfaceFormat::defaultFormat() so applications
|
|
that use QSurfaceFormat::setDefaultFormat() do not need to set the format
|
|
again.
|
|
|
|
\note The depth and stencil buffer sizes are set automatically to 24 and 8
|
|
when no size was explicitly set for these buffers in \l format. As there
|
|
are possible adjustments to \l format, applications can use
|
|
adjustedFormat() to query the effective format that is passed to
|
|
QOpenGLContext::setFormat() internally.
|
|
|
|
A QOffscreenSurface has to be specified in \l fallbackSurface. In order to
|
|
prevent mistakes in threaded situations, this is never created
|
|
automatically by the QRhi since, like QWindow, QOffscreenSurface can only
|
|
be created on the gui/main thread.
|
|
|
|
As a convenience, applications can use newFallbackSurface() which creates
|
|
and returns a QOffscreenSurface that is compatible with the QOpenGLContext
|
|
that is going to be created by the QRhi afterwards. Note that the ownership
|
|
of the returned QOffscreenSurface is transferred to the caller and the QRhi
|
|
will not destroy it.
|
|
|
|
\note QRhiSwapChain can only target QWindow instances that have their
|
|
surface type set to QSurface::OpenGLSurface.
|
|
|
|
\note \l window is optional. It is recommended to specify it whenever
|
|
possible, in order to avoid problems on multi-adapter and multi-screen
|
|
systems. When \l window is not set, the very first
|
|
QOpenGLContext::makeCurrent() happens with \l fallbackSurface which may be
|
|
an invisible window on some platforms (for example, Windows) and that may
|
|
trigger unexpected problems in some cases.
|
|
|
|
\section2 Working with existing OpenGL contexts
|
|
|
|
When interoperating with another graphics engine, it may be necessary to
|
|
get a QRhi instance that uses the same OpenGL context. This can be achieved
|
|
by passing a pointer to a QRhiGles2NativeHandles to QRhi::create(). The
|
|
\l{QRhiGles2NativeHandles::context}{context} must be set to a non-null
|
|
value.
|
|
|
|
An alternative approach is to create a QOpenGLContext that
|
|
\l{QOpenGLContext::setShareContext()}{shares resources} with the other
|
|
engine's context and passing in that context via QRhiGles2NativeHandles.
|
|
|
|
The QRhi does not take ownership of the QOpenGLContext passed in via
|
|
QRhiGles2NativeHandles.
|
|
*/
|
|
|
|
/*!
|
|
\class QRhiGles2NativeHandles
|
|
\internal
|
|
\inmodule QtGui
|
|
\brief Holds the OpenGL context used by the QRhi.
|
|
*/
|
|
|
|
#ifndef GL_BGRA
|
|
#define GL_BGRA 0x80E1
|
|
#endif
|
|
|
|
#ifndef GL_R8
|
|
#define GL_R8 0x8229
|
|
#endif
|
|
|
|
#ifndef GL_RG8
|
|
#define GL_RG8 0x822B
|
|
#endif
|
|
|
|
#ifndef GL_RG
|
|
#define GL_RG 0x8227
|
|
#endif
|
|
|
|
#ifndef GL_R16
|
|
#define GL_R16 0x822A
|
|
#endif
|
|
|
|
#ifndef GL_RED
|
|
#define GL_RED 0x1903
|
|
#endif
|
|
|
|
#ifndef GL_RGBA8
|
|
#define GL_RGBA8 0x8058
|
|
#endif
|
|
|
|
#ifndef GL_RGBA32F
|
|
#define GL_RGBA32F 0x8814
|
|
#endif
|
|
|
|
#ifndef GL_RGBA16F
|
|
#define GL_RGBA16F 0x881A
|
|
#endif
|
|
|
|
#ifndef GL_R16F
|
|
#define GL_R16F 0x822D
|
|
#endif
|
|
|
|
#ifndef GL_R32F
|
|
#define GL_R32F 0x822E
|
|
#endif
|
|
|
|
#ifndef GL_HALF_FLOAT
|
|
#define GL_HALF_FLOAT 0x140B
|
|
#endif
|
|
|
|
#ifndef GL_DEPTH_COMPONENT16
|
|
#define GL_DEPTH_COMPONENT16 0x81A5
|
|
#endif
|
|
|
|
#ifndef GL_DEPTH_COMPONENT24
|
|
#define GL_DEPTH_COMPONENT24 0x81A6
|
|
#endif
|
|
|
|
#ifndef GL_DEPTH_COMPONENT32F
|
|
#define GL_DEPTH_COMPONENT32F 0x8CAC
|
|
#endif
|
|
|
|
#ifndef GL_STENCIL_INDEX
|
|
#define GL_STENCIL_INDEX 0x1901
|
|
#endif
|
|
|
|
#ifndef GL_STENCIL_INDEX8
|
|
#define GL_STENCIL_INDEX8 0x8D48
|
|
#endif
|
|
|
|
#ifndef GL_DEPTH24_STENCIL8
|
|
#define GL_DEPTH24_STENCIL8 0x88F0
|
|
#endif
|
|
|
|
#ifndef GL_DEPTH_STENCIL_ATTACHMENT
|
|
#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A
|
|
#endif
|
|
|
|
#ifndef GL_DEPTH_STENCIL
|
|
#define GL_DEPTH_STENCIL 0x84F9
|
|
#endif
|
|
|
|
#ifndef GL_PRIMITIVE_RESTART_FIXED_INDEX
|
|
#define GL_PRIMITIVE_RESTART_FIXED_INDEX 0x8D69
|
|
#endif
|
|
|
|
#ifndef GL_FRAMEBUFFER_SRGB
|
|
#define GL_FRAMEBUFFER_SRGB 0x8DB9
|
|
#endif
|
|
|
|
#ifndef GL_READ_FRAMEBUFFER
|
|
#define GL_READ_FRAMEBUFFER 0x8CA8
|
|
#endif
|
|
|
|
#ifndef GL_DRAW_FRAMEBUFFER
|
|
#define GL_DRAW_FRAMEBUFFER 0x8CA9
|
|
#endif
|
|
|
|
#ifndef GL_MAX_DRAW_BUFFERS
|
|
#define GL_MAX_DRAW_BUFFERS 0x8824
|
|
#endif
|
|
|
|
#ifndef GL_TEXTURE_COMPARE_MODE
|
|
#define GL_TEXTURE_COMPARE_MODE 0x884C
|
|
#endif
|
|
|
|
#ifndef GL_COMPARE_REF_TO_TEXTURE
|
|
#define GL_COMPARE_REF_TO_TEXTURE 0x884E
|
|
#endif
|
|
|
|
#ifndef GL_TEXTURE_COMPARE_FUNC
|
|
#define GL_TEXTURE_COMPARE_FUNC 0x884D
|
|
#endif
|
|
|
|
#ifndef GL_MAX_SAMPLES
|
|
#define GL_MAX_SAMPLES 0x8D57
|
|
#endif
|
|
|
|
#ifndef GL_SHADER_STORAGE_BUFFER
|
|
#define GL_SHADER_STORAGE_BUFFER 0x90D2
|
|
#endif
|
|
|
|
#ifndef GL_READ_ONLY
|
|
#define GL_READ_ONLY 0x88B8
|
|
#endif
|
|
|
|
#ifndef GL_WRITE_ONLY
|
|
#define GL_WRITE_ONLY 0x88B9
|
|
#endif
|
|
|
|
#ifndef GL_READ_WRITE
|
|
#define GL_READ_WRITE 0x88BA
|
|
#endif
|
|
|
|
#ifndef GL_COMPUTE_SHADER
|
|
#define GL_COMPUTE_SHADER 0x91B9
|
|
#endif
|
|
|
|
#ifndef GL_ALL_BARRIER_BITS
|
|
#define GL_ALL_BARRIER_BITS 0xFFFFFFFF
|
|
#endif
|
|
|
|
#ifndef GL_SHADER_IMAGE_ACCESS_BARRIER_BIT
|
|
#define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT 0x00000020
|
|
#endif
|
|
|
|
#ifndef GL_SHADER_STORAGE_BARRIER_BIT
|
|
#define GL_SHADER_STORAGE_BARRIER_BIT 0x00002000
|
|
#endif
|
|
|
|
#ifndef GL_VERTEX_PROGRAM_POINT_SIZE
|
|
#define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642
|
|
#endif
|
|
|
|
#ifndef GL_POINT_SPRITE
|
|
#define GL_POINT_SPRITE 0x8861
|
|
#endif
|
|
|
|
#ifndef GL_MAP_READ_BIT
|
|
#define GL_MAP_READ_BIT 0x0001
|
|
#endif
|
|
|
|
#ifndef GL_TEXTURE_2D_MULTISAMPLE
|
|
#define GL_TEXTURE_2D_MULTISAMPLE 0x9100
|
|
#endif
|
|
|
|
#ifndef GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS
|
|
#define GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS 0x90EB
|
|
#endif
|
|
|
|
#ifndef GL_MAX_COMPUTE_WORK_GROUP_COUNT
|
|
#define GL_MAX_COMPUTE_WORK_GROUP_COUNT 0x91BE
|
|
#endif
|
|
|
|
#ifndef GL_MAX_COMPUTE_WORK_GROUP_SIZE
|
|
#define GL_MAX_COMPUTE_WORK_GROUP_SIZE 0x91BF
|
|
#endif
|
|
|
|
#ifndef GL_TEXTURE_CUBE_MAP_SEAMLESS
|
|
#define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F
|
|
#endif
|
|
|
|
/*!
|
|
Constructs a new QRhiGles2InitParams.
|
|
|
|
\l format is set to QSurfaceFormat::defaultFormat().
|
|
*/
|
|
QRhiGles2InitParams::QRhiGles2InitParams()
|
|
{
|
|
format = QSurfaceFormat::defaultFormat();
|
|
}
|
|
|
|
/*!
|
|
\return the QSurfaceFormat that will be set on the QOpenGLContext before
|
|
calling QOpenGLContext::create(). This format is based on \a format, but
|
|
may be adjusted. Applicable only when QRhi creates the context.
|
|
Applications are advised to set this format on their QWindow in order to
|
|
avoid potential BAD_MATCH failures.
|
|
*/
|
|
QSurfaceFormat QRhiGles2InitParams::adjustedFormat(const QSurfaceFormat &format)
|
|
{
|
|
QSurfaceFormat fmt = format;
|
|
|
|
if (fmt.depthBufferSize() == -1)
|
|
fmt.setDepthBufferSize(24);
|
|
if (fmt.stencilBufferSize() == -1)
|
|
fmt.setStencilBufferSize(8);
|
|
|
|
return fmt;
|
|
}
|
|
|
|
/*!
|
|
\return a new QOffscreenSurface that can be used with a QRhi by passing it
|
|
via a QRhiGles2InitParams.
|
|
|
|
\a format is adjusted as appropriate in order to avoid having problems
|
|
afterwards due to an incompatible context and surface.
|
|
|
|
\note This function must only be called on the gui/main thread.
|
|
|
|
\note It is the application's responsibility to destroy the returned
|
|
QOffscreenSurface on the gui/main thread once the associated QRhi has been
|
|
destroyed. The QRhi will not destroy the QOffscreenSurface.
|
|
*/
|
|
QOffscreenSurface *QRhiGles2InitParams::newFallbackSurface(const QSurfaceFormat &format)
|
|
{
|
|
QSurfaceFormat fmt = adjustedFormat(format);
|
|
|
|
// To resolve all fields in the format as much as possible, create a context.
|
|
// This may be heavy, but allows avoiding BAD_MATCH on some systems.
|
|
QOpenGLContext tempContext;
|
|
tempContext.setFormat(fmt);
|
|
if (tempContext.create())
|
|
fmt = tempContext.format();
|
|
else
|
|
qWarning("QRhiGles2: Failed to create temporary context");
|
|
|
|
QOffscreenSurface *s = new QOffscreenSurface;
|
|
s->setFormat(fmt);
|
|
s->create();
|
|
|
|
return s;
|
|
}
|
|
|
|
QRhiGles2::QRhiGles2(QRhiGles2InitParams *params, QRhiGles2NativeHandles *importDevice)
|
|
: ofr(this)
|
|
{
|
|
requestedFormat = QRhiGles2InitParams::adjustedFormat(params->format);
|
|
fallbackSurface = params->fallbackSurface;
|
|
maybeWindow = params->window; // may be null
|
|
|
|
importedContext = importDevice != nullptr;
|
|
if (importedContext) {
|
|
ctx = importDevice->context;
|
|
if (!ctx) {
|
|
qWarning("No OpenGL context given, cannot import");
|
|
importedContext = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool QRhiGles2::ensureContext(QSurface *surface) const
|
|
{
|
|
bool nativeWindowGone = false;
|
|
if (surface && surface->surfaceClass() == QSurface::Window && !surface->surfaceHandle()) {
|
|
surface = fallbackSurface;
|
|
nativeWindowGone = true;
|
|
}
|
|
|
|
if (!surface)
|
|
surface = fallbackSurface;
|
|
|
|
if (needsMakeCurrent)
|
|
needsMakeCurrent = false;
|
|
else if (!nativeWindowGone && QOpenGLContext::currentContext() == ctx && (surface == fallbackSurface || ctx->surface() == surface))
|
|
return true;
|
|
|
|
if (!ctx->makeCurrent(surface)) {
|
|
if (ctx->isValid()) {
|
|
qWarning("QRhiGles2: Failed to make context current. Expect bad things to happen.");
|
|
} else {
|
|
qWarning("QRhiGles2: Context is lost.");
|
|
contextLost = true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QRhiGles2::create(QRhi::Flags flags)
|
|
{
|
|
Q_UNUSED(flags);
|
|
Q_ASSERT(fallbackSurface);
|
|
|
|
if (!importedContext) {
|
|
ctx = new QOpenGLContext;
|
|
ctx->setFormat(requestedFormat);
|
|
if (QOpenGLContext *shareContext = qt_gl_global_share_context()) {
|
|
ctx->setShareContext(shareContext);
|
|
ctx->setScreen(shareContext->screen());
|
|
}
|
|
if (!ctx->create()) {
|
|
qWarning("QRhiGles2: Failed to create context");
|
|
delete ctx;
|
|
ctx = nullptr;
|
|
return false;
|
|
}
|
|
qCDebug(QRHI_LOG_INFO) << "Created OpenGL context" << ctx->format();
|
|
}
|
|
|
|
if (!ensureContext(maybeWindow ? maybeWindow : fallbackSurface)) // see 'window' discussion in QRhiGles2InitParams comments
|
|
return false;
|
|
|
|
f = static_cast<QOpenGLExtensions *>(ctx->extraFunctions());
|
|
|
|
const char *vendor = reinterpret_cast<const char *>(f->glGetString(GL_VENDOR));
|
|
const char *renderer = reinterpret_cast<const char *>(f->glGetString(GL_RENDERER));
|
|
const char *version = reinterpret_cast<const char *>(f->glGetString(GL_VERSION));
|
|
if (vendor && renderer && version)
|
|
qCDebug(QRHI_LOG_INFO, "OpenGL VENDOR: %s RENDERER: %s VERSION: %s", vendor, renderer, version);
|
|
|
|
const QSurfaceFormat actualFormat = ctx->format();
|
|
|
|
caps.ctxMajor = actualFormat.majorVersion();
|
|
caps.ctxMinor = actualFormat.minorVersion();
|
|
|
|
GLint n = 0;
|
|
f->glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &n);
|
|
supportedCompressedFormats.resize(n);
|
|
if (n > 0)
|
|
f->glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, supportedCompressedFormats.data());
|
|
|
|
f->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &caps.maxTextureSize);
|
|
|
|
if (caps.ctxMajor >= 3 || actualFormat.renderableType() == QSurfaceFormat::OpenGL) {
|
|
f->glGetIntegerv(GL_MAX_DRAW_BUFFERS, &caps.maxDrawBuffers);
|
|
f->glGetIntegerv(GL_MAX_SAMPLES, &caps.maxSamples);
|
|
caps.maxSamples = qMax(1, caps.maxSamples);
|
|
} else {
|
|
caps.maxDrawBuffers = 1;
|
|
caps.maxSamples = 1;
|
|
}
|
|
|
|
caps.msaaRenderBuffer = f->hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample)
|
|
&& f->hasOpenGLExtension(QOpenGLExtensions::FramebufferBlit);
|
|
|
|
caps.npotTextureFull = f->hasOpenGLFeature(QOpenGLFunctions::NPOTTextures)
|
|
&& f->hasOpenGLFeature(QOpenGLFunctions::NPOTTextureRepeat);
|
|
|
|
caps.gles = actualFormat.renderableType() == QSurfaceFormat::OpenGLES;
|
|
if (caps.gles)
|
|
caps.fixedIndexPrimitiveRestart = caps.ctxMajor >= 3; // ES 3.0
|
|
else
|
|
caps.fixedIndexPrimitiveRestart = caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 3); // 4.3
|
|
|
|
if (caps.fixedIndexPrimitiveRestart)
|
|
f->glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
|
|
|
|
caps.bgraExternalFormat = f->hasOpenGLExtension(QOpenGLExtensions::BGRATextureFormat);
|
|
caps.bgraInternalFormat = caps.bgraExternalFormat && caps.gles;
|
|
caps.r8Format = f->hasOpenGLFeature(QOpenGLFunctions::TextureRGFormats);
|
|
caps.r16Format = f->hasOpenGLExtension(QOpenGLExtensions::Sized16Formats);
|
|
caps.floatFormats = caps.ctxMajor >= 3; // 3.0 or ES 3.0
|
|
caps.depthTexture = caps.ctxMajor >= 3; // 3.0 or ES 3.0
|
|
caps.packedDepthStencil = f->hasOpenGLExtension(QOpenGLExtensions::PackedDepthStencil);
|
|
#ifdef Q_OS_WASM
|
|
caps.needsDepthStencilCombinedAttach = true;
|
|
#else
|
|
caps.needsDepthStencilCombinedAttach = false;
|
|
#endif
|
|
caps.srgbCapableDefaultFramebuffer = f->hasOpenGLExtension(QOpenGLExtensions::SRGBFrameBuffer);
|
|
caps.coreProfile = actualFormat.profile() == QSurfaceFormat::CoreProfile;
|
|
|
|
if (caps.gles)
|
|
caps.uniformBuffers = caps.ctxMajor >= 3; // ES 3.0
|
|
else
|
|
caps.uniformBuffers = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 1); // 3.1
|
|
|
|
caps.elementIndexUint = f->hasOpenGLExtension(QOpenGLExtensions::ElementIndexUint);
|
|
caps.depth24 = f->hasOpenGLExtension(QOpenGLExtensions::Depth24);
|
|
caps.rgba8Format = f->hasOpenGLExtension(QOpenGLExtensions::Sized8Formats);
|
|
|
|
if (caps.gles)
|
|
caps.instancing = caps.ctxMajor >= 3; // ES 3.0
|
|
else
|
|
caps.instancing = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 3); // 3.3
|
|
|
|
caps.baseVertex = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2); // 3.2 or ES 3.2
|
|
|
|
if (caps.gles)
|
|
caps.compute = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 1); // ES 3.1
|
|
else
|
|
caps.compute = caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 3); // 4.3
|
|
|
|
if (caps.compute) {
|
|
f->glGetIntegerv(GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, &caps.maxThreadsPerThreadGroup);
|
|
GLint tgPerDim[3];
|
|
f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0, &tgPerDim[0]);
|
|
f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1, &tgPerDim[1]);
|
|
f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 2, &tgPerDim[2]);
|
|
caps.maxThreadGroupsPerDimension = qMin(tgPerDim[0], qMin(tgPerDim[1], tgPerDim[2]));
|
|
f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 0, &caps.maxThreadGroupsX);
|
|
f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 1, &caps.maxThreadGroupsY);
|
|
f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 2, &caps.maxThreadGroupsZ);
|
|
}
|
|
|
|
if (caps.gles)
|
|
caps.textureCompareMode = caps.ctxMajor >= 3; // ES 3.0
|
|
else
|
|
caps.textureCompareMode = true;
|
|
|
|
// proper as in ES 3.0 (glMapBufferRange), not the old glMapBuffer
|
|
// extension(s) (which is not in ES 3.0...messy)
|
|
caps.properMapBuffer = f->hasOpenGLExtension(QOpenGLExtensions::MapBufferRange);
|
|
|
|
if (caps.gles)
|
|
caps.nonBaseLevelFramebufferTexture = caps.ctxMajor >= 3; // ES 3.0
|
|
else
|
|
caps.nonBaseLevelFramebufferTexture = true;
|
|
|
|
caps.texelFetch = caps.ctxMajor >= 3; // 3.0 or ES 3.0
|
|
caps.intAttributes = caps.ctxMajor >= 3; // 3.0 or ES 3.0
|
|
caps.screenSpaceDerivatives = f->hasOpenGLExtension(QOpenGLExtensions::StandardDerivatives);
|
|
|
|
// TO DO: We could also check for ARB_texture_multisample but it is not
|
|
// currently in QOpenGLExtensions
|
|
// 3.0 or ES 3.1
|
|
caps.multisampledTexture = caps.gles
|
|
? (caps.ctxMajor > 3 || (caps.ctxMajor >= 3 && caps.ctxMinor >= 1))
|
|
: (caps.ctxMajor >= 3);
|
|
|
|
if (!caps.gles) {
|
|
f->glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
|
|
f->glEnable(GL_POINT_SPRITE);
|
|
} // else (with gles) these are always on
|
|
|
|
// Match D3D and others when it comes to seamless cubemap filtering.
|
|
// ES 3.0+ has this always enabled. (hopefully)
|
|
// ES 2.0 and GL < 3.2 will not have it.
|
|
if (!caps.gles && (caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2)))
|
|
f->glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
|
|
|
|
nativeHandlesStruct.context = ctx;
|
|
|
|
contextLost = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void QRhiGles2::destroy()
|
|
{
|
|
if (!f)
|
|
return;
|
|
|
|
ensureContext();
|
|
executeDeferredReleases();
|
|
|
|
if (vao) {
|
|
f->glDeleteVertexArrays(1, &vao);
|
|
vao = 0;
|
|
}
|
|
|
|
for (uint shader : m_shaderCache)
|
|
f->glDeleteShader(shader);
|
|
m_shaderCache.clear();
|
|
|
|
if (!importedContext) {
|
|
delete ctx;
|
|
ctx = nullptr;
|
|
}
|
|
|
|
f = nullptr;
|
|
}
|
|
|
|
void QRhiGles2::executeDeferredReleases()
|
|
{
|
|
for (int i = releaseQueue.count() - 1; i >= 0; --i) {
|
|
const QRhiGles2::DeferredReleaseEntry &e(releaseQueue[i]);
|
|
switch (e.type) {
|
|
case QRhiGles2::DeferredReleaseEntry::Buffer:
|
|
f->glDeleteBuffers(1, &e.buffer.buffer);
|
|
break;
|
|
case QRhiGles2::DeferredReleaseEntry::Pipeline:
|
|
f->glDeleteProgram(e.pipeline.program);
|
|
break;
|
|
case QRhiGles2::DeferredReleaseEntry::Texture:
|
|
f->glDeleteTextures(1, &e.texture.texture);
|
|
break;
|
|
case QRhiGles2::DeferredReleaseEntry::RenderBuffer:
|
|
f->glDeleteRenderbuffers(1, &e.renderbuffer.renderbuffer);
|
|
f->glDeleteRenderbuffers(1, &e.renderbuffer.renderbuffer2);
|
|
break;
|
|
case QRhiGles2::DeferredReleaseEntry::TextureRenderTarget:
|
|
f->glDeleteFramebuffers(1, &e.textureRenderTarget.framebuffer);
|
|
break;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
break;
|
|
}
|
|
releaseQueue.removeAt(i);
|
|
}
|
|
}
|
|
|
|
QList<int> QRhiGles2::supportedSampleCounts() const
|
|
{
|
|
if (supportedSampleCountList.isEmpty()) {
|
|
// 1, 2, 4, 8, ...
|
|
for (int i = 1; i <= caps.maxSamples; i *= 2)
|
|
supportedSampleCountList.append(i);
|
|
}
|
|
return supportedSampleCountList;
|
|
}
|
|
|
|
int QRhiGles2::effectiveSampleCount(int sampleCount) const
|
|
{
|
|
// Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1.
|
|
const int s = qBound(1, sampleCount, 64);
|
|
if (!supportedSampleCounts().contains(s)) {
|
|
qWarning("Attempted to set unsupported sample count %d", sampleCount);
|
|
return 1;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
QRhiSwapChain *QRhiGles2::createSwapChain()
|
|
{
|
|
return new QGles2SwapChain(this);
|
|
}
|
|
|
|
QRhiBuffer *QRhiGles2::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size)
|
|
{
|
|
return new QGles2Buffer(this, type, usage, size);
|
|
}
|
|
|
|
int QRhiGles2::ubufAlignment() const
|
|
{
|
|
// No real uniform buffers are used so no need to pretend there is any
|
|
// alignment requirement.
|
|
return 1;
|
|
}
|
|
|
|
bool QRhiGles2::isYUpInFramebuffer() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool QRhiGles2::isYUpInNDC() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool QRhiGles2::isClipDepthZeroToOne() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QMatrix4x4 QRhiGles2::clipSpaceCorrMatrix() const
|
|
{
|
|
return QMatrix4x4(); // identity
|
|
}
|
|
|
|
static inline GLenum toGlCompressedTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
|
|
{
|
|
const bool srgb = flags.testFlag(QRhiTexture::sRGB);
|
|
switch (format) {
|
|
case QRhiTexture::BC1:
|
|
return srgb ? 0x8C4C : 0x83F0;
|
|
case QRhiTexture::BC2:
|
|
return srgb ? 0x8C4E : 0x83F2;
|
|
case QRhiTexture::BC3:
|
|
return srgb ? 0x8C4F : 0x83F3;
|
|
|
|
case QRhiTexture::ETC2_RGB8:
|
|
return srgb ? 0x9275 : 0x9274;
|
|
case QRhiTexture::ETC2_RGB8A1:
|
|
return srgb ? 0x9277 : 0x9276;
|
|
case QRhiTexture::ETC2_RGBA8:
|
|
return srgb ? 0x9279 : 0x9278;
|
|
|
|
case QRhiTexture::ASTC_4x4:
|
|
return srgb ? 0x93D0 : 0x93B0;
|
|
case QRhiTexture::ASTC_5x4:
|
|
return srgb ? 0x93D1 : 0x93B1;
|
|
case QRhiTexture::ASTC_5x5:
|
|
return srgb ? 0x93D2 : 0x93B2;
|
|
case QRhiTexture::ASTC_6x5:
|
|
return srgb ? 0x93D3 : 0x93B3;
|
|
case QRhiTexture::ASTC_6x6:
|
|
return srgb ? 0x93D4 : 0x93B4;
|
|
case QRhiTexture::ASTC_8x5:
|
|
return srgb ? 0x93D5 : 0x93B5;
|
|
case QRhiTexture::ASTC_8x6:
|
|
return srgb ? 0x93D6 : 0x93B6;
|
|
case QRhiTexture::ASTC_8x8:
|
|
return srgb ? 0x93D7 : 0x93B7;
|
|
case QRhiTexture::ASTC_10x5:
|
|
return srgb ? 0x93D8 : 0x93B8;
|
|
case QRhiTexture::ASTC_10x6:
|
|
return srgb ? 0x93D9 : 0x93B9;
|
|
case QRhiTexture::ASTC_10x8:
|
|
return srgb ? 0x93DA : 0x93BA;
|
|
case QRhiTexture::ASTC_10x10:
|
|
return srgb ? 0x93DB : 0x93BB;
|
|
case QRhiTexture::ASTC_12x10:
|
|
return srgb ? 0x93DC : 0x93BC;
|
|
case QRhiTexture::ASTC_12x12:
|
|
return srgb ? 0x93DD : 0x93BD;
|
|
|
|
default:
|
|
return 0; // this is reachable, just return an invalid format
|
|
}
|
|
}
|
|
|
|
static inline void toGlTextureFormat(QRhiTexture::Format format, const QRhiGles2::Caps &caps,
|
|
GLenum *glintformat, GLenum *glsizedintformat,
|
|
GLenum *glformat, GLenum *gltype)
|
|
{
|
|
switch (format) {
|
|
case QRhiTexture::RGBA8:
|
|
*glintformat = GL_RGBA;
|
|
*glsizedintformat = caps.rgba8Format ? GL_RGBA8 : GL_RGBA;
|
|
*glformat = GL_RGBA;
|
|
*gltype = GL_UNSIGNED_BYTE;
|
|
break;
|
|
case QRhiTexture::BGRA8:
|
|
*glintformat = caps.bgraInternalFormat ? GL_BGRA : GL_RGBA;
|
|
*glsizedintformat = caps.rgba8Format ? GL_RGBA8 : GL_RGBA;
|
|
*glformat = GL_BGRA;
|
|
*gltype = GL_UNSIGNED_BYTE;
|
|
break;
|
|
case QRhiTexture::R16:
|
|
*glintformat = GL_R16;
|
|
*glsizedintformat = *glintformat;
|
|
*glformat = GL_RED;
|
|
*gltype = GL_UNSIGNED_SHORT;
|
|
break;
|
|
case QRhiTexture::R8:
|
|
*glintformat = GL_R8;
|
|
*glsizedintformat = *glintformat;
|
|
*glformat = GL_RED;
|
|
*gltype = GL_UNSIGNED_BYTE;
|
|
break;
|
|
case QRhiTexture::RG8:
|
|
*glintformat = GL_RG8;
|
|
*glsizedintformat = *glintformat;
|
|
*glformat = GL_RG;
|
|
*gltype = GL_UNSIGNED_BYTE;
|
|
break;
|
|
case QRhiTexture::RED_OR_ALPHA8:
|
|
*glintformat = caps.coreProfile ? GL_R8 : GL_ALPHA;
|
|
*glsizedintformat = *glintformat;
|
|
*glformat = caps.coreProfile ? GL_RED : GL_ALPHA;
|
|
*gltype = GL_UNSIGNED_BYTE;
|
|
break;
|
|
case QRhiTexture::RGBA16F:
|
|
*glintformat = GL_RGBA16F;
|
|
*glsizedintformat = *glintformat;
|
|
*glformat = GL_RGBA;
|
|
*gltype = GL_HALF_FLOAT;
|
|
break;
|
|
case QRhiTexture::RGBA32F:
|
|
*glintformat = GL_RGBA32F;
|
|
*glsizedintformat = *glintformat;
|
|
*glformat = GL_RGBA;
|
|
*gltype = GL_FLOAT;
|
|
break;
|
|
case QRhiTexture::R16F:
|
|
*glintformat = GL_R16F;
|
|
*glsizedintformat = *glintformat;
|
|
*glformat = GL_RED;
|
|
*gltype = GL_HALF_FLOAT;
|
|
break;
|
|
case QRhiTexture::R32F:
|
|
*glintformat = GL_R32F;
|
|
*glsizedintformat = *glintformat;
|
|
*glformat = GL_RED;
|
|
*gltype = GL_FLOAT;
|
|
break;
|
|
case QRhiTexture::D16:
|
|
*glintformat = GL_DEPTH_COMPONENT16;
|
|
*glsizedintformat = *glintformat;
|
|
*glformat = GL_DEPTH_COMPONENT;
|
|
*gltype = GL_UNSIGNED_SHORT;
|
|
break;
|
|
case QRhiTexture::D24:
|
|
*glintformat = GL_DEPTH_COMPONENT24;
|
|
*glsizedintformat = *glintformat;
|
|
*glformat = GL_DEPTH_COMPONENT;
|
|
*gltype = GL_UNSIGNED_SHORT;
|
|
break;
|
|
case QRhiTexture::D24S8:
|
|
*glintformat = GL_DEPTH24_STENCIL8;
|
|
*glsizedintformat = *glintformat;
|
|
*glformat = GL_DEPTH_STENCIL;
|
|
*gltype = GL_UNSIGNED_SHORT;
|
|
break;
|
|
case QRhiTexture::D32F:
|
|
*glintformat = GL_DEPTH_COMPONENT32F;
|
|
*glsizedintformat = *glintformat;
|
|
*glformat = GL_DEPTH_COMPONENT;
|
|
*gltype = GL_FLOAT;
|
|
break;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
*glintformat = GL_RGBA;
|
|
*glsizedintformat = caps.rgba8Format ? GL_RGBA8 : GL_RGBA;
|
|
*glformat = GL_RGBA;
|
|
*gltype = GL_UNSIGNED_BYTE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool QRhiGles2::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
|
|
{
|
|
if (isCompressedFormat(format))
|
|
return supportedCompressedFormats.contains(GLint(toGlCompressedTextureFormat(format, flags)));
|
|
|
|
switch (format) {
|
|
case QRhiTexture::D16:
|
|
case QRhiTexture::D32F:
|
|
return caps.depthTexture;
|
|
|
|
case QRhiTexture::D24:
|
|
return caps.depth24;
|
|
|
|
case QRhiTexture::D24S8:
|
|
return caps.depth24 && caps.packedDepthStencil;
|
|
|
|
case QRhiTexture::BGRA8:
|
|
return caps.bgraExternalFormat;
|
|
|
|
case QRhiTexture::R8:
|
|
return caps.r8Format;
|
|
|
|
case QRhiTexture::RG8:
|
|
return caps.r8Format;
|
|
|
|
case QRhiTexture::R16:
|
|
return caps.r16Format;
|
|
|
|
case QRhiTexture::RGBA16F:
|
|
case QRhiTexture::RGBA32F:
|
|
return caps.floatFormats;
|
|
|
|
case QRhiTexture::R16F:
|
|
case QRhiTexture::R32F:
|
|
return caps.floatFormats;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const
|
|
{
|
|
switch (feature) {
|
|
case QRhi::MultisampleTexture:
|
|
return caps.multisampledTexture;
|
|
case QRhi::MultisampleRenderBuffer:
|
|
return caps.msaaRenderBuffer;
|
|
case QRhi::DebugMarkers:
|
|
return false;
|
|
case QRhi::Timestamps:
|
|
return false;
|
|
case QRhi::Instancing:
|
|
return caps.instancing;
|
|
case QRhi::CustomInstanceStepRate:
|
|
return false;
|
|
case QRhi::PrimitiveRestart:
|
|
return caps.fixedIndexPrimitiveRestart;
|
|
case QRhi::NonDynamicUniformBuffers:
|
|
return true;
|
|
case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
|
|
return true;
|
|
case QRhi::NPOTTextureRepeat:
|
|
return caps.npotTextureFull;
|
|
case QRhi::RedOrAlpha8IsRed:
|
|
return caps.coreProfile;
|
|
case QRhi::ElementIndexUint:
|
|
return caps.elementIndexUint;
|
|
case QRhi::Compute:
|
|
return caps.compute;
|
|
case QRhi::WideLines:
|
|
return !caps.coreProfile;
|
|
case QRhi::VertexShaderPointSize:
|
|
return true;
|
|
case QRhi::BaseVertex:
|
|
return caps.baseVertex;
|
|
case QRhi::BaseInstance:
|
|
return false; // not in ES 3.2, so won't bother
|
|
case QRhi::TriangleFanTopology:
|
|
return true;
|
|
case QRhi::ReadBackNonUniformBuffer:
|
|
return !caps.gles || caps.properMapBuffer;
|
|
case QRhi::ReadBackNonBaseMipLevel:
|
|
return caps.nonBaseLevelFramebufferTexture;
|
|
case QRhi::TexelFetch:
|
|
return caps.texelFetch;
|
|
case QRhi::RenderToNonBaseMipLevel:
|
|
return caps.nonBaseLevelFramebufferTexture;
|
|
case QRhi::IntAttributes:
|
|
return caps.intAttributes;
|
|
case QRhi::ScreenSpaceDerivatives:
|
|
return caps.screenSpaceDerivatives;
|
|
case QRhi::ReadBackAnyTextureFormat:
|
|
return false;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int QRhiGles2::resourceLimit(QRhi::ResourceLimit limit) const
|
|
{
|
|
switch (limit) {
|
|
case QRhi::TextureSizeMin:
|
|
return 1;
|
|
case QRhi::TextureSizeMax:
|
|
return caps.maxTextureSize;
|
|
case QRhi::MaxColorAttachments:
|
|
return caps.maxDrawBuffers;
|
|
case QRhi::FramesInFlight:
|
|
// From our perspective. What the GL impl does internally is another
|
|
// question, but that's out of our hands and does not concern us here.
|
|
return 1;
|
|
case QRhi::MaxAsyncReadbackFrames:
|
|
return 1;
|
|
case QRhi::MaxThreadGroupsPerDimension:
|
|
return caps.maxThreadGroupsPerDimension;
|
|
case QRhi::MaxThreadsPerThreadGroup:
|
|
return caps.maxThreadsPerThreadGroup;
|
|
case QRhi::MaxThreadGroupX:
|
|
return caps.maxThreadGroupsX;
|
|
case QRhi::MaxThreadGroupY:
|
|
return caps.maxThreadGroupsY;
|
|
case QRhi::MaxThreadGroupZ:
|
|
return caps.maxThreadGroupsZ;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
const QRhiNativeHandles *QRhiGles2::nativeHandles()
|
|
{
|
|
return &nativeHandlesStruct;
|
|
}
|
|
|
|
void QRhiGles2::sendVMemStatsToProfiler()
|
|
{
|
|
// nothing to do here
|
|
}
|
|
|
|
bool QRhiGles2::makeThreadLocalNativeContextCurrent()
|
|
{
|
|
if (inFrame && !ofr.active)
|
|
return ensureContext(currentSwapChain->surface);
|
|
else
|
|
return ensureContext();
|
|
}
|
|
|
|
void QRhiGles2::releaseCachedResources()
|
|
{
|
|
if (!ensureContext())
|
|
return;
|
|
|
|
for (uint shader : m_shaderCache)
|
|
f->glDeleteShader(shader);
|
|
|
|
m_shaderCache.clear();
|
|
}
|
|
|
|
bool QRhiGles2::isDeviceLost() const
|
|
{
|
|
return contextLost;
|
|
}
|
|
|
|
QRhiRenderBuffer *QRhiGles2::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
|
|
int sampleCount, QRhiRenderBuffer::Flags flags,
|
|
QRhiTexture::Format backingFormatHint)
|
|
{
|
|
return new QGles2RenderBuffer(this, type, pixelSize, sampleCount, flags, backingFormatHint);
|
|
}
|
|
|
|
QRhiTexture *QRhiGles2::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
|
|
int sampleCount, QRhiTexture::Flags flags)
|
|
{
|
|
return new QGles2Texture(this, format, pixelSize, sampleCount, flags);
|
|
}
|
|
|
|
QRhiSampler *QRhiGles2::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
|
|
QRhiSampler::Filter mipmapMode,
|
|
QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w)
|
|
{
|
|
return new QGles2Sampler(this, magFilter, minFilter, mipmapMode, u, v, w);
|
|
}
|
|
|
|
QRhiTextureRenderTarget *QRhiGles2::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
|
|
QRhiTextureRenderTarget::Flags flags)
|
|
{
|
|
return new QGles2TextureRenderTarget(this, desc, flags);
|
|
}
|
|
|
|
QRhiGraphicsPipeline *QRhiGles2::createGraphicsPipeline()
|
|
{
|
|
return new QGles2GraphicsPipeline(this);
|
|
}
|
|
|
|
QRhiShaderResourceBindings *QRhiGles2::createShaderResourceBindings()
|
|
{
|
|
return new QGles2ShaderResourceBindings(this);
|
|
}
|
|
|
|
QRhiComputePipeline *QRhiGles2::createComputePipeline()
|
|
{
|
|
return new QGles2ComputePipeline(this);
|
|
}
|
|
|
|
void QRhiGles2::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
|
|
QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, ps);
|
|
const bool pipelineChanged = cbD->currentGraphicsPipeline != ps || cbD->currentPipelineGeneration != psD->generation;
|
|
|
|
if (pipelineChanged) {
|
|
cbD->currentGraphicsPipeline = ps;
|
|
cbD->currentComputePipeline = nullptr;
|
|
cbD->currentPipelineGeneration = psD->generation;
|
|
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::BindGraphicsPipeline;
|
|
cmd.args.bindGraphicsPipeline.ps = ps;
|
|
cbD->commands.append(cmd);
|
|
}
|
|
}
|
|
|
|
void QRhiGles2::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
|
|
int dynamicOffsetCount,
|
|
const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
Q_ASSERT(cbD->recordingPass != QGles2CommandBuffer::NoPass);
|
|
QGles2GraphicsPipeline *gfxPsD = QRHI_RES(QGles2GraphicsPipeline, cbD->currentGraphicsPipeline);
|
|
QGles2ComputePipeline *compPsD = QRHI_RES(QGles2ComputePipeline, cbD->currentComputePipeline);
|
|
|
|
if (!srb) {
|
|
if (gfxPsD)
|
|
srb = gfxPsD->m_shaderResourceBindings;
|
|
else
|
|
srb = compPsD->m_shaderResourceBindings;
|
|
}
|
|
|
|
QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
|
|
QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, srb);
|
|
bool hasDynamicOffsetInSrb = false;
|
|
for (int i = 0, ie = srbD->m_bindings.count(); i != ie; ++i) {
|
|
const QRhiShaderResourceBinding::Data *b = srbD->m_bindings.at(i).data();
|
|
switch (b->type) {
|
|
case QRhiShaderResourceBinding::UniformBuffer:
|
|
// no BufUniformRead / AccessUniform because no real uniform buffers are used
|
|
if (b->u.ubuf.hasDynamicOffset)
|
|
hasDynamicOffsetInSrb = true;
|
|
break;
|
|
case QRhiShaderResourceBinding::SampledTexture:
|
|
if (cbD->passNeedsResourceTracking) {
|
|
for (int elem = 0; elem < b->u.stex.count; ++elem) {
|
|
trackedRegisterTexture(&passResTracker,
|
|
QRHI_RES(QGles2Texture, b->u.stex.texSamplers[elem].tex),
|
|
QRhiPassResourceTracker::TexSample,
|
|
QRhiPassResourceTracker::toPassTrackerTextureStage(b->stage));
|
|
}
|
|
}
|
|
break;
|
|
case QRhiShaderResourceBinding::ImageLoad:
|
|
case QRhiShaderResourceBinding::ImageStore:
|
|
case QRhiShaderResourceBinding::ImageLoadStore:
|
|
if (cbD->passNeedsResourceTracking) {
|
|
QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.simage.tex);
|
|
QRhiPassResourceTracker::TextureAccess access;
|
|
if (b->type == QRhiShaderResourceBinding::ImageLoad)
|
|
access = QRhiPassResourceTracker::TexStorageLoad;
|
|
else if (b->type == QRhiShaderResourceBinding::ImageStore)
|
|
access = QRhiPassResourceTracker::TexStorageStore;
|
|
else
|
|
access = QRhiPassResourceTracker::TexStorageLoadStore;
|
|
trackedRegisterTexture(&passResTracker, texD, access,
|
|
QRhiPassResourceTracker::toPassTrackerTextureStage(b->stage));
|
|
}
|
|
break;
|
|
case QRhiShaderResourceBinding::BufferLoad:
|
|
case QRhiShaderResourceBinding::BufferStore:
|
|
case QRhiShaderResourceBinding::BufferLoadStore:
|
|
if (cbD->passNeedsResourceTracking) {
|
|
QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, b->u.sbuf.buf);
|
|
QRhiPassResourceTracker::BufferAccess access;
|
|
if (b->type == QRhiShaderResourceBinding::BufferLoad)
|
|
access = QRhiPassResourceTracker::BufStorageLoad;
|
|
else if (b->type == QRhiShaderResourceBinding::BufferStore)
|
|
access = QRhiPassResourceTracker::BufStorageStore;
|
|
else
|
|
access = QRhiPassResourceTracker::BufStorageLoadStore;
|
|
trackedRegisterBuffer(&passResTracker, bufD, access,
|
|
QRhiPassResourceTracker::toPassTrackerBufferStage(b->stage));
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb);
|
|
const bool srbRebuilt = cbD->currentSrbGeneration != srbD->generation;
|
|
|
|
if (srbChanged || srbRebuilt || hasDynamicOffsetInSrb) {
|
|
if (gfxPsD) {
|
|
cbD->currentGraphicsSrb = srb;
|
|
cbD->currentComputeSrb = nullptr;
|
|
} else {
|
|
cbD->currentGraphicsSrb = nullptr;
|
|
cbD->currentComputeSrb = srb;
|
|
}
|
|
cbD->currentSrbGeneration = srbD->generation;
|
|
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::BindShaderResources;
|
|
cmd.args.bindShaderResources.maybeGraphicsPs = gfxPsD;
|
|
cmd.args.bindShaderResources.maybeComputePs = compPsD;
|
|
cmd.args.bindShaderResources.srb = srb;
|
|
cmd.args.bindShaderResources.dynamicOffsetCount = 0;
|
|
if (hasDynamicOffsetInSrb) {
|
|
if (dynamicOffsetCount < QGles2CommandBuffer::Command::MAX_UBUF_BINDINGS) {
|
|
cmd.args.bindShaderResources.dynamicOffsetCount = dynamicOffsetCount;
|
|
uint *p = cmd.args.bindShaderResources.dynamicOffsetPairs;
|
|
for (int i = 0; i < dynamicOffsetCount; ++i) {
|
|
const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
|
|
*p++ = uint(dynOfs.first);
|
|
*p++ = dynOfs.second;
|
|
}
|
|
} else {
|
|
qWarning("Too many dynamic offsets (%d, max is %d)",
|
|
dynamicOffsetCount, QGles2CommandBuffer::Command::MAX_UBUF_BINDINGS);
|
|
}
|
|
}
|
|
cbD->commands.append(cmd);
|
|
}
|
|
}
|
|
|
|
void QRhiGles2::setVertexInput(QRhiCommandBuffer *cb,
|
|
int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
|
|
QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
|
|
QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
|
|
|
|
for (int i = 0; i < bindingCount; ++i) {
|
|
QRhiBuffer *buf = bindings[i].first;
|
|
quint32 ofs = bindings[i].second;
|
|
QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, buf);
|
|
Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer));
|
|
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::BindVertexBuffer;
|
|
cmd.args.bindVertexBuffer.ps = cbD->currentGraphicsPipeline;
|
|
cmd.args.bindVertexBuffer.buffer = bufD->buffer;
|
|
cmd.args.bindVertexBuffer.offset = ofs;
|
|
cmd.args.bindVertexBuffer.binding = startBinding + i;
|
|
cbD->commands.append(cmd);
|
|
|
|
if (cbD->passNeedsResourceTracking) {
|
|
trackedRegisterBuffer(&passResTracker, bufD, QRhiPassResourceTracker::BufVertexInput,
|
|
QRhiPassResourceTracker::BufVertexInputStage);
|
|
}
|
|
}
|
|
|
|
if (indexBuf) {
|
|
QGles2Buffer *ibufD = QRHI_RES(QGles2Buffer, indexBuf);
|
|
Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer));
|
|
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::BindIndexBuffer;
|
|
cmd.args.bindIndexBuffer.buffer = ibufD->buffer;
|
|
cmd.args.bindIndexBuffer.offset = indexOffset;
|
|
cmd.args.bindIndexBuffer.type = indexFormat == QRhiCommandBuffer::IndexUInt16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
|
|
cbD->commands.append(cmd);
|
|
|
|
if (cbD->passNeedsResourceTracking) {
|
|
trackedRegisterBuffer(&passResTracker, ibufD, QRhiPassResourceTracker::BufIndexRead,
|
|
QRhiPassResourceTracker::BufVertexInputStage);
|
|
}
|
|
}
|
|
}
|
|
|
|
void QRhiGles2::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
|
|
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::Viewport;
|
|
const std::array<float, 4> r = viewport.viewport();
|
|
// A negative width or height is an error. A negative x or y is not.
|
|
if (r[2] < 0.0f || r[3] < 0.0f)
|
|
return;
|
|
|
|
cmd.args.viewport.x = r[0];
|
|
cmd.args.viewport.y = r[1];
|
|
cmd.args.viewport.w = r[2];
|
|
cmd.args.viewport.h = r[3];
|
|
cmd.args.viewport.d0 = viewport.minDepth();
|
|
cmd.args.viewport.d1 = viewport.maxDepth();
|
|
cbD->commands.append(cmd);
|
|
}
|
|
|
|
void QRhiGles2::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
|
|
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::Scissor;
|
|
const std::array<int, 4> r = scissor.scissor();
|
|
// A negative width or height is an error. A negative x or y is not.
|
|
if (r[2] < 0 || r[3] < 0)
|
|
return;
|
|
|
|
cmd.args.scissor.x = r[0];
|
|
cmd.args.scissor.y = r[1];
|
|
cmd.args.scissor.w = r[2];
|
|
cmd.args.scissor.h = r[3];
|
|
cbD->commands.append(cmd);
|
|
}
|
|
|
|
void QRhiGles2::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
|
|
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::BlendConstants;
|
|
cmd.args.blendConstants.r = float(c.redF());
|
|
cmd.args.blendConstants.g = float(c.greenF());
|
|
cmd.args.blendConstants.b = float(c.blueF());
|
|
cmd.args.blendConstants.a = float(c.alphaF());
|
|
cbD->commands.append(cmd);
|
|
}
|
|
|
|
void QRhiGles2::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
|
|
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::StencilRef;
|
|
cmd.args.stencilRef.ref = refValue;
|
|
cmd.args.stencilRef.ps = cbD->currentGraphicsPipeline;
|
|
cbD->commands.append(cmd);
|
|
}
|
|
|
|
void QRhiGles2::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
|
|
quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
|
|
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::Draw;
|
|
cmd.args.draw.ps = cbD->currentGraphicsPipeline;
|
|
cmd.args.draw.vertexCount = vertexCount;
|
|
cmd.args.draw.firstVertex = firstVertex;
|
|
cmd.args.draw.instanceCount = instanceCount;
|
|
cmd.args.draw.baseInstance = firstInstance;
|
|
cbD->commands.append(cmd);
|
|
}
|
|
|
|
void QRhiGles2::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
|
|
quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
|
|
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::DrawIndexed;
|
|
cmd.args.drawIndexed.ps = cbD->currentGraphicsPipeline;
|
|
cmd.args.drawIndexed.indexCount = indexCount;
|
|
cmd.args.drawIndexed.firstIndex = firstIndex;
|
|
cmd.args.drawIndexed.instanceCount = instanceCount;
|
|
cmd.args.drawIndexed.baseInstance = firstInstance;
|
|
cmd.args.drawIndexed.baseVertex = vertexOffset;
|
|
cbD->commands.append(cmd);
|
|
}
|
|
|
|
void QRhiGles2::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
|
|
{
|
|
if (!debugMarkers)
|
|
return;
|
|
|
|
Q_UNUSED(cb);
|
|
Q_UNUSED(name);
|
|
}
|
|
|
|
void QRhiGles2::debugMarkEnd(QRhiCommandBuffer *cb)
|
|
{
|
|
if (!debugMarkers)
|
|
return;
|
|
|
|
Q_UNUSED(cb);
|
|
}
|
|
|
|
void QRhiGles2::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
|
|
{
|
|
if (!debugMarkers)
|
|
return;
|
|
|
|
Q_UNUSED(cb);
|
|
Q_UNUSED(msg);
|
|
}
|
|
|
|
const QRhiNativeHandles *QRhiGles2::nativeHandles(QRhiCommandBuffer *cb)
|
|
{
|
|
Q_UNUSED(cb);
|
|
return nullptr;
|
|
}
|
|
|
|
static void addBoundaryCommand(QGles2CommandBuffer *cbD, QGles2CommandBuffer::Command::Cmd type)
|
|
{
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = type;
|
|
cbD->commands.append(cmd);
|
|
}
|
|
|
|
void QRhiGles2::beginExternal(QRhiCommandBuffer *cb)
|
|
{
|
|
if (ofr.active) {
|
|
Q_ASSERT(!currentSwapChain);
|
|
if (!ensureContext())
|
|
return;
|
|
} else {
|
|
Q_ASSERT(currentSwapChain);
|
|
if (!ensureContext(currentSwapChain->surface))
|
|
return;
|
|
}
|
|
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
|
|
if (cbD->recordingPass == QGles2CommandBuffer::ComputePass
|
|
&& !cbD->computePassState.writtenResources.isEmpty())
|
|
{
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::Barrier;
|
|
cmd.args.barrier.barriers = GL_ALL_BARRIER_BITS;
|
|
cbD->commands.append(cmd);
|
|
}
|
|
|
|
executeCommandBuffer(cbD);
|
|
|
|
cbD->resetCommands();
|
|
|
|
if (vao)
|
|
f->glBindVertexArray(0);
|
|
}
|
|
|
|
void QRhiGles2::endExternal(QRhiCommandBuffer *cb)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
Q_ASSERT(cbD->commands.isEmpty() && cbD->currentPassResTrackerIndex == -1);
|
|
|
|
cbD->resetCachedState();
|
|
|
|
if (cbD->recordingPass != QGles2CommandBuffer::NoPass) {
|
|
// Commands that come after this point need a resource tracker and also
|
|
// a BarriersForPass command enqueued. (the ones we had from
|
|
// beginPass() are now gone since beginExternal() processed all that
|
|
// due to calling executeCommandBuffer()).
|
|
enqueueBarriersForPass(cbD);
|
|
}
|
|
|
|
addBoundaryCommand(cbD, QGles2CommandBuffer::Command::ResetFrame);
|
|
|
|
if (cbD->currentTarget)
|
|
enqueueBindFramebuffer(cbD->currentTarget, cbD);
|
|
}
|
|
|
|
QRhi::FrameOpResult QRhiGles2::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags)
|
|
{
|
|
QGles2SwapChain *swapChainD = QRHI_RES(QGles2SwapChain, swapChain);
|
|
if (!ensureContext(swapChainD->surface))
|
|
return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError;
|
|
|
|
currentSwapChain = swapChainD;
|
|
|
|
QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
|
|
QRHI_PROF_F(beginSwapChainFrame(swapChain));
|
|
|
|
executeDeferredReleases();
|
|
swapChainD->cb.resetState();
|
|
|
|
addBoundaryCommand(&swapChainD->cb, QGles2CommandBuffer::Command::BeginFrame);
|
|
|
|
return QRhi::FrameOpSuccess;
|
|
}
|
|
|
|
QRhi::FrameOpResult QRhiGles2::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
|
|
{
|
|
QGles2SwapChain *swapChainD = QRHI_RES(QGles2SwapChain, swapChain);
|
|
Q_ASSERT(currentSwapChain == swapChainD);
|
|
|
|
addBoundaryCommand(&swapChainD->cb, QGles2CommandBuffer::Command::EndFrame);
|
|
|
|
if (!ensureContext(swapChainD->surface))
|
|
return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError;
|
|
|
|
executeCommandBuffer(&swapChainD->cb);
|
|
|
|
QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
|
|
// this must be done before the swap
|
|
QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1));
|
|
|
|
if (swapChainD->surface && !flags.testFlag(QRhi::SkipPresent)) {
|
|
ctx->swapBuffers(swapChainD->surface);
|
|
needsMakeCurrent = true;
|
|
} else {
|
|
f->glFlush();
|
|
}
|
|
|
|
swapChainD->frameCount += 1;
|
|
currentSwapChain = nullptr;
|
|
return QRhi::FrameOpSuccess;
|
|
}
|
|
|
|
QRhi::FrameOpResult QRhiGles2::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags)
|
|
{
|
|
if (!ensureContext())
|
|
return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError;
|
|
|
|
ofr.active = true;
|
|
|
|
executeDeferredReleases();
|
|
ofr.cbWrapper.resetState();
|
|
|
|
addBoundaryCommand(&ofr.cbWrapper, QGles2CommandBuffer::Command::BeginFrame);
|
|
*cb = &ofr.cbWrapper;
|
|
|
|
return QRhi::FrameOpSuccess;
|
|
}
|
|
|
|
QRhi::FrameOpResult QRhiGles2::endOffscreenFrame(QRhi::EndFrameFlags flags)
|
|
{
|
|
Q_UNUSED(flags);
|
|
Q_ASSERT(ofr.active);
|
|
ofr.active = false;
|
|
|
|
addBoundaryCommand(&ofr.cbWrapper, QGles2CommandBuffer::Command::EndFrame);
|
|
|
|
if (!ensureContext())
|
|
return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError;
|
|
|
|
executeCommandBuffer(&ofr.cbWrapper);
|
|
|
|
return QRhi::FrameOpSuccess;
|
|
}
|
|
|
|
QRhi::FrameOpResult QRhiGles2::finish()
|
|
{
|
|
if (inFrame) {
|
|
if (ofr.active) {
|
|
Q_ASSERT(!currentSwapChain);
|
|
Q_ASSERT(ofr.cbWrapper.recordingPass == QGles2CommandBuffer::NoPass);
|
|
if (!ensureContext())
|
|
return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError;
|
|
executeCommandBuffer(&ofr.cbWrapper);
|
|
ofr.cbWrapper.resetCommands();
|
|
} else {
|
|
Q_ASSERT(currentSwapChain);
|
|
Q_ASSERT(currentSwapChain->cb.recordingPass == QGles2CommandBuffer::NoPass);
|
|
if (!ensureContext(currentSwapChain->surface))
|
|
return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError;
|
|
executeCommandBuffer(¤tSwapChain->cb);
|
|
currentSwapChain->cb.resetCommands();
|
|
}
|
|
}
|
|
return QRhi::FrameOpSuccess;
|
|
}
|
|
|
|
static bool bufferAccessIsWrite(QGles2Buffer::Access access)
|
|
{
|
|
return access == QGles2Buffer::AccessStorageWrite
|
|
|| access == QGles2Buffer::AccessStorageReadWrite
|
|
|| access == QGles2Buffer::AccessUpdate;
|
|
}
|
|
|
|
static bool textureAccessIsWrite(QGles2Texture::Access access)
|
|
{
|
|
return access == QGles2Texture::AccessStorageWrite
|
|
|| access == QGles2Texture::AccessStorageReadWrite
|
|
|| access == QGles2Texture::AccessUpdate
|
|
|| access == QGles2Texture::AccessFramebuffer;
|
|
}
|
|
|
|
void QRhiGles2::trackedBufferBarrier(QGles2CommandBuffer *cbD, QGles2Buffer *bufD, QGles2Buffer::Access access)
|
|
{
|
|
Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass); // this is for resource updates only
|
|
const QGles2Buffer::Access prevAccess = bufD->usageState.access;
|
|
if (access == prevAccess)
|
|
return;
|
|
|
|
if (bufferAccessIsWrite(prevAccess)) {
|
|
// Generating the minimal barrier set is way too complicated to do
|
|
// correctly (prevAccess is overwritten so we won't have proper
|
|
// tracking across multiple passes) so setting all barrier bits will do
|
|
// for now.
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::Barrier;
|
|
cmd.args.barrier.barriers = GL_ALL_BARRIER_BITS;
|
|
cbD->commands.append(cmd);
|
|
}
|
|
|
|
bufD->usageState.access = access;
|
|
}
|
|
|
|
void QRhiGles2::trackedImageBarrier(QGles2CommandBuffer *cbD, QGles2Texture *texD, QGles2Texture::Access access)
|
|
{
|
|
Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass); // this is for resource updates only
|
|
const QGles2Texture::Access prevAccess = texD->usageState.access;
|
|
if (access == prevAccess)
|
|
return;
|
|
|
|
if (textureAccessIsWrite(prevAccess)) {
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::Barrier;
|
|
cmd.args.barrier.barriers = GL_ALL_BARRIER_BITS;
|
|
cbD->commands.append(cmd);
|
|
}
|
|
|
|
texD->usageState.access = access;
|
|
}
|
|
|
|
void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cbD,
|
|
int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc)
|
|
{
|
|
trackedImageBarrier(cbD, texD, QGles2Texture::AccessUpdate);
|
|
const bool isCompressed = isCompressedFormat(texD->m_format);
|
|
const bool isCubeMap = texD->m_flags.testFlag(QRhiTexture::CubeMap);
|
|
const GLenum faceTargetBase = isCubeMap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target;
|
|
const QPoint dp = subresDesc.destinationTopLeft();
|
|
const QByteArray rawData = subresDesc.data();
|
|
if (!subresDesc.image().isNull()) {
|
|
QImage img = subresDesc.image();
|
|
QSize size = img.size();
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::SubImage;
|
|
if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
|
|
const QPoint sp = subresDesc.sourceTopLeft();
|
|
if (!subresDesc.sourceSize().isEmpty())
|
|
size = subresDesc.sourceSize();
|
|
img = img.copy(sp.x(), sp.y(), size.width(), size.height());
|
|
}
|
|
cmd.args.subImage.target = texD->target;
|
|
cmd.args.subImage.texture = texD->texture;
|
|
cmd.args.subImage.faceTarget = faceTargetBase + uint(layer);
|
|
cmd.args.subImage.level = level;
|
|
cmd.args.subImage.dx = dp.x();
|
|
cmd.args.subImage.dy = dp.y();
|
|
cmd.args.subImage.w = size.width();
|
|
cmd.args.subImage.h = size.height();
|
|
cmd.args.subImage.glformat = texD->glformat;
|
|
cmd.args.subImage.gltype = texD->gltype;
|
|
cmd.args.subImage.rowStartAlign = 4;
|
|
cmd.args.subImage.data = cbD->retainImage(img);
|
|
cbD->commands.append(cmd);
|
|
} else if (!rawData.isEmpty() && isCompressed) {
|
|
if (!texD->compressedAtlasBuilt && (texD->flags() & QRhiTexture::UsedAsCompressedAtlas)) {
|
|
// Create on first upload since glCompressedTexImage2D cannot take nullptr data
|
|
quint32 byteSize = 0;
|
|
compressedFormatInfo(texD->m_format, texD->m_pixelSize, nullptr, &byteSize, nullptr);
|
|
QByteArray zeroBuf(byteSize, 0);
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::CompressedImage;
|
|
cmd.args.compressedImage.target = texD->target;
|
|
cmd.args.compressedImage.texture = texD->texture;
|
|
cmd.args.compressedImage.faceTarget = faceTargetBase + uint(layer);
|
|
cmd.args.compressedImage.level = level;
|
|
cmd.args.compressedImage.glintformat = texD->glintformat;
|
|
cmd.args.compressedImage.w = texD->m_pixelSize.width();
|
|
cmd.args.compressedImage.h = texD->m_pixelSize.height();
|
|
cmd.args.compressedImage.size = byteSize;
|
|
cmd.args.compressedImage.data = cbD->retainData(zeroBuf);
|
|
cbD->commands.append(cmd);
|
|
texD->compressedAtlasBuilt = true;
|
|
}
|
|
|
|
const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
|
|
: subresDesc.sourceSize();
|
|
if (texD->specified || texD->compressedAtlasBuilt) {
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::CompressedSubImage;
|
|
cmd.args.compressedSubImage.target = texD->target;
|
|
cmd.args.compressedSubImage.texture = texD->texture;
|
|
cmd.args.compressedSubImage.faceTarget = faceTargetBase + uint(layer);
|
|
cmd.args.compressedSubImage.level = level;
|
|
cmd.args.compressedSubImage.dx = dp.x();
|
|
cmd.args.compressedSubImage.dy = dp.y();
|
|
cmd.args.compressedSubImage.w = size.width();
|
|
cmd.args.compressedSubImage.h = size.height();
|
|
cmd.args.compressedSubImage.glintformat = texD->glintformat;
|
|
cmd.args.compressedSubImage.size = rawData.size();
|
|
cmd.args.compressedSubImage.data = cbD->retainData(rawData);
|
|
cbD->commands.append(cmd);
|
|
} else {
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::CompressedImage;
|
|
cmd.args.compressedImage.target = texD->target;
|
|
cmd.args.compressedImage.texture = texD->texture;
|
|
cmd.args.compressedImage.faceTarget = faceTargetBase + uint(layer);
|
|
cmd.args.compressedImage.level = level;
|
|
cmd.args.compressedImage.glintformat = texD->glintformat;
|
|
cmd.args.compressedImage.w = size.width();
|
|
cmd.args.compressedImage.h = size.height();
|
|
cmd.args.compressedImage.size = rawData.size();
|
|
cmd.args.compressedImage.data = cbD->retainData(rawData);
|
|
cbD->commands.append(cmd);
|
|
}
|
|
} else if (!rawData.isEmpty()) {
|
|
const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
|
|
: subresDesc.sourceSize();
|
|
quint32 bpl = 0;
|
|
textureFormatInfo(texD->m_format, size, &bpl, nullptr);
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::SubImage;
|
|
cmd.args.subImage.target = texD->target;
|
|
cmd.args.subImage.texture = texD->texture;
|
|
cmd.args.subImage.faceTarget = faceTargetBase + uint(layer);
|
|
cmd.args.subImage.level = level;
|
|
cmd.args.subImage.dx = dp.x();
|
|
cmd.args.subImage.dy = dp.y();
|
|
cmd.args.subImage.w = size.width();
|
|
cmd.args.subImage.h = size.height();
|
|
cmd.args.subImage.glformat = texD->glformat;
|
|
cmd.args.subImage.gltype = texD->gltype;
|
|
// Default unpack alignment (row start aligment
|
|
// requirement) is 4. QImage guarantees 4 byte aligned
|
|
// row starts, but our raw data here does not.
|
|
cmd.args.subImage.rowStartAlign = (bpl & 3) ? 1 : 4;
|
|
cmd.args.subImage.data = cbD->retainData(rawData);
|
|
cbD->commands.append(cmd);
|
|
} else {
|
|
qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
|
|
}
|
|
}
|
|
|
|
void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
|
|
|
|
for (int opIdx = 0; opIdx < ud->activeBufferOpCount; ++opIdx) {
|
|
const QRhiResourceUpdateBatchPrivate::BufferOp &u(ud->bufferOps[opIdx]);
|
|
if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) {
|
|
QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf);
|
|
Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
|
|
if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
|
|
memcpy(bufD->ubuf + u.offset, u.data.constData(), size_t(u.data.size()));
|
|
} else {
|
|
trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate);
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::BufferSubData;
|
|
cmd.args.bufferSubData.target = bufD->targetForDataOps;
|
|
cmd.args.bufferSubData.buffer = bufD->buffer;
|
|
cmd.args.bufferSubData.offset = u.offset;
|
|
cmd.args.bufferSubData.size = u.data.size();
|
|
cmd.args.bufferSubData.data = cbD->retainBufferData(u.data);
|
|
cbD->commands.append(cmd);
|
|
}
|
|
} else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) {
|
|
QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf);
|
|
Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
|
|
Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
|
|
if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
|
|
memcpy(bufD->ubuf + u.offset, u.data.constData(), size_t(u.data.size()));
|
|
} else {
|
|
trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate);
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::BufferSubData;
|
|
cmd.args.bufferSubData.target = bufD->targetForDataOps;
|
|
cmd.args.bufferSubData.buffer = bufD->buffer;
|
|
cmd.args.bufferSubData.offset = u.offset;
|
|
cmd.args.bufferSubData.size = u.data.size();
|
|
cmd.args.bufferSubData.data = cbD->retainBufferData(u.data);
|
|
cbD->commands.append(cmd);
|
|
}
|
|
} else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) {
|
|
QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf);
|
|
if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
|
|
u.result->data.resize(u.readSize);
|
|
memcpy(u.result->data.data(), bufD->ubuf + u.offset, size_t(u.readSize));
|
|
if (u.result->completed)
|
|
u.result->completed();
|
|
} else {
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::GetBufferSubData;
|
|
cmd.args.getBufferSubData.result = u.result;
|
|
cmd.args.getBufferSubData.target = bufD->targetForDataOps;
|
|
cmd.args.getBufferSubData.buffer = bufD->buffer;
|
|
cmd.args.getBufferSubData.offset = u.offset;
|
|
cmd.args.getBufferSubData.size = u.readSize;
|
|
cbD->commands.append(cmd);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int opIdx = 0; opIdx < ud->activeTextureOpCount; ++opIdx) {
|
|
const QRhiResourceUpdateBatchPrivate::TextureOp &u(ud->textureOps[opIdx]);
|
|
if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
|
|
QGles2Texture *texD = QRHI_RES(QGles2Texture, u.dst);
|
|
for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
|
|
for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
|
|
for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level]))
|
|
enqueueSubresUpload(texD, cbD, layer, level, subresDesc);
|
|
}
|
|
}
|
|
texD->specified = true;
|
|
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
|
|
Q_ASSERT(u.src && u.dst);
|
|
QGles2Texture *srcD = QRHI_RES(QGles2Texture, u.src);
|
|
QGles2Texture *dstD = QRHI_RES(QGles2Texture, u.dst);
|
|
|
|
trackedImageBarrier(cbD, srcD, QGles2Texture::AccessRead);
|
|
trackedImageBarrier(cbD, dstD, QGles2Texture::AccessUpdate);
|
|
|
|
const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize);
|
|
const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize();
|
|
// do not translate coordinates, even if sp is bottom-left from gl's pov
|
|
const QPoint sp = u.desc.sourceTopLeft();
|
|
const QPoint dp = u.desc.destinationTopLeft();
|
|
|
|
const GLenum srcFaceTargetBase = srcD->m_flags.testFlag(QRhiTexture::CubeMap)
|
|
? GL_TEXTURE_CUBE_MAP_POSITIVE_X : srcD->target;
|
|
const GLenum dstFaceTargetBase = dstD->m_flags.testFlag(QRhiTexture::CubeMap)
|
|
? GL_TEXTURE_CUBE_MAP_POSITIVE_X : dstD->target;
|
|
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::CopyTex;
|
|
|
|
cmd.args.copyTex.srcFaceTarget = srcFaceTargetBase + uint(u.desc.sourceLayer());
|
|
cmd.args.copyTex.srcTexture = srcD->texture;
|
|
cmd.args.copyTex.srcLevel = u.desc.sourceLevel();
|
|
cmd.args.copyTex.srcX = sp.x();
|
|
cmd.args.copyTex.srcY = sp.y();
|
|
|
|
cmd.args.copyTex.dstTarget = dstD->target;
|
|
cmd.args.copyTex.dstTexture = dstD->texture;
|
|
cmd.args.copyTex.dstFaceTarget = dstFaceTargetBase + uint(u.desc.destinationLayer());
|
|
cmd.args.copyTex.dstLevel = u.desc.destinationLevel();
|
|
cmd.args.copyTex.dstX = dp.x();
|
|
cmd.args.copyTex.dstY = dp.y();
|
|
|
|
cmd.args.copyTex.w = copySize.width();
|
|
cmd.args.copyTex.h = copySize.height();
|
|
|
|
cbD->commands.append(cmd);
|
|
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::ReadPixels;
|
|
cmd.args.readPixels.result = u.result;
|
|
QGles2Texture *texD = QRHI_RES(QGles2Texture, u.rb.texture());
|
|
if (texD)
|
|
trackedImageBarrier(cbD, texD, QGles2Texture::AccessRead);
|
|
cmd.args.readPixels.texture = texD ? texD->texture : 0;
|
|
if (texD) {
|
|
const QSize readImageSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
|
|
cmd.args.readPixels.w = readImageSize.width();
|
|
cmd.args.readPixels.h = readImageSize.height();
|
|
cmd.args.readPixels.format = texD->m_format;
|
|
const GLenum faceTargetBase = texD->m_flags.testFlag(QRhiTexture::CubeMap)
|
|
? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target;
|
|
cmd.args.readPixels.readTarget = faceTargetBase + uint(u.rb.layer());
|
|
cmd.args.readPixels.level = u.rb.level();
|
|
}
|
|
cbD->commands.append(cmd);
|
|
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) {
|
|
QGles2Texture *texD = QRHI_RES(QGles2Texture, u.dst);
|
|
trackedImageBarrier(cbD, texD, QGles2Texture::AccessFramebuffer);
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::GenMip;
|
|
cmd.args.genMip.target = texD->target;
|
|
cmd.args.genMip.texture = texD->texture;
|
|
cbD->commands.append(cmd);
|
|
}
|
|
}
|
|
|
|
ud->free();
|
|
}
|
|
|
|
static inline GLenum toGlTopology(QRhiGraphicsPipeline::Topology t)
|
|
{
|
|
switch (t) {
|
|
case QRhiGraphicsPipeline::Triangles:
|
|
return GL_TRIANGLES;
|
|
case QRhiGraphicsPipeline::TriangleStrip:
|
|
return GL_TRIANGLE_STRIP;
|
|
case QRhiGraphicsPipeline::TriangleFan:
|
|
return GL_TRIANGLE_FAN;
|
|
case QRhiGraphicsPipeline::Lines:
|
|
return GL_LINES;
|
|
case QRhiGraphicsPipeline::LineStrip:
|
|
return GL_LINE_STRIP;
|
|
case QRhiGraphicsPipeline::Points:
|
|
return GL_POINTS;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
return GL_TRIANGLES;
|
|
}
|
|
}
|
|
|
|
static inline GLenum toGlCullMode(QRhiGraphicsPipeline::CullMode c)
|
|
{
|
|
switch (c) {
|
|
case QRhiGraphicsPipeline::Front:
|
|
return GL_FRONT;
|
|
case QRhiGraphicsPipeline::Back:
|
|
return GL_BACK;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
return GL_BACK;
|
|
}
|
|
}
|
|
|
|
static inline GLenum toGlFrontFace(QRhiGraphicsPipeline::FrontFace f)
|
|
{
|
|
switch (f) {
|
|
case QRhiGraphicsPipeline::CCW:
|
|
return GL_CCW;
|
|
case QRhiGraphicsPipeline::CW:
|
|
return GL_CW;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
return GL_CCW;
|
|
}
|
|
}
|
|
|
|
static inline GLenum toGlBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
|
|
{
|
|
switch (f) {
|
|
case QRhiGraphicsPipeline::Zero:
|
|
return GL_ZERO;
|
|
case QRhiGraphicsPipeline::One:
|
|
return GL_ONE;
|
|
case QRhiGraphicsPipeline::SrcColor:
|
|
return GL_SRC_COLOR;
|
|
case QRhiGraphicsPipeline::OneMinusSrcColor:
|
|
return GL_ONE_MINUS_SRC_COLOR;
|
|
case QRhiGraphicsPipeline::DstColor:
|
|
return GL_DST_COLOR;
|
|
case QRhiGraphicsPipeline::OneMinusDstColor:
|
|
return GL_ONE_MINUS_DST_COLOR;
|
|
case QRhiGraphicsPipeline::SrcAlpha:
|
|
return GL_SRC_ALPHA;
|
|
case QRhiGraphicsPipeline::OneMinusSrcAlpha:
|
|
return GL_ONE_MINUS_SRC_ALPHA;
|
|
case QRhiGraphicsPipeline::DstAlpha:
|
|
return GL_DST_ALPHA;
|
|
case QRhiGraphicsPipeline::OneMinusDstAlpha:
|
|
return GL_ONE_MINUS_DST_ALPHA;
|
|
case QRhiGraphicsPipeline::ConstantColor:
|
|
return GL_CONSTANT_COLOR;
|
|
case QRhiGraphicsPipeline::OneMinusConstantColor:
|
|
return GL_ONE_MINUS_CONSTANT_COLOR;
|
|
case QRhiGraphicsPipeline::ConstantAlpha:
|
|
return GL_CONSTANT_ALPHA;
|
|
case QRhiGraphicsPipeline::OneMinusConstantAlpha:
|
|
return GL_ONE_MINUS_CONSTANT_ALPHA;
|
|
case QRhiGraphicsPipeline::SrcAlphaSaturate:
|
|
return GL_SRC_ALPHA_SATURATE;
|
|
case QRhiGraphicsPipeline::Src1Color:
|
|
case QRhiGraphicsPipeline::OneMinusSrc1Color:
|
|
case QRhiGraphicsPipeline::Src1Alpha:
|
|
case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
|
|
qWarning("Unsupported blend factor %d", f);
|
|
return GL_ZERO;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
return GL_ZERO;
|
|
}
|
|
}
|
|
|
|
static inline GLenum toGlBlendOp(QRhiGraphicsPipeline::BlendOp op)
|
|
{
|
|
switch (op) {
|
|
case QRhiGraphicsPipeline::Add:
|
|
return GL_FUNC_ADD;
|
|
case QRhiGraphicsPipeline::Subtract:
|
|
return GL_FUNC_SUBTRACT;
|
|
case QRhiGraphicsPipeline::ReverseSubtract:
|
|
return GL_FUNC_REVERSE_SUBTRACT;
|
|
case QRhiGraphicsPipeline::Min:
|
|
return GL_MIN;
|
|
case QRhiGraphicsPipeline::Max:
|
|
return GL_MAX;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
return GL_FUNC_ADD;
|
|
}
|
|
}
|
|
|
|
static inline GLenum toGlCompareOp(QRhiGraphicsPipeline::CompareOp op)
|
|
{
|
|
switch (op) {
|
|
case QRhiGraphicsPipeline::Never:
|
|
return GL_NEVER;
|
|
case QRhiGraphicsPipeline::Less:
|
|
return GL_LESS;
|
|
case QRhiGraphicsPipeline::Equal:
|
|
return GL_EQUAL;
|
|
case QRhiGraphicsPipeline::LessOrEqual:
|
|
return GL_LEQUAL;
|
|
case QRhiGraphicsPipeline::Greater:
|
|
return GL_GREATER;
|
|
case QRhiGraphicsPipeline::NotEqual:
|
|
return GL_NOTEQUAL;
|
|
case QRhiGraphicsPipeline::GreaterOrEqual:
|
|
return GL_GEQUAL;
|
|
case QRhiGraphicsPipeline::Always:
|
|
return GL_ALWAYS;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
return GL_ALWAYS;
|
|
}
|
|
}
|
|
|
|
static inline GLenum toGlStencilOp(QRhiGraphicsPipeline::StencilOp op)
|
|
{
|
|
switch (op) {
|
|
case QRhiGraphicsPipeline::StencilZero:
|
|
return GL_ZERO;
|
|
case QRhiGraphicsPipeline::Keep:
|
|
return GL_KEEP;
|
|
case QRhiGraphicsPipeline::Replace:
|
|
return GL_REPLACE;
|
|
case QRhiGraphicsPipeline::IncrementAndClamp:
|
|
return GL_INCR;
|
|
case QRhiGraphicsPipeline::DecrementAndClamp:
|
|
return GL_DECR;
|
|
case QRhiGraphicsPipeline::Invert:
|
|
return GL_INVERT;
|
|
case QRhiGraphicsPipeline::IncrementAndWrap:
|
|
return GL_INCR_WRAP;
|
|
case QRhiGraphicsPipeline::DecrementAndWrap:
|
|
return GL_DECR_WRAP;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
return GL_KEEP;
|
|
}
|
|
}
|
|
|
|
static inline GLenum toGlMinFilter(QRhiSampler::Filter f, QRhiSampler::Filter m)
|
|
{
|
|
switch (f) {
|
|
case QRhiSampler::Nearest:
|
|
if (m == QRhiSampler::None)
|
|
return GL_NEAREST;
|
|
else
|
|
return m == QRhiSampler::Nearest ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_LINEAR;
|
|
case QRhiSampler::Linear:
|
|
if (m == QRhiSampler::None)
|
|
return GL_LINEAR;
|
|
else
|
|
return m == QRhiSampler::Nearest ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
return GL_LINEAR;
|
|
}
|
|
}
|
|
|
|
static inline GLenum toGlMagFilter(QRhiSampler::Filter f)
|
|
{
|
|
switch (f) {
|
|
case QRhiSampler::Nearest:
|
|
return GL_NEAREST;
|
|
case QRhiSampler::Linear:
|
|
return GL_LINEAR;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
return GL_LINEAR;
|
|
}
|
|
}
|
|
|
|
static inline GLenum toGlWrapMode(QRhiSampler::AddressMode m)
|
|
{
|
|
switch (m) {
|
|
case QRhiSampler::Repeat:
|
|
return GL_REPEAT;
|
|
case QRhiSampler::ClampToEdge:
|
|
return GL_CLAMP_TO_EDGE;
|
|
case QRhiSampler::Mirror:
|
|
return GL_MIRRORED_REPEAT;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
return GL_CLAMP_TO_EDGE;
|
|
}
|
|
}
|
|
|
|
static inline GLenum toGlTextureCompareFunc(QRhiSampler::CompareOp op)
|
|
{
|
|
switch (op) {
|
|
case QRhiSampler::Never:
|
|
return GL_NEVER;
|
|
case QRhiSampler::Less:
|
|
return GL_LESS;
|
|
case QRhiSampler::Equal:
|
|
return GL_EQUAL;
|
|
case QRhiSampler::LessOrEqual:
|
|
return GL_LEQUAL;
|
|
case QRhiSampler::Greater:
|
|
return GL_GREATER;
|
|
case QRhiSampler::NotEqual:
|
|
return GL_NOTEQUAL;
|
|
case QRhiSampler::GreaterOrEqual:
|
|
return GL_GEQUAL;
|
|
case QRhiSampler::Always:
|
|
return GL_ALWAYS;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
return GL_NEVER;
|
|
}
|
|
}
|
|
|
|
static inline QGles2Buffer::Access toGlAccess(QRhiPassResourceTracker::BufferAccess access)
|
|
{
|
|
switch (access) {
|
|
case QRhiPassResourceTracker::BufVertexInput:
|
|
return QGles2Buffer::AccessVertex;
|
|
case QRhiPassResourceTracker::BufIndexRead:
|
|
return QGles2Buffer::AccessIndex;
|
|
case QRhiPassResourceTracker::BufUniformRead:
|
|
return QGles2Buffer::AccessUniform;
|
|
case QRhiPassResourceTracker::BufStorageLoad:
|
|
return QGles2Buffer::AccessStorageRead;
|
|
case QRhiPassResourceTracker::BufStorageStore:
|
|
return QGles2Buffer::AccessStorageWrite;
|
|
case QRhiPassResourceTracker::BufStorageLoadStore:
|
|
return QGles2Buffer::AccessStorageReadWrite;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
break;
|
|
}
|
|
return QGles2Buffer::AccessNone;
|
|
}
|
|
|
|
static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QGles2Buffer::UsageState &bufUsage)
|
|
{
|
|
QRhiPassResourceTracker::UsageState u;
|
|
u.layout = 0; // N/A
|
|
u.access = bufUsage.access;
|
|
u.stage = 0; // N/A
|
|
return u;
|
|
}
|
|
|
|
static inline QGles2Texture::Access toGlAccess(QRhiPassResourceTracker::TextureAccess access)
|
|
{
|
|
switch (access) {
|
|
case QRhiPassResourceTracker::TexSample:
|
|
return QGles2Texture::AccessSample;
|
|
case QRhiPassResourceTracker::TexColorOutput:
|
|
return QGles2Texture::AccessFramebuffer;
|
|
case QRhiPassResourceTracker::TexDepthOutput:
|
|
return QGles2Texture::AccessFramebuffer;
|
|
case QRhiPassResourceTracker::TexStorageLoad:
|
|
return QGles2Texture::AccessStorageRead;
|
|
case QRhiPassResourceTracker::TexStorageStore:
|
|
return QGles2Texture::AccessStorageWrite;
|
|
case QRhiPassResourceTracker::TexStorageLoadStore:
|
|
return QGles2Texture::AccessStorageReadWrite;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
break;
|
|
}
|
|
return QGles2Texture::AccessNone;
|
|
}
|
|
|
|
static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QGles2Texture::UsageState &texUsage)
|
|
{
|
|
QRhiPassResourceTracker::UsageState u;
|
|
u.layout = 0; // N/A
|
|
u.access = texUsage.access;
|
|
u.stage = 0; // N/A
|
|
return u;
|
|
}
|
|
|
|
void QRhiGles2::trackedRegisterBuffer(QRhiPassResourceTracker *passResTracker,
|
|
QGles2Buffer *bufD,
|
|
QRhiPassResourceTracker::BufferAccess access,
|
|
QRhiPassResourceTracker::BufferStage stage)
|
|
{
|
|
QGles2Buffer::UsageState &u(bufD->usageState);
|
|
passResTracker->registerBuffer(bufD, 0, &access, &stage, toPassTrackerUsageState(u));
|
|
u.access = toGlAccess(access);
|
|
}
|
|
|
|
void QRhiGles2::trackedRegisterTexture(QRhiPassResourceTracker *passResTracker,
|
|
QGles2Texture *texD,
|
|
QRhiPassResourceTracker::TextureAccess access,
|
|
QRhiPassResourceTracker::TextureStage stage)
|
|
{
|
|
QGles2Texture::UsageState &u(texD->usageState);
|
|
passResTracker->registerTexture(texD, &access, &stage, toPassTrackerUsageState(u));
|
|
u.access = toGlAccess(access);
|
|
}
|
|
|
|
void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
GLenum indexType = GL_UNSIGNED_SHORT;
|
|
quint32 indexStride = sizeof(quint16);
|
|
quint32 indexOffset = 0;
|
|
GLuint currentArrayBuffer = 0;
|
|
GLuint currentElementArrayBuffer = 0;
|
|
struct {
|
|
QRhiGraphicsPipeline *ps = nullptr;
|
|
GLuint buffer;
|
|
quint32 offset;
|
|
int binding;
|
|
} lastBindVertexBuffer;
|
|
static const int TRACKED_ATTRIB_COUNT = 16;
|
|
bool enabledAttribArrays[TRACKED_ATTRIB_COUNT];
|
|
memset(enabledAttribArrays, 0, sizeof(enabledAttribArrays));
|
|
|
|
for (const QGles2CommandBuffer::Command &cmd : qAsConst(cbD->commands)) {
|
|
switch (cmd.cmd) {
|
|
case QGles2CommandBuffer::Command::BeginFrame:
|
|
if (caps.coreProfile) {
|
|
if (!vao)
|
|
f->glGenVertexArrays(1, &vao);
|
|
f->glBindVertexArray(vao);
|
|
}
|
|
break;
|
|
case QGles2CommandBuffer::Command::EndFrame:
|
|
if (vao)
|
|
f->glBindVertexArray(0);
|
|
break;
|
|
case QGles2CommandBuffer::Command::ResetFrame:
|
|
if (vao)
|
|
f->glBindVertexArray(vao);
|
|
break;
|
|
case QGles2CommandBuffer::Command::Viewport:
|
|
f->glViewport(GLint(cmd.args.viewport.x), GLint(cmd.args.viewport.y), GLsizei(cmd.args.viewport.w), GLsizei(cmd.args.viewport.h));
|
|
f->glDepthRangef(cmd.args.viewport.d0, cmd.args.viewport.d1);
|
|
break;
|
|
case QGles2CommandBuffer::Command::Scissor:
|
|
f->glScissor(cmd.args.scissor.x, cmd.args.scissor.y, cmd.args.scissor.w, cmd.args.scissor.h);
|
|
break;
|
|
case QGles2CommandBuffer::Command::BlendConstants:
|
|
f->glBlendColor(cmd.args.blendConstants.r, cmd.args.blendConstants.g, cmd.args.blendConstants.b, cmd.args.blendConstants.a);
|
|
break;
|
|
case QGles2CommandBuffer::Command::StencilRef:
|
|
{
|
|
QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.stencilRef.ps);
|
|
if (psD) {
|
|
const GLint ref = GLint(cmd.args.stencilRef.ref);
|
|
f->glStencilFuncSeparate(GL_FRONT, toGlCompareOp(psD->m_stencilFront.compareOp), ref, psD->m_stencilReadMask);
|
|
f->glStencilFuncSeparate(GL_BACK, toGlCompareOp(psD->m_stencilBack.compareOp), ref, psD->m_stencilReadMask);
|
|
cbD->graphicsPassState.dynamic.stencilRef = ref;
|
|
} else {
|
|
qWarning("No graphics pipeline active for setStencilRef; ignored");
|
|
}
|
|
}
|
|
break;
|
|
case QGles2CommandBuffer::Command::BindVertexBuffer:
|
|
{
|
|
QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.bindVertexBuffer.ps);
|
|
if (psD) {
|
|
if (lastBindVertexBuffer.ps == psD
|
|
&& lastBindVertexBuffer.buffer == cmd.args.bindVertexBuffer.buffer
|
|
&& lastBindVertexBuffer.offset == cmd.args.bindVertexBuffer.offset
|
|
&& lastBindVertexBuffer.binding == cmd.args.bindVertexBuffer.binding)
|
|
{
|
|
// The pipeline and so the vertex input layout is
|
|
// immutable, no point in issuing the exact same set of
|
|
// glVertexAttribPointer again and again for the same buffer.
|
|
break;
|
|
}
|
|
lastBindVertexBuffer.ps = psD;
|
|
lastBindVertexBuffer.buffer = cmd.args.bindVertexBuffer.buffer;
|
|
lastBindVertexBuffer.offset = cmd.args.bindVertexBuffer.offset;
|
|
lastBindVertexBuffer.binding = cmd.args.bindVertexBuffer.binding;
|
|
|
|
if (cmd.args.bindVertexBuffer.buffer != currentArrayBuffer) {
|
|
currentArrayBuffer = cmd.args.bindVertexBuffer.buffer;
|
|
// we do not support more than one vertex buffer
|
|
f->glBindBuffer(GL_ARRAY_BUFFER, currentArrayBuffer);
|
|
}
|
|
for (auto it = psD->m_vertexInputLayout.cbeginAttributes(), itEnd = psD->m_vertexInputLayout.cendAttributes();
|
|
it != itEnd; ++it)
|
|
{
|
|
const int bindingIdx = it->binding();
|
|
if (bindingIdx != cmd.args.bindVertexBuffer.binding)
|
|
continue;
|
|
|
|
const QRhiVertexInputBinding *inputBinding = psD->m_vertexInputLayout.bindingAt(bindingIdx);
|
|
const int stride = int(inputBinding->stride());
|
|
int size = 1;
|
|
GLenum type = GL_FLOAT;
|
|
bool normalize = false;
|
|
switch (it->format()) {
|
|
case QRhiVertexInputAttribute::Float4:
|
|
type = GL_FLOAT;
|
|
size = 4;
|
|
break;
|
|
case QRhiVertexInputAttribute::Float3:
|
|
type = GL_FLOAT;
|
|
size = 3;
|
|
break;
|
|
case QRhiVertexInputAttribute::Float2:
|
|
type = GL_FLOAT;
|
|
size = 2;
|
|
break;
|
|
case QRhiVertexInputAttribute::Float:
|
|
type = GL_FLOAT;
|
|
size = 1;
|
|
break;
|
|
case QRhiVertexInputAttribute::UNormByte4:
|
|
type = GL_UNSIGNED_BYTE;
|
|
normalize = true;
|
|
size = 4;
|
|
break;
|
|
case QRhiVertexInputAttribute::UNormByte2:
|
|
type = GL_UNSIGNED_BYTE;
|
|
normalize = true;
|
|
size = 2;
|
|
break;
|
|
case QRhiVertexInputAttribute::UNormByte:
|
|
type = GL_UNSIGNED_BYTE;
|
|
normalize = true;
|
|
size = 1;
|
|
break;
|
|
case QRhiVertexInputAttribute::UInt4:
|
|
type = GL_UNSIGNED_INT;
|
|
size = 4;
|
|
break;
|
|
case QRhiVertexInputAttribute::UInt3:
|
|
type = GL_UNSIGNED_INT;
|
|
size = 3;
|
|
break;
|
|
case QRhiVertexInputAttribute::UInt2:
|
|
type = GL_UNSIGNED_INT;
|
|
size = 2;
|
|
break;
|
|
case QRhiVertexInputAttribute::UInt:
|
|
type = GL_UNSIGNED_INT;
|
|
size = 1;
|
|
break;
|
|
case QRhiVertexInputAttribute::SInt4:
|
|
type = GL_INT;
|
|
size = 4;
|
|
break;
|
|
case QRhiVertexInputAttribute::SInt3:
|
|
type = GL_INT;
|
|
size = 3;
|
|
break;
|
|
case QRhiVertexInputAttribute::SInt2:
|
|
type = GL_INT;
|
|
size = 2;
|
|
break;
|
|
case QRhiVertexInputAttribute::SInt:
|
|
type = GL_INT;
|
|
size = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
const int locationIdx = it->location();
|
|
quint32 ofs = it->offset() + cmd.args.bindVertexBuffer.offset;
|
|
if (type == GL_UNSIGNED_INT || type == GL_INT) {
|
|
if (caps.intAttributes) {
|
|
f->glVertexAttribIPointer(GLuint(locationIdx), size, type, stride,
|
|
reinterpret_cast<const GLvoid *>(quintptr(ofs)));
|
|
} else {
|
|
qWarning("Current RHI backend does not support IntAttributes. Check supported features.");
|
|
// This is a trick to disable this attribute
|
|
if (locationIdx < TRACKED_ATTRIB_COUNT)
|
|
enabledAttribArrays[locationIdx] = true;
|
|
}
|
|
} else {
|
|
f->glVertexAttribPointer(GLuint(locationIdx), size, type, normalize, stride,
|
|
reinterpret_cast<const GLvoid *>(quintptr(ofs)));
|
|
}
|
|
if (locationIdx >= TRACKED_ATTRIB_COUNT || !enabledAttribArrays[locationIdx]) {
|
|
if (locationIdx < TRACKED_ATTRIB_COUNT)
|
|
enabledAttribArrays[locationIdx] = true;
|
|
f->glEnableVertexAttribArray(GLuint(locationIdx));
|
|
}
|
|
if (inputBinding->classification() == QRhiVertexInputBinding::PerInstance && caps.instancing)
|
|
f->glVertexAttribDivisor(GLuint(locationIdx), GLuint(inputBinding->instanceStepRate()));
|
|
}
|
|
} else {
|
|
qWarning("No graphics pipeline active for setVertexInput; ignored");
|
|
}
|
|
}
|
|
break;
|
|
case QGles2CommandBuffer::Command::BindIndexBuffer:
|
|
indexType = cmd.args.bindIndexBuffer.type;
|
|
indexStride = indexType == GL_UNSIGNED_SHORT ? sizeof(quint16) : sizeof(quint32);
|
|
indexOffset = cmd.args.bindIndexBuffer.offset;
|
|
if (currentElementArrayBuffer != cmd.args.bindIndexBuffer.buffer) {
|
|
currentElementArrayBuffer = cmd.args.bindIndexBuffer.buffer;
|
|
f->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, currentElementArrayBuffer);
|
|
}
|
|
break;
|
|
case QGles2CommandBuffer::Command::Draw:
|
|
{
|
|
QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.draw.ps);
|
|
if (psD) {
|
|
if (cmd.args.draw.instanceCount == 1 || !caps.instancing) {
|
|
f->glDrawArrays(psD->drawMode, GLint(cmd.args.draw.firstVertex), GLsizei(cmd.args.draw.vertexCount));
|
|
} else {
|
|
f->glDrawArraysInstanced(psD->drawMode, GLint(cmd.args.draw.firstVertex), GLsizei(cmd.args.draw.vertexCount),
|
|
GLsizei(cmd.args.draw.instanceCount));
|
|
}
|
|
} else {
|
|
qWarning("No graphics pipeline active for draw; ignored");
|
|
}
|
|
}
|
|
break;
|
|
case QGles2CommandBuffer::Command::DrawIndexed:
|
|
{
|
|
QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.drawIndexed.ps);
|
|
if (psD) {
|
|
const GLvoid *ofs = reinterpret_cast<const GLvoid *>(
|
|
quintptr(cmd.args.drawIndexed.firstIndex * indexStride + indexOffset));
|
|
if (cmd.args.drawIndexed.instanceCount == 1 || !caps.instancing) {
|
|
if (cmd.args.drawIndexed.baseVertex != 0 && caps.baseVertex) {
|
|
f->glDrawElementsBaseVertex(psD->drawMode,
|
|
GLsizei(cmd.args.drawIndexed.indexCount),
|
|
indexType,
|
|
ofs,
|
|
cmd.args.drawIndexed.baseVertex);
|
|
} else {
|
|
f->glDrawElements(psD->drawMode,
|
|
GLsizei(cmd.args.drawIndexed.indexCount),
|
|
indexType,
|
|
ofs);
|
|
}
|
|
} else {
|
|
if (cmd.args.drawIndexed.baseVertex != 0 && caps.baseVertex) {
|
|
f->glDrawElementsInstancedBaseVertex(psD->drawMode,
|
|
GLsizei(cmd.args.drawIndexed.indexCount),
|
|
indexType,
|
|
ofs,
|
|
GLsizei(cmd.args.drawIndexed.instanceCount),
|
|
cmd.args.drawIndexed.baseVertex);
|
|
} else {
|
|
f->glDrawElementsInstanced(psD->drawMode,
|
|
GLsizei(cmd.args.drawIndexed.indexCount),
|
|
indexType,
|
|
ofs,
|
|
GLsizei(cmd.args.drawIndexed.instanceCount));
|
|
}
|
|
}
|
|
} else {
|
|
qWarning("No graphics pipeline active for drawIndexed; ignored");
|
|
}
|
|
}
|
|
break;
|
|
case QGles2CommandBuffer::Command::BindGraphicsPipeline:
|
|
executeBindGraphicsPipeline(cbD, QRHI_RES(QGles2GraphicsPipeline, cmd.args.bindGraphicsPipeline.ps));
|
|
break;
|
|
case QGles2CommandBuffer::Command::BindShaderResources:
|
|
bindShaderResources(cbD,
|
|
cmd.args.bindShaderResources.maybeGraphicsPs,
|
|
cmd.args.bindShaderResources.maybeComputePs,
|
|
cmd.args.bindShaderResources.srb,
|
|
cmd.args.bindShaderResources.dynamicOffsetPairs,
|
|
cmd.args.bindShaderResources.dynamicOffsetCount);
|
|
break;
|
|
case QGles2CommandBuffer::Command::BindFramebuffer:
|
|
if (cmd.args.bindFramebuffer.fbo) {
|
|
f->glBindFramebuffer(GL_FRAMEBUFFER, cmd.args.bindFramebuffer.fbo);
|
|
if (caps.maxDrawBuffers > 1) {
|
|
const int colorAttCount = cmd.args.bindFramebuffer.colorAttCount;
|
|
QVarLengthArray<GLenum, 8> bufs;
|
|
for (int i = 0; i < colorAttCount; ++i)
|
|
bufs.append(GL_COLOR_ATTACHMENT0 + uint(i));
|
|
f->glDrawBuffers(colorAttCount, bufs.constData());
|
|
}
|
|
} else {
|
|
f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
|
|
if (caps.maxDrawBuffers > 1) {
|
|
GLenum bufs = GL_BACK;
|
|
f->glDrawBuffers(1, &bufs);
|
|
}
|
|
}
|
|
if (caps.srgbCapableDefaultFramebuffer) {
|
|
if (cmd.args.bindFramebuffer.srgb)
|
|
f->glEnable(GL_FRAMEBUFFER_SRGB);
|
|
else
|
|
f->glDisable(GL_FRAMEBUFFER_SRGB);
|
|
}
|
|
break;
|
|
case QGles2CommandBuffer::Command::Clear:
|
|
f->glDisable(GL_SCISSOR_TEST);
|
|
if (cmd.args.clear.mask & GL_COLOR_BUFFER_BIT) {
|
|
f->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
f->glClearColor(cmd.args.clear.c[0], cmd.args.clear.c[1], cmd.args.clear.c[2], cmd.args.clear.c[3]);
|
|
}
|
|
if (cmd.args.clear.mask & GL_DEPTH_BUFFER_BIT) {
|
|
f->glDepthMask(GL_TRUE);
|
|
f->glClearDepthf(cmd.args.clear.d);
|
|
}
|
|
if (cmd.args.clear.mask & GL_STENCIL_BUFFER_BIT)
|
|
f->glClearStencil(GLint(cmd.args.clear.s));
|
|
f->glClear(cmd.args.clear.mask);
|
|
cbD->graphicsPassState.reset(); // altered depth/color write, invalidate in order to avoid confusing the state tracking
|
|
break;
|
|
case QGles2CommandBuffer::Command::BufferSubData:
|
|
f->glBindBuffer(cmd.args.bufferSubData.target, cmd.args.bufferSubData.buffer);
|
|
f->glBufferSubData(cmd.args.bufferSubData.target, cmd.args.bufferSubData.offset, cmd.args.bufferSubData.size,
|
|
cmd.args.bufferSubData.data);
|
|
break;
|
|
case QGles2CommandBuffer::Command::GetBufferSubData:
|
|
{
|
|
QRhiBufferReadbackResult *result = cmd.args.getBufferSubData.result;
|
|
f->glBindBuffer(cmd.args.getBufferSubData.target, cmd.args.getBufferSubData.buffer);
|
|
if (caps.gles) {
|
|
if (caps.properMapBuffer) {
|
|
void *p = f->glMapBufferRange(cmd.args.getBufferSubData.target,
|
|
cmd.args.getBufferSubData.offset,
|
|
cmd.args.getBufferSubData.size,
|
|
GL_MAP_READ_BIT);
|
|
if (p) {
|
|
result->data.resize(cmd.args.getBufferSubData.size);
|
|
memcpy(result->data.data(), p, size_t(cmd.args.getBufferSubData.size));
|
|
f->glUnmapBuffer(cmd.args.getBufferSubData.target);
|
|
}
|
|
}
|
|
} else {
|
|
result->data.resize(cmd.args.getBufferSubData.size);
|
|
f->glGetBufferSubData(cmd.args.getBufferSubData.target,
|
|
cmd.args.getBufferSubData.offset,
|
|
cmd.args.getBufferSubData.size,
|
|
result->data.data());
|
|
}
|
|
if (result->completed)
|
|
result->completed();
|
|
}
|
|
break;
|
|
case QGles2CommandBuffer::Command::CopyTex:
|
|
{
|
|
GLuint fbo;
|
|
f->glGenFramebuffers(1, &fbo);
|
|
f->glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
|
f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
|
cmd.args.copyTex.srcFaceTarget, cmd.args.copyTex.srcTexture, cmd.args.copyTex.srcLevel);
|
|
f->glBindTexture(cmd.args.copyTex.dstTarget, cmd.args.copyTex.dstTexture);
|
|
f->glCopyTexSubImage2D(cmd.args.copyTex.dstFaceTarget, cmd.args.copyTex.dstLevel,
|
|
cmd.args.copyTex.dstX, cmd.args.copyTex.dstY,
|
|
cmd.args.copyTex.srcX, cmd.args.copyTex.srcY,
|
|
cmd.args.copyTex.w, cmd.args.copyTex.h);
|
|
f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
|
|
f->glDeleteFramebuffers(1, &fbo);
|
|
}
|
|
break;
|
|
case QGles2CommandBuffer::Command::ReadPixels:
|
|
{
|
|
QRhiReadbackResult *result = cmd.args.readPixels.result;
|
|
GLuint tex = cmd.args.readPixels.texture;
|
|
GLuint fbo = 0;
|
|
int mipLevel = 0;
|
|
if (tex) {
|
|
result->pixelSize = QSize(cmd.args.readPixels.w, cmd.args.readPixels.h);
|
|
result->format = cmd.args.readPixels.format;
|
|
mipLevel = cmd.args.readPixels.level;
|
|
if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) {
|
|
f->glGenFramebuffers(1, &fbo);
|
|
f->glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
|
f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
|
cmd.args.readPixels.readTarget, cmd.args.readPixels.texture, mipLevel);
|
|
}
|
|
} else {
|
|
result->pixelSize = currentSwapChain->pixelSize;
|
|
result->format = QRhiTexture::RGBA8;
|
|
// readPixels handles multisample resolving implicitly
|
|
}
|
|
const int w = result->pixelSize.width();
|
|
const int h = result->pixelSize.height();
|
|
if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) {
|
|
// With GLES, GL_RGBA is the only mandated readback format, so stick with it.
|
|
// (and that's why we return false for the ReadBackAnyTextureFormat feature)
|
|
if (result->format == QRhiTexture::R8 || result->format == QRhiTexture::RED_OR_ALPHA8) {
|
|
result->data.resize(w * h);
|
|
QByteArray tmpBuf;
|
|
tmpBuf.resize(w * h * 4);
|
|
f->glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, tmpBuf.data());
|
|
const quint8 *srcBase = reinterpret_cast<const quint8 *>(tmpBuf.constData());
|
|
quint8 *dstBase = reinterpret_cast<quint8 *>(result->data.data());
|
|
const int componentIndex = isFeatureSupported(QRhi::RedOrAlpha8IsRed) ? 0 : 3;
|
|
for (int y = 0; y < h; ++y) {
|
|
const quint8 *src = srcBase + y * w * 4;
|
|
quint8 *dst = dstBase + y * w;
|
|
int count = w;
|
|
while (count-- > 0) {
|
|
*dst++ = src[componentIndex];
|
|
src += 4;
|
|
}
|
|
}
|
|
} else {
|
|
result->data.resize(w * h * 4);
|
|
f->glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, result->data.data());
|
|
}
|
|
} else {
|
|
result->data.resize(w * h * 4);
|
|
result->data.fill('\0');
|
|
}
|
|
if (fbo) {
|
|
f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
|
|
f->glDeleteFramebuffers(1, &fbo);
|
|
}
|
|
if (result->completed)
|
|
result->completed();
|
|
}
|
|
break;
|
|
case QGles2CommandBuffer::Command::SubImage:
|
|
f->glBindTexture(cmd.args.subImage.target, cmd.args.subImage.texture);
|
|
if (cmd.args.subImage.rowStartAlign != 4)
|
|
f->glPixelStorei(GL_UNPACK_ALIGNMENT, cmd.args.subImage.rowStartAlign);
|
|
f->glTexSubImage2D(cmd.args.subImage.faceTarget, cmd.args.subImage.level,
|
|
cmd.args.subImage.dx, cmd.args.subImage.dy,
|
|
cmd.args.subImage.w, cmd.args.subImage.h,
|
|
cmd.args.subImage.glformat, cmd.args.subImage.gltype,
|
|
cmd.args.subImage.data);
|
|
if (cmd.args.subImage.rowStartAlign != 4)
|
|
f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
break;
|
|
case QGles2CommandBuffer::Command::CompressedImage:
|
|
f->glBindTexture(cmd.args.compressedImage.target, cmd.args.compressedImage.texture);
|
|
f->glCompressedTexImage2D(cmd.args.compressedImage.faceTarget, cmd.args.compressedImage.level,
|
|
cmd.args.compressedImage.glintformat,
|
|
cmd.args.compressedImage.w, cmd.args.compressedImage.h, 0,
|
|
cmd.args.compressedImage.size, cmd.args.compressedImage.data);
|
|
break;
|
|
case QGles2CommandBuffer::Command::CompressedSubImage:
|
|
f->glBindTexture(cmd.args.compressedSubImage.target, cmd.args.compressedSubImage.texture);
|
|
f->glCompressedTexSubImage2D(cmd.args.compressedSubImage.faceTarget, cmd.args.compressedSubImage.level,
|
|
cmd.args.compressedSubImage.dx, cmd.args.compressedSubImage.dy,
|
|
cmd.args.compressedSubImage.w, cmd.args.compressedSubImage.h,
|
|
cmd.args.compressedSubImage.glintformat,
|
|
cmd.args.compressedSubImage.size, cmd.args.compressedSubImage.data);
|
|
break;
|
|
case QGles2CommandBuffer::Command::BlitFromRenderbuffer:
|
|
{
|
|
GLuint fbo[2];
|
|
f->glGenFramebuffers(2, fbo);
|
|
f->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]);
|
|
f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
|
GL_RENDERBUFFER, cmd.args.blitFromRb.renderbuffer);
|
|
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]);
|
|
|
|
f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromRb.target,
|
|
cmd.args.blitFromRb.texture, cmd.args.blitFromRb.dstLevel);
|
|
f->glBlitFramebuffer(0, 0, cmd.args.blitFromRb.w, cmd.args.blitFromRb.h,
|
|
0, 0, cmd.args.blitFromRb.w, cmd.args.blitFromRb.h,
|
|
GL_COLOR_BUFFER_BIT,
|
|
GL_LINEAR);
|
|
f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
|
|
f->glDeleteFramebuffers(2, fbo);
|
|
}
|
|
break;
|
|
case QGles2CommandBuffer::Command::GenMip:
|
|
f->glBindTexture(cmd.args.genMip.target, cmd.args.genMip.texture);
|
|
f->glGenerateMipmap(cmd.args.genMip.target);
|
|
break;
|
|
case QGles2CommandBuffer::Command::BindComputePipeline:
|
|
{
|
|
QGles2ComputePipeline *psD = QRHI_RES(QGles2ComputePipeline, cmd.args.bindComputePipeline.ps);
|
|
f->glUseProgram(psD->program);
|
|
}
|
|
break;
|
|
case QGles2CommandBuffer::Command::Dispatch:
|
|
f->glDispatchCompute(cmd.args.dispatch.x, cmd.args.dispatch.y, cmd.args.dispatch.z);
|
|
break;
|
|
case QGles2CommandBuffer::Command::BarriersForPass:
|
|
{
|
|
if (!caps.compute)
|
|
break;
|
|
GLbitfield barriers = 0;
|
|
QRhiPassResourceTracker &tracker(cbD->passResTrackers[cmd.args.barriersForPass.trackerIndex]);
|
|
// we only care about after-write, not any other accesses, and
|
|
// cannot tell if something was written in a shader several passes
|
|
// ago: now the previously written resource may be used with an
|
|
// access that was not in the previous passes, result in a missing
|
|
// barrier in theory. Hence setting all barrier bits whenever
|
|
// something previously written is used for the first time in a
|
|
// subsequent pass.
|
|
for (auto it = tracker.cbeginBuffers(), itEnd = tracker.cendBuffers(); it != itEnd; ++it) {
|
|
QGles2Buffer::Access accessBeforePass = QGles2Buffer::Access(it->stateAtPassBegin.access);
|
|
if (bufferAccessIsWrite(accessBeforePass))
|
|
barriers |= GL_ALL_BARRIER_BITS;
|
|
}
|
|
for (auto it = tracker.cbeginTextures(), itEnd = tracker.cendTextures(); it != itEnd; ++it) {
|
|
QGles2Texture::Access accessBeforePass = QGles2Texture::Access(it->stateAtPassBegin.access);
|
|
if (textureAccessIsWrite(accessBeforePass))
|
|
barriers |= GL_ALL_BARRIER_BITS;
|
|
}
|
|
if (barriers)
|
|
f->glMemoryBarrier(barriers);
|
|
}
|
|
break;
|
|
case QGles2CommandBuffer::Command::Barrier:
|
|
if (caps.compute)
|
|
f->glMemoryBarrier(cmd.args.barrier.barriers);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void QRhiGles2::executeBindGraphicsPipeline(QGles2CommandBuffer *cbD, QGles2GraphicsPipeline *psD)
|
|
{
|
|
QGles2CommandBuffer::GraphicsPassState &state(cbD->graphicsPassState);
|
|
const bool forceUpdate = !state.valid;
|
|
state.valid = true;
|
|
|
|
const bool scissor = psD->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor);
|
|
if (forceUpdate || scissor != state.scissor) {
|
|
state.scissor = scissor;
|
|
if (scissor)
|
|
f->glEnable(GL_SCISSOR_TEST);
|
|
else
|
|
f->glDisable(GL_SCISSOR_TEST);
|
|
}
|
|
|
|
const bool cullFace = psD->m_cullMode != QRhiGraphicsPipeline::None;
|
|
const GLenum cullMode = cullFace ? toGlCullMode(psD->m_cullMode) : GL_NONE;
|
|
if (forceUpdate || cullFace != state.cullFace || cullMode != state.cullMode) {
|
|
state.cullFace = cullFace;
|
|
state.cullMode = cullMode;
|
|
if (cullFace) {
|
|
f->glEnable(GL_CULL_FACE);
|
|
f->glCullFace(cullMode);
|
|
} else {
|
|
f->glDisable(GL_CULL_FACE);
|
|
}
|
|
}
|
|
|
|
const GLenum frontFace = toGlFrontFace(psD->m_frontFace);
|
|
if (forceUpdate || frontFace != state.frontFace) {
|
|
state.frontFace = frontFace;
|
|
f->glFrontFace(frontFace);
|
|
}
|
|
|
|
if (!psD->m_targetBlends.isEmpty()) {
|
|
// We do not have MRT support here, meaning all targets use the blend
|
|
// params from the first one. This is technically incorrect, even if
|
|
// nothing in Qt relies on it. However, considering that
|
|
// glBlendFuncSeparatei is only available in GL 4.0+ and GLES 3.2+, we
|
|
// may just live with this for now because no point in bothering if it
|
|
// won't be usable on many GLES (3.1 or 3.0) systems.
|
|
const QRhiGraphicsPipeline::TargetBlend &targetBlend(psD->m_targetBlends.first());
|
|
|
|
const QGles2CommandBuffer::GraphicsPassState::ColorMask colorMask = {
|
|
targetBlend.colorWrite.testFlag(QRhiGraphicsPipeline::R),
|
|
targetBlend.colorWrite.testFlag(QRhiGraphicsPipeline::G),
|
|
targetBlend.colorWrite.testFlag(QRhiGraphicsPipeline::B),
|
|
targetBlend.colorWrite.testFlag(QRhiGraphicsPipeline::A)
|
|
};
|
|
if (forceUpdate || colorMask != state.colorMask) {
|
|
state.colorMask = colorMask;
|
|
f->glColorMask(colorMask.r, colorMask.g, colorMask.b, colorMask.a);
|
|
}
|
|
|
|
const bool blendEnabled = targetBlend.enable;
|
|
const QGles2CommandBuffer::GraphicsPassState::Blend blend = {
|
|
toGlBlendFactor(targetBlend.srcColor),
|
|
toGlBlendFactor(targetBlend.dstColor),
|
|
toGlBlendFactor(targetBlend.srcAlpha),
|
|
toGlBlendFactor(targetBlend.dstAlpha),
|
|
toGlBlendOp(targetBlend.opColor),
|
|
toGlBlendOp(targetBlend.opAlpha)
|
|
};
|
|
if (forceUpdate || blendEnabled != state.blendEnabled || (blendEnabled && blend != state.blend)) {
|
|
state.blendEnabled = blendEnabled;
|
|
if (blendEnabled) {
|
|
state.blend = blend;
|
|
f->glEnable(GL_BLEND);
|
|
f->glBlendFuncSeparate(blend.srcColor, blend.dstColor, blend.srcAlpha, blend.dstAlpha);
|
|
f->glBlendEquationSeparate(blend.opColor, blend.opAlpha);
|
|
} else {
|
|
f->glDisable(GL_BLEND);
|
|
}
|
|
}
|
|
} else {
|
|
const QGles2CommandBuffer::GraphicsPassState::ColorMask colorMask = { true, true, true, true };
|
|
if (forceUpdate || colorMask != state.colorMask) {
|
|
state.colorMask = colorMask;
|
|
f->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
}
|
|
const bool blendEnabled = false;
|
|
if (forceUpdate || blendEnabled != state.blendEnabled) {
|
|
state.blendEnabled = blendEnabled;
|
|
f->glDisable(GL_BLEND);
|
|
}
|
|
}
|
|
|
|
const bool depthTest = psD->m_depthTest;
|
|
if (forceUpdate || depthTest != state.depthTest) {
|
|
state.depthTest = depthTest;
|
|
if (depthTest)
|
|
f->glEnable(GL_DEPTH_TEST);
|
|
else
|
|
f->glDisable(GL_DEPTH_TEST);
|
|
}
|
|
|
|
const bool depthWrite = psD->m_depthWrite;
|
|
if (forceUpdate || depthWrite != state.depthWrite) {
|
|
state.depthWrite = depthWrite;
|
|
f->glDepthMask(depthWrite);
|
|
}
|
|
|
|
const GLenum depthFunc = toGlCompareOp(psD->m_depthOp);
|
|
if (forceUpdate || depthFunc != state.depthFunc) {
|
|
state.depthFunc = depthFunc;
|
|
f->glDepthFunc(depthFunc);
|
|
}
|
|
|
|
const bool stencilTest = psD->m_stencilTest;
|
|
const GLuint stencilReadMask = psD->m_stencilReadMask;
|
|
const GLuint stencilWriteMask = psD->m_stencilWriteMask;
|
|
const QGles2CommandBuffer::GraphicsPassState::StencilFace stencilFront = {
|
|
toGlCompareOp(psD->m_stencilFront.compareOp),
|
|
toGlStencilOp(psD->m_stencilFront.failOp),
|
|
toGlStencilOp(psD->m_stencilFront.depthFailOp),
|
|
toGlStencilOp(psD->m_stencilFront.passOp)
|
|
};
|
|
const QGles2CommandBuffer::GraphicsPassState::StencilFace stencilBack = {
|
|
toGlCompareOp(psD->m_stencilBack.compareOp),
|
|
toGlStencilOp(psD->m_stencilBack.failOp),
|
|
toGlStencilOp(psD->m_stencilBack.depthFailOp),
|
|
toGlStencilOp(psD->m_stencilBack.passOp)
|
|
};
|
|
if (forceUpdate || stencilTest != state.stencilTest
|
|
|| (stencilTest
|
|
&& (stencilReadMask != state.stencilReadMask || stencilWriteMask != state.stencilWriteMask
|
|
|| stencilFront != state.stencil[0] || stencilBack != state.stencil[1])))
|
|
{
|
|
state.stencilTest = stencilTest;
|
|
if (stencilTest) {
|
|
state.stencilReadMask = stencilReadMask;
|
|
state.stencilWriteMask = stencilWriteMask;
|
|
state.stencil[0] = stencilFront;
|
|
state.stencil[1] = stencilBack;
|
|
|
|
f->glEnable(GL_STENCIL_TEST);
|
|
|
|
f->glStencilFuncSeparate(GL_FRONT, stencilFront.func, state.dynamic.stencilRef, stencilReadMask);
|
|
f->glStencilOpSeparate(GL_FRONT, stencilFront.failOp, stencilFront.zfailOp, stencilFront.zpassOp);
|
|
f->glStencilMaskSeparate(GL_FRONT, stencilWriteMask);
|
|
|
|
f->glStencilFuncSeparate(GL_BACK, stencilBack.func, state.dynamic.stencilRef, stencilReadMask);
|
|
f->glStencilOpSeparate(GL_BACK, stencilBack.failOp, stencilBack.zfailOp, stencilBack.zpassOp);
|
|
f->glStencilMaskSeparate(GL_BACK, stencilWriteMask);
|
|
} else {
|
|
f->glDisable(GL_STENCIL_TEST);
|
|
}
|
|
}
|
|
|
|
const bool polyOffsetFill = psD->m_depthBias != 0 || !qFuzzyIsNull(psD->m_slopeScaledDepthBias);
|
|
const float polyOffsetFactor = psD->m_slopeScaledDepthBias;
|
|
const float polyOffsetUnits = psD->m_depthBias;
|
|
if (forceUpdate || state.polyOffsetFill != polyOffsetFill
|
|
|| polyOffsetFactor != state.polyOffsetFactor || polyOffsetUnits != state.polyOffsetUnits)
|
|
{
|
|
state.polyOffsetFill = polyOffsetFill;
|
|
state.polyOffsetFactor = polyOffsetFactor;
|
|
state.polyOffsetUnits = polyOffsetUnits;
|
|
if (polyOffsetFill) {
|
|
f->glPolygonOffset(polyOffsetFactor, polyOffsetUnits);
|
|
f->glEnable(GL_POLYGON_OFFSET_FILL);
|
|
} else {
|
|
f->glDisable(GL_POLYGON_OFFSET_FILL);
|
|
}
|
|
}
|
|
|
|
if (psD->m_topology == QRhiGraphicsPipeline::Lines || psD->m_topology == QRhiGraphicsPipeline::LineStrip) {
|
|
const float lineWidth = psD->m_lineWidth;
|
|
if (forceUpdate || lineWidth != state.lineWidth) {
|
|
state.lineWidth = lineWidth;
|
|
f->glLineWidth(lineWidth);
|
|
}
|
|
}
|
|
|
|
f->glUseProgram(psD->program);
|
|
}
|
|
|
|
static inline void qrhi_std140_to_packed(float *dst, int vecSize, int elemCount, const void *src)
|
|
{
|
|
const float *p = reinterpret_cast<const float *>(src);
|
|
for (int i = 0; i < elemCount; ++i) {
|
|
for (int j = 0; j < vecSize; ++j)
|
|
dst[vecSize * i + j] = *p++;
|
|
p += 4 - vecSize;
|
|
}
|
|
}
|
|
|
|
void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD,
|
|
QRhiGraphicsPipeline *maybeGraphicsPs, QRhiComputePipeline *maybeComputePs,
|
|
QRhiShaderResourceBindings *srb,
|
|
const uint *dynOfsPairs, int dynOfsCount)
|
|
{
|
|
QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, srb);
|
|
int texUnit = 1; // start from unit 1, keep 0 for resource mgmt stuff to avoid clashes
|
|
bool activeTexUnitAltered = false;
|
|
QVarLengthArray<float, 256> packedFloatArray;
|
|
|
|
for (int i = 0, ie = srbD->m_bindings.count(); i != ie; ++i) {
|
|
const QRhiShaderResourceBinding::Data *b = srbD->m_bindings.at(i).data();
|
|
|
|
switch (b->type) {
|
|
case QRhiShaderResourceBinding::UniformBuffer:
|
|
{
|
|
int viewOffset = b->u.ubuf.offset;
|
|
for (int j = 0; j < dynOfsCount; ++j) {
|
|
if (dynOfsPairs[2 * j] == uint(b->binding)) {
|
|
viewOffset = int(dynOfsPairs[2 * j + 1]);
|
|
break;
|
|
}
|
|
}
|
|
QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, b->u.ubuf.buf);
|
|
const char *bufView = bufD->ubuf + viewOffset;
|
|
QGles2UniformDescriptionVector &uniforms(maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->uniforms
|
|
: QRHI_RES(QGles2ComputePipeline, maybeComputePs)->uniforms);
|
|
QGles2UniformState *uniformState = maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->uniformState
|
|
: QRHI_RES(QGles2ComputePipeline, maybeComputePs)->uniformState;
|
|
for (const QGles2UniformDescription &uniform : qAsConst(uniforms)) {
|
|
if (uniform.binding == b->binding) {
|
|
// in a uniform buffer everything is at least 4 byte aligned
|
|
// so this should not cause unaligned reads
|
|
const void *src = bufView + uniform.offset;
|
|
|
|
#ifndef QT_NO_DEBUG
|
|
if (uniform.arrayDim > 0
|
|
&& uniform.type != QShaderDescription::Float
|
|
&& uniform.type != QShaderDescription::Vec2
|
|
&& uniform.type != QShaderDescription::Vec3
|
|
&& uniform.type != QShaderDescription::Vec4
|
|
&& uniform.type != QShaderDescription::Mat3
|
|
&& uniform.type != QShaderDescription::Mat4)
|
|
{
|
|
qWarning("Uniform with buffer binding %d, buffer offset %d, type %d is an array, "
|
|
"but arrays are only supported for float, vec2, vec3, vec4, mat3 and mat4. "
|
|
"Only the first element will be set.",
|
|
uniform.binding, uniform.offset, uniform.type);
|
|
}
|
|
#endif
|
|
|
|
// Our input is an std140 layout uniform block. See
|
|
// "Standard Uniform Block Layout" in section 7.6.2.2 of
|
|
// the OpenGL spec. This has some peculiar alignment
|
|
// requirements, which is not what glUniform* wants. Hence
|
|
// the unpacking/repacking for arrays and certain types.
|
|
|
|
switch (uniform.type) {
|
|
case QShaderDescription::Float:
|
|
{
|
|
const int elemCount = uniform.arrayDim;
|
|
if (elemCount < 1) {
|
|
const float v = *reinterpret_cast<const float *>(src);
|
|
if (uniform.glslLocation <= QGles2UniformState::MAX_TRACKED_LOCATION) {
|
|
QGles2UniformState &thisUniformState(uniformState[uniform.glslLocation]);
|
|
if (thisUniformState.componentCount != 1 || thisUniformState.v[0] != v) {
|
|
thisUniformState.componentCount = 1;
|
|
thisUniformState.v[0] = v;
|
|
f->glUniform1f(uniform.glslLocation, v);
|
|
}
|
|
} else {
|
|
f->glUniform1f(uniform.glslLocation, v);
|
|
}
|
|
} else {
|
|
// input is 16 bytes per element as per std140, have to convert to packed
|
|
packedFloatArray.resize(elemCount);
|
|
qrhi_std140_to_packed(packedFloatArray.data(), 1, elemCount, src);
|
|
f->glUniform1fv(uniform.glslLocation, elemCount, packedFloatArray.constData());
|
|
}
|
|
}
|
|
break;
|
|
case QShaderDescription::Vec2:
|
|
{
|
|
const int elemCount = uniform.arrayDim;
|
|
if (elemCount < 1) {
|
|
const float *v = reinterpret_cast<const float *>(src);
|
|
if (uniform.glslLocation <= QGles2UniformState::MAX_TRACKED_LOCATION) {
|
|
QGles2UniformState &thisUniformState(uniformState[uniform.glslLocation]);
|
|
if (thisUniformState.componentCount != 2
|
|
|| thisUniformState.v[0] != v[0]
|
|
|| thisUniformState.v[1] != v[1])
|
|
{
|
|
thisUniformState.componentCount = 2;
|
|
thisUniformState.v[0] = v[0];
|
|
thisUniformState.v[1] = v[1];
|
|
f->glUniform2fv(uniform.glslLocation, 1, v);
|
|
}
|
|
} else {
|
|
f->glUniform2fv(uniform.glslLocation, 1, v);
|
|
}
|
|
} else {
|
|
packedFloatArray.resize(elemCount * 2);
|
|
qrhi_std140_to_packed(packedFloatArray.data(), 2, elemCount, src);
|
|
f->glUniform2fv(uniform.glslLocation, elemCount, packedFloatArray.constData());
|
|
}
|
|
}
|
|
break;
|
|
case QShaderDescription::Vec3:
|
|
{
|
|
const int elemCount = uniform.arrayDim;
|
|
if (elemCount < 1) {
|
|
const float *v = reinterpret_cast<const float *>(src);
|
|
if (uniform.glslLocation <= QGles2UniformState::MAX_TRACKED_LOCATION) {
|
|
QGles2UniformState &thisUniformState(uniformState[uniform.glslLocation]);
|
|
if (thisUniformState.componentCount != 3
|
|
|| thisUniformState.v[0] != v[0]
|
|
|| thisUniformState.v[1] != v[1]
|
|
|| thisUniformState.v[2] != v[2])
|
|
{
|
|
thisUniformState.componentCount = 3;
|
|
thisUniformState.v[0] = v[0];
|
|
thisUniformState.v[1] = v[1];
|
|
thisUniformState.v[2] = v[2];
|
|
f->glUniform3fv(uniform.glslLocation, 1, v);
|
|
}
|
|
} else {
|
|
f->glUniform3fv(uniform.glslLocation, 1, v);
|
|
}
|
|
} else {
|
|
packedFloatArray.resize(elemCount * 3);
|
|
qrhi_std140_to_packed(packedFloatArray.data(), 3, elemCount, src);
|
|
f->glUniform3fv(uniform.glslLocation, elemCount, packedFloatArray.constData());
|
|
}
|
|
}
|
|
break;
|
|
case QShaderDescription::Vec4:
|
|
{
|
|
const int elemCount = uniform.arrayDim;
|
|
if (elemCount < 1) {
|
|
const float *v = reinterpret_cast<const float *>(src);
|
|
if (uniform.glslLocation <= QGles2UniformState::MAX_TRACKED_LOCATION) {
|
|
QGles2UniformState &thisUniformState(uniformState[uniform.glslLocation]);
|
|
if (thisUniformState.componentCount != 4
|
|
|| thisUniformState.v[0] != v[0]
|
|
|| thisUniformState.v[1] != v[1]
|
|
|| thisUniformState.v[2] != v[2]
|
|
|| thisUniformState.v[3] != v[3])
|
|
{
|
|
thisUniformState.componentCount = 4;
|
|
thisUniformState.v[0] = v[0];
|
|
thisUniformState.v[1] = v[1];
|
|
thisUniformState.v[2] = v[2];
|
|
thisUniformState.v[3] = v[3];
|
|
f->glUniform4fv(uniform.glslLocation, 1, v);
|
|
}
|
|
} else {
|
|
f->glUniform4fv(uniform.glslLocation, 1, v);
|
|
}
|
|
} else {
|
|
f->glUniform4fv(uniform.glslLocation, qMax(1, uniform.arrayDim), reinterpret_cast<const float *>(src));
|
|
}
|
|
}
|
|
break;
|
|
case QShaderDescription::Mat2:
|
|
f->glUniformMatrix2fv(uniform.glslLocation, 1, GL_FALSE, reinterpret_cast<const float *>(src));
|
|
break;
|
|
case QShaderDescription::Mat3:
|
|
{
|
|
const int elemCount = uniform.arrayDim;
|
|
if (elemCount < 1) {
|
|
// 4 floats per column (or row, if row-major)
|
|
float mat[9];
|
|
const float *srcMat = reinterpret_cast<const float *>(src);
|
|
memcpy(mat, srcMat, 3 * sizeof(float));
|
|
memcpy(mat + 3, srcMat + 4, 3 * sizeof(float));
|
|
memcpy(mat + 6, srcMat + 8, 3 * sizeof(float));
|
|
f->glUniformMatrix3fv(uniform.glslLocation, 1, GL_FALSE, mat);
|
|
} else {
|
|
packedFloatArray.resize(elemCount * 9);
|
|
qrhi_std140_to_packed(packedFloatArray.data(), 3, elemCount * 3, src);
|
|
f->glUniformMatrix3fv(uniform.glslLocation, elemCount, GL_FALSE, packedFloatArray.constData());
|
|
}
|
|
}
|
|
break;
|
|
case QShaderDescription::Mat4:
|
|
f->glUniformMatrix4fv(uniform.glslLocation, qMax(1, uniform.arrayDim), GL_FALSE, reinterpret_cast<const float *>(src));
|
|
break;
|
|
case QShaderDescription::Int:
|
|
f->glUniform1i(uniform.glslLocation, *reinterpret_cast<const qint32 *>(src));
|
|
break;
|
|
case QShaderDescription::Int2:
|
|
f->glUniform2iv(uniform.glslLocation, 1, reinterpret_cast<const qint32 *>(src));
|
|
break;
|
|
case QShaderDescription::Int3:
|
|
f->glUniform3iv(uniform.glslLocation, 1, reinterpret_cast<const qint32 *>(src));
|
|
break;
|
|
case QShaderDescription::Int4:
|
|
f->glUniform4iv(uniform.glslLocation, 1, reinterpret_cast<const qint32 *>(src));
|
|
break;
|
|
case QShaderDescription::Uint:
|
|
f->glUniform1ui(uniform.glslLocation, *reinterpret_cast<const quint32 *>(src));
|
|
break;
|
|
case QShaderDescription::Uint2:
|
|
f->glUniform2uiv(uniform.glslLocation, 1, reinterpret_cast<const quint32 *>(src));
|
|
break;
|
|
case QShaderDescription::Uint3:
|
|
f->glUniform3uiv(uniform.glslLocation, 1, reinterpret_cast<const quint32 *>(src));
|
|
break;
|
|
case QShaderDescription::Uint4:
|
|
f->glUniform4uiv(uniform.glslLocation, 1, reinterpret_cast<const quint32 *>(src));
|
|
break;
|
|
case QShaderDescription::Bool: // a glsl bool is 4 bytes, like (u)int
|
|
f->glUniform1i(uniform.glslLocation, *reinterpret_cast<const qint32 *>(src));
|
|
break;
|
|
case QShaderDescription::Bool2:
|
|
f->glUniform2iv(uniform.glslLocation, 1, reinterpret_cast<const qint32 *>(src));
|
|
break;
|
|
case QShaderDescription::Bool3:
|
|
f->glUniform3iv(uniform.glslLocation, 1, reinterpret_cast<const qint32 *>(src));
|
|
break;
|
|
case QShaderDescription::Bool4:
|
|
f->glUniform4iv(uniform.glslLocation, 1, reinterpret_cast<const qint32 *>(src));
|
|
break;
|
|
default:
|
|
qWarning("Uniform with buffer binding %d, buffer offset %d has unsupported type %d",
|
|
uniform.binding, uniform.offset, uniform.type);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case QRhiShaderResourceBinding::SampledTexture:
|
|
{
|
|
const QGles2SamplerDescriptionVector &samplers(maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->samplers
|
|
: QRHI_RES(QGles2ComputePipeline, maybeComputePs)->samplers);
|
|
void *ps;
|
|
uint psGeneration;
|
|
if (maybeGraphicsPs) {
|
|
ps = maybeGraphicsPs;
|
|
psGeneration = QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->generation;
|
|
} else {
|
|
ps = maybeComputePs;
|
|
psGeneration = QRHI_RES(QGles2ComputePipeline, maybeComputePs)->generation;
|
|
}
|
|
for (int elem = 0; elem < b->u.stex.count; ++elem) {
|
|
QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.stex.texSamplers[elem].tex);
|
|
QGles2Sampler *samplerD = QRHI_RES(QGles2Sampler, b->u.stex.texSamplers[elem].sampler);
|
|
for (const QGles2SamplerDescription &shaderSampler : samplers) {
|
|
if (shaderSampler.binding == b->binding) {
|
|
const bool samplerStateValid = texD->samplerState == samplerD->d;
|
|
const bool cachedStateInRange = texUnit < 16;
|
|
bool updateTextureBinding = true;
|
|
if (samplerStateValid && cachedStateInRange) {
|
|
// If we already encountered the same texture with
|
|
// the same pipeline for this texture unit in the
|
|
// current pass, then the shader program already
|
|
// has the uniform set. As in a 3D scene one model
|
|
// often has more than one associated texture map,
|
|
// the savings here can become significant,
|
|
// depending on the scene.
|
|
if (cbD->textureUnitState[texUnit].ps == ps
|
|
&& cbD->textureUnitState[texUnit].psGeneration == psGeneration
|
|
&& cbD->textureUnitState[texUnit].texture == texD->texture)
|
|
{
|
|
updateTextureBinding = false;
|
|
}
|
|
}
|
|
if (updateTextureBinding) {
|
|
f->glActiveTexture(GL_TEXTURE0 + uint(texUnit));
|
|
activeTexUnitAltered = true;
|
|
f->glBindTexture(texD->target, texD->texture);
|
|
f->glUniform1i(shaderSampler.glslLocation + elem, texUnit);
|
|
if (cachedStateInRange) {
|
|
cbD->textureUnitState[texUnit].ps = ps;
|
|
cbD->textureUnitState[texUnit].psGeneration = psGeneration;
|
|
cbD->textureUnitState[texUnit].texture = texD->texture;
|
|
}
|
|
}
|
|
++texUnit;
|
|
if (!samplerStateValid) {
|
|
f->glTexParameteri(texD->target, GL_TEXTURE_MIN_FILTER, GLint(samplerD->d.glminfilter));
|
|
f->glTexParameteri(texD->target, GL_TEXTURE_MAG_FILTER, GLint(samplerD->d.glmagfilter));
|
|
f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_S, GLint(samplerD->d.glwraps));
|
|
f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_T, GLint(samplerD->d.glwrapt));
|
|
// 3D textures not supported by GLES 2.0 or by us atm...
|
|
//f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_R, samplerD->d.glwrapr);
|
|
if (caps.textureCompareMode) {
|
|
if (samplerD->d.gltexcomparefunc != GL_NEVER) {
|
|
f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
|
|
f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_FUNC, GLint(samplerD->d.gltexcomparefunc));
|
|
} else {
|
|
f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
|
}
|
|
}
|
|
texD->samplerState = samplerD->d;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case QRhiShaderResourceBinding::ImageLoad:
|
|
case QRhiShaderResourceBinding::ImageStore:
|
|
case QRhiShaderResourceBinding::ImageLoadStore:
|
|
{
|
|
QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.simage.tex);
|
|
const bool layered = texD->m_flags.testFlag(QRhiTexture::CubeMap);
|
|
GLenum access = GL_READ_WRITE;
|
|
if (b->type == QRhiShaderResourceBinding::ImageLoad)
|
|
access = GL_READ_ONLY;
|
|
else if (b->type == QRhiShaderResourceBinding::ImageStore)
|
|
access = GL_WRITE_ONLY;
|
|
f->glBindImageTexture(GLuint(b->binding), texD->texture,
|
|
b->u.simage.level, layered, 0,
|
|
access, texD->glsizedintformat);
|
|
}
|
|
break;
|
|
case QRhiShaderResourceBinding::BufferLoad:
|
|
case QRhiShaderResourceBinding::BufferStore:
|
|
case QRhiShaderResourceBinding::BufferLoadStore:
|
|
{
|
|
QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, b->u.sbuf.buf);
|
|
if (b->u.sbuf.offset == 0 && b->u.sbuf.maybeSize == 0)
|
|
f->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, GLuint(b->binding), bufD->buffer);
|
|
else
|
|
f->glBindBufferRange(GL_SHADER_STORAGE_BUFFER, GLuint(b->binding), bufD->buffer,
|
|
b->u.sbuf.offset, b->u.sbuf.maybeSize ? b->u.sbuf.maybeSize : bufD->m_size);
|
|
}
|
|
break;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (activeTexUnitAltered)
|
|
f->glActiveTexture(GL_TEXTURE0);
|
|
}
|
|
|
|
void QRhiGles2::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
|
|
{
|
|
Q_ASSERT(QRHI_RES(QGles2CommandBuffer, cb)->recordingPass == QGles2CommandBuffer::NoPass);
|
|
|
|
enqueueResourceUpdates(cb, resourceUpdates);
|
|
}
|
|
|
|
QGles2RenderTargetData *QRhiGles2::enqueueBindFramebuffer(QRhiRenderTarget *rt, QGles2CommandBuffer *cbD,
|
|
bool *wantsColorClear, bool *wantsDsClear)
|
|
{
|
|
QGles2RenderTargetData *rtD = nullptr;
|
|
QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
|
|
|
|
QGles2CommandBuffer::Command fbCmd;
|
|
fbCmd.cmd = QGles2CommandBuffer::Command::BindFramebuffer;
|
|
|
|
switch (rt->resourceType()) {
|
|
case QRhiResource::RenderTarget:
|
|
rtD = &QRHI_RES(QGles2ReferenceRenderTarget, rt)->d;
|
|
if (wantsColorClear)
|
|
*wantsColorClear = true;
|
|
if (wantsDsClear)
|
|
*wantsDsClear = true;
|
|
fbCmd.args.bindFramebuffer.fbo = 0;
|
|
fbCmd.args.bindFramebuffer.colorAttCount = 1;
|
|
break;
|
|
case QRhiResource::TextureRenderTarget:
|
|
{
|
|
QGles2TextureRenderTarget *rtTex = QRHI_RES(QGles2TextureRenderTarget, rt);
|
|
rtD = &rtTex->d;
|
|
if (wantsColorClear)
|
|
*wantsColorClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents);
|
|
if (wantsDsClear)
|
|
*wantsDsClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents);
|
|
fbCmd.args.bindFramebuffer.fbo = rtTex->framebuffer;
|
|
fbCmd.args.bindFramebuffer.colorAttCount = rtD->colorAttCount;
|
|
|
|
for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments();
|
|
it != itEnd; ++it)
|
|
{
|
|
const QRhiColorAttachment &colorAtt(*it);
|
|
QGles2Texture *texD = QRHI_RES(QGles2Texture, colorAtt.texture());
|
|
QGles2Texture *resolveTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture());
|
|
if (texD && cbD->passNeedsResourceTracking) {
|
|
trackedRegisterTexture(&passResTracker, texD,
|
|
QRhiPassResourceTracker::TexColorOutput,
|
|
QRhiPassResourceTracker::TexColorOutputStage);
|
|
}
|
|
if (resolveTexD && cbD->passNeedsResourceTracking) {
|
|
trackedRegisterTexture(&passResTracker, resolveTexD,
|
|
QRhiPassResourceTracker::TexColorOutput,
|
|
QRhiPassResourceTracker::TexColorOutputStage);
|
|
}
|
|
// renderbuffers cannot be written in shaders (no image store) so
|
|
// they do not matter here
|
|
}
|
|
if (rtTex->m_desc.depthTexture() && cbD->passNeedsResourceTracking) {
|
|
trackedRegisterTexture(&passResTracker, QRHI_RES(QGles2Texture, rtTex->m_desc.depthTexture()),
|
|
QRhiPassResourceTracker::TexDepthOutput,
|
|
QRhiPassResourceTracker::TexDepthOutputStage);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
break;
|
|
}
|
|
|
|
fbCmd.args.bindFramebuffer.srgb = rtD->srgbUpdateAndBlend;
|
|
cbD->commands.append(fbCmd);
|
|
|
|
return rtD;
|
|
}
|
|
|
|
void QRhiGles2::enqueueBarriersForPass(QGles2CommandBuffer *cbD)
|
|
{
|
|
cbD->passResTrackers.append(QRhiPassResourceTracker());
|
|
cbD->currentPassResTrackerIndex = cbD->passResTrackers.count() - 1;
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::BarriersForPass;
|
|
cmd.args.barriersForPass.trackerIndex = cbD->currentPassResTrackerIndex;
|
|
cbD->commands.append(cmd);
|
|
}
|
|
|
|
void QRhiGles2::beginPass(QRhiCommandBuffer *cb,
|
|
QRhiRenderTarget *rt,
|
|
const QColor &colorClearValue,
|
|
const QRhiDepthStencilClearValue &depthStencilClearValue,
|
|
QRhiResourceUpdateBatch *resourceUpdates,
|
|
QRhiCommandBuffer::BeginPassFlags flags)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass);
|
|
|
|
if (resourceUpdates)
|
|
enqueueResourceUpdates(cb, resourceUpdates);
|
|
|
|
// Get a new resource tracker. Then add a command that will generate
|
|
// glMemoryBarrier() calls based on that tracker when submitted.
|
|
enqueueBarriersForPass(cbD);
|
|
|
|
bool wantsColorClear, wantsDsClear;
|
|
QGles2RenderTargetData *rtD = enqueueBindFramebuffer(rt, cbD, &wantsColorClear, &wantsDsClear);
|
|
|
|
QGles2CommandBuffer::Command clearCmd;
|
|
clearCmd.cmd = QGles2CommandBuffer::Command::Clear;
|
|
clearCmd.args.clear.mask = 0;
|
|
if (rtD->colorAttCount && wantsColorClear)
|
|
clearCmd.args.clear.mask |= GL_COLOR_BUFFER_BIT;
|
|
if (rtD->dsAttCount && wantsDsClear)
|
|
clearCmd.args.clear.mask |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
|
|
clearCmd.args.clear.c[0] = float(colorClearValue.redF());
|
|
clearCmd.args.clear.c[1] = float(colorClearValue.greenF());
|
|
clearCmd.args.clear.c[2] = float(colorClearValue.blueF());
|
|
clearCmd.args.clear.c[3] = float(colorClearValue.alphaF());
|
|
clearCmd.args.clear.d = depthStencilClearValue.depthClearValue();
|
|
clearCmd.args.clear.s = depthStencilClearValue.stencilClearValue();
|
|
cbD->commands.append(clearCmd);
|
|
|
|
cbD->recordingPass = QGles2CommandBuffer::RenderPass;
|
|
cbD->passNeedsResourceTracking = !flags.testFlag(QRhiCommandBuffer::DoNotTrackResourcesForCompute);
|
|
cbD->currentTarget = rt;
|
|
|
|
cbD->resetCachedState();
|
|
}
|
|
|
|
void QRhiGles2::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
|
|
|
|
if (cbD->currentTarget->resourceType() == QRhiResource::TextureRenderTarget) {
|
|
QGles2TextureRenderTarget *rtTex = QRHI_RES(QGles2TextureRenderTarget, cbD->currentTarget);
|
|
if (rtTex->m_desc.cbeginColorAttachments() != rtTex->m_desc.cendColorAttachments()) {
|
|
// handle only 1 color attachment and only (msaa) renderbuffer
|
|
const QRhiColorAttachment &colorAtt(*rtTex->m_desc.cbeginColorAttachments());
|
|
if (colorAtt.resolveTexture()) {
|
|
Q_ASSERT(colorAtt.renderBuffer());
|
|
QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, colorAtt.renderBuffer());
|
|
const QSize size = colorAtt.resolveTexture()->pixelSize();
|
|
if (rbD->pixelSize() != size) {
|
|
qWarning("Resolve source (%dx%d) and target (%dx%d) size does not match",
|
|
rbD->pixelSize().width(), rbD->pixelSize().height(), size.width(), size.height());
|
|
}
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::BlitFromRenderbuffer;
|
|
cmd.args.blitFromRb.renderbuffer = rbD->renderbuffer;
|
|
cmd.args.blitFromRb.w = size.width();
|
|
cmd.args.blitFromRb.h = size.height();
|
|
QGles2Texture *colorTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture());
|
|
const GLenum faceTargetBase = colorTexD->m_flags.testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X
|
|
: colorTexD->target;
|
|
cmd.args.blitFromRb.target = faceTargetBase + uint(colorAtt.resolveLayer());
|
|
cmd.args.blitFromRb.texture = colorTexD->texture;
|
|
cmd.args.blitFromRb.dstLevel = colorAtt.resolveLevel();
|
|
cbD->commands.append(cmd);
|
|
}
|
|
}
|
|
}
|
|
|
|
cbD->recordingPass = QGles2CommandBuffer::NoPass;
|
|
cbD->currentTarget = nullptr;
|
|
|
|
if (resourceUpdates)
|
|
enqueueResourceUpdates(cb, resourceUpdates);
|
|
}
|
|
|
|
void QRhiGles2::beginComputePass(QRhiCommandBuffer *cb,
|
|
QRhiResourceUpdateBatch *resourceUpdates,
|
|
QRhiCommandBuffer::BeginPassFlags)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass);
|
|
|
|
if (resourceUpdates)
|
|
enqueueResourceUpdates(cb, resourceUpdates);
|
|
|
|
enqueueBarriersForPass(cbD);
|
|
|
|
cbD->recordingPass = QGles2CommandBuffer::ComputePass;
|
|
|
|
cbD->resetCachedState();
|
|
}
|
|
|
|
void QRhiGles2::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::ComputePass);
|
|
|
|
cbD->recordingPass = QGles2CommandBuffer::NoPass;
|
|
|
|
if (resourceUpdates)
|
|
enqueueResourceUpdates(cb, resourceUpdates);
|
|
}
|
|
|
|
void QRhiGles2::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::ComputePass);
|
|
QGles2ComputePipeline *psD = QRHI_RES(QGles2ComputePipeline, ps);
|
|
const bool pipelineChanged = cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation;
|
|
|
|
if (pipelineChanged) {
|
|
cbD->currentGraphicsPipeline = nullptr;
|
|
cbD->currentComputePipeline = ps;
|
|
cbD->currentPipelineGeneration = psD->generation;
|
|
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::BindComputePipeline;
|
|
cmd.args.bindComputePipeline.ps = ps;
|
|
cbD->commands.append(cmd);
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
inline void qrhigl_accumulateComputeResource(T *writtenResources, QRhiResource *resource,
|
|
QRhiShaderResourceBinding::Type bindingType,
|
|
int loadTypeVal, int storeTypeVal, int loadStoreTypeVal)
|
|
{
|
|
int access = 0;
|
|
if (bindingType == loadTypeVal) {
|
|
access = QGles2CommandBuffer::ComputePassState::Read;
|
|
} else {
|
|
access = QGles2CommandBuffer::ComputePassState::Write;
|
|
if (bindingType == loadStoreTypeVal)
|
|
access |= QGles2CommandBuffer::ComputePassState::Read;
|
|
}
|
|
auto it = writtenResources->find(resource);
|
|
if (it != writtenResources->end())
|
|
it->first |= access;
|
|
else if (bindingType == storeTypeVal || bindingType == loadStoreTypeVal)
|
|
writtenResources->insert(resource, { access, true });
|
|
}
|
|
|
|
void QRhiGles2::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
|
|
{
|
|
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
|
Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::ComputePass);
|
|
|
|
if (cbD->currentComputeSrb) {
|
|
GLbitfield barriers = 0;
|
|
|
|
// The key in the writtenResources map indicates that the resource was
|
|
// written in a previous dispatch, whereas the value accumulates the
|
|
// access mask in the current one.
|
|
for (auto &accessAndIsNewFlag : cbD->computePassState.writtenResources)
|
|
accessAndIsNewFlag = { 0, false };
|
|
|
|
QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, cbD->currentComputeSrb);
|
|
const int bindingCount = srbD->m_bindings.count();
|
|
for (int i = 0; i < bindingCount; ++i) {
|
|
const QRhiShaderResourceBinding::Data *b = srbD->m_bindings.at(i).data();
|
|
switch (b->type) {
|
|
case QRhiShaderResourceBinding::ImageLoad:
|
|
case QRhiShaderResourceBinding::ImageStore:
|
|
case QRhiShaderResourceBinding::ImageLoadStore:
|
|
qrhigl_accumulateComputeResource(&cbD->computePassState.writtenResources,
|
|
b->u.simage.tex,
|
|
b->type,
|
|
QRhiShaderResourceBinding::ImageLoad,
|
|
QRhiShaderResourceBinding::ImageStore,
|
|
QRhiShaderResourceBinding::ImageLoadStore);
|
|
break;
|
|
case QRhiShaderResourceBinding::BufferLoad:
|
|
case QRhiShaderResourceBinding::BufferStore:
|
|
case QRhiShaderResourceBinding::BufferLoadStore:
|
|
qrhigl_accumulateComputeResource(&cbD->computePassState.writtenResources,
|
|
b->u.sbuf.buf,
|
|
b->type,
|
|
QRhiShaderResourceBinding::BufferLoad,
|
|
QRhiShaderResourceBinding::BufferStore,
|
|
QRhiShaderResourceBinding::BufferLoadStore);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (auto it = cbD->computePassState.writtenResources.begin(); it != cbD->computePassState.writtenResources.end(); ) {
|
|
const int accessInThisDispatch = it->first;
|
|
const bool isNewInThisDispatch = it->second;
|
|
if (accessInThisDispatch && !isNewInThisDispatch) {
|
|
if (it.key()->resourceType() == QRhiResource::Texture)
|
|
barriers |= GL_SHADER_IMAGE_ACCESS_BARRIER_BIT;
|
|
else
|
|
barriers |= GL_SHADER_STORAGE_BARRIER_BIT;
|
|
}
|
|
// Anything that was previously written, but is only read now, can be
|
|
// removed from the written list (because that previous write got a
|
|
// corresponding barrier now).
|
|
if (accessInThisDispatch == QGles2CommandBuffer::ComputePassState::Read)
|
|
it = cbD->computePassState.writtenResources.erase(it);
|
|
else
|
|
++it;
|
|
}
|
|
|
|
if (barriers) {
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::Barrier;
|
|
cmd.args.barrier.barriers = barriers;
|
|
cbD->commands.append(cmd);
|
|
}
|
|
}
|
|
|
|
QGles2CommandBuffer::Command cmd;
|
|
cmd.cmd = QGles2CommandBuffer::Command::Dispatch;
|
|
cmd.args.dispatch.x = GLuint(x);
|
|
cmd.args.dispatch.y = GLuint(y);
|
|
cmd.args.dispatch.z = GLuint(z);
|
|
cbD->commands.append(cmd);
|
|
}
|
|
|
|
static inline GLenum toGlShaderType(QRhiShaderStage::Type type)
|
|
{
|
|
switch (type) {
|
|
case QRhiShaderStage::Vertex:
|
|
return GL_VERTEX_SHADER;
|
|
case QRhiShaderStage::Fragment:
|
|
return GL_FRAGMENT_SHADER;
|
|
case QRhiShaderStage::Compute:
|
|
return GL_COMPUTE_SHADER;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
return GL_VERTEX_SHADER;
|
|
}
|
|
}
|
|
|
|
QByteArray QRhiGles2::shaderSource(const QRhiShaderStage &shaderStage, int *glslVersion)
|
|
{
|
|
const QShader bakedShader = shaderStage.shader();
|
|
QList<int> versionsToTry;
|
|
QByteArray source;
|
|
if (caps.gles) {
|
|
if (caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2)) {
|
|
versionsToTry << 320 << 310 << 300 << 100;
|
|
} else if (caps.ctxMajor == 3 && caps.ctxMinor == 1) {
|
|
versionsToTry << 310 << 300 << 100;
|
|
} else if (caps.ctxMajor == 3 && caps.ctxMinor == 0) {
|
|
versionsToTry << 300 << 100;
|
|
} else {
|
|
versionsToTry << 100;
|
|
}
|
|
for (int v : versionsToTry) {
|
|
QShaderVersion ver(v, QShaderVersion::GlslEs);
|
|
source = bakedShader.shader({ QShader::GlslShader, ver, shaderStage.shaderVariant() }).shader();
|
|
if (!source.isEmpty()) {
|
|
if (glslVersion)
|
|
*glslVersion = v;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if (caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 6)) {
|
|
versionsToTry << 460 << 450 << 440 << 430 << 420 << 410 << 400 << 330 << 150 << 140 << 130;
|
|
} else if (caps.ctxMajor == 4 && caps.ctxMinor == 5) {
|
|
versionsToTry << 450 << 440 << 430 << 420 << 410 << 400 << 330 << 150 << 140 << 130;
|
|
} else if (caps.ctxMajor == 4 && caps.ctxMinor == 4) {
|
|
versionsToTry << 440 << 430 << 420 << 410 << 400 << 330 << 150 << 140 << 130;
|
|
} else if (caps.ctxMajor == 4 && caps.ctxMinor == 3) {
|
|
versionsToTry << 430 << 420 << 410 << 400 << 330 << 150 << 140 << 130;
|
|
} else if (caps.ctxMajor == 4 && caps.ctxMinor == 2) {
|
|
versionsToTry << 420 << 410 << 400 << 330 << 150 << 140 << 130;
|
|
} else if (caps.ctxMajor == 4 && caps.ctxMinor == 1) {
|
|
versionsToTry << 410 << 400 << 330 << 150 << 140 << 130;
|
|
} else if (caps.ctxMajor == 4 && caps.ctxMinor == 0) {
|
|
versionsToTry << 400 << 330 << 150 << 140 << 130;
|
|
} else if (caps.ctxMajor == 3 && caps.ctxMinor == 3) {
|
|
versionsToTry << 330 << 150 << 140 << 130;
|
|
} else if (caps.ctxMajor == 3 && caps.ctxMinor == 2) {
|
|
versionsToTry << 150 << 140 << 130;
|
|
} else if (caps.ctxMajor == 3 && caps.ctxMinor == 1) {
|
|
versionsToTry << 140 << 130;
|
|
} else if (caps.ctxMajor == 3 && caps.ctxMinor == 0) {
|
|
versionsToTry << 130;
|
|
}
|
|
if (!caps.coreProfile)
|
|
versionsToTry << 120;
|
|
for (int v : versionsToTry) {
|
|
source = bakedShader.shader({ QShader::GlslShader, v, shaderStage.shaderVariant() }).shader();
|
|
if (!source.isEmpty()) {
|
|
if (glslVersion)
|
|
*glslVersion = v;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (source.isEmpty()) {
|
|
qWarning() << "No GLSL shader code found (versions tried: " << versionsToTry
|
|
<< ") in baked shader" << bakedShader;
|
|
}
|
|
return source;
|
|
}
|
|
|
|
bool QRhiGles2::compileShader(GLuint program, const QRhiShaderStage &shaderStage, int *glslVersion)
|
|
{
|
|
const QByteArray source = shaderSource(shaderStage, glslVersion);
|
|
if (source.isEmpty())
|
|
return false;
|
|
|
|
GLuint shader;
|
|
auto cacheIt = m_shaderCache.constFind(shaderStage);
|
|
if (cacheIt != m_shaderCache.constEnd()) {
|
|
shader = *cacheIt;
|
|
} else {
|
|
shader = f->glCreateShader(toGlShaderType(shaderStage.type()));
|
|
const char *srcStr = source.constData();
|
|
const GLint srcLength = source.count();
|
|
f->glShaderSource(shader, 1, &srcStr, &srcLength);
|
|
f->glCompileShader(shader);
|
|
GLint compiled = 0;
|
|
f->glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
|
|
if (!compiled) {
|
|
GLint infoLogLength = 0;
|
|
f->glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
|
|
QByteArray log;
|
|
if (infoLogLength > 1) {
|
|
GLsizei length = 0;
|
|
log.resize(infoLogLength);
|
|
f->glGetShaderInfoLog(shader, infoLogLength, &length, log.data());
|
|
}
|
|
qWarning("Failed to compile shader: %s\nSource was:\n%s", log.constData(), source.constData());
|
|
return false;
|
|
}
|
|
if (m_shaderCache.count() >= MAX_SHADER_CACHE_ENTRIES) {
|
|
// Use the simplest strategy: too many cached shaders -> drop them all.
|
|
for (uint shader : m_shaderCache)
|
|
f->glDeleteShader(shader); // does not actually get released yet when attached to a not-yet-released program
|
|
m_shaderCache.clear();
|
|
}
|
|
m_shaderCache.insert(shaderStage, shader);
|
|
}
|
|
|
|
f->glAttachShader(program, shader);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QRhiGles2::linkProgram(GLuint program)
|
|
{
|
|
f->glLinkProgram(program);
|
|
GLint linked = 0;
|
|
f->glGetProgramiv(program, GL_LINK_STATUS, &linked);
|
|
if (!linked) {
|
|
GLint infoLogLength = 0;
|
|
f->glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
|
|
QByteArray log;
|
|
if (infoLogLength > 1) {
|
|
GLsizei length = 0;
|
|
log.resize(infoLogLength);
|
|
f->glGetProgramInfoLog(program, infoLogLength, &length, log.data());
|
|
}
|
|
qWarning("Failed to link shader program: %s", log.constData());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void QRhiGles2::registerUniformIfActive(const QShaderDescription::BlockVariable &var,
|
|
const QByteArray &namePrefix,
|
|
int binding,
|
|
int baseOffset,
|
|
GLuint program,
|
|
QSet<int> *activeUniformLocations,
|
|
QGles2UniformDescriptionVector *dst)
|
|
{
|
|
if (var.type == QShaderDescription::Struct) {
|
|
qWarning("Nested structs are not supported at the moment. '%s' ignored.",
|
|
var.name.constData());
|
|
return;
|
|
}
|
|
QGles2UniformDescription uniform;
|
|
uniform.type = var.type;
|
|
const QByteArray name = namePrefix + var.name;
|
|
// Here we expect that the OpenGL implementation has proper active uniform
|
|
// handling, meaning that a uniform that is declared but not accessed
|
|
// elsewhere in the code is reported as -1 when querying the location. If
|
|
// that is not the case, it won't break anything, but we'll generate
|
|
// unnecessary glUniform* calls then.
|
|
uniform.glslLocation = f->glGetUniformLocation(program, name.constData());
|
|
if (uniform.glslLocation >= 0 && !activeUniformLocations->contains(uniform.glslLocation)) {
|
|
activeUniformLocations->insert(uniform.glslLocation);
|
|
if (var.arrayDims.count() > 1) {
|
|
qWarning("Array '%s' has more than one dimension. This is not supported.",
|
|
var.name.constData());
|
|
return;
|
|
}
|
|
uniform.binding = binding;
|
|
uniform.offset = uint(baseOffset + var.offset);
|
|
uniform.size = var.size;
|
|
uniform.arrayDim = var.arrayDims.isEmpty() ? 0 : var.arrayDims.first();
|
|
dst->append(uniform);
|
|
}
|
|
}
|
|
|
|
void QRhiGles2::gatherUniforms(GLuint program,
|
|
const QShaderDescription::UniformBlock &ub,
|
|
QSet<int> *activeUniformLocations,
|
|
QGles2UniformDescriptionVector *dst)
|
|
{
|
|
QByteArray prefix = ub.structName + '.';
|
|
for (const QShaderDescription::BlockVariable &blockMember : ub.members) {
|
|
if (blockMember.type == QShaderDescription::Struct) {
|
|
QByteArray structPrefix = prefix + blockMember.name;
|
|
|
|
const int baseOffset = blockMember.offset;
|
|
if (blockMember.arrayDims.isEmpty()) {
|
|
for (const QShaderDescription::BlockVariable &structMember : blockMember.structMembers)
|
|
registerUniformIfActive(structMember, structPrefix + ".", ub.binding,
|
|
baseOffset, program, activeUniformLocations, dst);
|
|
} else {
|
|
if (blockMember.arrayDims.count() > 1) {
|
|
qWarning("Array of struct '%s' has more than one dimension. Only the first "
|
|
"dimension is used.",
|
|
blockMember.name.constData());
|
|
}
|
|
const int dim = blockMember.arrayDims.first();
|
|
const int elemSize = blockMember.size / dim;
|
|
int elemOffset = baseOffset;
|
|
for (int di = 0; di < dim; ++di) {
|
|
const QByteArray arrayPrefix = structPrefix + '[' + QByteArray::number(di) + ']' + '.';
|
|
for (const QShaderDescription::BlockVariable &structMember : blockMember.structMembers)
|
|
registerUniformIfActive(structMember, arrayPrefix, ub.binding, elemOffset, program, activeUniformLocations, dst);
|
|
elemOffset += elemSize;
|
|
}
|
|
}
|
|
} else {
|
|
registerUniformIfActive(blockMember, prefix, ub.binding, 0, program, activeUniformLocations, dst);
|
|
}
|
|
}
|
|
}
|
|
|
|
void QRhiGles2::gatherSamplers(GLuint program,
|
|
const QShaderDescription::InOutVariable &v,
|
|
QGles2SamplerDescriptionVector *dst)
|
|
{
|
|
QGles2SamplerDescription sampler;
|
|
sampler.glslLocation = f->glGetUniformLocation(program, v.name.constData());
|
|
if (sampler.glslLocation >= 0) {
|
|
sampler.binding = v.binding;
|
|
dst->append(sampler);
|
|
}
|
|
}
|
|
|
|
bool QRhiGles2::isProgramBinaryDiskCacheEnabled() const
|
|
{
|
|
static QOpenGLProgramBinarySupportCheckWrapper checker;
|
|
return checker.get(ctx)->isSupported();
|
|
}
|
|
|
|
Q_GLOBAL_STATIC(QOpenGLProgramBinaryCache, qrhi_programBinaryCache);
|
|
|
|
static inline QShader::Stage toShaderStage(QRhiShaderStage::Type type)
|
|
{
|
|
switch (type) {
|
|
case QRhiShaderStage::Vertex:
|
|
return QShader::VertexStage;
|
|
case QRhiShaderStage::Fragment:
|
|
return QShader::FragmentStage;
|
|
case QRhiShaderStage::Compute:
|
|
return QShader::ComputeStage;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
return QShader::VertexStage;
|
|
}
|
|
}
|
|
|
|
QRhiGles2::DiskCacheResult QRhiGles2::tryLoadFromDiskCache(const QRhiShaderStage *stages,
|
|
int stageCount,
|
|
GLuint program,
|
|
const QVector<QShaderDescription::InOutVariable> &inputVars,
|
|
QByteArray *cacheKey)
|
|
{
|
|
QRhiGles2::DiskCacheResult result = QRhiGles2::DiskCacheMiss;
|
|
QByteArray diskCacheKey;
|
|
|
|
if (isProgramBinaryDiskCacheEnabled()) {
|
|
QOpenGLProgramBinaryCache::ProgramDesc binaryProgram;
|
|
for (int i = 0; i < stageCount; ++i) {
|
|
const QRhiShaderStage &stage(stages[i]);
|
|
QByteArray source = shaderSource(stage, nullptr);
|
|
if (source.isEmpty())
|
|
return QRhiGles2::DiskCacheError;
|
|
|
|
if (stage.type() == QRhiShaderStage::Vertex) {
|
|
// Now add something to the key that indicates the vertex input locations.
|
|
// A GLSL shader lower than 330 (150, 140, ...) will not have location
|
|
// qualifiers. This means that the shader source code is the same
|
|
// regardless of what locations inputVars contains. This becomes a problem
|
|
// because we'll glBindAttribLocation the shader based on inputVars, but
|
|
// that's only when compiling/linking when there was no cache hit. Picking
|
|
// from the cache afterwards should take the input locations into account
|
|
// since if inputVars has now different locations for the attributes, then
|
|
// it is not ok to reuse a program binary that used different attribute
|
|
// locations. For a lot of clients this would not be an issue since they
|
|
// typically hardcode and use the same vertex locations on every run. Some
|
|
// systems that dynamically generate shaders may end up with a non-stable
|
|
// order (and so location numbers), however. This is sub-optimal because
|
|
// it makes caching inefficient, and said clients should be fixed, but in
|
|
// any case this should not break rendering. Hence including the locations
|
|
// in the cache key.
|
|
QMap<QByteArray, int> inputLocations; // sorted by key when iterating
|
|
for (const QShaderDescription::InOutVariable &var : inputVars)
|
|
inputLocations.insert(var.name, var.location);
|
|
source += QByteArrayLiteral("\n // "); // just to be nice; treated as an arbitrary string regardless
|
|
for (auto it = inputLocations.cbegin(), end = inputLocations.cend(); it != end; ++it) {
|
|
source += it.key();
|
|
source += QByteArray::number(it.value());
|
|
}
|
|
source += QByteArrayLiteral("\n");
|
|
}
|
|
|
|
binaryProgram.shaders.append(QOpenGLProgramBinaryCache::ShaderDesc(toShaderStage(stage.type()), source));
|
|
}
|
|
|
|
diskCacheKey = binaryProgram.cacheKey();
|
|
|
|
if (qrhi_programBinaryCache()->load(diskCacheKey, program)) {
|
|
qCDebug(lcOpenGLProgramDiskCache, "Program binary received from cache, program %u, key %s",
|
|
program, diskCacheKey.constData());
|
|
result = QRhiGles2::DiskCacheHit;
|
|
}
|
|
}
|
|
|
|
if (cacheKey)
|
|
*cacheKey = diskCacheKey;
|
|
|
|
return result;
|
|
}
|
|
|
|
void QRhiGles2::trySaveToDiskCache(GLuint program, const QByteArray &cacheKey)
|
|
{
|
|
if (isProgramBinaryDiskCacheEnabled()) {
|
|
qCDebug(lcOpenGLProgramDiskCache, "Saving program binary, program %u, key %s",
|
|
program, cacheKey.constData());
|
|
qrhi_programBinaryCache()->save(cacheKey, program);
|
|
}
|
|
}
|
|
|
|
QGles2Buffer::QGles2Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size)
|
|
: QRhiBuffer(rhi, type, usage, size)
|
|
{
|
|
}
|
|
|
|
QGles2Buffer::~QGles2Buffer()
|
|
{
|
|
destroy();
|
|
}
|
|
|
|
void QGles2Buffer::destroy()
|
|
{
|
|
if (!buffer)
|
|
return;
|
|
|
|
QRhiGles2::DeferredReleaseEntry e;
|
|
e.type = QRhiGles2::DeferredReleaseEntry::Buffer;
|
|
|
|
e.buffer.buffer = buffer;
|
|
|
|
buffer = 0;
|
|
delete[] ubuf;
|
|
ubuf = nullptr;
|
|
|
|
QRHI_RES_RHI(QRhiGles2);
|
|
rhiD->releaseQueue.append(e);
|
|
QRHI_PROF;
|
|
QRHI_PROF_F(releaseBuffer(this));
|
|
rhiD->unregisterResource(this);
|
|
}
|
|
|
|
bool QGles2Buffer::create()
|
|
{
|
|
if (buffer)
|
|
destroy();
|
|
|
|
QRHI_RES_RHI(QRhiGles2);
|
|
QRHI_PROF;
|
|
|
|
const int nonZeroSize = m_size <= 0 ? 256 : m_size;
|
|
|
|
if (m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
|
|
if (int(m_usage) != QRhiBuffer::UniformBuffer) {
|
|
qWarning("Uniform buffer: multiple usages specified, this is not supported by the OpenGL backend");
|
|
return false;
|
|
}
|
|
ubuf = new char[nonZeroSize];
|
|
QRHI_PROF_F(newBuffer(this, uint(nonZeroSize), 0, 1));
|
|
return true;
|
|
}
|
|
|
|
if (!rhiD->ensureContext())
|
|
return false;
|
|
|
|
targetForDataOps = GL_ARRAY_BUFFER;
|
|
if (m_usage.testFlag(QRhiBuffer::IndexBuffer))
|
|
targetForDataOps = GL_ELEMENT_ARRAY_BUFFER;
|
|
else if (m_usage.testFlag(QRhiBuffer::StorageBuffer))
|
|
targetForDataOps = GL_SHADER_STORAGE_BUFFER;
|
|
|
|
rhiD->f->glGenBuffers(1, &buffer);
|
|
rhiD->f->glBindBuffer(targetForDataOps, buffer);
|
|
rhiD->f->glBufferData(targetForDataOps, nonZeroSize, nullptr, m_type == Dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
|
|
|
|
usageState.access = AccessNone;
|
|
|
|
QRHI_PROF_F(newBuffer(this, uint(nonZeroSize), 1, 0));
|
|
rhiD->registerResource(this);
|
|
return true;
|
|
}
|
|
|
|
QRhiBuffer::NativeBuffer QGles2Buffer::nativeBuffer()
|
|
{
|
|
if (m_usage.testFlag(QRhiBuffer::UniformBuffer))
|
|
return { {}, 0 };
|
|
|
|
return { { &buffer }, 1 };
|
|
}
|
|
|
|
QGles2RenderBuffer::QGles2RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
|
|
int sampleCount, QRhiRenderBuffer::Flags flags,
|
|
QRhiTexture::Format backingFormatHint)
|
|
: QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags, backingFormatHint)
|
|
{
|
|
}
|
|
|
|
QGles2RenderBuffer::~QGles2RenderBuffer()
|
|
{
|
|
destroy();
|
|
}
|
|
|
|
void QGles2RenderBuffer::destroy()
|
|
{
|
|
if (!renderbuffer)
|
|
return;
|
|
|
|
QRhiGles2::DeferredReleaseEntry e;
|
|
e.type = QRhiGles2::DeferredReleaseEntry::RenderBuffer;
|
|
|
|
e.renderbuffer.renderbuffer = renderbuffer;
|
|
e.renderbuffer.renderbuffer2 = stencilRenderbuffer;
|
|
|
|
renderbuffer = 0;
|
|
stencilRenderbuffer = 0;
|
|
|
|
QRHI_RES_RHI(QRhiGles2);
|
|
rhiD->releaseQueue.append(e);
|
|
QRHI_PROF;
|
|
QRHI_PROF_F(releaseRenderBuffer(this));
|
|
rhiD->unregisterResource(this);
|
|
}
|
|
|
|
bool QGles2RenderBuffer::create()
|
|
{
|
|
if (renderbuffer)
|
|
destroy();
|
|
|
|
QRHI_RES_RHI(QRhiGles2);
|
|
QRHI_PROF;
|
|
samples = rhiD->effectiveSampleCount(m_sampleCount);
|
|
|
|
if (m_flags.testFlag(UsedWithSwapChainOnly)) {
|
|
if (m_type == DepthStencil) {
|
|
QRHI_PROF_F(newRenderBuffer(this, false, true, samples));
|
|
return true;
|
|
}
|
|
|
|
qWarning("RenderBuffer: UsedWithSwapChainOnly is meaningless in combination with Color");
|
|
}
|
|
|
|
if (!rhiD->ensureContext())
|
|
return false;
|
|
|
|
rhiD->f->glGenRenderbuffers(1, &renderbuffer);
|
|
rhiD->f->glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
|
|
|
|
const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
|
|
|
|
switch (m_type) {
|
|
case QRhiRenderBuffer::DepthStencil:
|
|
if (rhiD->caps.msaaRenderBuffer && samples > 1) {
|
|
rhiD->f->glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH24_STENCIL8,
|
|
size.width(), size.height());
|
|
stencilRenderbuffer = 0;
|
|
} else if (rhiD->caps.packedDepthStencil || rhiD->caps.needsDepthStencilCombinedAttach) {
|
|
const GLenum storage = rhiD->caps.needsDepthStencilCombinedAttach ? GL_DEPTH_STENCIL : GL_DEPTH24_STENCIL8;
|
|
rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, storage,
|
|
size.width(), size.height());
|
|
stencilRenderbuffer = 0;
|
|
} else {
|
|
GLenum depthStorage = GL_DEPTH_COMPONENT;
|
|
if (rhiD->caps.gles) {
|
|
if (rhiD->caps.depth24)
|
|
depthStorage = GL_DEPTH_COMPONENT24;
|
|
else
|
|
depthStorage = GL_DEPTH_COMPONENT16; // plain ES 2.0 only has this
|
|
}
|
|
const GLenum stencilStorage = rhiD->caps.gles ? GL_STENCIL_INDEX8 : GL_STENCIL_INDEX;
|
|
rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, depthStorage,
|
|
size.width(), size.height());
|
|
rhiD->f->glGenRenderbuffers(1, &stencilRenderbuffer);
|
|
rhiD->f->glBindRenderbuffer(GL_RENDERBUFFER, stencilRenderbuffer);
|
|
rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, stencilStorage,
|
|
size.width(), size.height());
|
|
}
|
|
QRHI_PROF_F(newRenderBuffer(this, false, false, samples));
|
|
break;
|
|
case QRhiRenderBuffer::Color:
|
|
{
|
|
GLenum internalFormat = GL_RGBA4; // ES 2.0
|
|
if (rhiD->caps.rgba8Format) {
|
|
internalFormat = GL_RGBA8;
|
|
if (m_backingFormatHint != QRhiTexture::UnknownFormat) {
|
|
GLenum glintformat, glformat, gltype;
|
|
// only care about the sized internal format, the rest is not used here
|
|
toGlTextureFormat(m_backingFormatHint, rhiD->caps,
|
|
&glintformat, &internalFormat, &glformat, &gltype);
|
|
}
|
|
}
|
|
if (rhiD->caps.msaaRenderBuffer && samples > 1) {
|
|
rhiD->f->glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, internalFormat,
|
|
size.width(), size.height());
|
|
} else {
|
|
rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, internalFormat,
|
|
size.width(), size.height());
|
|
}
|
|
QRHI_PROF_F(newRenderBuffer(this, false, false, samples));
|
|
}
|
|
break;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
break;
|
|
}
|
|
|
|
rhiD->registerResource(this);
|
|
return true;
|
|
}
|
|
|
|
QRhiTexture::Format QGles2RenderBuffer::backingFormat() const
|
|
{
|
|
if (m_backingFormatHint != QRhiTexture::UnknownFormat)
|
|
return m_backingFormatHint;
|
|
else
|
|
return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
|
|
}
|
|
|
|
QGles2Texture::QGles2Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
|
|
int sampleCount, Flags flags)
|
|
: QRhiTexture(rhi, format, pixelSize, sampleCount, flags)
|
|
{
|
|
}
|
|
|
|
QGles2Texture::~QGles2Texture()
|
|
{
|
|
destroy();
|
|
}
|
|
|
|
void QGles2Texture::destroy()
|
|
{
|
|
if (!texture)
|
|
return;
|
|
|
|
QRhiGles2::DeferredReleaseEntry e;
|
|
e.type = QRhiGles2::DeferredReleaseEntry::Texture;
|
|
|
|
e.texture.texture = texture;
|
|
|
|
texture = 0;
|
|
specified = false;
|
|
compressedAtlasBuilt = false;
|
|
|
|
QRHI_RES_RHI(QRhiGles2);
|
|
if (owns)
|
|
rhiD->releaseQueue.append(e);
|
|
QRHI_PROF;
|
|
QRHI_PROF_F(releaseTexture(this));
|
|
rhiD->unregisterResource(this);
|
|
}
|
|
|
|
bool QGles2Texture::prepareCreate(QSize *adjustedSize)
|
|
{
|
|
if (texture)
|
|
destroy();
|
|
|
|
QRHI_RES_RHI(QRhiGles2);
|
|
if (!rhiD->ensureContext())
|
|
return false;
|
|
|
|
const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
|
|
|
|
const bool isCube = m_flags.testFlag(CubeMap);
|
|
const bool hasMipMaps = m_flags.testFlag(MipMapped);
|
|
const bool isCompressed = rhiD->isCompressedFormat(m_format);
|
|
|
|
target = isCube ? GL_TEXTURE_CUBE_MAP
|
|
: m_sampleCount > 1 ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
|
|
mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
|
|
gltype = GL_UNSIGNED_BYTE;
|
|
|
|
if (isCompressed) {
|
|
if (m_flags.testFlag(UsedWithLoadStore)) {
|
|
qWarning("Compressed texture cannot be used with image load/store");
|
|
return false;
|
|
}
|
|
glintformat = toGlCompressedTextureFormat(m_format, m_flags);
|
|
if (!glintformat) {
|
|
qWarning("Compressed format %d not mappable to GL compressed format", m_format);
|
|
return false;
|
|
}
|
|
glsizedintformat = glintformat;
|
|
glformat = GL_RGBA;
|
|
} else {
|
|
toGlTextureFormat(m_format, rhiD->caps,
|
|
&glintformat, &glsizedintformat, &glformat, &gltype);
|
|
}
|
|
|
|
samplerState = QGles2SamplerData();
|
|
|
|
usageState.access = AccessNone;
|
|
|
|
if (adjustedSize)
|
|
*adjustedSize = size;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QGles2Texture::create()
|
|
{
|
|
QSize size;
|
|
if (!prepareCreate(&size))
|
|
return false;
|
|
|
|
QRHI_RES_RHI(QRhiGles2);
|
|
rhiD->f->glGenTextures(1, &texture);
|
|
|
|
const bool isCube = m_flags.testFlag(CubeMap);
|
|
const bool hasMipMaps = m_flags.testFlag(MipMapped);
|
|
const bool isCompressed = rhiD->isCompressedFormat(m_format);
|
|
if (!isCompressed) {
|
|
rhiD->f->glBindTexture(target, texture);
|
|
if (!m_flags.testFlag(UsedWithLoadStore)) {
|
|
if (hasMipMaps || isCube) {
|
|
const GLenum faceTargetBase = isCube ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : target;
|
|
for (int layer = 0, layerCount = isCube ? 6 : 1; layer != layerCount; ++layer) {
|
|
for (int level = 0; level != mipLevelCount; ++level) {
|
|
const QSize mipSize = rhiD->q->sizeForMipLevel(level, size);
|
|
rhiD->f->glTexImage2D(faceTargetBase + uint(layer), level, GLint(glintformat),
|
|
mipSize.width(), mipSize.height(), 0,
|
|
glformat, gltype, nullptr);
|
|
}
|
|
}
|
|
} else {
|
|
rhiD->f->glTexImage2D(target, 0, GLint(glintformat), size.width(), size.height(),
|
|
0, glformat, gltype, nullptr);
|
|
}
|
|
} else {
|
|
// Must be specified with immutable storage functions otherwise
|
|
// bindImageTexture may fail. Also, the internal format must be a
|
|
// sized format here.
|
|
rhiD->f->glTexStorage2D(target, mipLevelCount, glsizedintformat, size.width(), size.height());
|
|
}
|
|
specified = true;
|
|
} else {
|
|
// Cannot use glCompressedTexImage2D without valid data, so defer.
|
|
// Compressed textures will not be used as render targets so this is
|
|
// not an issue.
|
|
specified = false;
|
|
}
|
|
|
|
QRHI_PROF;
|
|
QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, 1));
|
|
|
|
owns = true;
|
|
|
|
generation += 1;
|
|
rhiD->registerResource(this);
|
|
return true;
|
|
}
|
|
|
|
bool QGles2Texture::createFrom(QRhiTexture::NativeTexture src)
|
|
{
|
|
const uint textureId = uint(src.object);
|
|
if (textureId == 0)
|
|
return false;
|
|
|
|
if (!prepareCreate())
|
|
return false;
|
|
|
|
texture = textureId;
|
|
specified = true;
|
|
compressedAtlasBuilt = true;
|
|
|
|
QRHI_RES_RHI(QRhiGles2);
|
|
QRHI_PROF;
|
|
QRHI_PROF_F(newTexture(this, false, mipLevelCount, m_flags.testFlag(CubeMap) ? 6 : 1, 1));
|
|
|
|
owns = false;
|
|
|
|
generation += 1;
|
|
rhiD->registerResource(this);
|
|
return true;
|
|
}
|
|
|
|
QRhiTexture::NativeTexture QGles2Texture::nativeTexture()
|
|
{
|
|
return {texture, 0};
|
|
}
|
|
|
|
QGles2Sampler::QGles2Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
|
|
AddressMode u, AddressMode v, AddressMode w)
|
|
: QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w)
|
|
{
|
|
}
|
|
|
|
QGles2Sampler::~QGles2Sampler()
|
|
{
|
|
destroy();
|
|
}
|
|
|
|
void QGles2Sampler::destroy()
|
|
{
|
|
// nothing to do here
|
|
}
|
|
|
|
bool QGles2Sampler::create()
|
|
{
|
|
d.glminfilter = toGlMinFilter(m_minFilter, m_mipmapMode);
|
|
d.glmagfilter = toGlMagFilter(m_magFilter);
|
|
d.glwraps = toGlWrapMode(m_addressU);
|
|
d.glwrapt = toGlWrapMode(m_addressV);
|
|
d.glwrapr = toGlWrapMode(m_addressW);
|
|
d.gltexcomparefunc = toGlTextureCompareFunc(m_compareOp);
|
|
|
|
generation += 1;
|
|
return true;
|
|
}
|
|
|
|
// dummy, no Vulkan-style RenderPass+Framebuffer concept here
|
|
QGles2RenderPassDescriptor::QGles2RenderPassDescriptor(QRhiImplementation *rhi)
|
|
: QRhiRenderPassDescriptor(rhi)
|
|
{
|
|
}
|
|
|
|
QGles2RenderPassDescriptor::~QGles2RenderPassDescriptor()
|
|
{
|
|
destroy();
|
|
}
|
|
|
|
void QGles2RenderPassDescriptor::destroy()
|
|
{
|
|
// nothing to do here
|
|
}
|
|
|
|
bool QGles2RenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const
|
|
{
|
|
Q_UNUSED(other);
|
|
return true;
|
|
}
|
|
|
|
QGles2ReferenceRenderTarget::QGles2ReferenceRenderTarget(QRhiImplementation *rhi)
|
|
: QRhiRenderTarget(rhi),
|
|
d(rhi)
|
|
{
|
|
}
|
|
|
|
QGles2ReferenceRenderTarget::~QGles2ReferenceRenderTarget()
|
|
{
|
|
destroy();
|
|
}
|
|
|
|
void QGles2ReferenceRenderTarget::destroy()
|
|
{
|
|
// nothing to do here
|
|
}
|
|
|
|
QSize QGles2ReferenceRenderTarget::pixelSize() const
|
|
{
|
|
return d.pixelSize;
|
|
}
|
|
|
|
float QGles2ReferenceRenderTarget::devicePixelRatio() const
|
|
{
|
|
return d.dpr;
|
|
}
|
|
|
|
int QGles2ReferenceRenderTarget::sampleCount() const
|
|
{
|
|
return d.sampleCount;
|
|
}
|
|
|
|
QGles2TextureRenderTarget::QGles2TextureRenderTarget(QRhiImplementation *rhi,
|
|
const QRhiTextureRenderTargetDescription &desc,
|
|
Flags flags)
|
|
: QRhiTextureRenderTarget(rhi, desc, flags),
|
|
d(rhi)
|
|
{
|
|
}
|
|
|
|
QGles2TextureRenderTarget::~QGles2TextureRenderTarget()
|
|
{
|
|
destroy();
|
|
}
|
|
|
|
void QGles2TextureRenderTarget::destroy()
|
|
{
|
|
if (!framebuffer)
|
|
return;
|
|
|
|
QRhiGles2::DeferredReleaseEntry e;
|
|
e.type = QRhiGles2::DeferredReleaseEntry::TextureRenderTarget;
|
|
|
|
e.textureRenderTarget.framebuffer = framebuffer;
|
|
|
|
framebuffer = 0;
|
|
|
|
QRHI_RES_RHI(QRhiGles2);
|
|
rhiD->releaseQueue.append(e);
|
|
|
|
rhiD->unregisterResource(this);
|
|
}
|
|
|
|
QRhiRenderPassDescriptor *QGles2TextureRenderTarget::newCompatibleRenderPassDescriptor()
|
|
{
|
|
return new QGles2RenderPassDescriptor(m_rhi);
|
|
}
|
|
|
|
bool QGles2TextureRenderTarget::create()
|
|
{
|
|
QRHI_RES_RHI(QRhiGles2);
|
|
|
|
if (framebuffer)
|
|
destroy();
|
|
|
|
const bool hasColorAttachments = m_desc.cbeginColorAttachments() != m_desc.cendColorAttachments();
|
|
Q_ASSERT(hasColorAttachments || m_desc.depthTexture());
|
|
Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
|
|
const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
|
|
|
|
if (hasColorAttachments) {
|
|
const int count = m_desc.cendColorAttachments() - m_desc.cbeginColorAttachments();
|
|
if (count > rhiD->caps.maxDrawBuffers) {
|
|
qWarning("QGles2TextureRenderTarget: Too many color attachments (%d, max is %d)",
|
|
count, rhiD->caps.maxDrawBuffers);
|
|
}
|
|
}
|
|
if (m_desc.depthTexture() && !rhiD->caps.depthTexture)
|
|
qWarning("QGles2TextureRenderTarget: Depth texture is not supported and will be ignored");
|
|
|
|
if (!rhiD->ensureContext())
|
|
return false;
|
|
|
|
rhiD->f->glGenFramebuffers(1, &framebuffer);
|
|
rhiD->f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
|
|
|
d.colorAttCount = 0;
|
|
int attIndex = 0;
|
|
for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) {
|
|
d.colorAttCount += 1;
|
|
const QRhiColorAttachment &colorAtt(*it);
|
|
QRhiTexture *texture = colorAtt.texture();
|
|
QRhiRenderBuffer *renderBuffer = colorAtt.renderBuffer();
|
|
Q_ASSERT(texture || renderBuffer);
|
|
if (texture) {
|
|
QGles2Texture *texD = QRHI_RES(QGles2Texture, texture);
|
|
Q_ASSERT(texD->texture && texD->specified);
|
|
const GLenum faceTargetBase = texD->flags().testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target;
|
|
rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), faceTargetBase + uint(colorAtt.layer()),
|
|
texD->texture, colorAtt.level());
|
|
if (attIndex == 0) {
|
|
d.pixelSize = rhiD->q->sizeForMipLevel(colorAtt.level(), texD->pixelSize());
|
|
d.sampleCount = 1;
|
|
}
|
|
} else if (renderBuffer) {
|
|
QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, renderBuffer);
|
|
rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), GL_RENDERBUFFER, rbD->renderbuffer);
|
|
if (attIndex == 0) {
|
|
d.pixelSize = rbD->pixelSize();
|
|
d.sampleCount = rbD->samples;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasDepthStencil) {
|
|
if (m_desc.depthStencilBuffer()) {
|
|
QGles2RenderBuffer *depthRbD = QRHI_RES(QGles2RenderBuffer, m_desc.depthStencilBuffer());
|
|
if (rhiD->caps.needsDepthStencilCombinedAttach) {
|
|
rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
|
|
depthRbD->renderbuffer);
|
|
} else {
|
|
rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
|
|
depthRbD->renderbuffer);
|
|
if (depthRbD->stencilRenderbuffer)
|
|
rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
|
|
depthRbD->stencilRenderbuffer);
|
|
else // packed
|
|
rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
|
|
depthRbD->renderbuffer);
|
|
}
|
|
if (d.colorAttCount == 0) {
|
|
d.pixelSize = depthRbD->pixelSize();
|
|
d.sampleCount = depthRbD->samples;
|
|
}
|
|
} else {
|
|
QGles2Texture *depthTexD = QRHI_RES(QGles2Texture, m_desc.depthTexture());
|
|
rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexD->target,
|
|
depthTexD->texture, 0);
|
|
if (d.colorAttCount == 0) {
|
|
d.pixelSize = depthTexD->pixelSize();
|
|
d.sampleCount = 1;
|
|
}
|
|
}
|
|
d.dsAttCount = 1;
|
|
} else {
|
|
d.dsAttCount = 0;
|
|
}
|
|
|
|
d.dpr = 1;
|
|
d.rp = QRHI_RES(QGles2RenderPassDescriptor, m_renderPassDesc);
|
|
|
|
GLenum status = rhiD->f->glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
if (status != GL_NO_ERROR && status != GL_FRAMEBUFFER_COMPLETE) {
|
|
qWarning("Framebuffer incomplete: 0x%x", status);
|
|
return false;
|
|
}
|
|
|
|
rhiD->registerResource(this);
|
|
return true;
|
|
}
|
|
|
|
QSize QGles2TextureRenderTarget::pixelSize() const
|
|
{
|
|
return d.pixelSize;
|
|
}
|
|
|
|
float QGles2TextureRenderTarget::devicePixelRatio() const
|
|
{
|
|
return d.dpr;
|
|
}
|
|
|
|
int QGles2TextureRenderTarget::sampleCount() const
|
|
{
|
|
return d.sampleCount;
|
|
}
|
|
|
|
QGles2ShaderResourceBindings::QGles2ShaderResourceBindings(QRhiImplementation *rhi)
|
|
: QRhiShaderResourceBindings(rhi)
|
|
{
|
|
}
|
|
|
|
QGles2ShaderResourceBindings::~QGles2ShaderResourceBindings()
|
|
{
|
|
destroy();
|
|
}
|
|
|
|
void QGles2ShaderResourceBindings::destroy()
|
|
{
|
|
// nothing to do here
|
|
}
|
|
|
|
bool QGles2ShaderResourceBindings::create()
|
|
{
|
|
QRHI_RES_RHI(QRhiGles2);
|
|
if (!rhiD->sanityCheckShaderResourceBindings(this))
|
|
return false;
|
|
|
|
rhiD->updateLayoutDesc(this);
|
|
|
|
generation += 1;
|
|
return true;
|
|
}
|
|
|
|
QGles2GraphicsPipeline::QGles2GraphicsPipeline(QRhiImplementation *rhi)
|
|
: QRhiGraphicsPipeline(rhi)
|
|
{
|
|
}
|
|
|
|
QGles2GraphicsPipeline::~QGles2GraphicsPipeline()
|
|
{
|
|
destroy();
|
|
}
|
|
|
|
void QGles2GraphicsPipeline::destroy()
|
|
{
|
|
if (!program)
|
|
return;
|
|
|
|
QRhiGles2::DeferredReleaseEntry e;
|
|
e.type = QRhiGles2::DeferredReleaseEntry::Pipeline;
|
|
|
|
e.pipeline.program = program;
|
|
|
|
program = 0;
|
|
uniforms.clear();
|
|
samplers.clear();
|
|
|
|
QRHI_RES_RHI(QRhiGles2);
|
|
rhiD->releaseQueue.append(e);
|
|
|
|
rhiD->unregisterResource(this);
|
|
}
|
|
|
|
bool QGles2GraphicsPipeline::create()
|
|
{
|
|
QRHI_RES_RHI(QRhiGles2);
|
|
|
|
if (program)
|
|
destroy();
|
|
|
|
if (!rhiD->ensureContext())
|
|
return false;
|
|
|
|
if (!rhiD->sanityCheckGraphicsPipeline(this))
|
|
return false;
|
|
|
|
drawMode = toGlTopology(m_topology);
|
|
|
|
program = rhiD->f->glCreateProgram();
|
|
|
|
QShaderDescription vsDesc;
|
|
QShaderDescription fsDesc;
|
|
for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) {
|
|
if (shaderStage.type() == QRhiShaderStage::Vertex)
|
|
vsDesc = shaderStage.shader().description();
|
|
else if (shaderStage.type() == QRhiShaderStage::Fragment)
|
|
fsDesc = shaderStage.shader().description();
|
|
}
|
|
|
|
QByteArray diskCacheKey;
|
|
QRhiGles2::DiskCacheResult diskCacheResult = rhiD->tryLoadFromDiskCache(m_shaderStages.constData(),
|
|
m_shaderStages.count(),
|
|
program,
|
|
vsDesc.inputVariables(),
|
|
&diskCacheKey);
|
|
if (diskCacheResult == QRhiGles2::DiskCacheError)
|
|
return false;
|
|
|
|
if (diskCacheResult == QRhiGles2::DiskCacheMiss) {
|
|
for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) {
|
|
if (shaderStage.type() == QRhiShaderStage::Vertex) {
|
|
if (!rhiD->compileShader(program, shaderStage, nullptr))
|
|
return false;
|
|
} else if (shaderStage.type() == QRhiShaderStage::Fragment) {
|
|
if (!rhiD->compileShader(program, shaderStage, nullptr))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// important when GLSL <= 150 is used that does not have location qualifiers
|
|
for (const QShaderDescription::InOutVariable &inVar : vsDesc.inputVariables())
|
|
rhiD->f->glBindAttribLocation(program, GLuint(inVar.location), inVar.name);
|
|
|
|
if (!rhiD->linkProgram(program))
|
|
return false;
|
|
|
|
rhiD->trySaveToDiskCache(program, diskCacheKey);
|
|
}
|
|
|
|
// Use the same work area for the vertex & fragment stages, thus ensuring
|
|
// that we will not do superfluous glUniform calls for uniforms that are
|
|
// present in both shaders.
|
|
QSet<int> activeUniformLocations;
|
|
|
|
for (const QShaderDescription::UniformBlock &ub : vsDesc.uniformBlocks())
|
|
rhiD->gatherUniforms(program, ub, &activeUniformLocations, &uniforms);
|
|
|
|
for (const QShaderDescription::UniformBlock &ub : fsDesc.uniformBlocks())
|
|
rhiD->gatherUniforms(program, ub, &activeUniformLocations, &uniforms);
|
|
|
|
std::sort(uniforms.begin(), uniforms.end(),
|
|
[](const QGles2UniformDescription &a, const QGles2UniformDescription &b)
|
|
{
|
|
return a.offset < b.offset;
|
|
});
|
|
|
|
for (const QShaderDescription::InOutVariable &v : vsDesc.combinedImageSamplers())
|
|
rhiD->gatherSamplers(program, v, &samplers);
|
|
|
|
for (const QShaderDescription::InOutVariable &v : fsDesc.combinedImageSamplers())
|
|
rhiD->gatherSamplers(program, v, &samplers);
|
|
|
|
memset(uniformState, 0, sizeof(uniformState));
|
|
|
|
generation += 1;
|
|
rhiD->registerResource(this);
|
|
return true;
|
|
}
|
|
|
|
QGles2ComputePipeline::QGles2ComputePipeline(QRhiImplementation *rhi)
|
|
: QRhiComputePipeline(rhi)
|
|
{
|
|
}
|
|
|
|
QGles2ComputePipeline::~QGles2ComputePipeline()
|
|
{
|
|
destroy();
|
|
}
|
|
|
|
void QGles2ComputePipeline::destroy()
|
|
{
|
|
if (!program)
|
|
return;
|
|
|
|
QRhiGles2::DeferredReleaseEntry e;
|
|
e.type = QRhiGles2::DeferredReleaseEntry::Pipeline;
|
|
|
|
e.pipeline.program = program;
|
|
|
|
program = 0;
|
|
uniforms.clear();
|
|
samplers.clear();
|
|
|
|
QRHI_RES_RHI(QRhiGles2);
|
|
rhiD->releaseQueue.append(e);
|
|
|
|
rhiD->unregisterResource(this);
|
|
}
|
|
|
|
bool QGles2ComputePipeline::create()
|
|
{
|
|
QRHI_RES_RHI(QRhiGles2);
|
|
|
|
if (program)
|
|
destroy();
|
|
|
|
if (!rhiD->ensureContext())
|
|
return false;
|
|
|
|
const QShaderDescription csDesc = m_shaderStage.shader().description();
|
|
program = rhiD->f->glCreateProgram();
|
|
|
|
QByteArray diskCacheKey;
|
|
QRhiGles2::DiskCacheResult diskCacheResult = rhiD->tryLoadFromDiskCache(&m_shaderStage, 1, program, {}, &diskCacheKey);
|
|
if (diskCacheResult == QRhiGles2::DiskCacheError)
|
|
return false;
|
|
|
|
if (diskCacheResult == QRhiGles2::DiskCacheMiss) {
|
|
if (!rhiD->compileShader(program, m_shaderStage, nullptr))
|
|
return false;
|
|
|
|
if (!rhiD->linkProgram(program))
|
|
return false;
|
|
|
|
rhiD->trySaveToDiskCache(program, diskCacheKey);
|
|
}
|
|
|
|
QSet<int> activeUniformLocations;
|
|
for (const QShaderDescription::UniformBlock &ub : csDesc.uniformBlocks())
|
|
rhiD->gatherUniforms(program, ub, &activeUniformLocations, &uniforms);
|
|
for (const QShaderDescription::InOutVariable &v : csDesc.combinedImageSamplers())
|
|
rhiD->gatherSamplers(program, v, &samplers);
|
|
|
|
// storage images and buffers need no special steps here
|
|
|
|
memset(uniformState, 0, sizeof(uniformState));
|
|
|
|
generation += 1;
|
|
rhiD->registerResource(this);
|
|
return true;
|
|
}
|
|
|
|
QGles2CommandBuffer::QGles2CommandBuffer(QRhiImplementation *rhi)
|
|
: QRhiCommandBuffer(rhi)
|
|
{
|
|
resetState();
|
|
}
|
|
|
|
QGles2CommandBuffer::~QGles2CommandBuffer()
|
|
{
|
|
destroy();
|
|
}
|
|
|
|
void QGles2CommandBuffer::destroy()
|
|
{
|
|
// nothing to do here
|
|
}
|
|
|
|
QGles2SwapChain::QGles2SwapChain(QRhiImplementation *rhi)
|
|
: QRhiSwapChain(rhi),
|
|
rt(rhi),
|
|
cb(rhi)
|
|
{
|
|
}
|
|
|
|
QGles2SwapChain::~QGles2SwapChain()
|
|
{
|
|
destroy();
|
|
}
|
|
|
|
void QGles2SwapChain::destroy()
|
|
{
|
|
QRHI_PROF;
|
|
QRHI_PROF_F(releaseSwapChain(this));
|
|
}
|
|
|
|
QRhiCommandBuffer *QGles2SwapChain::currentFrameCommandBuffer()
|
|
{
|
|
return &cb;
|
|
}
|
|
|
|
QRhiRenderTarget *QGles2SwapChain::currentFrameRenderTarget()
|
|
{
|
|
return &rt;
|
|
}
|
|
|
|
QSize QGles2SwapChain::surfacePixelSize()
|
|
{
|
|
Q_ASSERT(m_window);
|
|
return m_window->size() * m_window->devicePixelRatio();
|
|
}
|
|
|
|
QRhiRenderPassDescriptor *QGles2SwapChain::newCompatibleRenderPassDescriptor()
|
|
{
|
|
return new QGles2RenderPassDescriptor(m_rhi);
|
|
}
|
|
|
|
bool QGles2SwapChain::createOrResize()
|
|
{
|
|
surface = m_window;
|
|
m_currentPixelSize = surfacePixelSize();
|
|
pixelSize = m_currentPixelSize;
|
|
|
|
if (m_depthStencil && m_depthStencil->flags().testFlag(QRhiRenderBuffer::UsedWithSwapChainOnly)
|
|
&& m_depthStencil->pixelSize() != pixelSize)
|
|
{
|
|
m_depthStencil->setPixelSize(pixelSize);
|
|
m_depthStencil->create();
|
|
}
|
|
|
|
rt.d.rp = QRHI_RES(QGles2RenderPassDescriptor, m_renderPassDesc);
|
|
rt.d.pixelSize = pixelSize;
|
|
rt.d.dpr = float(m_window->devicePixelRatio());
|
|
rt.d.sampleCount = qBound(1, m_sampleCount, 64);
|
|
rt.d.colorAttCount = 1;
|
|
rt.d.dsAttCount = m_depthStencil ? 1 : 0;
|
|
rt.d.srgbUpdateAndBlend = m_flags.testFlag(QRhiSwapChain::sRGB);
|
|
|
|
frameCount = 0;
|
|
|
|
QRHI_PROF;
|
|
// make something up
|
|
QRHI_PROF_F(resizeSwapChain(this, 2, m_sampleCount > 1 ? 2 : 0, m_sampleCount));
|
|
|
|
return true;
|
|
}
|
|
|
|
QT_END_NAMESPACE
|