summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2021-12-21 13:26:45 +0100
committerLaszlo Agocs <laszlo.agocs@qt.io>2022-01-06 14:56:25 +0100
commit0a59101495634a02e7b893682904f4cfc5898624 (patch)
treeaadaf9ee66173de05d85579dc85338daa48a4a84
parentc20b213eabd8138f8566c7a1fd0633625c47b520 (diff)
rhi: Add support for separate image and sampler objects
For Direct 3D, Metal, and Vulkan this is natively supported. (and makes no difference in particular for D3D and Metal because they do not have the legacy combined image sampler concept anyways) With OpenGL it will work too, but this relies on SPIR-Cross magic and is still using a combined sampler (e.g. a sampler2D) in the GLSL shader. The GL backend walks back and forth in the mapping tables from the shader baker in order to make this work, which is presumably slightly more expensive than combined image samplers. Do note that combined image samplers (i.e. sampler2D in the shader and QRhiShaderResourceBinding::sampledTexture() in code) continue to be the primary, recommended way for any user of the rhi for the time being. Change-Id: I194721bc657b1ffbcc1bb79e6eadebe569a25087 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
-rw-r--r--src/gui/rhi/qrhi.cpp166
-rw-r--r--src/gui/rhi/qrhi_p.h19
-rw-r--r--src/gui/rhi/qrhid3d11.cpp68
-rw-r--r--src/gui/rhi/qrhigles2.cpp212
-rw-r--r--src/gui/rhi/qrhigles2_p_p.h10
-rw-r--r--src/gui/rhi/qrhimetal.mm78
-rw-r--r--src/gui/rhi/qrhivulkan.cpp88
-rw-r--r--tests/auto/gui/rhi/qrhi/BLACKLIST3
-rw-r--r--tests/auto/gui/rhi/qrhi/data/buildshaders.bat1
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletextured_separate.frag14
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletextured_separate.frag.qsbbin0 -> 1258 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/tst_qrhi.cpp127
12 files changed, 652 insertions, 134 deletions
diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp
index 3da1edfdac..a2704b8491 100644
--- a/src/gui/rhi/qrhi.cpp
+++ b/src/gui/rhi/qrhi.cpp
@@ -3157,7 +3157,7 @@ void QRhiImplementation::updateLayoutDesc(QRhiShaderResourceBindings *srb)
for (const QRhiShaderResourceBinding &b : qAsConst(srb->m_bindings)) {
const QRhiShaderResourceBinding::Data *d = b.data();
srb->m_layoutDescHash ^= uint(d->binding) ^ uint(d->stage) ^ uint(d->type)
- ^ uint(d->type == QRhiShaderResourceBinding::SampledTexture ? d->u.stex.count : 1);
+ ^ uint(d->arraySize());
layoutDescAppender = d->serialize(layoutDescAppender);
}
}
@@ -3223,10 +3223,11 @@ void QRhiImplementation::updateLayoutDesc(QRhiShaderResourceBindings *srb)
*/
bool QRhiShaderResourceBinding::isLayoutCompatible(const QRhiShaderResourceBinding &other) const
{
- // i.e. everything that goes into a VkDescriptorSetLayoutBinding must match
- const int thisCount = d.type == QRhiShaderResourceBinding::SampledTexture ? d.u.stex.count : 1;
- const int otherCount = other.d.type == QRhiShaderResourceBinding::SampledTexture ? other.d.u.stex.count : 1;
- return d.binding == other.d.binding && d.stage == other.d.stage && d.type == other.d.type && thisCount == otherCount;
+ // everything that goes into a VkDescriptorSetLayoutBinding must match
+ return d.binding == other.d.binding
+ && d.stage == other.d.stage
+ && d.type == other.d.type
+ && d.arraySize() == other.d.arraySize();
}
/*!
@@ -3360,8 +3361,7 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::sampledTexture(
b.d.stage = stage;
b.d.type = SampledTexture;
b.d.u.stex.count = 1;
- b.d.u.stex.texSamplers[0].tex = tex;
- b.d.u.stex.texSamplers[0].sampler = sampler;
+ b.d.u.stex.texSamplers[0] = { tex, sampler };
return b;
}
@@ -3412,12 +3412,116 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::sampledTextures(
if (texSamplers)
b.d.u.stex.texSamplers[i] = texSamplers[i];
else
- b.d.u.stex.texSamplers[i] = {};
+ b.d.u.stex.texSamplers[i] = { nullptr, nullptr };
}
return b;
}
/*!
+ \return a shader resource binding for the given binding number, pipeline
+ stages, and texture specified by \a binding, \a stage, \a tex.
+
+ \note This function is equivalent to calling textures() with a
+ \c count of 1.
+
+ \note \a tex can be null. It is valid to create a
+ QRhiShaderResourceBindings with unspecified resources, but such an object
+ cannot be used with QRhiCommandBuffer::setShaderResources(). It is however
+ suitable for creating pipelines. Such a pipeline must then always be used
+ together with another, layout compatible QRhiShaderResourceBindings with
+ resources present passed to QRhiCommandBuffer::setShaderResources().
+
+ This creates a binding for a separate texture (image) object, whereas
+ sampledTexture() is suitable for combined image samplers. In
+ Vulkan-compatible GLSL code separate textures are declared as \c texture2D
+ as opposed to \c sampler2D: \c{layout(binding = 1) uniform texture2D tex;}
+
+ \sa textures(), sampler()
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::texture(int binding, StageFlags stage, QRhiTexture *tex)
+{
+ QRhiShaderResourceBinding b;
+ b.d.binding = binding;
+ b.d.stage = stage;
+ b.d.type = Texture;
+ b.d.u.stex.count = 1;
+ b.d.u.stex.texSamplers[0] = { tex, nullptr };
+ return b;
+}
+
+/*!
+ \return a shader resource binding for the given binding number, pipeline
+ stages, and the array of (separate) textures specified by \a binding, \a
+ stage, \a count, and \a tex.
+
+ \note \a count must be at least 1, and not larger than 16.
+
+ \note When \a count is 1, this function is equivalent to texture().
+
+ \warning All elements of the array must be specified.
+
+ \note \a tex can be null. It is valid to create a
+ QRhiShaderResourceBindings with unspecified resources, but such an object
+ cannot be used with QRhiCommandBuffer::setShaderResources(). It is however
+ suitable for creating pipelines. Such a pipeline must then always be used
+ together with another, layout compatible QRhiShaderResourceBindings with
+ resources present passed to QRhiCommandBuffer::setShaderResources().
+
+ \sa texture(), sampler()
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::textures(int binding, StageFlags stage, int count, QRhiTexture **tex)
+{
+ Q_ASSERT(count >= 1 && count <= Data::MAX_TEX_SAMPLER_ARRAY_SIZE);
+ QRhiShaderResourceBinding b;
+ b.d.binding = binding;
+ b.d.stage = stage;
+ b.d.type = Texture;
+ b.d.u.stex.count = count;
+ for (int i = 0; i < count; ++i) {
+ if (tex)
+ b.d.u.stex.texSamplers[i] = { tex[i], nullptr };
+ else
+ b.d.u.stex.texSamplers[i] = { nullptr, nullptr };
+ }
+ return b;
+}
+
+/*!
+ \return a shader resource binding for the given binding number, pipeline
+ stages, and sampler specified by \a binding, \a stage, \a sampler.
+
+ \note \a sampler can be null. It is valid to create a
+ QRhiShaderResourceBindings with unspecified resources, but such an object
+ cannot be used with QRhiCommandBuffer::setShaderResources(). It is however
+ suitable for creating pipelines. Such a pipeline must then always be used
+ together with another, layout compatible QRhiShaderResourceBindings with
+ resources present passed to QRhiCommandBuffer::setShaderResources().
+
+ Arrays of separate samplers are not supported.
+
+ This creates a binding for a separate sampler object, whereas
+ sampledTexture() is suitable for combined image samplers. In
+ Vulkan-compatible GLSL code separate samplers are declared as \c sampler
+ as opposed to \c sampler2D: \c{layout(binding = 2) uniform sampler samp;}
+
+ With both a \c texture2D and \c sampler present, they can be used together
+ to sample the texture: \c{fragColor = texture(sampler2D(tex, samp),
+ texcoord);}.
+
+ \sa texture()
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::sampler(int binding, StageFlags stage, QRhiSampler *sampler)
+{
+ QRhiShaderResourceBinding b;
+ b.d.binding = binding;
+ b.d.stage = stage;
+ b.d.type = Sampler;
+ b.d.u.stex.count = 1;
+ b.d.u.stex.texSamplers[0] = { nullptr, sampler };
+ return b;
+}
+
+/*!
\return a shader resource binding for a read-only storage image with the
given \a binding number and pipeline \a stage. The image load operations
will have access to all layers of the specified \a level. (so if the texture
@@ -3715,6 +3819,18 @@ bool operator==(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBind
}
}
break;
+ case QRhiShaderResourceBinding::Texture:
+ 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)
+ return false;
+ }
+ break;
+ case QRhiShaderResourceBinding::Sampler:
+ if (da->u.stex.texSamplers[0].sampler != db->u.stex.texSamplers[0].sampler)
+ return false;
+ break;
case QRhiShaderResourceBinding::ImageLoad:
Q_FALLTHROUGH();
case QRhiShaderResourceBinding::ImageStore:
@@ -3774,6 +3890,12 @@ size_t qHash(const QRhiShaderResourceBinding &b, size_t seed) noexcept
h ^= qHash(reinterpret_cast<quintptr>(d->u.stex.texSamplers[0].tex));
h ^= qHash(reinterpret_cast<quintptr>(d->u.stex.texSamplers[0].sampler));
break;
+ case QRhiShaderResourceBinding::Texture:
+ h ^= qHash(reinterpret_cast<quintptr>(d->u.stex.texSamplers[0].tex));
+ break;
+ case QRhiShaderResourceBinding::Sampler:
+ h ^= qHash(reinterpret_cast<quintptr>(d->u.stex.texSamplers[0].sampler));
+ break;
case QRhiShaderResourceBinding::ImageLoad:
Q_FALLTHROUGH();
case QRhiShaderResourceBinding::ImageStore:
@@ -3820,6 +3942,18 @@ QDebug operator<<(QDebug dbg, const QRhiShaderResourceBinding &b)
}
dbg.nospace() << ')';
break;
+ case QRhiShaderResourceBinding::Texture:
+ dbg.nospace() << " Textures("
+ << "count=" << d->u.stex.count;
+ for (int i = 0; i < d->u.stex.count; ++i)
+ dbg.nospace() << " texture=" << d->u.stex.texSamplers[i].tex;
+ dbg.nospace() << ')';
+ break;
+ case QRhiShaderResourceBinding::Sampler:
+ dbg.nospace() << " Sampler("
+ << " sampler=" << d->u.stex.texSamplers[0].sampler
+ << ')';
+ break;
case QRhiShaderResourceBinding::ImageLoad:
dbg.nospace() << " ImageLoad("
<< "texture=" << d->u.simage.tex
@@ -4931,6 +5065,22 @@ bool QRhiImplementation::sanityCheckShaderResourceBindings(QRhiShaderResourceBin
bindingsOk = false;
}
break;
+ case QRhiShaderResourceBinding::Texture:
+ if (!bindingSeen[binding]) {
+ bindingSeen[binding] = true;
+ } else {
+ qWarning("Texture duplicates an existing binding number %d", binding);
+ bindingsOk = false;
+ }
+ break;
+ case QRhiShaderResourceBinding::Sampler:
+ if (!bindingSeen[binding]) {
+ bindingSeen[binding] = true;
+ } else {
+ qWarning("Sampler duplicates an existing binding number %d", binding);
+ bindingsOk = false;
+ }
+ break;
case QRhiShaderResourceBinding::ImageLoad:
Q_FALLTHROUGH();
case QRhiShaderResourceBinding::ImageStore:
diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h
index 9edad2e10c..8d5d21278b 100644
--- a/src/gui/rhi/qrhi_p.h
+++ b/src/gui/rhi/qrhi_p.h
@@ -335,6 +335,8 @@ public:
enum Type {
UniformBuffer,
SampledTexture,
+ Texture,
+ Sampler,
ImageLoad,
ImageStore,
ImageLoadStore,
@@ -366,6 +368,10 @@ public:
};
static QRhiShaderResourceBinding sampledTextures(int binding, StageFlags stage, int count, const TextureAndSampler *texSamplers);
+ static QRhiShaderResourceBinding texture(int binding, StageFlags stage, QRhiTexture *tex);
+ static QRhiShaderResourceBinding textures(int binding, StageFlags stage, int count, QRhiTexture **tex);
+ static QRhiShaderResourceBinding sampler(int binding, StageFlags stage, QRhiSampler *sampler);
+
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);
@@ -389,7 +395,7 @@ public:
bool hasDynamicOffset;
};
static const int MAX_TEX_SAMPLER_ARRAY_SIZE = 16;
- struct SampledTextureData {
+ struct TextureAndOrSamplerData {
int count;
TextureAndSampler texSamplers[MAX_TEX_SAMPLER_ARRAY_SIZE];
};
@@ -404,11 +410,18 @@ public:
};
union {
UniformBufferData ubuf;
- SampledTextureData stex;
+ TextureAndOrSamplerData stex;
StorageImageData simage;
StorageBufferData sbuf;
} u;
+ int arraySize() const
+ {
+ return type == QRhiShaderResourceBinding::SampledTexture || type == QRhiShaderResourceBinding::Texture
+ ? u.stex.count
+ : 1;
+ }
+
template<typename Output>
Output serialize(Output dst) const
{
@@ -416,7 +429,7 @@ public:
*dst++ = quint32(binding);
*dst++ = quint32(stage);
*dst++ = quint32(type);
- *dst++ = quint32(type == QRhiShaderResourceBinding::SampledTexture ? u.stex.count : 1);
+ *dst++ = quint32(arraySize());
return dst;
}
};
diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp
index 0c8fff88df..74abfdff87 100644
--- a/src/gui/rhi/qrhid3d11.cpp
+++ b/src/gui/rhi/qrhid3d11.cpp
@@ -734,8 +734,10 @@ void QRhiD3D11::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind
}
break;
case QRhiShaderResourceBinding::SampledTexture:
+ case QRhiShaderResourceBinding::Texture:
+ case QRhiShaderResourceBinding::Sampler:
{
- const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
+ const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex;
if (bd.stex.count != data->count) {
bd.stex.count = data->count;
srbUpdate = true;
@@ -743,16 +745,24 @@ void QRhiD3D11::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind
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)
+ // We use the same code path for both combined and separate
+ // images and samplers, so tex or sampler (but not both) can be
+ // null here.
+ Q_ASSERT(texD || samplerD);
+ const quint64 texId = texD ? texD->m_id : 0;
+ const uint texGen = texD ? texD->generation : 0;
+ const quint64 samplerId = samplerD ? samplerD->m_id : 0;
+ const uint samplerGen = samplerD ? samplerD->generation : 0;
+ if (texGen != bd.stex.d[elem].texGeneration
+ || texId != bd.stex.d[elem].texId
+ || samplerGen != bd.stex.d[elem].samplerGeneration
+ || samplerId != bd.stex.d[elem].samplerId)
{
srbUpdate = true;
- 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;
+ bd.stex.d[elem].texId = texId;
+ bd.stex.d[elem].texGeneration = texGen;
+ bd.stex.d[elem].samplerId = samplerId;
+ bd.stex.d[elem].samplerGeneration = samplerGen;
}
}
}
@@ -2020,8 +2030,10 @@ void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD,
}
break;
case QRhiShaderResourceBinding::SampledTexture:
+ case QRhiShaderResourceBinding::Texture:
+ case QRhiShaderResourceBinding::Sampler:
{
- const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
+ const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *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);
@@ -2032,27 +2044,37 @@ void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD,
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;
+ bd.stex.d[elem].texId = texD ? texD->m_id : 0;
+ bd.stex.d[elem].texGeneration = texD ? texD->generation : 0;
+ bd.stex.d[elem].samplerId = samplerD ? samplerD->m_id : 0;
+ bd.stex.d[elem].samplerGeneration = samplerD ? samplerD->generation : 0;
if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
- if (nativeBindingVert.first >= 0 && nativeBindingVert.second >= 0) {
+ // Must handle all three cases (combined, separate, separate):
+ // first = texture binding, second = sampler binding
+ // first = texture binding
+ // first = sampler binding
+ const int samplerBinding = texD && samplerD ? nativeBindingVert.second
+ : (samplerD ? nativeBindingVert.first : -1);
+ if (nativeBindingVert.first >= 0 && texD)
res[RBM_VERTEX].textures.append({ nativeBindingVert.first + elem, texD->srv });
- res[RBM_VERTEX].samplers.append({ nativeBindingVert.second + elem, samplerD->samplerState });
- }
+ if (samplerBinding >= 0)
+ res[RBM_VERTEX].samplers.append({ samplerBinding + elem, samplerD->samplerState });
}
if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
- if (nativeBindingFrag.first >= 0 && nativeBindingFrag.second >= 0) {
+ const int samplerBinding = texD && samplerD ? nativeBindingFrag.second
+ : (samplerD ? nativeBindingVert.first : -1);
+ if (nativeBindingFrag.first >= 0 && texD)
res[RBM_FRAGMENT].textures.append({ nativeBindingFrag.first + elem, texD->srv });
- res[RBM_FRAGMENT].samplers.append({ nativeBindingFrag.second + elem, samplerD->samplerState });
- }
+ if (samplerBinding >= 0)
+ res[RBM_FRAGMENT].samplers.append({ samplerBinding + elem, samplerD->samplerState });
}
if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
- if (nativeBindingComp.first >= 0 && nativeBindingComp.second >= 0) {
+ const int samplerBinding = texD && samplerD ? nativeBindingComp.second
+ : (samplerD ? nativeBindingVert.first : -1);
+ if (nativeBindingComp.first >= 0 && texD)
res[RBM_COMPUTE].textures.append({ nativeBindingComp.first + elem, texD->srv });
- res[RBM_COMPUTE].samplers.append({ nativeBindingComp.second + elem, samplerD->samplerState });
- }
+ if (samplerBinding >= 0)
+ res[RBM_COMPUTE].samplers.append({ samplerBinding + elem, samplerD->samplerState });
}
}
}
diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp
index 2f41279e4f..6788942db8 100644
--- a/src/gui/rhi/qrhigles2.cpp
+++ b/src/gui/rhi/qrhigles2.cpp
@@ -1437,6 +1437,7 @@ void QRhiGles2::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind
// no BufUniformRead / AccessUniform because no real uniform buffers are used
break;
case QRhiShaderResourceBinding::SampledTexture:
+ case QRhiShaderResourceBinding::Texture:
for (int elem = 0; elem < b->u.stex.count; ++elem) {
trackedRegisterTexture(&passResTracker,
QRHI_RES(QGles2Texture, b->u.stex.texSamplers[elem].tex),
@@ -3342,6 +3343,59 @@ static inline void qrhi_std140_to_packed(float *dst, int vecSize, int elemCount,
}
}
+void QRhiGles2::bindCombinedSampler(QGles2CommandBuffer *cbD, QGles2Texture *texD, QGles2Sampler *samplerD,
+ void *ps, uint psGeneration, int glslLocation,
+ int *texUnit, bool *activeTexUnitAltered)
+{
+ const bool samplerStateValid = texD->samplerState == samplerD->d;
+ const bool cachedStateInRange = *texUnit < 16;
+ bool updateTextureBinding = true;
+ if (samplerStateValid && cachedStateInRange) {
+ // If we already encountered the same texture with
+ // the same pipeline for this texture unit in the
+ // current pass, then the shader program already
+ // has the uniform set. As in a 3D scene one model
+ // often has more than one associated texture map,
+ // the savings here can become significant,
+ // depending on the scene.
+ if (cbD->textureUnitState[*texUnit].ps == ps
+ && cbD->textureUnitState[*texUnit].psGeneration == psGeneration
+ && cbD->textureUnitState[*texUnit].texture == texD->texture)
+ {
+ updateTextureBinding = false;
+ }
+ }
+ if (updateTextureBinding) {
+ f->glActiveTexture(GL_TEXTURE0 + uint(*texUnit));
+ *activeTexUnitAltered = true;
+ f->glBindTexture(texD->target, texD->texture);
+ f->glUniform1i(glslLocation, *texUnit);
+ if (cachedStateInRange) {
+ cbD->textureUnitState[*texUnit].ps = ps;
+ cbD->textureUnitState[*texUnit].psGeneration = psGeneration;
+ cbD->textureUnitState[*texUnit].texture = texD->texture;
+ }
+ }
+ ++(*texUnit);
+ if (!samplerStateValid) {
+ f->glTexParameteri(texD->target, GL_TEXTURE_MIN_FILTER, GLint(samplerD->d.glminfilter));
+ f->glTexParameteri(texD->target, GL_TEXTURE_MAG_FILTER, GLint(samplerD->d.glmagfilter));
+ f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_S, GLint(samplerD->d.glwraps));
+ f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_T, GLint(samplerD->d.glwrapt));
+ if (caps.texture3D)
+ f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_R, GLint(samplerD->d.glwrapr));
+ if (caps.textureCompareMode) {
+ if (samplerD->d.gltexcomparefunc != GL_NEVER) {
+ f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
+ f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_FUNC, GLint(samplerD->d.gltexcomparefunc));
+ } else {
+ f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_MODE, GL_NONE);
+ }
+ }
+ texD->samplerState = samplerD->d;
+ }
+}
+
void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD,
QRhiGraphicsPipeline *maybeGraphicsPs, QRhiComputePipeline *maybeComputePs,
QRhiShaderResourceBindings *srb,
@@ -3355,6 +3409,17 @@ void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD,
: QRHI_RES(QGles2ComputePipeline, maybeComputePs)->uniforms);
QGles2UniformState *uniformState = maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->uniformState
: QRHI_RES(QGles2ComputePipeline, maybeComputePs)->uniformState;
+ struct SeparateTexture {
+ QGles2Texture *texture;
+ int binding;
+ int elem;
+ };
+ QVarLengthArray<SeparateTexture, 8> separateTextureBindings;
+ struct SeparateSampler {
+ QGles2Sampler *sampler;
+ int binding;
+ };
+ QVarLengthArray<SeparateSampler, 4> separateSamplerBindings;
for (int i = 0, ie = srbD->m_bindings.count(); i != ie; ++i) {
const QRhiShaderResourceBinding::Data *b = srbD->m_bindings.at(i).data();
@@ -3591,59 +3656,27 @@ void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD,
QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.stex.texSamplers[elem].tex);
QGles2Sampler *samplerD = QRHI_RES(QGles2Sampler, b->u.stex.texSamplers[elem].sampler);
for (const QGles2SamplerDescription &shaderSampler : samplers) {
- if (shaderSampler.binding == b->binding) {
- const bool samplerStateValid = texD->samplerState == samplerD->d;
- const bool cachedStateInRange = texUnit < 16;
- bool updateTextureBinding = true;
- if (samplerStateValid && cachedStateInRange) {
- // If we already encountered the same texture with
- // the same pipeline for this texture unit in the
- // current pass, then the shader program already
- // has the uniform set. As in a 3D scene one model
- // often has more than one associated texture map,
- // the savings here can become significant,
- // depending on the scene.
- if (cbD->textureUnitState[texUnit].ps == ps
- && cbD->textureUnitState[texUnit].psGeneration == psGeneration
- && cbD->textureUnitState[texUnit].texture == texD->texture)
- {
- updateTextureBinding = false;
- }
- }
- if (updateTextureBinding) {
- f->glActiveTexture(GL_TEXTURE0 + uint(texUnit));
- activeTexUnitAltered = true;
- f->glBindTexture(texD->target, texD->texture);
- f->glUniform1i(shaderSampler.glslLocation + elem, texUnit);
- if (cachedStateInRange) {
- cbD->textureUnitState[texUnit].ps = ps;
- cbD->textureUnitState[texUnit].psGeneration = psGeneration;
- cbD->textureUnitState[texUnit].texture = texD->texture;
- }
- }
- ++texUnit;
- if (!samplerStateValid) {
- f->glTexParameteri(texD->target, GL_TEXTURE_MIN_FILTER, GLint(samplerD->d.glminfilter));
- f->glTexParameteri(texD->target, GL_TEXTURE_MAG_FILTER, GLint(samplerD->d.glmagfilter));
- f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_S, GLint(samplerD->d.glwraps));
- f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_T, GLint(samplerD->d.glwrapt));
- if (caps.texture3D)
- f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_R, GLint(samplerD->d.glwrapr));
- if (caps.textureCompareMode) {
- if (samplerD->d.gltexcomparefunc != GL_NEVER) {
- f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
- f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_FUNC, GLint(samplerD->d.gltexcomparefunc));
- } else {
- f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_MODE, GL_NONE);
- }
- }
- texD->samplerState = samplerD->d;
- }
+ if (shaderSampler.combinedBinding == b->binding) {
+ const int loc = shaderSampler.glslLocation + elem;
+ bindCombinedSampler(cbD, texD, samplerD, ps, psGeneration, loc, &texUnit, &activeTexUnitAltered);
+ break;
}
}
}
}
break;
+ case QRhiShaderResourceBinding::Texture:
+ for (int elem = 0; elem < b->u.stex.count; ++elem) {
+ QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.stex.texSamplers[elem].tex);
+ separateTextureBindings.append({ texD, b->binding, elem });
+ }
+ break;
+ case QRhiShaderResourceBinding::Sampler:
+ {
+ QGles2Sampler *samplerD = QRHI_RES(QGles2Sampler, b->u.stex.texSamplers[0].sampler);
+ separateSamplerBindings.append({ samplerD, b->binding });
+ }
+ break;
case QRhiShaderResourceBinding::ImageLoad:
case QRhiShaderResourceBinding::ImageStore:
case QRhiShaderResourceBinding::ImageLoadStore:
@@ -3680,6 +3713,35 @@ void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD,
}
}
+ if (!separateTextureBindings.isEmpty() || !separateSamplerBindings.isEmpty()) {
+ const QGles2SamplerDescriptionVector &samplers(maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->samplers
+ : QRHI_RES(QGles2ComputePipeline, maybeComputePs)->samplers);
+ void *ps;
+ uint psGeneration;
+ if (maybeGraphicsPs) {
+ ps = maybeGraphicsPs;
+ psGeneration = QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->generation;
+ } else {
+ ps = maybeComputePs;
+ psGeneration = QRHI_RES(QGles2ComputePipeline, maybeComputePs)->generation;
+ }
+ for (const QGles2SamplerDescription &shaderSampler : samplers) {
+ if (shaderSampler.combinedBinding >= 0)
+ continue;
+ for (const SeparateSampler &sepSampler : separateSamplerBindings) {
+ if (sepSampler.binding != shaderSampler.sbinding)
+ continue;
+ for (const SeparateTexture &sepTex : separateTextureBindings) {
+ if (sepTex.binding != shaderSampler.tbinding)
+ continue;
+ const int loc = shaderSampler.glslLocation + sepTex.elem;
+ bindCombinedSampler(cbD, sepTex.texture, sepSampler.sampler, ps, psGeneration,
+ loc, &texUnit, &activeTexUnitAltered);
+ }
+ }
+ }
+ }
+
if (activeTexUnitAltered)
f->glActiveTexture(GL_TEXTURE0);
}
@@ -4228,7 +4290,23 @@ void QRhiGles2::gatherSamplers(GLuint program,
QGles2SamplerDescription sampler;
sampler.glslLocation = f->glGetUniformLocation(program, v.name.constData());
if (sampler.glslLocation >= 0) {
- sampler.binding = v.binding;
+ sampler.combinedBinding = v.binding;
+ sampler.tbinding = -1;
+ sampler.sbinding = -1;
+ dst->append(sampler);
+ }
+}
+
+void QRhiGles2::gatherGeneratedSamplers(GLuint program,
+ const QShader::SeparateToCombinedImageSamplerMapping &mapping,
+ QGles2SamplerDescriptionVector *dst)
+{
+ QGles2SamplerDescription sampler;
+ sampler.glslLocation = f->glGetUniformLocation(program, mapping.combinedSamplerName.constData());
+ if (sampler.glslLocation >= 0) {
+ sampler.combinedBinding = -1;
+ sampler.tbinding = mapping.textureBinding;
+ sampler.sbinding = mapping.samplerBinding;
dst->append(sampler);
}
}
@@ -5206,12 +5284,25 @@ bool QGles2GraphicsPipeline::create()
program = rhiD->f->glCreateProgram();
QShaderDescription vsDesc;
+ QShader::SeparateToCombinedImageSamplerMappingList vsSamplerMappingList;
QShaderDescription fsDesc;
+ QShader::SeparateToCombinedImageSamplerMappingList fsSamplerMappingList;
for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) {
- if (shaderStage.type() == QRhiShaderStage::Vertex)
- vsDesc = shaderStage.shader().description();
- else if (shaderStage.type() == QRhiShaderStage::Fragment)
- fsDesc = shaderStage.shader().description();
+ QShader shader = shaderStage.shader();
+ int glslVersion = 0;
+ if (shaderStage.type() == QRhiShaderStage::Vertex) {
+ vsDesc = shader.description();
+ if (!rhiD->shaderSource(shaderStage, &glslVersion).isEmpty()) {
+ if (auto *m = shader.separateToCombinedImageSamplerMappingList({ QShader::GlslShader, glslVersion, shaderStage.shaderVariant() }))
+ vsSamplerMappingList = *m;
+ }
+ } else if (shaderStage.type() == QRhiShaderStage::Fragment) {
+ fsDesc = shader.description();
+ if (!rhiD->shaderSource(shaderStage, &glslVersion).isEmpty()) {
+ if (auto *m = shader.separateToCombinedImageSamplerMappingList({ QShader::GlslShader, glslVersion, shaderStage.shaderVariant() }))
+ fsSamplerMappingList = *m;
+ }
+ }
}
QByteArray cacheKey;
@@ -5281,9 +5372,15 @@ bool QGles2GraphicsPipeline::create()
for (const QShaderDescription::InOutVariable &v : vsDesc.combinedImageSamplers())
rhiD->gatherSamplers(program, v, &samplers);
+ for (const QShader::SeparateToCombinedImageSamplerMapping &mapping : vsSamplerMappingList)
+ rhiD->gatherGeneratedSamplers(program, mapping, &samplers);
+
for (const QShaderDescription::InOutVariable &v : fsDesc.combinedImageSamplers())
rhiD->gatherSamplers(program, v, &samplers);
+ for (const QShader::SeparateToCombinedImageSamplerMapping &mapping : fsSamplerMappingList)
+ rhiD->gatherGeneratedSamplers(program, mapping, &samplers);
+
memset(uniformState, 0, sizeof(uniformState));
currentSrb = nullptr;
@@ -5336,6 +5433,13 @@ bool QGles2ComputePipeline::create()
return false;
const QShaderDescription csDesc = m_shaderStage.shader().description();
+ QShader::SeparateToCombinedImageSamplerMappingList csSamplerMappingList;
+ int glslVersion = 0;
+ if (!rhiD->shaderSource(m_shaderStage, &glslVersion).isEmpty()) {
+ if (auto *m = m_shaderStage.shader().separateToCombinedImageSamplerMappingList({ QShader::GlslShader, glslVersion, m_shaderStage.shaderVariant() }))
+ csSamplerMappingList = *m;
+ }
+
program = rhiD->f->glCreateProgram();
QByteArray cacheKey;
@@ -5373,6 +5477,8 @@ bool QGles2ComputePipeline::create()
rhiD->gatherUniforms(program, ub, &activeUniformLocations, &uniforms);
for (const QShaderDescription::InOutVariable &v : csDesc.combinedImageSamplers())
rhiD->gatherSamplers(program, v, &samplers);
+ for (const QShader::SeparateToCombinedImageSamplerMapping &mapping : csSamplerMappingList)
+ rhiD->gatherGeneratedSamplers(program, mapping, &samplers);
// storage images and buffers need no special steps here
diff --git a/src/gui/rhi/qrhigles2_p_p.h b/src/gui/rhi/qrhigles2_p_p.h
index 07725995d6..1e9ffc911f 100644
--- a/src/gui/rhi/qrhigles2_p_p.h
+++ b/src/gui/rhi/qrhigles2_p_p.h
@@ -276,7 +276,9 @@ Q_DECLARE_TYPEINFO(QGles2UniformDescription, Q_RELOCATABLE_TYPE);
struct QGles2SamplerDescription
{
int glslLocation;
- int binding;
+ int combinedBinding;
+ int tbinding;
+ int sbinding;
};
Q_DECLARE_TYPEINFO(QGles2SamplerDescription, Q_RELOCATABLE_TYPE);
@@ -862,6 +864,9 @@ public:
QRhiPassResourceTracker::TextureStage stage);
void executeCommandBuffer(QRhiCommandBuffer *cb);
void executeBindGraphicsPipeline(QGles2CommandBuffer *cbD, QGles2GraphicsPipeline *psD);
+ void bindCombinedSampler(QGles2CommandBuffer *cbD, QGles2Texture *texD, QGles2Sampler *samplerD,
+ void *ps, uint psGeneration, int glslLocation,
+ int *texUnit, bool *activeTexUnitAltered);
void bindShaderResources(QGles2CommandBuffer *cbD,
QRhiGraphicsPipeline *maybeGraphicsPs, QRhiComputePipeline *maybeComputePs,
QRhiShaderResourceBindings *srb,
@@ -882,6 +887,9 @@ public:
QSet<int> *activeUniformLocations, QGles2UniformDescriptionVector *dst);
void gatherSamplers(GLuint program, const QShaderDescription::InOutVariable &v,
QGles2SamplerDescriptionVector *dst);
+ void gatherGeneratedSamplers(GLuint program,
+ const QShader::SeparateToCombinedImageSamplerMapping &mapping,
+ QGles2SamplerDescriptionVector *dst);
void sanityCheckVertexFragmentInterface(const QShaderDescription &vsDesc, const QShaderDescription &fsDesc);
bool isProgramBinaryDiskCacheEnabled() const;
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm
index 4e7f092c54..3a1eaccc92 100644
--- a/src/gui/rhi/qrhimetal.mm
+++ b/src/gui/rhi/qrhimetal.mm
@@ -835,34 +835,43 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD
}
break;
case QRhiShaderResourceBinding::SampledTexture:
+ case QRhiShaderResourceBinding::Texture:
+ case QRhiShaderResourceBinding::Sampler:
{
- const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
+ const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *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 + elem, texD->d->tex });
- res[VERTEX].samplers.append({ nativeBindingSampler + elem, samplerD->d->samplerState });
- }
+ // Must handle all three cases (combined, separate, separate):
+ // first = texture binding, second = sampler binding
+ // first = texture binding
+ // first = sampler binding (i.e. BindingType::Texture...)
+ const int textureBinding = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture);
+ const int samplerBinding = texD && samplerD ? mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Sampler)
+ : (samplerD ? mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture) : -1);
+ if (textureBinding >= 0 && texD)
+ res[VERTEX].textures.append({ textureBinding + elem, texD->d->tex });
+ if (samplerBinding >= 0)
+ res[VERTEX].samplers.append({ samplerBinding + 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 + elem, texD->d->tex });
- res[FRAGMENT].samplers.append({ nativeBindingSampler + elem, samplerD->d->samplerState });
- }
+ const int textureBinding = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture);
+ const int samplerBinding = texD && samplerD ? mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Sampler)
+ : (samplerD ? mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture) : -1);
+ if (textureBinding >= 0 && texD)
+ res[FRAGMENT].textures.append({ textureBinding + elem, texD->d->tex });
+ if (samplerBinding >= 0)
+ res[FRAGMENT].samplers.append({ samplerBinding + 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 + elem, texD->d->tex });
- res[COMPUTE].samplers.append({ nativeBindingSampler + elem, samplerD->d->samplerState });
- }
+ const int textureBinding = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture);
+ const int samplerBinding = texD && samplerD ? mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Sampler)
+ : (samplerD ? mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture) : -1);
+ if (textureBinding >= 0 && texD)
+ res[COMPUTE].textures.append({ textureBinding + elem, texD->d->tex });
+ if (samplerBinding >= 0)
+ res[COMPUTE].samplers.append({ samplerBinding + elem, samplerD->d->samplerState });
}
}
}
@@ -1110,8 +1119,10 @@ void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind
}
break;
case QRhiShaderResourceBinding::SampledTexture:
+ case QRhiShaderResourceBinding::Texture:
+ case QRhiShaderResourceBinding::Sampler:
{
- const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
+ const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex;
if (bd.stex.count != data->count) {
bd.stex.count = data->count;
resNeedsRebind = true;
@@ -1119,19 +1130,26 @@ void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind
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)
+ Q_ASSERT(texD || samplerD);
+ const quint64 texId = texD ? texD->m_id : 0;
+ const uint texGen = texD ? texD->generation : 0;
+ const quint64 samplerId = samplerD ? samplerD->m_id : 0;
+ const uint samplerGen = samplerD ? samplerD->generation : 0;
+ if (texGen != bd.stex.d[elem].texGeneration
+ || texId != bd.stex.d[elem].texId
+ || samplerGen != bd.stex.d[elem].samplerGeneration
+ || samplerId != bd.stex.d[elem].samplerId)
{
resNeedsRebind = true;
- 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;
+ bd.stex.d[elem].texId = texId;
+ bd.stex.d[elem].texGeneration = texGen;
+ bd.stex.d[elem].samplerId = samplerId;
+ bd.stex.d[elem].samplerGeneration = samplerGen;
}
- texD->lastActiveFrameSlot = currentFrameSlot;
- samplerD->lastActiveFrameSlot = currentFrameSlot;
+ if (texD)
+ texD->lastActiveFrameSlot = currentFrameSlot;
+ if (samplerD)
+ samplerD->lastActiveFrameSlot = currentFrameSlot;
}
}
break;
diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp
index 11f06531a6..2a9e0f8c69 100644
--- a/src/gui/rhi/qrhivulkan.cpp
+++ b/src/gui/rhi/qrhivulkan.cpp
@@ -2707,7 +2707,7 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i
break;
case QRhiShaderResourceBinding::SampledTexture:
{
- const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
+ const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex;
writeInfo.descriptorCount = data->count; // arrays of combined image samplers are supported
writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
ArrayOfImageDesc imageInfo(data->count);
@@ -2727,6 +2727,43 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i
imageInfos.append(imageInfo);
}
break;
+ case QRhiShaderResourceBinding::Texture:
+ {
+ const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex;
+ writeInfo.descriptorCount = data->count; // arrays of (separate) images are supported
+ writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
+ ArrayOfImageDesc imageInfo(data->count);
+ for (int elem = 0; elem < data->count; ++elem) {
+ QVkTexture *texD = QRHI_RES(QVkTexture, data->texSamplers[elem].tex);
+ bd.stex.d[elem].texId = texD->m_id;
+ bd.stex.d[elem].texGeneration = texD->generation;
+ bd.stex.d[elem].samplerId = 0;
+ bd.stex.d[elem].samplerGeneration = 0;
+ imageInfo[elem].sampler = VK_NULL_HANDLE;
+ 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);
+ }
+ break;
+ case QRhiShaderResourceBinding::Sampler:
+ {
+ QVkSampler *samplerD = QRHI_RES(QVkSampler, b->u.stex.texSamplers[0].sampler);
+ writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
+ bd.stex.d[0].texId = 0;
+ bd.stex.d[0].texGeneration = 0;
+ bd.stex.d[0].samplerId = samplerD->m_id;
+ bd.stex.d[0].samplerGeneration = samplerD->generation;
+ ArrayOfImageDesc imageInfo(1);
+ imageInfo[0].sampler = samplerD->sampler;
+ imageInfo[0].imageView = VK_NULL_HANDLE;
+ imageInfo[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+ imageInfoIndex = imageInfos.count();
+ imageInfos.append(imageInfo);
+ }
+ break;
case QRhiShaderResourceBinding::ImageLoad:
case QRhiShaderResourceBinding::ImageStore:
case QRhiShaderResourceBinding::ImageLoadStore:
@@ -4592,8 +4629,10 @@ void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBin
}
break;
case QRhiShaderResourceBinding::SampledTexture:
+ case QRhiShaderResourceBinding::Texture:
+ case QRhiShaderResourceBinding::Sampler:
{
- const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
+ const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex;
if (bd.stex.count != data->count) {
bd.stex.count = data->count;
rewriteDescSet = true;
@@ -4601,21 +4640,32 @@ void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBin
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.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)
+ // We use the same code path for both combined and separate
+ // images and samplers, so tex or sampler (but not both) can be
+ // null here.
+ Q_ASSERT(texD || samplerD);
+ if (texD) {
+ texD->lastActiveFrameSlot = currentFrameSlot;
+ trackedRegisterTexture(&passResTracker, texD,
+ QRhiPassResourceTracker::TexSample,
+ QRhiPassResourceTracker::toPassTrackerTextureStage(b->stage));
+ }
+ if (samplerD)
+ samplerD->lastActiveFrameSlot = currentFrameSlot;
+ const quint64 texId = texD ? texD->m_id : 0;
+ const uint texGen = texD ? texD->generation : 0;
+ const quint64 samplerId = samplerD ? samplerD->m_id : 0;
+ const uint samplerGen = samplerD ? samplerD->generation : 0;
+ if (texGen != bd.stex.d[elem].texGeneration
+ || texId != bd.stex.d[elem].texId
+ || samplerGen != bd.stex.d[elem].samplerGeneration
+ || samplerId != bd.stex.d[elem].samplerId)
{
rewriteDescSet = true;
- 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;
+ bd.stex.d[elem].texId = texId;
+ bd.stex.d[elem].texGeneration = texGen;
+ bd.stex.d[elem].samplerId = samplerId;
+ bd.stex.d[elem].samplerGeneration = samplerGen;
}
}
}
@@ -5441,6 +5491,12 @@ static inline VkDescriptorType toVkDescriptorType(const QRhiShaderResourceBindin
case QRhiShaderResourceBinding::SampledTexture:
return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ case QRhiShaderResourceBinding::Texture:
+ return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
+
+ case QRhiShaderResourceBinding::Sampler:
+ return VK_DESCRIPTOR_TYPE_SAMPLER;
+
case QRhiShaderResourceBinding::ImageLoad:
case QRhiShaderResourceBinding::ImageStore:
case QRhiShaderResourceBinding::ImageLoadStore:
@@ -6647,7 +6703,7 @@ bool QVkShaderResourceBindings::create()
memset(&vkbinding, 0, sizeof(vkbinding));
vkbinding.binding = uint32_t(b->binding);
vkbinding.descriptorType = toVkDescriptorType(b);
- if (b->type == QRhiShaderResourceBinding::SampledTexture)
+ if (b->type == QRhiShaderResourceBinding::SampledTexture || b->type == QRhiShaderResourceBinding::Texture)
vkbinding.descriptorCount = b->u.stex.count;
else
vkbinding.descriptorCount = 1;
diff --git a/tests/auto/gui/rhi/qrhi/BLACKLIST b/tests/auto/gui/rhi/qrhi/BLACKLIST
index 65c9e4834b..4e7b7dbc29 100644
--- a/tests/auto/gui/rhi/qrhi/BLACKLIST
+++ b/tests/auto/gui/rhi/qrhi/BLACKLIST
@@ -10,3 +10,6 @@ android
# Same here, GLES 3.0 features seem hopeless
[renderToTextureTextureArray]
android
+# Ditto
+[renderToTextureSampleWithSeparateTextureAndSampler]
+android
diff --git a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
index 0102457b8a..b2348b42f5 100644
--- a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
+++ b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
@@ -42,6 +42,7 @@ 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 simpletextured_separate.frag.qsb simpletextured_separate.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
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured_multiubuf.vert.qsb textured_multiubuf.vert
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured_separate.frag b/tests/auto/gui/rhi/qrhi/data/simpletextured_separate.frag
new file mode 100644
index 0000000000..41b0e4f1c2
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletextured_separate.frag
@@ -0,0 +1,14 @@
+#version 440
+
+layout(location = 0) in vec2 uv;
+layout(location = 0) out vec4 fragColor;
+
+layout(binding = 3) uniform texture2D tex;
+layout(binding = 5) uniform sampler samp;
+
+void main()
+{
+ vec4 c = texture(sampler2D(tex, samp), uv);
+ c.rgb *= c.a;
+ fragColor = c;
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured_separate.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simpletextured_separate.frag.qsb
new file mode 100644
index 0000000000..c5afe1a8eb
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletextured_separate.frag.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
index 770d4be291..6a06fc4ef5 100644
--- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
+++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
@@ -117,6 +117,8 @@ private slots:
void renderToTextureTextureArray();
void renderToTextureTexturedQuad_data();
void renderToTextureTexturedQuad();
+ void renderToTextureSampleWithSeparateTextureAndSampler_data();
+ void renderToTextureSampleWithSeparateTextureAndSampler();
void renderToTextureArrayOfTexturedQuad_data();
void renderToTextureArrayOfTexturedQuad();
void renderToTextureTexturedQuadAndUniformBuffer_data();
@@ -2157,6 +2159,131 @@ void tst_QRhi::renderToTextureTexturedQuad()
QVERIFY(qGreen(result.pixel(214, 191)) > 2 * qBlue(result.pixel(214, 191)));
}
+void tst_QRhi::renderToTextureSampleWithSeparateTextureAndSampler_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::renderToTextureSampleWithSeparateTextureAndSampler()
+{
+ // Same as renderToTextureTexturedQuad but the fragment shader uses a
+ // separate image and sampler. For Vulkan/Metal/D3D11 these are natively
+ // supported. For OpenGL this exercises the auto-generated combined sampler
+ // in the GLSL code and the mapping table that gets applied at run time by
+ // the backend.
+
+ 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->create());
+
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->create());
+
+ QRhiCommandBuffer *cb = nullptr;
+ QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
+ QVERIFY(cb);
+
+ QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch();
+
+ QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(quadVerticesUvs)));
+ QVERIFY(vbuf->create());
+ updates->uploadStaticBuffer(vbuf.data(), quadVerticesUvs);
+
+ QScopedPointer<QRhiTexture> inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size()));
+ QVERIFY(inputTexture->create());
+ updates->uploadTexture(inputTexture.data(), inputImage);
+
+ QScopedPointer<QRhiSampler> sampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
+ QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
+ QVERIFY(sampler->create());
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ srb->setBindings({
+ QRhiShaderResourceBinding::texture(3, QRhiShaderResourceBinding::FragmentStage, inputTexture.data()),
+ QRhiShaderResourceBinding::sampler(5, QRhiShaderResourceBinding::FragmentStage, sampler.data())
+ });
+ QVERIFY(srb->create());
+
+ QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
+ pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
+ QShader vs = loadShader(":/data/simpletextured.vert.qsb");
+ QVERIFY(vs.isValid());
+ QShader fs = loadShader(":/data/simpletextured_separate.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->create());
+
+ 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;
+
+ if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC())
+ result = std::move(result).mirrored();
+
+ QRgb white = qRgba(255, 255, 255, 255);
+ QCOMPARE(result.pixel(79, 77), white);
+ QCOMPARE(result.pixel(124, 81), white);
+ QCOMPARE(result.pixel(128, 149), white);
+ QCOMPARE(result.pixel(120, 189), white);
+ QCOMPARE(result.pixel(116, 185), white);
+
+ QRgb empty = qRgba(0, 0, 0, 0);
+ QCOMPARE(result.pixel(11, 45), empty);
+ QCOMPARE(result.pixel(246, 202), empty);
+ QCOMPARE(result.pixel(130, 18), empty);
+ QCOMPARE(result.pixel(4, 227), empty);
+
+ QVERIFY(qGreen(result.pixel(32, 52)) > 2 * qRed(result.pixel(32, 52)));
+ QVERIFY(qGreen(result.pixel(32, 52)) > 2 * qBlue(result.pixel(32, 52)));
+ QVERIFY(qGreen(result.pixel(214, 191)) > 2 * qRed(result.pixel(214, 191)));
+ QVERIFY(qGreen(result.pixel(214, 191)) > 2 * qBlue(result.pixel(214, 191)));
+}
+
void tst_QRhi::renderToTextureArrayOfTexturedQuad_data()
{
rhiTestData();