rhi: Add texture array support

Arrays of textures have always been supported, but we will encounter
cases when we need to work with texture array objects as well.

Note that currently it is not possible to expose only a slice of the
array to the shader, because there is no dedicated API in the SRB,
and thus the same SRV/UAV (or equivalent) is used always, capturing
all elements in the array. Therefore in the shader the last component
of P in texture() is in range 0..array_size-1.

Change-Id: I5a032ed016aeefbbcd743d5bfb9fbc49ba00a1fa
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Laszlo Agocs 2021-10-19 14:11:20 +02:00
parent e7371fa159
commit e7a1fbfc47
24 changed files with 780 additions and 114 deletions

View File

@ -680,6 +680,11 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
texture is supported. This can be unsupported with Vulkan 1.0 due to
relying on VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT which is a Vulkan 1.1
feature.
\value TextureArrays Indicates that texture arrays are supported and
QRhi::newTextureArray() is functional. Note that even when texture arrays
are not supported, arrays of textures are still available as those are two
independent features.
*/
/*!
@ -767,6 +772,10 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
\value MaxThreadGroupZ The maximum size of a work/thread group in the Z
dimension. Effectively the maximum value of \c local_size_z in the compute
shader. Typically 64 or 256.
\value TextureArraySizeMax Maximum texture array size. Typically in range
256 - 2048. Attempting to \l{QRhi::newTextureArray()}{create a texture
array} with more elements will likely fail.
*/
/*!
@ -1503,7 +1512,7 @@ QDebug operator<<(QDebug dbg, const QRhiShaderStage &s)
When targeting a non-multisample texture, the layer() and level() indicate
the targeted layer (face index \c{0-5} for cubemaps) and mip level. For 3D
textures layer() specifies the slice (one 2D image within the 3D texture)
to render to.
to render to. For texture arrays layer() is the array index.
When texture() or renderBuffer() is multisample, resolveTexture() can be
set optionally. When set, samples are resolved automatically into that
@ -1700,7 +1709,9 @@ QRhiTextureSubresourceUploadDescription::QRhiTextureSubresourceUploadDescription
\class QRhiTextureUploadEntry
\internal
\inmodule QtGui
\brief Describes one layer (face for cubemaps) in a texture upload operation.
\brief Describes one layer (face for cubemaps, slice for 3D textures,
element for texture arrays) in a texture upload operation.
*/
/*!
@ -1840,12 +1851,12 @@ QRhiTextureUploadDescription::QRhiTextureUploadDescription(std::initializer_list
sourceTopLeft(), and destinationTopLeft() must fit the source and
destination textures, respectively. The behavior is undefined otherwise.
With cubemap and 3D textures one face or slice can be copied at a time. The
face or slice is specified by the source and destination layer indices.
With mipmapped textures one mip level can be copied at a time. The source
and destination layer and mip level indices can differ, but the size and
position must be carefully controlled to avoid out of bounds copies, in
which case the behavior is undefined.
With cubemaps, 3D textures, and texture arrays one face or slice can be
copied at a time. The face or slice is specified by the source and
destination layer indices. With mipmapped textures one mip level can be
copied at a time. The source and destination layer and mip level indices can
differ, but the size and position must be carefully controlled to avoid out
of bounds copies, in which case the behavior is undefined.
*/
/*!
@ -2440,10 +2451,11 @@ bool QRhiRenderBuffer::createFrom(NativeRenderBuffer src)
\value ThreeDimensional The texture is a 3D texture. Such textures should
be created with the QRhi::newTexture() overload taking a depth in addition
to width and height. A 3D texture can have mipmaps but cannot be
multisample. When rendering into a 3D texture, the layer specified in the
render target's color attachment refers to a slice in range [0..depth-1].
The underlying graphics API may not support 3D textures at run time.
Support is indicated by the QRhi::ThreeDimensionalTextures feature.
multisample. When rendering into, or uploading data to a 3D texture, the \c
layer specified in the render target's color attachment or the upload
description refers to a single slice in range [0..depth-1]. The underlying
graphics API may not support 3D textures at run time. Support is indicated
by the QRhi::ThreeDimensionalTextures feature.
\value TextureRectangleGL The texture should use the GL_TEXTURE_RECTANGLE
target with OpenGL. This flag is ignored with other graphics APIs. Just
@ -2451,6 +2463,14 @@ bool QRhiRenderBuffer::createFrom(NativeRenderBuffer src)
native OpenGL texture objects received from the platform are wrapped in a
QRhiTexture, and the platform can only provide textures for a non-2D
texture target.
\value TextureArray The texture is a texture array, i.e. a single texture
object that is a homogeneous array of 2D textures. Texture arrays are
created with QRhi::newTextureArray(). The underlying graphics API may not
support texture array objects at run time. Support is indicated by the
QRhi::TextureArrays feature. When rendering into, or uploading data to a
texture array, the \c layer specified in the render target's color
attachment or the upload description selects a single element in the array.
*/
/*!
@ -2542,9 +2562,10 @@ bool QRhiRenderBuffer::createFrom(NativeRenderBuffer src)
\internal
*/
QRhiTexture::QRhiTexture(QRhiImplementation *rhi, Format format_, const QSize &pixelSize_, int depth_,
int sampleCount_, Flags flags_)
int arraySize_, int sampleCount_, Flags flags_)
: QRhiResource(rhi),
m_format(format_), m_pixelSize(pixelSize_), m_depth(depth_), m_sampleCount(sampleCount_), m_flags(flags_)
m_format(format_), m_pixelSize(pixelSize_), m_depth(depth_),
m_arraySize(arraySize_), m_sampleCount(sampleCount_), m_flags(flags_)
{
}
@ -2599,6 +2620,10 @@ QRhiTexture::NativeTexture QRhiTexture::nativeTexture()
The opposite of this operation, exposing a QRhiTexture-created native
texture object to a foreign engine, is possible via nativeTexture().
\note When importing a 3D texture, or a texture array object, or, with
OpenGL ES, an external texture, it is then especially important to set the
corresponding flags (ThreeDimensional, TextureArray, ExternalOES) via
setFlags() before calling this function.
*/
bool QRhiTexture::createFrom(QRhiTexture::NativeTexture src)
{
@ -3118,8 +3143,9 @@ void QRhiImplementation::updateLayoutDesc(QRhiShaderResourceBindings *srb)
\inmodule QtGui
\brief Describes the shader resource for a single binding point.
A QRhiShaderResourceBinding cannot be constructed directly. Instead, use
the static functions uniformBuffer(), sampledTexture() to get an instance.
A QRhiShaderResourceBinding cannot be constructed directly. Instead, use the
static functions such as uniformBuffer() or sampledTexture() to get an
instance.
*/
/*!
@ -5432,8 +5458,8 @@ void QRhiResourceUpdateBatch::copyTexture(QRhiTexture *dst, QRhiTexture *src, co
QRhi::MaxAsyncReadbackFrames.
A single readback operation copies one mip level of one layer (cubemap face
or 3D slice) at a time. The level and layer are specified by the respective
fields in \a rb.
or 3D slice or texture array element) at a time. The level and layer are
specified by the respective fields in \a rb.
\sa readBackBuffer(), QRhi::resourceLimit()
*/
@ -6496,7 +6522,7 @@ QRhiRenderBuffer *QRhi::newRenderBuffer(QRhiRenderBuffer::Type type,
}
/*!
\return a new texture with the specified \a format, \a pixelSize, \a
\return a new 2D texture with the specified \a format, \a pixelSize, \a
sampleCount, and \a flags.
\note \a format specifies the requested internal and external format,
@ -6511,12 +6537,12 @@ QRhiTexture *QRhi::newTexture(QRhiTexture::Format format,
int sampleCount,
QRhiTexture::Flags flags)
{
return d->createTexture(format, pixelSize, 1, sampleCount, flags);
return d->createTexture(format, pixelSize, 1, 0, sampleCount, flags);
}
/*!
\return a new texture with the specified \a format, \a width, \a height, \a
depth, \a sampleCount, and \a flags.
\return a new 2D or 3D texture with the specified \a format, \a width, \a
height, \a depth, \a sampleCount, and \a flags.
This overload is suitable for 3D textures because it allows specifying \a
depth. A 3D texture must have QRhiTexture::ThreeDimensional set in \a
@ -6524,6 +6550,9 @@ QRhiTexture *QRhi::newTexture(QRhiTexture::Format format,
implicitly whenever \a depth is greater than 0. For 2D and cube textures \a
depth should be set to 0.
\note 3D textures are only functional when the ThreeDimensionalTextures
feature is reported as supported at run time.
\overload
*/
QRhiTexture *QRhi::newTexture(QRhiTexture::Format format,
@ -6534,7 +6563,36 @@ QRhiTexture *QRhi::newTexture(QRhiTexture::Format format,
if (depth > 0)
flags |= QRhiTexture::ThreeDimensional;
return d->createTexture(format, QSize(width, height), depth, sampleCount, flags);
return d->createTexture(format, QSize(width, height), depth, 0, sampleCount, flags);
}
/*!
\return a new 2D texture array with the specified \a format, \a arraySize,
\a pixelSize, \a sampleCount, and \a flags.
This function implicitly sets QRhiTexture::TextureArray in \a flags.
\note Do not confuse texture arrays with arrays of textures. A QRhiTexture
created by this function is usable with 2D array samplers in the shader, for
example: \c{layout(binding = 1) uniform sampler2DArray texArr;}. Arrays of
textures refers to a list of textures that are exposed to the shader via
QRhiShaderResourceBinding::sampledTextures() and a count > 1, and declared
in the shader for example like this: \c{layout(binding = 1) uniform
sampler2D textures[4];}
\note This is only functional when the TextureArrays feature is reported as
supported at run time.
\sa newTexture()
*/
QRhiTexture *QRhi::newTextureArray(QRhiTexture::Format format,
int arraySize,
const QSize &pixelSize,
int sampleCount,
QRhiTexture::Flags flags)
{
flags |= QRhiTexture::TextureArray;
return d->createTexture(format, pixelSize, 1, arraySize, sampleCount, flags);
}
/*!

View File

@ -773,7 +773,8 @@ public:
UsedAsCompressedAtlas = 1 << 8,
ExternalOES = 1 << 9,
ThreeDimensional = 1 << 10,
TextureRectangleGL = 1 << 11
TextureRectangleGL = 1 << 11,
TextureArray = 1 << 12
};
Q_DECLARE_FLAGS(Flags, Flag)
@ -842,6 +843,9 @@ public:
int depth() const { return m_depth; }
void setDepth(int depth) { m_depth = depth; }
int arraySize() const { return m_arraySize; }
void setArraySize(int arraySize) { m_arraySize = arraySize; }
Flags flags() const { return m_flags; }
void setFlags(Flags f) { m_flags = f; }
@ -855,10 +859,11 @@ public:
protected:
QRhiTexture(QRhiImplementation *rhi, Format format_, const QSize &pixelSize_, int depth_,
int sampleCount_, Flags flags_);
int arraySize_, int sampleCount_, Flags flags_);
Format m_format;
QSize m_pixelSize;
int m_depth;
int m_arraySize;
int m_sampleCount;
Flags m_flags;
};
@ -1583,7 +1588,8 @@ public:
ImageDataStride,
RenderBufferImport,
ThreeDimensionalTextures,
RenderTo3DTextureSlice
RenderTo3DTextureSlice,
TextureArrays
};
enum BeginFrameFlag {
@ -1605,7 +1611,8 @@ public:
MaxThreadsPerThreadGroup,
MaxThreadGroupX,
MaxThreadGroupY,
MaxThreadGroupZ
MaxThreadGroupZ,
TextureArraySizeMax
};
~QRhi();
@ -1648,6 +1655,12 @@ public:
int sampleCount = 1,
QRhiTexture::Flags flags = {});
QRhiTexture *newTextureArray(QRhiTexture::Format format,
int arraySize,
const QSize &pixelSize,
int sampleCount = 1,
QRhiTexture::Flags flags = {});
QRhiSampler *newSampler(QRhiSampler::Filter magFilter,
QRhiSampler::Filter minFilter,
QRhiSampler::Filter mipmapMode,

View File

@ -88,6 +88,7 @@ public:
virtual QRhiTexture *createTexture(QRhiTexture::Format format,
const QSize &pixelSize,
int depth,
int arraySize,
int sampleCount,
QRhiTexture::Flags flags) = 0;
virtual QRhiSampler *createSampler(QRhiSampler::Filter magFilter,

View File

@ -543,6 +543,8 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::RenderTo3DTextureSlice:
return true;
case QRhi::TextureArrays:
return true;
default:
Q_UNREACHABLE();
return false;
@ -576,6 +578,8 @@ int QRhiD3D11::resourceLimit(QRhi::ResourceLimit limit) const
return D3D11_CS_THREAD_GROUP_MAX_Y;
case QRhi::MaxThreadGroupZ:
return D3D11_CS_THREAD_GROUP_MAX_Z;
case QRhi::TextureArraySizeMax:
return D3D11_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION;
default:
Q_UNREACHABLE();
return 0;
@ -631,10 +635,10 @@ QRhiRenderBuffer *QRhiD3D11::createRenderBuffer(QRhiRenderBuffer::Type type, con
}
QRhiTexture *QRhiD3D11::createTexture(QRhiTexture::Format format,
const QSize &pixelSize, int depth,
const QSize &pixelSize, int depth, int arraySize,
int sampleCount, QRhiTexture::Flags flags)
{
return new QD3D11Texture(this, format, pixelSize, depth, sampleCount, flags);
return new QD3D11Texture(this, format, pixelSize, depth, arraySize, sampleCount, flags);
}
QRhiSampler *QRhiD3D11::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
@ -2907,8 +2911,8 @@ QRhiTexture::Format QD3D11RenderBuffer::backingFormat() const
}
QD3D11Texture::QD3D11Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
int sampleCount, Flags flags)
: QRhiTexture(rhi, format, pixelSize, depth, sampleCount, flags)
int arraySize, int sampleCount, Flags flags)
: QRhiTexture(rhi, format, pixelSize, depth, arraySize, sampleCount, flags)
{
for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i)
perLevelViews[i] = nullptr;
@ -2997,6 +3001,7 @@ bool QD3D11Texture::prepareCreate(QSize *adjustedSize)
const bool isDepth = isDepthTextureFormat(m_format);
const bool isCube = m_flags.testFlag(CubeMap);
const bool is3D = m_flags.testFlag(ThreeDimensional);
const bool isArray = m_flags.testFlag(TextureArray);
const bool hasMipMaps = m_flags.testFlag(MipMapped);
QRHI_RES_RHI(QRhiD3D11);
@ -3025,11 +3030,24 @@ bool QD3D11Texture::prepareCreate(QSize *adjustedSize)
qWarning("Texture cannot be both cube and 3D");
return false;
}
if (isArray && is3D) {
qWarning("Texture cannot be both array and 3D");
return false;
}
m_depth = qMax(1, m_depth);
if (m_depth > 1 && !is3D) {
qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth);
return false;
}
m_arraySize = qMax(0, m_arraySize);
if (m_arraySize > 0 && !isArray) {
qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize);
return false;
}
if (m_arraySize < 1 && isArray) {
qWarning("Texture is an array but array size is %d", m_arraySize);
return false;
}
if (adjustedSize)
*adjustedSize = size;
@ -3043,6 +3061,7 @@ bool QD3D11Texture::finishCreate()
const bool isDepth = isDepthTextureFormat(m_format);
const bool isCube = m_flags.testFlag(CubeMap);
const bool is3D = m_flags.testFlag(ThreeDimensional);
const bool isArray = m_flags.testFlag(TextureArray);
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
memset(&srvDesc, 0, sizeof(srvDesc));
@ -3050,6 +3069,18 @@ bool QD3D11Texture::finishCreate()
if (isCube) {
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE;
srvDesc.TextureCube.MipLevels = mipLevelCount;
} else {
if (isArray) {
if (sampleDesc.Count > 1) {
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DMSARRAY;
srvDesc.Texture2DMSArray.FirstArraySlice = 0;
srvDesc.Texture2DMSArray.ArraySize = UINT(m_arraySize);
} else {
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
srvDesc.Texture2DArray.MipLevels = mipLevelCount;
srvDesc.Texture2DArray.FirstArraySlice = 0;
srvDesc.Texture2DArray.ArraySize = UINT(m_arraySize);
}
} else {
if (sampleDesc.Count > 1) {
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DMS;
@ -3061,6 +3092,7 @@ bool QD3D11Texture::finishCreate()
srvDesc.Texture2D.MipLevels = mipLevelCount;
}
}
}
HRESULT hr = rhiD->dev->CreateShaderResourceView(textureResource(), &srvDesc, &srv);
if (FAILED(hr)) {
@ -3081,6 +3113,7 @@ bool QD3D11Texture::create()
const bool isDepth = isDepthTextureFormat(m_format);
const bool isCube = m_flags.testFlag(CubeMap);
const bool is3D = m_flags.testFlag(ThreeDimensional);
const bool isArray = m_flags.testFlag(TextureArray);
uint bindFlags = D3D11_BIND_SHADER_RESOURCE;
uint miscFlags = isCube ? D3D11_RESOURCE_MISC_TEXTURECUBE : 0;
@ -3108,7 +3141,7 @@ bool QD3D11Texture::create()
desc.Width = UINT(size.width());
desc.Height = UINT(size.height());
desc.MipLevels = mipLevelCount;
desc.ArraySize = isCube ? 6 : 1;
desc.ArraySize = isCube ? 6 : (isArray ? UINT(m_arraySize) : 1);
desc.Format = dxgiFormat;
desc.SampleDesc = sampleDesc;
desc.Usage = D3D11_USAGE_DEFAULT;
@ -3147,7 +3180,7 @@ bool QD3D11Texture::create()
return false;
QRHI_PROF;
QRHI_PROF_F(newTexture(this, true, int(mipLevelCount), isCube ? 6 : 1, int(sampleDesc.Count)));
QRHI_PROF_F(newTexture(this, true, int(mipLevelCount), isCube ? 6 : (isArray ? m_arraySize : 1), int(sampleDesc.Count)));
owns = true;
rhiD->registerResource(this);
@ -3170,8 +3203,11 @@ bool QD3D11Texture::createFrom(QRhiTexture::NativeTexture src)
if (!finishCreate())
return false;
const bool isCube = m_flags.testFlag(CubeMap);
const bool isArray = m_flags.testFlag(TextureArray);
QRHI_PROF;
QRHI_PROF_F(newTexture(this, false, int(mipLevelCount), m_flags.testFlag(CubeMap) ? 6 : 1, int(sampleDesc.Count)));
QRHI_PROF_F(newTexture(this, false, int(mipLevelCount), isCube ? 6 : (isArray ? m_arraySize : 1), int(sampleDesc.Count)));
owns = false;
QRHI_RES_RHI(QRhiD3D11);
@ -3190,6 +3226,7 @@ ID3D11UnorderedAccessView *QD3D11Texture::unorderedAccessViewForLevel(int level)
return perLevelViews[level];
const bool isCube = m_flags.testFlag(CubeMap);
const bool isArray = m_flags.testFlag(TextureArray);
const bool is3D = m_flags.testFlag(ThreeDimensional);
D3D11_UNORDERED_ACCESS_VIEW_DESC desc;
memset(&desc, 0, sizeof(desc));
@ -3199,6 +3236,11 @@ ID3D11UnorderedAccessView *QD3D11Texture::unorderedAccessViewForLevel(int level)
desc.Texture2DArray.MipSlice = UINT(level);
desc.Texture2DArray.FirstArraySlice = 0;
desc.Texture2DArray.ArraySize = 6;
} else if (isArray) {
desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2DARRAY;
desc.Texture2DArray.MipSlice = UINT(level);
desc.Texture2DArray.FirstArraySlice = 0;
desc.Texture2DArray.ArraySize = UINT(m_arraySize);
} else if (is3D) {
desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE3D;
desc.Texture3D.MipSlice = UINT(level);
@ -3483,6 +3525,17 @@ bool QD3D11TextureRenderTarget::create()
rtvDesc.Texture2DArray.MipSlice = UINT(colorAtt.level());
rtvDesc.Texture2DArray.FirstArraySlice = UINT(colorAtt.layer());
rtvDesc.Texture2DArray.ArraySize = 1;
} else if (texD->flags().testFlag(QRhiTexture::TextureArray)) {
if (texD->sampleDesc.Count > 1) {
rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMSARRAY;
rtvDesc.Texture2DMSArray.FirstArraySlice = UINT(colorAtt.layer());
rtvDesc.Texture2DMSArray.ArraySize = 1;
} else {
rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;
rtvDesc.Texture2DArray.MipSlice = UINT(colorAtt.level());
rtvDesc.Texture2DArray.FirstArraySlice = UINT(colorAtt.layer());
rtvDesc.Texture2DArray.ArraySize = 1;
}
} else if (texD->flags().testFlag(QRhiTexture::ThreeDimensional)) {
rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE3D;
rtvDesc.Texture3D.MipSlice = UINT(colorAtt.level());

View File

@ -102,7 +102,7 @@ struct QD3D11RenderBuffer : public QRhiRenderBuffer
struct QD3D11Texture : public QRhiTexture
{
QD3D11Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
int sampleCount, Flags flags);
int arraySize, int sampleCount, Flags flags);
~QD3D11Texture();
void destroy() override;
bool create() override;
@ -607,6 +607,7 @@ public:
QRhiTexture *createTexture(QRhiTexture::Format format,
const QSize &pixelSize,
int depth,
int arraySize,
int sampleCount,
QRhiTexture::Flags flags) override;
QRhiSampler *createSampler(QRhiSampler::Filter magFilter,

View File

@ -397,6 +397,14 @@ QT_BEGIN_NAMESPACE
#define GL_TEXTURE_RECTANGLE 0x84F5
#endif
#ifndef GL_TEXTURE_2D_ARRAY
#define GL_TEXTURE_2D_ARRAY 0x8C1A
#endif
#ifndef GL_MAX_ARRAY_TEXTURE_LAYERS
#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF
#endif
/*!
Constructs a new QRhiGles2InitParams.
@ -678,6 +686,14 @@ bool QRhiGles2::create(QRhi::Flags flags)
caps.texture3D = caps.ctxMajor >= 3; // 3.0
if (caps.ctxMajor >= 3) { // 3.0 or ES 3.0
GLint maxArraySize = 0;
f->glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxArraySize);
caps.maxTextureArraySize = maxArraySize;
} else {
caps.maxTextureArraySize = 0;
}
if (!caps.gles) {
f->glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
f->glEnable(GL_POINT_SPRITE);
@ -1074,6 +1090,8 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const
return caps.texture3D;
case QRhi::RenderTo3DTextureSlice:
return caps.texture3D;
case QRhi::TextureArrays:
return caps.maxTextureArraySize > 0;
default:
Q_UNREACHABLE();
return false;
@ -1105,6 +1123,8 @@ int QRhiGles2::resourceLimit(QRhi::ResourceLimit limit) const
return caps.maxThreadGroupsY;
case QRhi::MaxThreadGroupZ:
return caps.maxThreadGroupsZ;
case QRhi::TextureArraySizeMax:
return 2048;
default:
Q_UNREACHABLE();
return 0;
@ -1291,10 +1311,10 @@ QRhiRenderBuffer *QRhiGles2::createRenderBuffer(QRhiRenderBuffer::Type type, con
}
QRhiTexture *QRhiGles2::createTexture(QRhiTexture::Format format,
const QSize &pixelSize, int depth,
const QSize &pixelSize, int depth, int arraySize,
int sampleCount, QRhiTexture::Flags flags)
{
return new QGles2Texture(this, format, pixelSize, depth, sampleCount, flags);
return new QGles2Texture(this, format, pixelSize, depth, arraySize, sampleCount, flags);
}
QRhiSampler *QRhiGles2::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
@ -1878,7 +1898,10 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb
trackedImageBarrier(cbD, texD, QGles2Texture::AccessUpdate);
const bool isCompressed = isCompressedFormat(texD->m_format);
const bool isCubeMap = texD->m_flags.testFlag(QRhiTexture::CubeMap);
const bool is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
const bool isArray = texD->m_flags.testFlag(QRhiTexture::TextureArray);
const GLenum faceTargetBase = isCubeMap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target;
const GLenum effectiveTarget = faceTargetBase + (isCubeMap ? uint(layer) : 0u);
const QPoint dp = subresDesc.destinationTopLeft();
const QByteArray rawData = subresDesc.data();
if (!subresDesc.image().isNull()) {
@ -1894,11 +1917,11 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb
}
cmd.args.subImage.target = texD->target;
cmd.args.subImage.texture = texD->texture;
cmd.args.subImage.faceTarget = faceTargetBase + uint(layer);
cmd.args.subImage.faceTarget = effectiveTarget;
cmd.args.subImage.level = level;
cmd.args.subImage.dx = dp.x();
cmd.args.subImage.dy = dp.y();
cmd.args.subImage.dz = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional) ? layer : 0;
cmd.args.subImage.dz = is3D || isArray ? layer : 0;
cmd.args.subImage.w = size.width();
cmd.args.subImage.h = size.height();
cmd.args.subImage.glformat = texD->glformat;
@ -1907,8 +1930,7 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb
cmd.args.subImage.rowLength = 0;
cmd.args.subImage.data = cbD->retainImage(img);
} else if (!rawData.isEmpty() && isCompressed) {
const bool is3D = texD->flags().testFlag(QRhiTexture::ThreeDimensional);
if ((texD->flags().testFlag(QRhiTexture::UsedAsCompressedAtlas) || is3D)
if ((texD->flags().testFlag(QRhiTexture::UsedAsCompressedAtlas) || is3D || isArray)
&& !texD->zeroInitialized)
{
// Create on first upload since glCompressedTexImage2D cannot take
@ -1920,17 +1942,19 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb
compressedFormatInfo(texD->m_format, texD->m_pixelSize, nullptr, &byteSize, nullptr);
if (is3D)
byteSize *= texD->m_depth;
if (isArray)
byteSize *= texD->m_arraySize;
QByteArray zeroBuf(byteSize, 0);
QGles2CommandBuffer::Command &cmd(cbD->commands.get());
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.faceTarget = effectiveTarget;
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.depth = is3D ? texD->m_depth : 0;
cmd.args.compressedImage.depth = is3D ? texD->m_depth : (isArray ? texD->m_arraySize : 0);
cmd.args.compressedImage.size = byteSize;
cmd.args.compressedImage.data = cbD->retainData(zeroBuf);
texD->zeroInitialized = true;
@ -1943,11 +1967,11 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb
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.faceTarget = effectiveTarget;
cmd.args.compressedSubImage.level = level;
cmd.args.compressedSubImage.dx = dp.x();
cmd.args.compressedSubImage.dy = dp.y();
cmd.args.compressedSubImage.dz = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional) ? layer : 0;
cmd.args.compressedSubImage.dz = is3D || isArray ? layer : 0;
cmd.args.compressedSubImage.w = size.width();
cmd.args.compressedSubImage.h = size.height();
cmd.args.compressedSubImage.glintformat = texD->glintformat;
@ -1958,12 +1982,12 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb
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.faceTarget = effectiveTarget;
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.depth = is3D ? texD->m_depth : 0;
cmd.args.compressedImage.depth = is3D ? texD->m_depth : (isArray ? texD->m_arraySize : 0);
cmd.args.compressedImage.size = rawData.size();
cmd.args.compressedImage.data = cbD->retainData(rawData);
}
@ -1977,11 +2001,11 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb
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.faceTarget = effectiveTarget;
cmd.args.subImage.level = level;
cmd.args.subImage.dx = dp.x();
cmd.args.subImage.dy = dp.y();
cmd.args.subImage.dz = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional) ? layer : 0;
cmd.args.subImage.dz = is3D || isArray ? layer : 0;
cmd.args.subImage.w = size.width();
cmd.args.subImage.h = size.height();
cmd.args.subImage.glformat = texD->glformat;
@ -2090,21 +2114,24 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
QGles2CommandBuffer::Command &cmd(cbD->commands.get());
cmd.cmd = QGles2CommandBuffer::Command::CopyTex;
const bool srcHasZ = srcD->m_flags.testFlag(QRhiTexture::ThreeDimensional) || srcD->m_flags.testFlag(QRhiTexture::TextureArray);
const bool dstHasZ = dstD->m_flags.testFlag(QRhiTexture::ThreeDimensional) || dstD->m_flags.testFlag(QRhiTexture::TextureArray);
cmd.args.copyTex.srcTarget = srcD->target;
cmd.args.copyTex.srcFaceTarget = srcFaceTargetBase + uint(u.desc.sourceLayer());
cmd.args.copyTex.srcFaceTarget = srcFaceTargetBase + (srcHasZ ? 0u : 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.srcZ = srcD->m_flags.testFlag(QRhiTexture::ThreeDimensional) ? u.desc.sourceLayer() : 0;
cmd.args.copyTex.srcZ = srcHasZ ? u.desc.sourceLayer() : 0;
cmd.args.copyTex.dstTarget = dstD->target;
cmd.args.copyTex.dstFaceTarget = dstFaceTargetBase + uint(u.desc.destinationLayer());
cmd.args.copyTex.dstFaceTarget = dstFaceTargetBase + (dstHasZ ? 0u : uint(u.desc.destinationLayer()));
cmd.args.copyTex.dstTexture = dstD->texture;
cmd.args.copyTex.dstLevel = u.desc.destinationLevel();
cmd.args.copyTex.dstX = dp.x();
cmd.args.copyTex.dstY = dp.y();
cmd.args.copyTex.dstZ = dstD->m_flags.testFlag(QRhiTexture::ThreeDimensional) ? u.desc.destinationLayer() : 0;
cmd.args.copyTex.dstZ = dstHasZ ? u.desc.destinationLayer() : 0;
cmd.args.copyTex.w = copySize.width();
cmd.args.copyTex.h = copySize.height();
@ -2122,7 +2149,9 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
cmd.args.readPixels.w = readImageSize.width();
cmd.args.readPixels.h = readImageSize.height();
cmd.args.readPixels.format = texD->m_format;
if (texD->m_flags.testFlag(QRhiTexture::ThreeDimensional)) {
if (texD->m_flags.testFlag(QRhiTexture::ThreeDimensional)
|| texD->m_flags.testFlag(QRhiTexture::TextureArray))
{
cmd.args.readPixels.readTarget = texD->target;
cmd.args.readPixels.slice3D = u.rb.layer();
} else {
@ -2843,7 +2872,7 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
GLuint fbo;
f->glGenFramebuffers(1, &fbo);
f->glBindFramebuffer(GL_FRAMEBUFFER, fbo);
if (cmd.args.copyTex.srcTarget == GL_TEXTURE_3D) {
if (cmd.args.copyTex.srcTarget == GL_TEXTURE_3D || cmd.args.copyTex.srcTarget == GL_TEXTURE_2D_ARRAY) {
f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.copyTex.srcTexture,
cmd.args.copyTex.srcLevel, cmd.args.copyTex.srcZ);
} else {
@ -2851,7 +2880,7 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
cmd.args.copyTex.srcFaceTarget, cmd.args.copyTex.srcTexture, cmd.args.copyTex.srcLevel);
}
f->glBindTexture(cmd.args.copyTex.dstTarget, cmd.args.copyTex.dstTexture);
if (cmd.args.copyTex.dstTarget == GL_TEXTURE_3D) {
if (cmd.args.copyTex.dstTarget == GL_TEXTURE_3D || cmd.args.copyTex.dstTarget == GL_TEXTURE_2D_ARRAY) {
f->glCopyTexSubImage3D(cmd.args.copyTex.dstTarget, cmd.args.copyTex.dstLevel,
cmd.args.copyTex.dstX, cmd.args.copyTex.dstY, cmd.args.copyTex.dstZ,
cmd.args.copyTex.srcX, cmd.args.copyTex.srcY,
@ -2951,7 +2980,7 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
f->glPixelStorei(GL_UNPACK_ALIGNMENT, cmd.args.subImage.rowStartAlign);
if (cmd.args.subImage.rowLength != 0)
f->glPixelStorei(GL_UNPACK_ROW_LENGTH, cmd.args.subImage.rowLength);
if (cmd.args.subImage.target == GL_TEXTURE_3D) {
if (cmd.args.subImage.target == GL_TEXTURE_3D || cmd.args.subImage.target == GL_TEXTURE_2D_ARRAY) {
f->glTexSubImage3D(cmd.args.subImage.target, cmd.args.subImage.level,
cmd.args.subImage.dx, cmd.args.subImage.dy, cmd.args.subImage.dz,
cmd.args.subImage.w, cmd.args.subImage.h, 1,
@ -2971,7 +3000,7 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
break;
case QGles2CommandBuffer::Command::CompressedImage:
f->glBindTexture(cmd.args.compressedImage.target, cmd.args.compressedImage.texture);
if (cmd.args.compressedImage.target == GL_TEXTURE_3D) {
if (cmd.args.compressedImage.target == GL_TEXTURE_3D || cmd.args.compressedImage.target == GL_TEXTURE_2D_ARRAY) {
f->glCompressedTexImage3D(cmd.args.compressedImage.target, cmd.args.compressedImage.level,
cmd.args.compressedImage.glintformat,
cmd.args.compressedImage.w, cmd.args.compressedImage.h, cmd.args.compressedImage.depth,
@ -2985,7 +3014,7 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
break;
case QGles2CommandBuffer::Command::CompressedSubImage:
f->glBindTexture(cmd.args.compressedSubImage.target, cmd.args.compressedSubImage.texture);
if (cmd.args.compressedSubImage.target == GL_TEXTURE_3D) {
if (cmd.args.compressedSubImage.target == GL_TEXTURE_3D || cmd.args.compressedSubImage.target == GL_TEXTURE_2D_ARRAY) {
f->glCompressedTexSubImage3D(cmd.args.compressedSubImage.target, cmd.args.compressedSubImage.level,
cmd.args.compressedSubImage.dx, cmd.args.compressedSubImage.dy, cmd.args.compressedSubImage.dz,
cmd.args.compressedSubImage.w, cmd.args.compressedSubImage.h, 1,
@ -3007,9 +3036,13 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, cmd.args.blitFromRb.renderbuffer);
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]);
if (cmd.args.blitFromRb.target == GL_TEXTURE_3D || cmd.args.blitFromRb.target == GL_TEXTURE_2D_ARRAY) {
f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
cmd.args.blitFromRb.texture, cmd.args.blitFromRb.dstLevel, cmd.args.blitFromRb.dstLayer);
} else {
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,
@ -3755,11 +3788,15 @@ void QRhiGles2::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resource
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());
if (colorTexD->m_flags.testFlag(QRhiTexture::CubeMap))
cmd.args.blitFromRb.target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + uint(colorAtt.resolveLayer());
else
cmd.args.blitFromRb.target = colorTexD->target;
cmd.args.blitFromRb.texture = colorTexD->texture;
cmd.args.blitFromRb.dstLevel = colorAtt.resolveLevel();
const bool hasZ = colorTexD->m_flags.testFlag(QRhiTexture::ThreeDimensional)
|| colorTexD->m_flags.testFlag(QRhiTexture::TextureArray);
cmd.args.blitFromRb.dstLayer = hasZ ? colorAtt.resolveLayer() : 0;
}
}
}
@ -4576,8 +4613,8 @@ QRhiTexture::Format QGles2RenderBuffer::backingFormat() const
}
QGles2Texture::QGles2Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
int sampleCount, Flags flags)
: QRhiTexture(rhi, format, pixelSize, depth, sampleCount, flags)
int arraySize, int sampleCount, Flags flags)
: QRhiTexture(rhi, format, pixelSize, depth, arraySize, sampleCount, flags)
{
}
@ -4622,6 +4659,7 @@ bool QGles2Texture::prepareCreate(QSize *adjustedSize)
const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
const bool isCube = m_flags.testFlag(CubeMap);
const bool isArray = m_flags.testFlag(QRhiTexture::TextureArray);
const bool is3D = m_flags.testFlag(ThreeDimensional);
const bool hasMipMaps = m_flags.testFlag(MipMapped);
const bool isCompressed = rhiD->isCompressedFormat(m_format);
@ -4634,14 +4672,28 @@ bool QGles2Texture::prepareCreate(QSize *adjustedSize)
qWarning("Texture cannot be both cube and 3D");
return false;
}
if (isArray && is3D) {
qWarning("Texture cannot be both array and 3D");
return false;
}
m_depth = qMax(1, m_depth);
if (m_depth > 1 && !is3D) {
qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth);
return false;
}
m_arraySize = qMax(0, m_arraySize);
if (m_arraySize > 0 && !isArray) {
qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize);
return false;
}
if (m_arraySize < 1 && isArray) {
qWarning("Texture is an array but array size is %d", m_arraySize);
return false;
}
target = isCube ? GL_TEXTURE_CUBE_MAP
: m_sampleCount > 1 ? GL_TEXTURE_2D_MULTISAMPLE : (is3D ? GL_TEXTURE_3D : GL_TEXTURE_2D);
: m_sampleCount > 1 ? GL_TEXTURE_2D_MULTISAMPLE
: (is3D ? GL_TEXTURE_3D : (isArray ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D));
if (m_flags.testFlag(ExternalOES))
target = GL_TEXTURE_EXTERNAL_OES;
else if (m_flags.testFlag(TextureRectangleGL))
@ -4687,21 +4739,23 @@ bool QGles2Texture::create()
rhiD->f->glGenTextures(1, &texture);
const bool isCube = m_flags.testFlag(CubeMap);
const bool isArray = m_flags.testFlag(QRhiTexture::TextureArray);
const bool is3D = m_flags.testFlag(ThreeDimensional);
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 (is3D) {
if (is3D || isArray) {
const int layerCount = is3D ? m_depth : m_arraySize;
if (hasMipMaps) {
for (int level = 0; level != mipLevelCount; ++level) {
const QSize mipSize = rhiD->q->sizeForMipLevel(level, size);
rhiD->f->glTexImage3D(target, level, GLint(glintformat), mipSize.width(), mipSize.height(), m_depth,
rhiD->f->glTexImage3D(target, level, GLint(glintformat), mipSize.width(), mipSize.height(), layerCount,
0, glformat, gltype, nullptr);
}
} else {
rhiD->f->glTexImage3D(target, 0, GLint(glintformat), size.width(), size.height(), m_depth,
rhiD->f->glTexImage3D(target, 0, GLint(glintformat), size.width(), size.height(), layerCount,
0, glformat, gltype, nullptr);
}
} else if (hasMipMaps || isCube) {
@ -4722,8 +4776,8 @@ bool QGles2Texture::create()
// Must be specified with immutable storage functions otherwise
// bindImageTexture may fail. Also, the internal format must be a
// sized format here.
if (is3D)
rhiD->f->glTexStorage3D(target, mipLevelCount, glsizedintformat, size.width(), size.height(), m_depth);
if (is3D || isArray)
rhiD->f->glTexStorage3D(target, mipLevelCount, glsizedintformat, size.width(), size.height(), is3D ? m_depth : m_arraySize);
else
rhiD->f->glTexStorage2D(target, mipLevelCount, glsizedintformat, size.width(), size.height());
}
@ -4942,7 +4996,7 @@ bool QGles2TextureRenderTarget::create()
if (texture) {
QGles2Texture *texD = QRHI_RES(QGles2Texture, texture);
Q_ASSERT(texD->texture && texD->specified);
if (texD->flags().testFlag(QRhiTexture::ThreeDimensional)) {
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());
} else {

View File

@ -139,7 +139,7 @@ inline bool operator!=(const QGles2SamplerData &a, const QGles2SamplerData &b)
struct QGles2Texture : public QRhiTexture
{
QGles2Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
int sampleCount, Flags flags);
int arraySize, int sampleCount, Flags flags);
~QGles2Texture();
void destroy() override;
bool create() override;
@ -521,6 +521,7 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
GLenum target;
GLuint texture;
int dstLevel;
int dstLayer;
} blitFromRb;
struct {
GLenum target;
@ -752,6 +753,7 @@ public:
QRhiTexture *createTexture(QRhiTexture::Format format,
const QSize &pixelSize,
int depth,
int arraySize,
int sampleCount,
QRhiTexture::Flags flags) override;
QRhiSampler *createSampler(QRhiSampler::Filter magFilter,
@ -910,6 +912,7 @@ public:
maxTextureSize(2048),
maxDrawBuffers(4),
maxSamples(16),
maxTextureArraySize(0),
maxThreadGroupsPerDimension(0),
maxThreadsPerThreadGroup(0),
maxThreadGroupsX(0),
@ -951,6 +954,7 @@ public:
int maxTextureSize;
int maxDrawBuffers;
int maxSamples;
int maxTextureArraySize;
int maxThreadGroupsPerDimension;
int maxThreadsPerThreadGroup;
int maxThreadGroupsX;

View File

@ -613,6 +613,8 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::RenderTo3DTextureSlice:
return true;
case QRhi::TextureArrays:
return true;
default:
Q_UNREACHABLE();
return false;
@ -646,6 +648,8 @@ int QRhiMetal::resourceLimit(QRhi::ResourceLimit limit) const
#else
return 512;
#endif
case QRhi::TextureArraySizeMax:
return 2048;
default:
Q_UNREACHABLE();
return 0;
@ -704,10 +708,10 @@ QRhiRenderBuffer *QRhiMetal::createRenderBuffer(QRhiRenderBuffer::Type type, con
}
QRhiTexture *QRhiMetal::createTexture(QRhiTexture::Format format,
const QSize &pixelSize, int depth,
const QSize &pixelSize, int depth, int arraySize,
int sampleCount, QRhiTexture::Flags flags)
{
return new QMetalTexture(this, format, pixelSize, depth, sampleCount, flags);
return new QMetalTexture(this, format, pixelSize, depth, arraySize, sampleCount, flags);
}
QRhiSampler *QRhiMetal::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
@ -2617,8 +2621,8 @@ QRhiTexture::Format QMetalRenderBuffer::backingFormat() const
}
QMetalTexture::QMetalTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
int sampleCount, Flags flags)
: QRhiTexture(rhi, format, pixelSize, depth, sampleCount, flags),
int arraySize, int sampleCount, Flags flags)
: QRhiTexture(rhi, format, pixelSize, depth, arraySize, sampleCount, flags),
d(new QMetalTextureData(this))
{
for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
@ -2673,6 +2677,7 @@ bool QMetalTexture::prepareCreate(QSize *adjustedSize)
const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
const bool isCube = m_flags.testFlag(CubeMap);
const bool is3D = m_flags.testFlag(ThreeDimensional);
const bool isArray = m_flags.testFlag(TextureArray);
const bool hasMipMaps = m_flags.testFlag(MipMapped);
QRHI_RES_RHI(QRhiMetal);
@ -2697,11 +2702,24 @@ bool QMetalTexture::prepareCreate(QSize *adjustedSize)
qWarning("Texture cannot be both cube and 3D");
return false;
}
if (isArray && is3D) {
qWarning("Texture cannot be both array and 3D");
return false;
}
m_depth = qMax(1, m_depth);
if (m_depth > 1 && !is3D) {
qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth);
return false;
}
m_arraySize = qMax(0, m_arraySize);
if (m_arraySize > 0 && !isArray) {
qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize);
return false;
}
if (m_arraySize < 1 && isArray) {
qWarning("Texture is an array but array size is %d", m_arraySize);
return false;
}
if (adjustedSize)
*adjustedSize = size;
@ -2719,12 +2737,24 @@ bool QMetalTexture::create()
const bool isCube = m_flags.testFlag(CubeMap);
const bool is3D = m_flags.testFlag(ThreeDimensional);
if (isCube)
const bool isArray = m_flags.testFlag(TextureArray);
if (isCube) {
desc.textureType = MTLTextureTypeCube;
else if (is3D)
} else if (is3D) {
desc.textureType = MTLTextureType3D;
else
} else if (isArray) {
#ifdef Q_OS_IOS
if (samples > 1) {
// would be available on iOS 14.0+ but cannot test for that with a 13 SDK
qWarning("Multisample 2D texture array is not supported on iOS");
}
desc.textureType = MTLTextureType2DArray;
#else
desc.textureType = samples > 1 ? MTLTextureType2DMultisampleArray : MTLTextureType2DArray;
#endif
} else {
desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;
}
desc.pixelFormat = d->format;
desc.width = NSUInteger(size.width());
desc.height = NSUInteger(size.height());
@ -2732,6 +2762,8 @@ bool QMetalTexture::create()
desc.mipmapLevelCount = NSUInteger(mipLevelCount);
if (samples > 1)
desc.sampleCount = NSUInteger(samples);
if (isArray)
desc.arrayLength = NSUInteger(m_arraySize);
desc.resourceOptions = MTLResourceStorageModePrivate;
desc.storageMode = MTLStorageModePrivate;
desc.usage = MTLTextureUsageShaderRead;
@ -2750,7 +2782,7 @@ bool QMetalTexture::create()
d->owns = true;
QRHI_PROF;
QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, samples));
QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : (isArray ? m_arraySize : 1), samples));
lastActiveFrameSlot = -1;
generation += 1;
@ -2794,8 +2826,9 @@ id<MTLTexture> QMetalTextureData::viewForLevel(int level)
const MTLTextureType type = [tex textureType];
const bool isCube = q->m_flags.testFlag(QRhiTexture::CubeMap);
const bool isArray = q->m_flags.testFlag(QRhiTexture::TextureArray);
id<MTLTexture> view = [tex newTextureViewWithPixelFormat: format textureType: type
levels: NSMakeRange(NSUInteger(level), 1) slices: NSMakeRange(0, isCube ? 6 : 1)];
levels: NSMakeRange(NSUInteger(level), 1) slices: NSMakeRange(0, isCube ? 6 : (isArray ? q->m_arraySize : 1))];
perLevelViews[level] = view;
return view;

View File

@ -103,7 +103,7 @@ struct QMetalTextureData;
struct QMetalTexture : public QRhiTexture
{
QMetalTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
int sampleCount, Flags flags);
int arraySize, int sampleCount, Flags flags);
~QMetalTexture();
void destroy() override;
bool create() override;
@ -366,6 +366,7 @@ public:
QRhiTexture *createTexture(QRhiTexture::Format format,
const QSize &pixelSize,
int depth,
int arraySize,
int sampleCount,
QRhiTexture::Flags flags) override;
QRhiSampler *createSampler(QRhiSampler::Filter magFilter,

View File

@ -162,6 +162,8 @@ int QRhiNull::resourceLimit(QRhi::ResourceLimit limit) const
return 0;
case QRhi::MaxThreadGroupZ:
return 0;
case QRhi::TextureArraySizeMax:
return 2048;
default:
Q_UNREACHABLE();
return 0;
@ -219,10 +221,10 @@ QRhiRenderBuffer *QRhiNull::createRenderBuffer(QRhiRenderBuffer::Type type, cons
}
QRhiTexture *QRhiNull::createTexture(QRhiTexture::Format format,
const QSize &pixelSize, int depth,
const QSize &pixelSize, int depth, int arraySize,
int sampleCount, QRhiTexture::Flags flags)
{
return new QNullTexture(this, format, pixelSize, depth, sampleCount, flags);
return new QNullTexture(this, format, pixelSize, depth, arraySize, sampleCount, flags);
}
QRhiSampler *QRhiNull::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
@ -671,8 +673,8 @@ QRhiTexture::Format QNullRenderBuffer::backingFormat() const
}
QNullTexture::QNullTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
int sampleCount, Flags flags)
: QRhiTexture(rhi, format, pixelSize, depth, sampleCount, flags)
int arraySize, int sampleCount, Flags flags)
: QRhiTexture(rhi, format, pixelSize, depth, arraySize, sampleCount, flags)
{
}
@ -703,11 +705,13 @@ bool QNullTexture::create()
QRHI_RES_RHI(QRhiNull);
const bool isCube = m_flags.testFlag(CubeMap);
const bool is3D = m_flags.testFlag(ThreeDimensional);
const bool isArray = m_flags.testFlag(TextureArray);
const bool hasMipMaps = m_flags.testFlag(MipMapped);
QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
m_depth = qMax(1, m_depth);
const int mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
const int layerCount = is3D ? m_depth : (isCube ? 6 : 1);
m_arraySize = qMax(0, m_arraySize);
const int layerCount = is3D ? m_depth : (isCube ? 6 : (isArray ? m_arraySize : 1));
if (m_format == RGBA8) {
image.resize(layerCount);
@ -737,12 +741,13 @@ bool QNullTexture::createFrom(QRhiTexture::NativeTexture src)
QRHI_RES_RHI(QRhiNull);
const bool isCube = m_flags.testFlag(CubeMap);
const bool isArray = m_flags.testFlag(TextureArray);
const bool hasMipMaps = m_flags.testFlag(MipMapped);
QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
const int mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
QRHI_PROF;
QRHI_PROF_F(newTexture(this, false, mipLevelCount, isCube ? 6 : 1, 1));
QRHI_PROF_F(newTexture(this, false, mipLevelCount, isCube ? 6 : (isArray ? m_arraySize : 1), 1));
rhiD->registerResource(this);
return true;

View File

@ -83,7 +83,7 @@ struct QNullRenderBuffer : public QRhiRenderBuffer
struct QNullTexture : public QRhiTexture
{
QNullTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
int sampleCount, Flags flags);
int arraySize, int sampleCount, Flags flags);
~QNullTexture();
void destroy() override;
bool create() override;
@ -224,6 +224,7 @@ public:
QRhiTexture *createTexture(QRhiTexture::Format format,
const QSize &pixelSize,
int depth,
int arraySize,
int sampleCount,
QRhiTexture::Flags flags) override;
QRhiSampler *createSampler(QRhiSampler::Filter magFilter,

View File

@ -3454,6 +3454,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
QVkTexture *utexD = QRHI_RES(QVkTexture, u.dst);
Q_ASSERT(utexD->m_flags.testFlag(QRhiTexture::UsedWithGenerateMips));
const bool isCube = utexD->m_flags.testFlag(QRhiTexture::CubeMap);
const bool isArray = utexD->m_flags.testFlag(QRhiTexture::TextureArray);
const bool is3D = utexD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
VkImageLayout origLayout = utexD->usageState.layout;
@ -3462,7 +3463,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
if (!origStage)
origStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
for (int layer = 0; layer < (isCube ? 6 : 1); ++layer) {
for (int layer = 0; layer < (isCube ? 6 : (isArray ? utexD->m_arraySize : 1)); ++layer) {
int w = utexD->m_pixelSize.width();
int h = utexD->m_pixelSize.height();
int depth = is3D ? utexD->m_depth : 1;
@ -4269,6 +4270,8 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::RenderTo3DTextureSlice:
return caps.texture3DSliceAs2D;
case QRhi::TextureArrays:
return true;
default:
Q_UNREACHABLE();
return false;
@ -4300,6 +4303,8 @@ int QRhiVulkan::resourceLimit(QRhi::ResourceLimit limit) const
return int(physDevProperties.limits.maxComputeWorkGroupSize[1]);
case QRhi::MaxThreadGroupZ:
return int(physDevProperties.limits.maxComputeWorkGroupSize[2]);
case QRhi::TextureArraySizeMax:
return int(physDevProperties.limits.maxImageArrayLayers);
default:
Q_UNREACHABLE();
return 0;
@ -4475,10 +4480,10 @@ QRhiRenderBuffer *QRhiVulkan::createRenderBuffer(QRhiRenderBuffer::Type type, co
}
QRhiTexture *QRhiVulkan::createTexture(QRhiTexture::Format format,
const QSize &pixelSize, int depth,
const QSize &pixelSize, int depth, int arraySize,
int sampleCount, QRhiTexture::Flags flags)
{
return new QVkTexture(this, format, pixelSize, depth, sampleCount, flags);
return new QVkTexture(this, format, pixelSize, depth, arraySize, sampleCount, flags);
}
QRhiSampler *QRhiVulkan::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
@ -5713,6 +5718,7 @@ bool QVkRenderBuffer::create()
backingTexture = QRHI_RES(QVkTexture, rhiD->createTexture(backingFormat(),
m_pixelSize,
1,
0,
m_sampleCount,
QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
} else {
@ -5762,8 +5768,8 @@ QRhiTexture::Format QVkRenderBuffer::backingFormat() const
}
QVkTexture::QVkTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
int sampleCount, Flags flags)
: QRhiTexture(rhi, format, pixelSize, depth, sampleCount, flags)
int arraySize, int sampleCount, Flags flags)
: QRhiTexture(rhi, format, pixelSize, depth, arraySize, sampleCount, flags)
{
for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
stagingBuffers[i] = VK_NULL_HANDLE;
@ -5834,6 +5840,7 @@ bool QVkTexture::prepareCreate(QSize *adjustedSize)
const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
const bool isCube = m_flags.testFlag(CubeMap);
const bool isArray = m_flags.testFlag(TextureArray);
const bool is3D = m_flags.testFlag(ThreeDimensional);
const bool hasMipMaps = m_flags.testFlag(MipMapped);
@ -5862,11 +5869,24 @@ bool QVkTexture::prepareCreate(QSize *adjustedSize)
qWarning("Texture cannot be both cube and 3D");
return false;
}
if (isArray && is3D) {
qWarning("Texture cannot be both array and 3D");
return false;
}
m_depth = qMax(1, m_depth);
if (m_depth > 1 && !is3D) {
qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth);
return false;
}
m_arraySize = qMax(0, m_arraySize);
if (m_arraySize > 0 && !isArray) {
qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize);
return false;
}
if (m_arraySize < 1 && isArray) {
qWarning("Texture is an array but array size is %d", m_arraySize);
return false;
}
usageState.layout = VK_IMAGE_LAYOUT_PREINITIALIZED;
usageState.access = 0;
@ -5884,13 +5904,16 @@ bool QVkTexture::finishCreate()
const auto aspectMask = aspectMaskForTextureFormat(m_format);
const bool isCube = m_flags.testFlag(CubeMap);
const bool isArray = m_flags.testFlag(TextureArray);
const bool is3D = m_flags.testFlag(ThreeDimensional);
VkImageViewCreateInfo viewInfo;
memset(&viewInfo, 0, sizeof(viewInfo));
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = image;
viewInfo.viewType = isCube ? VK_IMAGE_VIEW_TYPE_CUBE : (is3D ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D);
viewInfo.viewType = isCube ? VK_IMAGE_VIEW_TYPE_CUBE
: (is3D ? VK_IMAGE_VIEW_TYPE_3D
: (isArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D));
viewInfo.format = vkformat;
viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
@ -5898,7 +5921,7 @@ bool QVkTexture::finishCreate()
viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
viewInfo.subresourceRange.aspectMask = aspectMask;
viewInfo.subresourceRange.levelCount = mipLevelCount;
viewInfo.subresourceRange.layerCount = isCube ? 6 : 1;
viewInfo.subresourceRange.layerCount = isCube ? 6 : (isArray ? m_arraySize : 1);
VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &imageView);
if (err != VK_SUCCESS) {
@ -5922,6 +5945,7 @@ bool QVkTexture::create()
const bool isRenderTarget = m_flags.testFlag(QRhiTexture::RenderTarget);
const bool isDepth = isDepthTextureFormat(m_format);
const bool isCube = m_flags.testFlag(CubeMap);
const bool isArray = m_flags.testFlag(TextureArray);
const bool is3D = m_flags.testFlag(ThreeDimensional);
VkImageCreateInfo imageInfo;
@ -5952,7 +5976,7 @@ bool QVkTexture::create()
imageInfo.extent.height = uint32_t(size.height());
imageInfo.extent.depth = is3D ? m_depth : 1;
imageInfo.mipLevels = mipLevelCount;
imageInfo.arrayLayers = isCube ? 6 : 1;
imageInfo.arrayLayers = isCube ? 6 : (isArray ? m_arraySize : 1);
imageInfo.samples = samples;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
@ -5989,7 +6013,7 @@ bool QVkTexture::create()
rhiD->setObjectName(uint64_t(image), VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, m_objectName);
QRHI_PROF;
QRHI_PROF_F(newTexture(this, true, int(mipLevelCount), isCube ? 6 : 1, samples));
QRHI_PROF_F(newTexture(this, true, int(mipLevelCount), isCube ? 6 : (isArray ? m_arraySize : 1), samples));
owns = true;
rhiD->registerResource(this);
@ -6010,8 +6034,11 @@ bool QVkTexture::createFrom(QRhiTexture::NativeTexture src)
if (!finishCreate())
return false;
const bool isCube = m_flags.testFlag(CubeMap);
const bool isArray = m_flags.testFlag(TextureArray);
QRHI_PROF;
QRHI_PROF_F(newTexture(this, false, int(mipLevelCount), m_flags.testFlag(CubeMap) ? 6 : 1, samples));
QRHI_PROF_F(newTexture(this, false, int(mipLevelCount), isCube ? 6 : (isArray ? m_arraySize : 1), samples));
usageState.layout = VkImageLayout(src.layout);
@ -6039,13 +6066,16 @@ VkImageView QVkTexture::imageViewForLevel(int level)
const VkImageAspectFlags aspectMask = aspectMaskForTextureFormat(m_format);
const bool isCube = m_flags.testFlag(CubeMap);
const bool isArray = m_flags.testFlag(TextureArray);
const bool is3D = m_flags.testFlag(ThreeDimensional);
VkImageViewCreateInfo viewInfo;
memset(&viewInfo, 0, sizeof(viewInfo));
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = image;
viewInfo.viewType = isCube ? VK_IMAGE_VIEW_TYPE_CUBE : (is3D ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D);
viewInfo.viewType = isCube ? VK_IMAGE_VIEW_TYPE_CUBE
: (is3D ? VK_IMAGE_VIEW_TYPE_3D
: (isArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D));
viewInfo.format = vkformat;
viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
@ -6055,7 +6085,7 @@ VkImageView QVkTexture::imageViewForLevel(int level)
viewInfo.subresourceRange.baseMipLevel = uint32_t(level);
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = isCube ? 6 : 1;
viewInfo.subresourceRange.layerCount = isCube ? 6 : (isArray ? m_arraySize : 1);
VkImageView v = VK_NULL_HANDLE;
QRHI_RES_RHI(QRhiVulkan);

View File

@ -129,7 +129,7 @@ struct QVkRenderBuffer : public QRhiRenderBuffer
struct QVkTexture : public QRhiTexture
{
QVkTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
int sampleCount, Flags flags);
int arraySize, int sampleCount, Flags flags);
~QVkTexture();
void destroy() override;
bool create() override;
@ -682,6 +682,7 @@ public:
QRhiTexture *createTexture(QRhiTexture::Format format,
const QSize &pixelSize,
int depth,
int arraySize,
int sampleCount,
QRhiTexture::Flags flags) override;
QRhiSampler *createSampler(QRhiSampler::Filter magFilter,

View File

@ -7,3 +7,6 @@ android
# Skip 3D textures with Android emulator, the sw-based GL there is no good
[threeDimTexture]
android
# Same here, GLES 3.0 features seem hopeless
[renderToTextureTextureArray]
android

View File

@ -113,6 +113,8 @@ private slots:
void renderToTextureMip();
void renderToTextureCubemapFace_data();
void renderToTextureCubemapFace();
void renderToTextureTextureArray_data();
void renderToTextureTextureArray();
void renderToTextureTexturedQuad_data();
void renderToTextureTexturedQuad();
void renderToTextureArrayOfTexturedQuad_data();
@ -336,10 +338,13 @@ void tst_QRhi::create()
const int texMax = rhi->resourceLimit(QRhi::TextureSizeMax);
const int maxAtt = rhi->resourceLimit(QRhi::MaxColorAttachments);
const int framesInFlight = rhi->resourceLimit(QRhi::FramesInFlight);
const int texArrayMax = rhi->resourceLimit(QRhi::TextureArraySizeMax);
QVERIFY(texMin >= 1);
QVERIFY(texMax >= texMin);
QVERIFY(maxAtt >= 1);
QVERIFY(framesInFlight >= 1);
if (rhi->isFeatureSupported(QRhi::TextureArrays))
QVERIFY(texArrayMax > 1);
QVERIFY(rhi->nativeHandles());
QVERIFY(rhi->profiler());
@ -1880,6 +1885,133 @@ void tst_QRhi::renderToTextureCubemapFace()
QFAIL("Encountered a pixel that is neither red or blue");
}
QVERIFY(redCount > 0 && blueCount > 0);
QCOMPARE(redCount + blueCount, outputSize.width());
if (rhi->isYUpInFramebuffer() == rhi->isYUpInNDC())
QVERIFY(redCount < blueCount); // 100, 412
else
QVERIFY(redCount > blueCount); // 412, 100
}
void tst_QRhi::renderToTextureTextureArray_data()
{
rhiTestData();
}
void tst_QRhi::renderToTextureTextureArray()
{
QFETCH(QRhi::Implementation, impl);
QFETCH(QRhiInitParams *, initParams);
QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
if (!rhi)
QSKIP("QRhi could not be created, skipping testing rendering");
if (!rhi->isFeatureSupported(QRhi::TextureArrays))
QSKIP("TextureArrays is not supported with this backend, skipping test");
const QSize outputSize(512, 256);
const int ARRAY_SIZE = 8;
QScopedPointer<QRhiTexture> texture(rhi->newTextureArray(QRhiTexture::RGBA8,
ARRAY_SIZE,
outputSize,
1,
QRhiTexture::RenderTarget
| QRhiTexture::UsedAsTransferSource));
QVERIFY(texture->create());
const int LAYER = 5; // render into element #5
QRhiColorAttachment colorAtt(texture.data());
colorAtt.setLayer(LAYER);
QRhiTextureRenderTargetDescription rtDesc(colorAtt);
QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc));
QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
rt->setRenderPassDescriptor(rpDesc.data());
QVERIFY(rt->create());
QCOMPARE(rt->pixelSize(), texture->pixelSize());
QCOMPARE(rt->pixelSize(), outputSize);
QRhiCommandBuffer *cb = nullptr;
QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
QVERIFY(cb);
QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch();
static const float vertices[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
0.0f, 1.0f
};
QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices)));
QVERIFY(vbuf->create());
updates->uploadStaticBuffer(vbuf.data(), vertices);
QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
QVERIFY(srb->create());
QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
QShader vs = loadShader(":/data/simple.vert.qsb");
QVERIFY(vs.isValid());
QShader fs = loadShader(":/data/simple.frag.qsb");
QVERIFY(fs.isValid());
pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } });
QRhiVertexInputLayout inputLayout;
inputLayout.setBindings({ { 2 * sizeof(float) } });
inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } });
pipeline->setVertexInputLayout(inputLayout);
pipeline->setShaderResourceBindings(srb.data());
pipeline->setRenderPassDescriptor(rpDesc.data());
QVERIFY(pipeline->create());
cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 }, updates);
cb->setGraphicsPipeline(pipeline.data());
cb->setViewport({ 0, 0, float(rt->pixelSize().width()), float(rt->pixelSize().height()) });
QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0);
cb->setVertexInput(0, 1, &vbindings);
cb->draw(3);
QRhiReadbackResult readResult;
QImage result;
readResult.completed = [&readResult, &result] {
result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
readResult.pixelSize.width(), readResult.pixelSize.height(),
QImage::Format_RGBA8888);
};
QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
QRhiReadbackDescription readbackDescription(texture.data());
readbackDescription.setLayer(LAYER);
readbackBatch->readBackTexture(readbackDescription, &readResult);
cb->endPass(readbackBatch);
rhi->endOffscreenFrame();
QCOMPARE(result.size(), outputSize);
if (impl == QRhi::Null)
return;
const int y = 100;
const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y));
int x = result.width() - 1;
int redCount = 0;
int blueCount = 0;
const int maxFuzz = 1;
while (x-- >= 0) {
const QRgb c(*p++);
if (qRed(c) >= (255 - maxFuzz) && qGreen(c) == 0 && qBlue(c) == 0)
++redCount;
else if (qRed(c) == 0 && qGreen(c) == 0 && qBlue(c) >= (255 - maxFuzz))
++blueCount;
else
QFAIL("Encountered a pixel that is neither red or blue");
}
QVERIFY(redCount > 0 && blueCount > 0);
QCOMPARE(redCount + blueCount, outputSize.width());
if (rhi->isYUpInFramebuffer() == rhi->isYUpInNDC())

View File

@ -22,6 +22,7 @@ add_subdirectory(computeimage)
add_subdirectory(instancing)
add_subdirectory(noninstanced)
add_subdirectory(tex3d)
add_subdirectory(texturearray)
if(QT_FEATURE_widgets)
add_subdirectory(qrhiprof)
endif()

View File

@ -3,3 +3,5 @@ qsb --glsl "100 es,120" --hlsl 50 --msl 12 -c color.frag -o color.frag.qsb
qsb --glsl "100 es,120" --hlsl 50 --msl 12 -c texture.vert -o texture.vert.qsb
qsb --glsl "100 es,120" --hlsl 50 --msl 12 -c texture.frag -o texture.frag.qsb
qsb --glsl "310 es,150" --hlsl 50 --msl 12 -c texture_ms4.frag -o texture_ms4.frag.qsb
qsb --glsl "100 es,120,150" --hlsl 50 --msl 12 -c texture_arr.vert -o texture_arr.vert.qsb
qsb --glsl "100 es,120,150" --hlsl 50 --msl 12 -c texture_arr.frag -o texture_arr.frag.qsb

View File

@ -109,7 +109,7 @@ QString graphicsApiName()
case Null:
return QLatin1String("Null (no output)");
case OpenGL:
return QLatin1String("OpenGL 2.x");
return QLatin1String("OpenGL");
case Vulkan:
return QLatin1String("Vulkan");
case D3D11:
@ -462,7 +462,7 @@ int main(int argc, char **argv)
cmdLineParser.addHelpOption();
QCommandLineOption nullOption({ "n", "null" }, QLatin1String("Null"));
cmdLineParser.addOption(nullOption);
QCommandLineOption glOption({ "g", "opengl" }, QLatin1String("OpenGL (2.x)"));
QCommandLineOption glOption({ "g", "opengl" }, QLatin1String("OpenGL"));
cmdLineParser.addOption(glOption);
QCommandLineOption vkOption({ "v", "vulkan" }, QLatin1String("Vulkan"));
cmdLineParser.addOption(vkOption);
@ -475,6 +475,8 @@ int main(int argc, char **argv)
// Use this parameter for the latter.
QCommandLineOption sdOption({ "s", "self-destruct" }, QLatin1String("Self-destruct after 5 seconds."));
cmdLineParser.addOption(sdOption);
QCommandLineOption coreProfOption({ "c", "core" }, QLatin1String("Request a core profile context for OpenGL"));
cmdLineParser.addOption(coreProfOption);
// Attempt testing device lost situations on D3D at least.
QCommandLineOption tdrOption(QLatin1String("curse"), QLatin1String("Curse the graphics device. "
"(generate a device reset every <count> frames when on D3D11)"),
@ -517,6 +519,14 @@ int main(int argc, char **argv)
QSurfaceFormat fmt;
fmt.setDepthBufferSize(24);
fmt.setStencilBufferSize(8);
if (cmdLineParser.isSet(coreProfOption)) {
#ifdef Q_OS_DARWIN
fmt.setVersion(4, 1);
#else
fmt.setVersion(4, 3);
#endif
fmt.setProfile(QSurfaceFormat::CoreProfile);
}
if (sampleCount > 1)
fmt.setSamples(sampleCount);
if (scFlags.testFlag(QRhiSwapChain::NoVSync))

View File

@ -0,0 +1,19 @@
#version 440
layout(location = 0) in vec2 v_texcoord;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 mvp;
int flip;
int array_index;
};
layout(binding = 1) uniform sampler2DArray texArr;
void main()
{
vec4 c = texture(texArr, vec3(v_texcoord, float(array_index)));
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;
int array_index;
};
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

@ -0,0 +1,26 @@
qt_internal_add_manual_test(texturearray
GUI
SOURCES
texturearray.cpp
PUBLIC_LIBRARIES
Qt::Gui
Qt::GuiPrivate
)
set_source_files_properties("../shared/texture_arr.vert.qsb"
PROPERTIES QT_RESOURCE_ALIAS "texture_arr.vert.qsb"
)
set_source_files_properties("../shared/texture_arr.frag.qsb"
PROPERTIES QT_RESOURCE_ALIAS "texture_arr.frag.qsb"
)
set(texturearray_resource_files
"../shared/texture_arr.vert.qsb"
"../shared/texture_arr.frag.qsb"
)
qt_internal_add_resource(texturearray "texturearray"
PREFIX
"/"
FILES
${texturearray_resource_files}
)

View File

@ -0,0 +1,198 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** 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 https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "../shared/examplefw.h"
#include <QElapsedTimer>
// Creates a texture array object with size 4, uploads a different
// image to each, and cycles through them on-screen.
static const int ARRAY_SIZE = 4;
static const int UBUF_SIZE = 72;
static float vertexData[] =
{ // 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 indexData[] =
{
0, 1, 2, 0, 2, 3
};
struct {
QList<QRhiResource *> releasePool;
QRhiTexture *texArr = nullptr;
QRhiSampler *sampler = nullptr;
QRhiShaderResourceBindings *srb = nullptr;
QRhiBuffer *vbuf = nullptr;
QRhiBuffer *ibuf = nullptr;
QRhiBuffer *ubuf = nullptr;
QRhiGraphicsPipeline *ps = nullptr;
QRhiResourceUpdateBatch *initialUpdates = nullptr;
QMatrix4x4 winProj;
QElapsedTimer t;
int arrayIndex = 0;
} d;
void Window::customInit()
{
if (!m_r->isFeatureSupported(QRhi::TextureArrays))
qFatal("Texture array objects are not supported by this backend");
d.texArr = m_r->newTextureArray(QRhiTexture::RGBA8, ARRAY_SIZE, QSize(512, 512));
d.releasePool << d.texArr;
d.texArr->create();
d.initialUpdates = m_r->nextResourceUpdateBatch();
QImage img(512, 512, QImage::Format_RGBA8888);
img.fill(Qt::red);
d.initialUpdates->uploadTexture(d.texArr, QRhiTextureUploadDescription(QRhiTextureUploadEntry(0, 0, QRhiTextureSubresourceUploadDescription(img))));
img.fill(Qt::green);
d.initialUpdates->uploadTexture(d.texArr, QRhiTextureUploadDescription(QRhiTextureUploadEntry(1, 0, QRhiTextureSubresourceUploadDescription(img))));
img.fill(Qt::blue);
d.initialUpdates->uploadTexture(d.texArr, QRhiTextureUploadDescription(QRhiTextureUploadEntry(2, 0, QRhiTextureSubresourceUploadDescription(img))));
img.fill(Qt::yellow);
d.initialUpdates->uploadTexture(d.texArr, QRhiTextureUploadDescription(QRhiTextureUploadEntry(3, 0, QRhiTextureSubresourceUploadDescription(img))));
d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
d.releasePool << d.sampler;
d.sampler->create();
d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, UBUF_SIZE);
d.releasePool << d.ubuf;
d.ubuf->create();
d.srb = m_r->newShaderResourceBindings();
d.releasePool << d.srb;
d.srb->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf, 0, UBUF_SIZE),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.texArr, d.sampler)
});
d.srb->create();
d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData));
d.releasePool << d.vbuf;
d.vbuf->create();
d.ibuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, sizeof(indexData));
d.releasePool << d.ibuf;
d.ibuf->create();
d.initialUpdates->uploadStaticBuffer(d.vbuf, 0, sizeof(vertexData), vertexData);
d.initialUpdates->uploadStaticBuffer(d.ibuf, indexData);
d.ps = m_r->newGraphicsPipeline();
d.releasePool << d.ps;
d.ps->setShaderStages({
{ QRhiShaderStage::Vertex, getShader(QLatin1String(":/texture_arr.vert.qsb")) },
{ QRhiShaderStage::Fragment, getShader(QLatin1String(":/texture_arr.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);
d.ps->setRenderPassDescriptor(m_rp);
d.ps->create();
d.t.start();
}
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;
}
if (d.winProj != m_proj) {
d.winProj = m_proj;
QMatrix4x4 mvp = m_proj;
mvp.scale(2);
u->updateDynamicBuffer(d.ubuf, 0, 64, mvp.constData());
const qint32 flip = 0;
u->updateDynamicBuffer(d.ubuf, 64, 4, &flip);
u->updateDynamicBuffer(d.ubuf, 68, 4, &d.arrayIndex);
}
if (d.t.elapsed() > 2000) {
d.t.restart();
d.arrayIndex = (d.arrayIndex + 1) % ARRAY_SIZE;
u->updateDynamicBuffer(d.ubuf, 68, 4, &d.arrayIndex);
}
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()) });
cb->setShaderResources();
QRhiCommandBuffer::VertexInput vbufBinding(d.vbuf, 0);
cb->setVertexInput(0, 1, &vbufBinding, d.ibuf, 0, QRhiCommandBuffer::IndexUInt16);
cb->drawIndexed(6);
cb->endPass();
}