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,11 +3412,115 @@ 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 @@ -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(d->u.stex.texSamplers[0].tex)); h ^= qHash(reinterpret_cast(d->u.stex.texSamplers[0].sampler)); break; + case QRhiShaderResourceBinding::Texture: + h ^= qHash(reinterpret_cast(d->u.stex.texSamplers[0].tex)); + break; + case QRhiShaderResourceBinding::Sampler: + h ^= qHash(reinterpret_cast(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 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 nativeBindingVert = mapBinding(b->binding, RBM_VERTEX, nativeResourceBindingMaps); const QPair 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 separateTextureBindings; + struct SeparateSampler { + QGles2Sampler *sampler; + int binding; + }; + QVarLengthArray 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 *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 Binary files /dev/null and b/tests/auto/gui/rhi/qrhi/data/simpletextured_separate.frag.qsb 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 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 texture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size(), 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->create()); + + QScopedPointer rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer 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 vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(quadVerticesUvs))); + QVERIFY(vbuf->create()); + updates->uploadStaticBuffer(vbuf.data(), quadVerticesUvs); + + QScopedPointer inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size())); + QVERIFY(inputTexture->create()); + updates->uploadTexture(inputTexture.data(), inputImage); + + QScopedPointer sampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge)); + QVERIFY(sampler->create()); + + QScopedPointer srb(rhi->newShaderResourceBindings()); + srb->setBindings({ + QRhiShaderResourceBinding::texture(3, QRhiShaderResourceBinding::FragmentStage, inputTexture.data()), + QRhiShaderResourceBinding::sampler(5, QRhiShaderResourceBinding::FragmentStage, sampler.data()) + }); + QVERIFY(srb->create()); + + QScopedPointer 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(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();