rhi: Add support for arrays of combined image samplers

Introduces a new QRhiShaderResourceBinding function that takes an array
of texture-sampler pairs. The existing function is also available and is
equivalent to calling the array-based version with array size 1.

It is important to note that for Metal one needs MSL 2.0 for array of
textures, so qsb needs --msl 20 instead of --msl 12 for such shaders.

Comes with an autotest, and also updates all .qsb files for said test
with the latest shadertools.

Task-number: QTBUG-82624
Change-Id: Ibc1973aae826836f16d842c41d6c8403fd7ff876
Reviewed-by: Christian Strømme <christian.stromme@qt.io>
This commit is contained in:
Laszlo Agocs 2020-03-03 14:24:11 +01:00
parent e1e0862990
commit c3ae30085e
19 changed files with 450 additions and 167 deletions

View File

@ -2885,16 +2885,57 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::uniformBufferWithDynamicOff
\return a shader resource binding for the given binding number, pipeline
stages, texture, and sampler specified by \a binding, \a stage, \a tex,
\a sampler.
\note This function is equivalent to calling sampledTextures() with a
\c count of 1.
\sa sampledTextures()
*/
QRhiShaderResourceBinding QRhiShaderResourceBinding::sampledTexture(
int binding, StageFlags stage, QRhiTexture *tex, QRhiSampler *sampler)
{
const TextureAndSampler texSampler = { tex, sampler };
return sampledTextures(binding, stage, 1, &texSampler);
}
/*!
\return a shader resource binding for the given binding number, pipeline
stages, and the array of texture-sampler pairs specified by \a binding, \a
stage, \a count, and \a texSamplers.
\note \a count must be at least 1, and not larger than 16.
\note When \a count is 1, this function is equivalent to sampledTexture().
This function is relevant when arrays of combined image samplers are
involved. For example, in GLSL \c{layout(binding = 5) uniform sampler2D
shadowMaps[8];} declares an array of combined image samplers. The
application is then expected provide a QRhiShaderResourceBinding for
binding point 5, set up by calling this function with \a count set to 8 and
a valid texture and sampler for each element of the array.
\warning All elements of the array must be specified. With the above
example, the only valid, portable approach is calling this function with a
\a count of 8. Additionally, all QRhiTexture and QRhiSampler instances must
be valid, meaning nullptr is not an accepted value. This is due to some of
the underlying APIs, such as, Vulkan, that require a valid image and
sampler object for each element in descriptor arrays. Applications are
advised to provide "dummy" samplers and textures if some array elements are
not relevant (due to not being accessed in the shader).
\sa sampledTexture()
*/
QRhiShaderResourceBinding QRhiShaderResourceBinding::sampledTextures(
int binding, StageFlags stage, int count, const TextureAndSampler *texSamplers)
{
Q_ASSERT(count >= 1 && count <= Data::MAX_TEX_SAMPLER_ARRAY_SIZE);
QRhiShaderResourceBinding b;
b.d.binding = binding;
b.d.stage = stage;
b.d.type = SampledTexture;
b.d.u.stex.tex = tex;
b.d.u.stex.sampler = sampler;
b.d.u.stex.count = count;
for (int i = 0; i < count; ++i)
b.d.u.stex.texSamplers[i] = texSamplers[i];
return b;
}
@ -3084,11 +3125,15 @@ bool operator==(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBind
}
break;
case QRhiShaderResourceBinding::SampledTexture:
if (da->u.stex.tex != db->u.stex.tex
|| da->u.stex.sampler != db->u.stex.sampler)
if (da->u.stex.count != db->u.stex.count)
return false;
for (int i = 0; i < da->u.stex.count; ++i) {
if (da->u.stex.texSamplers[i].tex != db->u.stex.texSamplers[i].tex
|| da->u.stex.texSamplers[i].sampler != db->u.stex.texSamplers[i].sampler)
{
return false;
}
}
break;
case QRhiShaderResourceBinding::ImageLoad:
Q_FALLTHROUGH();
@ -3162,10 +3207,13 @@ QDebug operator<<(QDebug dbg, const QRhiShaderResourceBinding &b)
<< ')';
break;
case QRhiShaderResourceBinding::SampledTexture:
dbg.nospace() << " SampledTexture("
<< "texture=" << d->u.stex.tex
<< " sampler=" << d->u.stex.sampler
<< ')';
dbg.nospace() << " SampledTextures("
<< "count=" << d->u.stex.count;
for (int i = 0; i < d->u.stex.count; ++i) {
dbg.nospace() << " texture=" << d->u.stex.texSamplers[i].tex
<< " sampler=" << d->u.stex.texSamplers[i].sampler;
}
dbg.nospace() << ')';
break;
case QRhiShaderResourceBinding::ImageLoad:
dbg.nospace() << " ImageLoad("

View File

@ -348,6 +348,12 @@ public:
static QRhiShaderResourceBinding sampledTexture(int binding, StageFlags stage, QRhiTexture *tex, QRhiSampler *sampler);
struct TextureAndSampler {
QRhiTexture *tex;
QRhiSampler *sampler;
};
static QRhiShaderResourceBinding sampledTextures(int binding, StageFlags stage, int count, const TextureAndSampler *texSamplers);
static QRhiShaderResourceBinding imageLoad(int binding, StageFlags stage, QRhiTexture *tex, int level);
static QRhiShaderResourceBinding imageStore(int binding, StageFlags stage, QRhiTexture *tex, int level);
static QRhiShaderResourceBinding imageLoadStore(int binding, StageFlags stage, QRhiTexture *tex, int level);
@ -370,9 +376,10 @@ public:
int maybeSize;
bool hasDynamicOffset;
};
static const int MAX_TEX_SAMPLER_ARRAY_SIZE = 16;
struct SampledTextureData {
QRhiTexture *tex;
QRhiSampler *sampler;
int count;
TextureAndSampler texSamplers[MAX_TEX_SAMPLER_ARRAY_SIZE];
};
struct StorageImageData {
QRhiTexture *tex;

View File

@ -627,18 +627,25 @@ void QRhiD3D11::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind
break;
case QRhiShaderResourceBinding::SampledTexture:
{
QD3D11Texture *texD = QRHI_RES(QD3D11Texture, b->u.stex.tex);
QD3D11Sampler *samplerD = QRHI_RES(QD3D11Sampler, b->u.stex.sampler);
if (texD->generation != bd.stex.texGeneration
|| texD->m_id != bd.stex.texId
|| samplerD->generation != bd.stex.samplerGeneration
|| samplerD->m_id != bd.stex.samplerId)
const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
if (bd.stex.count != data->count) {
bd.stex.count = data->count;
srbUpdate = true;
}
for (int elem = 0; elem < data->count; ++elem) {
QD3D11Texture *texD = QRHI_RES(QD3D11Texture, data->texSamplers[elem].tex);
QD3D11Sampler *samplerD = QRHI_RES(QD3D11Sampler, data->texSamplers[elem].sampler);
if (texD->generation != bd.stex.d[elem].texGeneration
|| texD->m_id != bd.stex.d[elem].texId
|| samplerD->generation != bd.stex.d[elem].samplerGeneration
|| samplerD->m_id != bd.stex.d[elem].samplerId)
{
srbUpdate = true;
bd.stex.texId = texD->m_id;
bd.stex.texGeneration = texD->generation;
bd.stex.samplerId = samplerD->m_id;
bd.stex.samplerGeneration = samplerD->generation;
bd.stex.d[elem].texId = texD->m_id;
bd.stex.d[elem].texGeneration = texD->generation;
bd.stex.d[elem].samplerId = samplerD->m_id;
bd.stex.d[elem].samplerGeneration = samplerD->generation;
}
}
}
break;
@ -1894,31 +1901,38 @@ void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD,
break;
case QRhiShaderResourceBinding::SampledTexture:
{
QD3D11Texture *texD = QRHI_RES(QD3D11Texture, b->u.stex.tex);
QD3D11Sampler *samplerD = QRHI_RES(QD3D11Sampler, b->u.stex.sampler);
bd.stex.texId = texD->m_id;
bd.stex.texGeneration = texD->generation;
bd.stex.samplerId = samplerD->m_id;
bd.stex.samplerGeneration = samplerD->generation;
const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
bd.stex.count = data->count;
const QPair<int, int> nativeBindingVert = mapBinding(b->binding, RBM_VERTEX, nativeResourceBindingMaps);
const QPair<int, int> nativeBindingFrag = mapBinding(b->binding, RBM_FRAGMENT, nativeResourceBindingMaps);
const QPair<int, int> nativeBindingComp = mapBinding(b->binding, RBM_COMPUTE, nativeResourceBindingMaps);
// if SPIR-V binding b is mapped to tN and sN in HLSL, and it
// is an array, then it will use tN, tN+1, tN+2, ..., and sN,
// sN+1, sN+2, ...
for (int elem = 0; elem < data->count; ++elem) {
QD3D11Texture *texD = QRHI_RES(QD3D11Texture, data->texSamplers[elem].tex);
QD3D11Sampler *samplerD = QRHI_RES(QD3D11Sampler, data->texSamplers[elem].sampler);
bd.stex.d[elem].texId = texD->m_id;
bd.stex.d[elem].texGeneration = texD->generation;
bd.stex.d[elem].samplerId = samplerD->m_id;
bd.stex.d[elem].samplerGeneration = samplerD->generation;
if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
QPair<int, int> nativeBinding = mapBinding(b->binding, RBM_VERTEX, nativeResourceBindingMaps);
if (nativeBinding.first >= 0 && nativeBinding.second >= 0) {
res[RBM_VERTEX].textures.append({ nativeBinding.first, texD->srv });
res[RBM_VERTEX].samplers.append({ nativeBinding.second, samplerD->samplerState });
if (nativeBindingVert.first >= 0 && nativeBindingVert.second >= 0) {
res[RBM_VERTEX].textures.append({ nativeBindingVert.first + elem, texD->srv });
res[RBM_VERTEX].samplers.append({ nativeBindingVert.second + elem, samplerD->samplerState });
}
}
if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
QPair<int, int> nativeBinding = mapBinding(b->binding, RBM_FRAGMENT, nativeResourceBindingMaps);
if (nativeBinding.first >= 0 && nativeBinding.second >= 0) {
res[RBM_FRAGMENT].textures.append({ nativeBinding.first, texD->srv });
res[RBM_FRAGMENT].samplers.append({ nativeBinding.second, samplerD->samplerState });
if (nativeBindingFrag.first >= 0 && nativeBindingFrag.second >= 0) {
res[RBM_FRAGMENT].textures.append({ nativeBindingFrag.first + elem, texD->srv });
res[RBM_FRAGMENT].samplers.append({ nativeBindingFrag.second + elem, samplerD->samplerState });
}
}
if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
QPair<int, int> nativeBinding = mapBinding(b->binding, RBM_COMPUTE, nativeResourceBindingMaps);
if (nativeBinding.first >= 0 && nativeBinding.second >= 0) {
res[RBM_COMPUTE].textures.append({ nativeBinding.first, texD->srv });
res[RBM_COMPUTE].samplers.append({ nativeBinding.second, samplerD->samplerState });
if (nativeBindingComp.first >= 0 && nativeBindingComp.second >= 0) {
res[RBM_COMPUTE].textures.append({ nativeBindingComp.first + elem, texD->srv });
res[RBM_COMPUTE].samplers.append({ nativeBindingComp.second + elem, samplerD->samplerState });
}
}
}
}
@ -3529,11 +3543,15 @@ static pD3DCompile resolveD3DCompile()
static QByteArray compileHlslShaderSource(const QShader &shader, QShader::Variant shaderVariant, QString *error, QShaderKey *usedShaderKey)
{
QShaderCode dxbc = shader.shader({ QShader::DxbcShader, 50, shaderVariant });
if (!dxbc.shader().isEmpty())
QShaderKey key = { QShader::DxbcShader, 50, shaderVariant };
QShaderCode dxbc = shader.shader(key);
if (!dxbc.shader().isEmpty()) {
if (usedShaderKey)
*usedShaderKey = key;
return dxbc.shader();
}
const QShaderKey key = { QShader::HlslShader, 50, shaderVariant };
key = { QShader::HlslShader, 50, shaderVariant };
QShaderCode hlslSource = shader.shader(key);
if (hlslSource.shader().isEmpty()) {
qWarning() << "No HLSL (shader model 5.0) code found in baked shader" << shader;

View File

@ -210,10 +210,13 @@ struct QD3D11ShaderResourceBindings : public QRhiShaderResourceBindings
uint generation;
};
struct BoundSampledTextureData {
int count;
struct {
quint64 texId;
uint texGeneration;
quint64 samplerId;
uint samplerGeneration;
} d[QRhiShaderResourceBinding::Data::MAX_TEX_SAMPLER_ARRAY_SIZE];
};
struct BoundStorageImageData {
quint64 id;

View File

@ -919,10 +919,12 @@ void QRhiGles2::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind
hasDynamicOffsetInSrb = true;
break;
case QRhiShaderResourceBinding::SampledTexture:
for (int elem = 0; elem < b->u.stex.count; ++elem) {
trackedRegisterTexture(&passResTracker,
QRHI_RES(QGles2Texture, b->u.stex.tex),
QRHI_RES(QGles2Texture, b->u.stex.texSamplers[elem].tex),
QRhiPassResourceTracker::TexSample,
QRhiPassResourceTracker::toPassTrackerTextureStage(b->stage));
}
break;
case QRhiShaderResourceBinding::ImageLoad:
case QRhiShaderResourceBinding::ImageStore:
@ -2574,11 +2576,11 @@ void QRhiGles2::bindShaderResources(QRhiGraphicsPipeline *maybeGraphicsPs, QRhiC
break;
case QRhiShaderResourceBinding::SampledTexture:
{
QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.stex.tex);
QGles2Sampler *samplerD = QRHI_RES(QGles2Sampler, b->u.stex.sampler);
QVector<QGles2SamplerDescription> &samplers(maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->samplers
: QRHI_RES(QGles2ComputePipeline, maybeComputePs)->samplers);
for (int elem = 0; elem < b->u.stex.count; ++elem) {
QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.stex.texSamplers[elem].tex);
QGles2Sampler *samplerD = QRHI_RES(QGles2Sampler, b->u.stex.texSamplers[elem].sampler);
for (QGles2SamplerDescription &sampler : samplers) {
if (sampler.binding == b->binding) {
f->glActiveTexture(GL_TEXTURE0 + uint(texUnit));
@ -2602,11 +2604,12 @@ void QRhiGles2::bindShaderResources(QRhiGraphicsPipeline *maybeGraphicsPs, QRhiC
texD->samplerState = samplerD->d;
}
f->glUniform1i(sampler.glslLocation, texUnit);
f->glUniform1i(sampler.glslLocation + elem, texUnit);
++texUnit;
}
}
}
}
break;
case QRhiShaderResourceBinding::ImageLoad:
case QRhiShaderResourceBinding::ImageStore:

View File

@ -748,30 +748,33 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
break;
case QRhiShaderResourceBinding::SampledTexture:
{
QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
for (int elem = 0; elem < data->count; ++elem) {
QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.texSamplers[elem].tex);
QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.texSamplers[elem].sampler);
if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
const int nativeBindingTexture = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture);
const int nativeBindingSampler = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Sampler);
if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) {
res[VERTEX].textures.append({ nativeBindingTexture, texD->d->tex });
res[VERTEX].samplers.append({ nativeBindingSampler, samplerD->d->samplerState });
res[VERTEX].textures.append({ nativeBindingTexture + elem, texD->d->tex });
res[VERTEX].samplers.append({ nativeBindingSampler + elem, samplerD->d->samplerState });
}
}
if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
const int nativeBindingTexture = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture);
const int nativeBindingSampler = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Sampler);
if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) {
res[FRAGMENT].textures.append({ nativeBindingTexture, texD->d->tex });
res[FRAGMENT].samplers.append({ nativeBindingSampler, samplerD->d->samplerState });
res[FRAGMENT].textures.append({ nativeBindingTexture + elem, texD->d->tex });
res[FRAGMENT].samplers.append({ nativeBindingSampler + elem, samplerD->d->samplerState });
}
}
if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
const int nativeBindingTexture = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture);
const int nativeBindingSampler = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Sampler);
if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) {
res[COMPUTE].textures.append({ nativeBindingTexture, texD->d->tex });
res[COMPUTE].samplers.append({ nativeBindingSampler, samplerD->d->samplerState });
res[COMPUTE].textures.append({ nativeBindingTexture + elem, texD->d->tex });
res[COMPUTE].samplers.append({ nativeBindingSampler + elem, samplerD->d->samplerState });
}
}
}
}
@ -1020,22 +1023,29 @@ void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind
break;
case QRhiShaderResourceBinding::SampledTexture:
{
QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
if (texD->generation != bd.stex.texGeneration
|| texD->m_id != bd.stex.texId
|| samplerD->generation != bd.stex.samplerGeneration
|| samplerD->m_id != bd.stex.samplerId)
const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
if (bd.stex.count != data->count) {
bd.stex.count = data->count;
resNeedsRebind = true;
}
for (int elem = 0; elem < data->count; ++elem) {
QMetalTexture *texD = QRHI_RES(QMetalTexture, data->texSamplers[elem].tex);
QMetalSampler *samplerD = QRHI_RES(QMetalSampler, data->texSamplers[elem].sampler);
if (texD->generation != bd.stex.d[elem].texGeneration
|| texD->m_id != bd.stex.d[elem].texId
|| samplerD->generation != bd.stex.d[elem].samplerGeneration
|| samplerD->m_id != bd.stex.d[elem].samplerId)
{
resNeedsRebind = true;
bd.stex.texId = texD->m_id;
bd.stex.texGeneration = texD->generation;
bd.stex.samplerId = samplerD->m_id;
bd.stex.samplerGeneration = samplerD->generation;
bd.stex.d[elem].texId = texD->m_id;
bd.stex.d[elem].texGeneration = texD->generation;
bd.stex.d[elem].samplerId = samplerD->m_id;
bd.stex.d[elem].samplerGeneration = samplerD->generation;
}
texD->lastActiveFrameSlot = currentFrameSlot;
samplerD->lastActiveFrameSlot = currentFrameSlot;
}
}
break;
case QRhiShaderResourceBinding::ImageLoad:
case QRhiShaderResourceBinding::ImageStore:
@ -2981,12 +2991,16 @@ bool QMetalShaderResourceBindings::build()
break;
case QRhiShaderResourceBinding::SampledTexture:
{
QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
bd.stex.texId = texD->m_id;
bd.stex.texGeneration = texD->generation;
bd.stex.samplerId = samplerD->m_id;
bd.stex.samplerGeneration = samplerD->generation;
const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
bd.stex.count = data->count;
for (int elem = 0; elem < data->count; ++elem) {
QMetalTexture *texD = QRHI_RES(QMetalTexture, data->texSamplers[elem].tex);
QMetalSampler *samplerD = QRHI_RES(QMetalSampler, data->texSamplers[elem].sampler);
bd.stex.d[elem].texId = texD->m_id;
bd.stex.d[elem].texGeneration = texD->generation;
bd.stex.d[elem].samplerId = samplerD->m_id;
bd.stex.d[elem].samplerGeneration = samplerD->generation;
}
}
break;
case QRhiShaderResourceBinding::ImageLoad:
@ -3241,8 +3255,12 @@ static inline MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c)
id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
QString *error, QByteArray *entryPoint, QShaderKey *activeKey)
{
QShaderKey key = { QShader::MetalLibShader, 12, shaderVariant };
QShaderKey key = { QShader::MetalLibShader, 20, shaderVariant };
QShaderCode mtllib = shader.shader(key);
if (mtllib.shader().isEmpty()) {
key.setSourceVersion(12);
mtllib = shader.shader(key);
}
if (!mtllib.shader().isEmpty()) {
dispatch_data_t data = dispatch_data_create(mtllib.shader().constData(),
size_t(mtllib.shader().size()),
@ -3261,16 +3279,20 @@ id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Var
}
}
key = { QShader::MslShader, 12, shaderVariant };
key = { QShader::MslShader, 20, shaderVariant };
QShaderCode mslSource = shader.shader(key);
if (mslSource.shader().isEmpty()) {
qWarning() << "No MSL 1.2 code found in baked shader" << shader;
key.setSourceVersion(12);
mslSource = shader.shader(key);
}
if (mslSource.shader().isEmpty()) {
qWarning() << "No MSL 2.0 or 1.2 code found in baked shader" << shader;
return nil;
}
NSString *src = [NSString stringWithUTF8String: mslSource.shader().constData()];
MTLCompileOptions *opts = [[MTLCompileOptions alloc] init];
opts.languageVersion = MTLLanguageVersion1_2;
opts.languageVersion = key.sourceVersion() == 20 ? MTLLanguageVersion2_0 : MTLLanguageVersion1_2;
NSError *err = nil;
id<MTLLibrary> lib = [dev newLibraryWithSource: src options: opts error: &err];
[opts release];

View File

@ -197,10 +197,13 @@ struct QMetalShaderResourceBindings : public QRhiShaderResourceBindings
uint generation;
};
struct BoundSampledTextureData {
int count;
struct {
quint64 texId;
uint texGeneration;
quint64 samplerId;
uint samplerGeneration;
} d[QRhiShaderResourceBinding::Data::MAX_TEX_SAMPLER_ARRAY_SIZE];
};
struct BoundStorageImageData {
quint64 id;

View File

@ -2487,7 +2487,8 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i
QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, srb);
QVarLengthArray<VkDescriptorBufferInfo, 8> bufferInfos;
QVarLengthArray<VkDescriptorImageInfo, 8> imageInfos;
using ArrayOfImageDesc = QVarLengthArray<VkDescriptorImageInfo, 8>;
QVarLengthArray<ArrayOfImageDesc, 8> imageInfos;
QVarLengthArray<VkWriteDescriptorSet, 12> writeInfos;
QVarLengthArray<QPair<int, int>, 12> infoIndices;
@ -2530,17 +2531,22 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i
break;
case QRhiShaderResourceBinding::SampledTexture:
{
QVkTexture *texD = QRHI_RES(QVkTexture, b->u.stex.tex);
QVkSampler *samplerD = QRHI_RES(QVkSampler, b->u.stex.sampler);
const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
writeInfo.descriptorCount = data->count; // arrays of combined image samplers are supported
writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
bd.stex.texId = texD->m_id;
bd.stex.texGeneration = texD->generation;
bd.stex.samplerId = samplerD->m_id;
bd.stex.samplerGeneration = samplerD->generation;
VkDescriptorImageInfo imageInfo;
imageInfo.sampler = samplerD->sampler;
imageInfo.imageView = texD->imageView;
imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
ArrayOfImageDesc imageInfo(data->count);
for (int elem = 0; elem < data->count; ++elem) {
QVkTexture *texD = QRHI_RES(QVkTexture, data->texSamplers[elem].tex);
QVkSampler *samplerD = QRHI_RES(QVkSampler, data->texSamplers[elem].sampler);
bd.stex.d[elem].texId = texD->m_id;
bd.stex.d[elem].texGeneration = texD->generation;
bd.stex.d[elem].samplerId = samplerD->m_id;
bd.stex.d[elem].samplerGeneration = samplerD->generation;
imageInfo[elem].sampler = samplerD->sampler;
imageInfo[elem].imageView = texD->imageView;
imageInfo[elem].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
}
bd.stex.count = data->count;
imageInfoIndex = imageInfos.count();
imageInfos.append(imageInfo);
}
@ -2555,10 +2561,10 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i
writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
bd.simage.id = texD->m_id;
bd.simage.generation = texD->generation;
VkDescriptorImageInfo imageInfo;
imageInfo.sampler = VK_NULL_HANDLE;
imageInfo.imageView = view;
imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
ArrayOfImageDesc imageInfo(1);
imageInfo[0].sampler = VK_NULL_HANDLE;
imageInfo[0].imageView = view;
imageInfo[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
imageInfoIndex = imageInfos.count();
imageInfos.append(imageInfo);
}
@ -2596,7 +2602,7 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i
if (bufferInfoIndex >= 0)
writeInfos[i].pBufferInfo = &bufferInfos[bufferInfoIndex];
else if (imageInfoIndex >= 0)
writeInfos[i].pImageInfo = &imageInfos[imageInfoIndex];
writeInfos[i].pImageInfo = imageInfos[imageInfoIndex].constData();
}
df->vkUpdateDescriptorSets(dev, uint32_t(writeInfos.count()), writeInfos.constData(), 0, nullptr);
@ -4210,24 +4216,30 @@ void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBin
break;
case QRhiShaderResourceBinding::SampledTexture:
{
QVkTexture *texD = QRHI_RES(QVkTexture, b->u.stex.tex);
QVkSampler *samplerD = QRHI_RES(QVkSampler, b->u.stex.sampler);
const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
if (bd.stex.count != data->count) {
bd.stex.count = data->count;
rewriteDescSet = true;
}
for (int elem = 0; elem < data->count; ++elem) {
QVkTexture *texD = QRHI_RES(QVkTexture, data->texSamplers[elem].tex);
QVkSampler *samplerD = QRHI_RES(QVkSampler, data->texSamplers[elem].sampler);
texD->lastActiveFrameSlot = currentFrameSlot;
samplerD->lastActiveFrameSlot = currentFrameSlot;
trackedRegisterTexture(&passResTracker, texD,
QRhiPassResourceTracker::TexSample,
QRhiPassResourceTracker::toPassTrackerTextureStage(b->stage));
if (texD->generation != bd.stex.texGeneration
|| texD->m_id != bd.stex.texId
|| samplerD->generation != bd.stex.samplerGeneration
|| samplerD->m_id != bd.stex.samplerId)
if (texD->generation != bd.stex.d[elem].texGeneration
|| texD->m_id != bd.stex.d[elem].texId
|| samplerD->generation != bd.stex.d[elem].samplerGeneration
|| samplerD->m_id != bd.stex.d[elem].samplerId)
{
rewriteDescSet = true;
bd.stex.texId = texD->m_id;
bd.stex.texGeneration = texD->generation;
bd.stex.samplerId = samplerD->m_id;
bd.stex.samplerGeneration = samplerD->generation;
bd.stex.d[elem].texId = texD->m_id;
bd.stex.d[elem].texGeneration = texD->generation;
bd.stex.d[elem].samplerId = samplerD->m_id;
bd.stex.d[elem].samplerGeneration = samplerD->generation;
}
}
}
break;
@ -6065,7 +6077,10 @@ bool QVkShaderResourceBindings::build()
memset(&vkbinding, 0, sizeof(vkbinding));
vkbinding.binding = uint32_t(b->binding);
vkbinding.descriptorType = toVkDescriptorType(b);
vkbinding.descriptorCount = 1; // no array support yet
if (b->type == QRhiShaderResourceBinding::SampledTexture)
vkbinding.descriptorCount = b->u.stex.count;
else
vkbinding.descriptorCount = 1;
vkbinding.stageFlags = toVkShaderStageFlags(b->stage);
vkbindings.append(vkbinding);
}

View File

@ -254,10 +254,13 @@ struct QVkShaderResourceBindings : public QRhiShaderResourceBindings
uint generation;
};
struct BoundSampledTextureData {
int count;
struct {
quint64 texId;
uint texGeneration;
quint64 samplerId;
uint samplerGeneration;
} d[QRhiShaderResourceBinding::Data::MAX_TEX_SAMPLER_ARRAY_SIZE];
};
struct BoundStorageImageData {
quint64 id;

View File

@ -44,5 +44,6 @@ qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.vert.qsb simple.vert
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.frag.qsb simple.frag
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured.vert.qsb simpletextured.vert
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured.frag.qsb simpletextured.frag
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 20 -o simpletextured_array.frag.qsb simpletextured_array.frag
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.vert.qsb textured.vert
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.frag.qsb textured.frag

View File

@ -0,0 +1,17 @@
#version 440
layout(location = 0) in vec2 uv;
layout(location = 0) out vec4 fragColor;
layout(binding = 0) uniform sampler2D tex[3];
void main()
{
vec4 c0 = texture(tex[0], uv);
vec4 c1 = texture(tex[1], uv);
vec4 c2 = texture(tex[2], uv);
vec4 cc = c0 + c1 + c2;
vec4 c = vec4(clamp(cc.r, 0.0, 1.0), clamp(cc.g, 0.0, 1.0), clamp(cc.b, 0.0, 1.0), clamp(cc.a, 0.0, 1.0));
c.rgb *= c.a;
fragColor = c;
}

View File

@ -91,6 +91,8 @@ private slots:
void renderToTextureSimple();
void renderToTextureTexturedQuad_data();
void renderToTextureTexturedQuad();
void renderToTextureArrayOfTexturedQuad_data();
void renderToTextureArrayOfTexturedQuad();
void renderToTextureTexturedQuadAndUniformBuffer_data();
void renderToTextureTexturedQuadAndUniformBuffer();
void renderToWindowSimple_data();
@ -1468,6 +1470,147 @@ void tst_QRhi::renderToTextureTexturedQuad()
QVERIFY(qGreen(result.pixel(214, 191)) > 2 * qBlue(result.pixel(214, 191)));
}
void tst_QRhi::renderToTextureArrayOfTexturedQuad_data()
{
rhiTestData();
}
void tst_QRhi::renderToTextureArrayOfTexturedQuad()
{
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");
QImage inputImage;
inputImage.load(QLatin1String(":/data/qt256.png"));
QVERIFY(!inputImage.isNull());
QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size(), 1,
QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
QVERIFY(texture->build());
QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
rt->setRenderPassDescriptor(rpDesc.data());
QVERIFY(rt->build());
QRhiCommandBuffer *cb = nullptr;
QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
QVERIFY(cb);
QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch();
static const float verticesUvs[] = {
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(verticesUvs)));
QVERIFY(vbuf->build());
updates->uploadStaticBuffer(vbuf.data(), verticesUvs);
// In this test we pass 3 textures (and samplers) to the fragment shader in
// form of an array of combined image samplers.
QScopedPointer<QRhiTexture> inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size()));
QVERIFY(inputTexture->build());
updates->uploadTexture(inputTexture.data(), inputImage);
QImage redImage(inputImage.size(), QImage::Format_RGBA8888);
redImage.fill(Qt::red);
QScopedPointer<QRhiTexture> redTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size()));
QVERIFY(redTexture->build());
updates->uploadTexture(redTexture.data(), redImage);
QImage greenImage(inputImage.size(), QImage::Format_RGBA8888);
greenImage.fill(Qt::green);
QScopedPointer<QRhiTexture> greenTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size()));
QVERIFY(greenTexture->build());
updates->uploadTexture(greenTexture.data(), greenImage);
QScopedPointer<QRhiSampler> sampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
QVERIFY(sampler->build());
QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
QRhiShaderResourceBinding::TextureAndSampler texSamplers[3] = {
{ inputTexture.data(), sampler.data() },
{ redTexture.data(), sampler.data() },
{ greenTexture.data(), sampler.data() }
};
srb->setBindings({
QRhiShaderResourceBinding::sampledTextures(0, QRhiShaderResourceBinding::FragmentStage, 3, texSamplers)
});
QVERIFY(srb->build());
QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
QShader vs = loadShader(":/data/simpletextured.vert.qsb");
QVERIFY(vs.isValid());
QShader fs = loadShader(":/data/simpletextured_array.frag.qsb");
QVERIFY(fs.isValid());
pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } });
QRhiVertexInputLayout inputLayout;
inputLayout.setBindings({ { 4 * sizeof(float) } });
inputLayout.setAttributes({
{ 0, 0, QRhiVertexInputAttribute::Float2, 0 },
{ 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) }
});
pipeline->setVertexInputLayout(inputLayout);
pipeline->setShaderResourceBindings(srb.data());
pipeline->setRenderPassDescriptor(rpDesc.data());
QVERIFY(pipeline->build());
cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, updates);
cb->setGraphicsPipeline(pipeline.data());
cb->setShaderResources();
cb->setViewport({ 0, 0, float(texture->pixelSize().width()), float(texture->pixelSize().height()) });
QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0);
cb->setVertexInput(0, 1, &vbindings);
cb->draw(4);
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_Premultiplied);
};
QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
readbackBatch->readBackTexture({ texture.data() }, &readResult);
cb->endPass(readbackBatch);
rhi->endOffscreenFrame();
QVERIFY(!result.isNull());
if (impl == QRhi::Null)
return;
// Flip with D3D and Metal because these have Y down in images. Vulkan does
// not need this because there Y is down both in images and in NDC, which
// just happens to give correct results with our OpenGL-targeted vertex and
// UV data.
if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC())
result = std::move(result).mirrored();
// we added the input image + red + green together, so red and green must be all 1
for (int y = 0; y < result.height(); ++y) {
for (int x = 0; x < result.width(); ++x) {
const QRgb pixel = result.pixel(x, y);
QCOMPARE(qRed(pixel), 255);
QCOMPARE(qGreen(pixel), 255);
}
}
}
void tst_QRhi::renderToTextureTexturedQuadAndUniformBuffer_data()
{
rhiTestData();