rhi: Introduce multiview starting with OpenGL (ES)

Fixes: QTBUG-114770
Change-Id: Ibb1ced7f19d15a5116c60e95fd3e6b86ace63155
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Laszlo Agocs 2023-06-19 11:56:48 +02:00
parent 2b9ef2eb44
commit f9d90c6fba
25 changed files with 483 additions and 6 deletions

View File

@ -59,7 +59,9 @@ public:
StandardDerivatives = 0x02000000,
ASTCTextureCompression = 0x04000000,
ETC2TextureCompression = 0x08000000,
HalfFloatVertex = 0x10000000
HalfFloatVertex = 0x10000000,
MultiView = 0x20000000,
MultiViewExtended = 0x40000000
};
Q_DECLARE_FLAGS(OpenGLExtensions, OpenGLExtension)

View File

@ -348,6 +348,10 @@ static int qt_gl_resolve_extensions()
extensions |= QOpenGLExtensions::StandardDerivatives;
if (extensionMatcher.match("GL_ARB_half_float_vertex"))
extensions |= QOpenGLExtensions::HalfFloatVertex;
if (extensionMatcher.match("GL_OVR_multiview"))
extensions |= QOpenGLExtensions::MultiView;
if (extensionMatcher.match("GL_OVR_multiview2"))
extensions |= QOpenGLExtensions::MultiViewExtended;
if (ctx->isOpenGLES()) {
if (format.majorVersion() >= 2)

View File

@ -967,6 +967,22 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
\value ThreeDimensionalTextureMipmaps Indicates that generating 3D texture
mipmaps are supported. In practice this feature will be unsupported with
Direct 3D 12.
\value MultiView Indicates that multiview, see e.g.
\l{https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_multiview.html}{VK_KHR_multiview}
is supported. With OpenGL ES 2.0, Direct 3D 11, and OpenGL (ES)
implementations without \c{GL_OVR_multiview2} this feature will not be
supported. With Vulkan 1.1 and newer, and Direct 3D 12 multiview is
typically supported. When reported as supported, creating a
QRhiTextureRenderTarget with a QRhiColorAttachment that references a texture
array and has \l{QRhiColorAttachment::setMultiViewCount()}{multiViewCount}
set enables recording a render pass that uses multiview rendering. Note that
multiview is only available in combination with 2D texture arrays. It cannot
be used to optimize the rendering into individual textures (e.g. two, for
the left and right eyes). Rather, the target of a multiview render pass is
always a texture array, automatically rendering to the layer (array element)
corresponding to each view. Therefore this feature implies \l TextureArrays
as well. This enum value has been introduced in Qt 6.7.
*/
/*!
@ -2296,6 +2312,70 @@ QRhiColorAttachment::QRhiColorAttachment(QRhiRenderBuffer *renderBuffer)
Sets the resolve texture mip \a level to use.
*/
/*!
\fn int QRhiColorAttachment::multiViewCount() const
\return the currently set number of views. Defaults to 0 which indicates
the render target with this color attachment is not going to be used with
multiview rendering.
\since 6.7
*/
/*!
\fn void QRhiColorAttachment::setMultiViewCount(int count)
Sets the view \a count. Setting a value larger than 1 indicates that the
render target with this color attachment is going to be used with multiview
rendering. The default value is 0. Values smaller than 2 indicate no
multiview rendering.
When \a count is set to \c 2 or greater, the color attachment must be
associated with a 2D texture array. layer() and multiViewCount() together
define the range of texture array elements that are targeted during
multiview rendering.
For example, if \c layer is \c 0 and \c multiViewCount is \c 2, the texture
array must have 2 (or more) elements, and the multiview rendering will
target elements 0 and 1. The \c{gl_ViewIndex} variable in the shaders has a
value of \c 0 or \c 1 then, where view \c 0 corresponds to the texture array
element \c 0, and view \c 1 to the array element \c 1.
\note Setting a \a count larger than 1, using a texture array as texture(),
and calling \l{QRhiCommandBuffer::beginPass()}{beginPass()} on a
QRhiTextureRenderTarget with this color attachment implies multiview
rendering for the entire render pass. multiViewCount() should not be set
unless multiview rendering is wanted. Multiview cannot be used with texture
types other than 2D texture arrays. (although 3D textures may work,
depending on the graphics API and backend; applications are nonetheless
advised not to rely on that and only use 2D texture arrays as the render
targets of multiview rendering)
See
\l{https://registry.khronos.org/OpenGL/extensions/OVR/OVR_multiview.txt}{GL_OVR_multiview}
for more details regarding multiview rendering. Do note that Qt requires
\l{https://registry.khronos.org/OpenGL/extensions/OVR/OVR_multiview2.txt}{GL_OVR_multiview2}
as well, when running on OpenGL (ES).
Multiview rendering is available only when the
\l{QRhi::MultiView}{MultiView} feature is reported as supported from
\l{QRhi::isFeatureSupported()}{isFeatureSupported()}.
\note For portability, be aware of limitations that exist for multiview
rendering with some of the graphics APIs. For example, OpenGL disallows
tessellation or geometry shaders with multiview. With other APIs, e.g.
Vulkan, some of these are optional features, the actual support depending
on the implementation. It is therefore recommended that multiview render
passes do not rely on any of the features that
\l{https://registry.khronos.org/OpenGL/extensions/OVR/OVR_multiview.txt}{GL_OVR_multiview}
declares as unsupported. The one exception is shader stage outputs other
than \c{gl_Position} depending on \c{gl_ViewIndex}: that can be relied on
(even with OpenGL) because QRhi never reports multiview as supported without
\c{GL_OVR_multiview2} also being present.
\since 6.7
*/
/*!
\class QRhiTextureRenderTargetDescription
\inmodule QtGui

View File

@ -592,6 +592,9 @@ public:
int resolveLevel() const { return m_resolveLevel; }
void setResolveLevel(int level) { m_resolveLevel = level; }
int multiViewCount() const { return m_multiViewCount; }
void setMultiViewCount(int count) { m_multiViewCount = count; }
private:
QRhiTexture *m_texture = nullptr;
QRhiRenderBuffer *m_renderBuffer = nullptr;
@ -600,6 +603,7 @@ private:
QRhiTexture *m_resolveTexture = nullptr;
int m_resolveLayer = 0;
int m_resolveLevel = 0;
int m_multiViewCount = 0;
};
Q_DECLARE_TYPEINFO(QRhiColorAttachment, Q_RELOCATABLE_TYPE);
@ -1822,7 +1826,8 @@ public:
OneDimensionalTextureMipmaps,
HalfAttributes,
RenderToOneDimensionalTexture,
ThreeDimensionalTextureMipmaps
ThreeDimensionalTextureMipmaps,
MultiView
};
enum BeginFrameFlag {

View File

@ -588,6 +588,8 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::ThreeDimensionalTextureMipmaps:
return true;
case QRhi::MultiView:
return false;
default:
Q_UNREACHABLE();
return false;

View File

@ -638,6 +638,8 @@ bool QRhiD3D12::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::ThreeDimensionalTextureMipmaps:
return false; // we generate mipmaps ourselves with compute and this is not implemented
case QRhi::MultiView:
return false;
}
return false;
}

View File

@ -1009,6 +1009,15 @@ bool QRhiGles2::create(QRhi::Flags flags)
caps.halfAttributes = f->hasOpenGLExtension(QOpenGLExtensions::HalfFloatVertex);
// We always require GL_OVR_multiview2 for symmetry with other backends.
caps.multiView = f->hasOpenGLExtension(QOpenGLExtensions::MultiView)
&& f->hasOpenGLExtension(QOpenGLExtensions::MultiViewExtended);
if (caps.multiView) {
glFramebufferTextureMultiviewOVR =
reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLenum, GLuint, GLint, GLint, GLsizei)>(
ctx->getProcAddress(QByteArrayLiteral("glFramebufferTextureMultiviewOVR")));
}
nativeHandlesStruct.context = ctx;
contextLost = false;
@ -1371,6 +1380,8 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const
return caps.texture1D;
case QRhi::ThreeDimensionalTextureMipmaps:
return caps.texture3D;
case QRhi::MultiView:
return caps.multiView && caps.maxTextureArraySize > 0;
default:
Q_UNREACHABLE_RETURN(false);
}
@ -5554,8 +5565,13 @@ bool QGles2TextureRenderTarget::create()
QGles2Texture *texD = QRHI_RES(QGles2Texture, texture);
Q_ASSERT(texD->texture && texD->specified);
if (texD->flags().testFlag(QRhiTexture::ThreeDimensional) || texD->flags().testFlag(QRhiTexture::TextureArray)) {
rhiD->f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), texD->texture,
colorAtt.level(), colorAtt.layer());
if (it->multiViewCount() < 2) {
rhiD->f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), texD->texture,
colorAtt.level(), colorAtt.layer());
} else {
rhiD->glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), texD->texture,
colorAtt.level(), colorAtt.layer(), colorAtt.multiViewCount());
}
} else if (texD->flags().testFlag(QRhiTexture::OneDimensional)) {
rhiD->glFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex),
texD->target + uint(colorAtt.layer()), texD->texture,

View File

@ -909,6 +909,8 @@ public:
GLsizei, const GLvoid *) = nullptr;
void(QOPENGLF_APIENTRYP glFramebufferTexture1D)(GLenum, GLenum, GLenum, GLuint,
GLint) = nullptr;
void(QOPENGLF_APIENTRYP glFramebufferTextureMultiviewOVR)(GLenum, GLenum, GLuint, GLint,
GLint, GLsizei) = nullptr;
uint vao = 0;
struct Caps {
@ -962,7 +964,8 @@ public:
geometryShader(false),
texture1D(false),
hasDrawBuffersFunc(false),
halfAttributes(false)
halfAttributes(false),
multiView(false)
{ }
int ctxMajor;
int ctxMinor;
@ -1016,6 +1019,7 @@ public:
uint texture1D : 1;
uint hasDrawBuffersFunc : 1;
uint halfAttributes : 1;
uint multiView : 1;
} caps;
QGles2SwapChain *currentSwapChain = nullptr;
QSet<GLint> supportedCompressedFormats;

View File

@ -833,6 +833,8 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
return false;
case QRhi::ThreeDimensionalTextureMipmaps:
return true;
case QRhi::MultiView:
return false;
default:
Q_UNREACHABLE();
return false;

View File

@ -4437,6 +4437,8 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::ThreeDimensionalTextureMipmaps:
return true;
case QRhi::MultiView:
return false;
default:
Q_UNREACHABLE_RETURN(false);
}

View File

@ -32,6 +32,7 @@ add_subdirectory(stereo)
add_subdirectory(tex1d)
add_subdirectory(displacement)
add_subdirectory(imguirenderer)
add_subdirectory(multiview)
if(QT_FEATURE_widgets)
add_subdirectory(rhiwidget)
endif()

View File

@ -0,0 +1,21 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_manual_test(multiview
GUI
SOURCES
multiview.cpp
LIBRARIES
Qt::Gui
Qt::GuiPrivate
)
qt_internal_add_resource(multiview "multiview"
PREFIX
"/"
FILES
"multiview.vert.qsb"
"multiview.frag.qsb"
"texture.vert.qsb"
"texture.frag.qsb"
)

View File

@ -0,0 +1,4 @@
qsb --view-count 2 --glsl "300 es,330" --hlsl 61 multiview.vert -o multiview.vert.qsb
qsb --glsl "300 es,330" --hlsl 61 multiview.frag -o multiview.frag.qsb
qsb --glsl "300 es,330" --hlsl 61 texture.vert -o texture.vert.qsb
qsb --glsl "300 es,330" --hlsl 61 texture.frag -o texture.frag.qsb

View File

@ -0,0 +1,239 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "../shared/examplefw.h"
// Multiview rendering. Renders the same geometry (a triangle) with two
// different transforms into two layers of a texture array object in a *single*
// draw call. (NB under the hood it is at the hardware/driver's discretion what
// happens; it may very well map to some simple looping and still drawing
// twice, whereas with modern hardware it can be expected to be implemented
// more efficiently, but that's hidden from us)
static float quadVertexData[] =
{ // Y up, CCW
-0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 1.0f, 0.0f
};
static quint16 quadIndexData[] =
{
0, 1, 2, 0, 2, 3
};
static float triangleData[] =
{ // Y up, CCW
0.0f, 0.5f, 1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
};
struct {
QList<QRhiResource *> releasePool;
QRhiBuffer *vbuf = nullptr;
QRhiBuffer *ibuf = nullptr;
QRhiBuffer *ubuf = nullptr;
QRhiTextureRenderTarget *rt = nullptr;
QRhiRenderPassDescriptor *rtRp = nullptr;
QRhiSampler *sampler = nullptr;
QRhiGraphicsPipeline *ps = nullptr;
QRhiResourceUpdateBatch *initialUpdates = nullptr;
QMatrix4x4 winProj;
QRhiTexture *tex = nullptr;
QRhiShaderResourceBindings *srb[2] = {};
QRhiBuffer *triUbuf = nullptr;
QRhiShaderResourceBindings *triSrb = nullptr;
QRhiGraphicsPipeline *triPs = nullptr;
QMatrix4x4 triBaseMvp;
} d;
void Window::customInit()
{
if (!m_r->isFeatureSupported(QRhi::MultiView))
qFatal("Multiview is not supported");
// texture array with 2 elements, e.g. 0 is left eye, 1 is right
d.tex = m_r->newTextureArray(QRhiTexture::RGBA8, 2, QSize(512, 512), 1, QRhiTexture::RenderTarget);
d.releasePool << d.tex;
d.tex->create();
// set up the multiview render target
QRhiColorAttachment multiViewAtt(d.tex);
// using array elements 0 and 1
multiViewAtt.setLayer(0);
multiViewAtt.setMultiViewCount(2);
QRhiTextureRenderTargetDescription rtDesc(multiViewAtt);
d.rt = m_r->newTextureRenderTarget(rtDesc);
d.releasePool << d.rt;
d.rtRp = d.rt->newCompatibleRenderPassDescriptor();
d.releasePool << d.rtRp;
d.rt->setRenderPassDescriptor(d.rtRp);
d.rt->create();
// vertex buffer used by both passes
d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(quadVertexData) + sizeof(triangleData));
d.vbuf->create();
d.releasePool << d.vbuf;
// resources for the on-screen visualizer
d.ibuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, sizeof(quadIndexData));
d.ibuf->create();
d.releasePool << d.ibuf;
const int oneRoundedUniformBlockSize = m_r->ubufAligned(72);
d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, oneRoundedUniformBlockSize * 2);
d.ubuf->create();
d.releasePool << d.ubuf;
d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
d.releasePool << d.sampler;
d.sampler->create();
// two srbs, just for the quad positioning on-screen
for (int i = 0; i < 2; ++i) {
QRhiShaderResourceBindings *srb = m_r->newShaderResourceBindings();
d.releasePool << srb;
srb->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage,
d.ubuf, i * oneRoundedUniformBlockSize, 72),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.tex, d.sampler)
});
srb->create();
d.srb[i] = srb;
}
d.ps = m_r->newGraphicsPipeline();
d.releasePool << d.ps;
d.ps->setShaderStages({
{ QRhiShaderStage::Vertex, getShader(QLatin1String(":/texture.vert.qsb")) },
{ QRhiShaderStage::Fragment, getShader(QLatin1String(":/texture.frag.qsb")) }
});
QRhiVertexInputLayout inputLayout;
inputLayout.setBindings({
{ 4 * sizeof(float) }
});
inputLayout.setAttributes({
{ 0, 0, QRhiVertexInputAttribute::Float2, 0 },
{ 0, 1, QRhiVertexInputAttribute::Float2, quint32(2 * sizeof(float)) }
});
d.ps->setVertexInputLayout(inputLayout);
d.ps->setShaderResourceBindings(d.srb[0]); // all of them are layout-compatible
d.ps->setRenderPassDescriptor(m_rp);
d.ps->create();
d.initialUpdates = m_r->nextResourceUpdateBatch();
d.initialUpdates->uploadStaticBuffer(d.vbuf, 0, sizeof(quadVertexData), quadVertexData);
d.initialUpdates->uploadStaticBuffer(d.vbuf, sizeof(quadVertexData), sizeof(triangleData), triangleData);
d.initialUpdates->uploadStaticBuffer(d.ibuf, quadIndexData);
qint32 flip = m_r->isYUpInFramebuffer() ? 1 : 0;
for (int i = 0; i < 2; ++i) {
d.initialUpdates->updateDynamicBuffer(d.ubuf, i * oneRoundedUniformBlockSize + 64, 4, &flip);
float layer = i;
d.initialUpdates->updateDynamicBuffer(d.ubuf, i * oneRoundedUniformBlockSize + 68, 4, &layer);
}
// create resources for the multiview render pass
d.triUbuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 128); // mat4 mvp[2]
d.releasePool << d.triUbuf;
d.triUbuf->create();
d.triSrb = m_r->newShaderResourceBindings();
d.releasePool << d.triSrb;
d.triSrb->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.triUbuf)
});
d.triSrb->create();
d.triPs = m_r->newGraphicsPipeline();
d.releasePool << d.triPs;
d.triPs->setShaderStages({
{ QRhiShaderStage::Vertex, getShader(QLatin1String(":/multiview.vert.qsb")) },
{ QRhiShaderStage::Fragment, getShader(QLatin1String(":/multiview.frag.qsb")) }
});
inputLayout.setBindings({
{ 5 * sizeof(float) }
});
inputLayout.setAttributes({
{ 0, 0, QRhiVertexInputAttribute::Float2, 0 },
{ 0, 1, QRhiVertexInputAttribute::Float3, quint32(2 * sizeof(float)) }
});
d.triPs->setVertexInputLayout(inputLayout);
d.triPs->setShaderResourceBindings(d.triSrb);
d.triPs->setRenderPassDescriptor(d.rtRp);
d.triPs->create();
d.triBaseMvp = m_r->clipSpaceCorrMatrix();
d.triBaseMvp.perspective(45.0f, d.rt->pixelSize().width() / float(d.rt->pixelSize().height()), 0.01f, 1000.0f);
d.triBaseMvp.translate(0, 0, -2);
}
void Window::customRelease()
{
qDeleteAll(d.releasePool);
d.releasePool.clear();
}
void Window::customRender()
{
QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer();
QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch();
if (d.initialUpdates) {
u->merge(d.initialUpdates);
d.initialUpdates->release();
d.initialUpdates = nullptr;
}
QRhiCommandBuffer::VertexInput vbufBinding(d.vbuf, quint32(sizeof(quadVertexData)));
QMatrix4x4 triMvp = d.triBaseMvp;
// let's say this is the left eye, make the triangle point left for now
triMvp.rotate(90, 0, 0, 1);
u->updateDynamicBuffer(d.triUbuf, 0, 64, triMvp.constData());
triMvp = d.triBaseMvp;
// right for the right eye
triMvp.rotate(270, 0, 0, 1);
u->updateDynamicBuffer(d.triUbuf, 64, 64, triMvp.constData());
cb->beginPass(d.rt, QColor::fromRgbF(0.5f, 0.2f, 0.0f, 1.0f), { 1.0f, 0 }, u);
cb->setGraphicsPipeline(d.triPs);
cb->setViewport({ 0, 0, float(d.rt->pixelSize().width()), float(d.rt->pixelSize().height()) });
cb->setShaderResources();
cb->setVertexInput(0, 1, &vbufBinding);
cb->draw(3);
cb->endPass();
cb->resourceUpdate(u);
// "blit" the two texture layers on-screen just to visualize the contents
u = m_r->nextResourceUpdateBatch();
if (d.winProj != m_proj) {
d.winProj = m_proj;
const int oneRoundedUniformBlockSize = m_r->ubufAligned(72);
for (int i = 0; i < 2; ++i) {
QMatrix4x4 mvp = m_proj;
mvp.translate(0, 0, 1);
if (i == 0)
mvp.translate(-1.0f, 0, 0);
else
mvp.translate(1.0f, 0, 0);
u->updateDynamicBuffer(d.ubuf, i * oneRoundedUniformBlockSize, 64, mvp.constData());
}
}
const QSize outputSizeInPixels = m_sc->currentPixelSize();
cb->beginPass(m_sc->currentFrameRenderTarget(), m_clearColor, { 1.0f, 0 }, u);
cb->setGraphicsPipeline(d.ps);
cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) });
vbufBinding.second = 0;
cb->setVertexInput(0, 1, &vbufBinding, d.ibuf, 0, QRhiCommandBuffer::IndexUInt16);
for (int i = 0; i < 2; ++i) {
cb->setShaderResources(d.srb[i]);
cb->drawIndexed(6);
}
cb->endPass();
}

View File

@ -0,0 +1,10 @@
#version 440
layout(location = 0) in vec3 v_color;
layout(location = 0) out vec4 fragColor;
void main()
{
fragColor = vec4(v_color, 1.0);
}

Binary file not shown.

View File

@ -0,0 +1,8 @@
TEMPLATE = app
QT += gui-private
SOURCES = \
multiview.cpp
RESOURCES = multiview.qrc

View File

@ -0,0 +1,8 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>multiview.vert.qsb</file>
<file>multiview.frag.qsb</file>
<file>texture.vert.qsb</file>
<file>texture.frag.qsb</file>
</qresource>
</RCC>

View File

@ -0,0 +1,18 @@
#version 440
#extension GL_EXT_multiview : require
layout(location = 0) in vec4 pos;
layout(location = 1) in vec3 color;
layout(location = 0) out vec3 v_color;
layout(std140, binding = 0) uniform buf
{
mat4 mvp[2];
};
void main()
{
v_color = color;
gl_Position = mvp[gl_ViewIndex] * pos;
}

Binary file not shown.

View File

@ -0,0 +1,19 @@
#version 440
layout(location = 0) in vec2 v_texcoord;
layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2DArray tex;
layout(std140, binding = 0) uniform buf {
mat4 mvp;
int flip;
float layer;
};
void main()
{
vec4 c = texture(tex, vec3(v_texcoord, layer));
fragColor = vec4(c.rgb * c.a, c.a);
}

Binary file not shown.

View File

@ -0,0 +1,20 @@
#version 440
layout(location = 0) in vec4 position;
layout(location = 1) in vec2 texcoord;
layout(location = 0) out vec2 v_texcoord;
layout(std140, binding = 0) uniform buf {
mat4 mvp;
int flip;
float layer;
};
void main()
{
v_texcoord = vec2(texcoord.x, texcoord.y);
if (flip != 0)
v_texcoord.y = 1.0 - v_texcoord.y;
gl_Position = mvp * position;
}

Binary file not shown.

View File

@ -22,7 +22,17 @@ SUBDIRS += \
computeimage \
instancing \
noninstanced \
tex3d
tex3d \
texturearray \
polygonmode \
tessellation \
geometryshader \
stenciloutline \
stereo \
tex1d \
displacement \
imguirenderer \
multiview
qtConfig(widgets) {
SUBDIRS += \