From c3ae30085e4047ee7d5a945c36e2055c2de5197d Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Tue, 3 Mar 2020 14:24:11 +0100 Subject: rhi: Add support for arrays of combined image samplers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces a new QRhiShaderResourceBinding function that takes an array of texture-sampler pairs. The existing function is also available and is equivalent to calling the array-based version with array size 1. It is important to note that for Metal one needs MSL 2.0 for array of textures, so qsb needs --msl 20 instead of --msl 12 for such shaders. Comes with an autotest, and also updates all .qsb files for said test with the latest shadertools. Task-number: QTBUG-82624 Change-Id: Ibc1973aae826836f16d842c41d6c8403fd7ff876 Reviewed-by: Christian Strømme --- src/gui/rhi/qrhi.cpp | 66 ++++++++-- src/gui/rhi/qrhi_p.h | 11 +- src/gui/rhi/qrhid3d11.cpp | 92 +++++++------ src/gui/rhi/qrhid3d11_p_p.h | 11 +- src/gui/rhi/qrhigles2.cpp | 61 ++++----- src/gui/rhi/qrhimetal.mm | 112 +++++++++------- src/gui/rhi/qrhimetal_p_p.h | 11 +- src/gui/rhi/qrhivulkan.cpp | 83 +++++++----- src/gui/rhi/qrhivulkan_p_p.h | 11 +- tests/auto/gui/rhi/qrhi/data/buildshaders.bat | 1 + tests/auto/gui/rhi/qrhi/data/simple.frag.qsb | Bin 730 -> 740 bytes tests/auto/gui/rhi/qrhi/data/simple.vert.qsb | Bin 791 -> 786 bytes .../auto/gui/rhi/qrhi/data/simpletextured.frag.qsb | Bin 1208 -> 1206 bytes .../auto/gui/rhi/qrhi/data/simpletextured.vert.qsb | Bin 964 -> 983 bytes .../gui/rhi/qrhi/data/simpletextured_array.frag | 17 +++ .../rhi/qrhi/data/simpletextured_array.frag.qsb | Bin 0 -> 1693 bytes tests/auto/gui/rhi/qrhi/data/textured.frag.qsb | Bin 1574 -> 1590 bytes tests/auto/gui/rhi/qrhi/data/textured.vert.qsb | Bin 1342 -> 1368 bytes tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 143 +++++++++++++++++++++ 19 files changed, 451 insertions(+), 168 deletions(-) create mode 100644 tests/auto/gui/rhi/qrhi/data/simpletextured_array.frag create mode 100644 tests/auto/gui/rhi/qrhi/data/simpletextured_array.frag.qsb diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index c805e23ad0..83c1e8eaa2 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -2885,16 +2885,57 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::uniformBufferWithDynamicOff \return a shader resource binding for the given binding number, pipeline stages, texture, and sampler specified by \a binding, \a stage, \a tex, \a sampler. + + \note This function is equivalent to calling sampledTextures() with a + \c count of 1. + + \sa sampledTextures() */ QRhiShaderResourceBinding QRhiShaderResourceBinding::sampledTexture( int binding, StageFlags stage, QRhiTexture *tex, QRhiSampler *sampler) { + const TextureAndSampler texSampler = { tex, sampler }; + return sampledTextures(binding, stage, 1, &texSampler); +} + +/*! + \return a shader resource binding for the given binding number, pipeline + stages, and the array of texture-sampler pairs specified by \a binding, \a + stage, \a count, and \a texSamplers. + + \note \a count must be at least 1, and not larger than 16. + + \note When \a count is 1, this function is equivalent to sampledTexture(). + + This function is relevant when arrays of combined image samplers are + involved. For example, in GLSL \c{layout(binding = 5) uniform sampler2D + shadowMaps[8];} declares an array of combined image samplers. The + application is then expected provide a QRhiShaderResourceBinding for + binding point 5, set up by calling this function with \a count set to 8 and + a valid texture and sampler for each element of the array. + + \warning All elements of the array must be specified. With the above + example, the only valid, portable approach is calling this function with a + \a count of 8. Additionally, all QRhiTexture and QRhiSampler instances must + be valid, meaning nullptr is not an accepted value. This is due to some of + the underlying APIs, such as, Vulkan, that require a valid image and + sampler object for each element in descriptor arrays. Applications are + advised to provide "dummy" samplers and textures if some array elements are + not relevant (due to not being accessed in the shader). + + \sa sampledTexture() + */ +QRhiShaderResourceBinding QRhiShaderResourceBinding::sampledTextures( + int binding, StageFlags stage, int count, const TextureAndSampler *texSamplers) +{ + Q_ASSERT(count >= 1 && count <= Data::MAX_TEX_SAMPLER_ARRAY_SIZE); QRhiShaderResourceBinding b; b.d.binding = binding; b.d.stage = stage; b.d.type = SampledTexture; - b.d.u.stex.tex = tex; - b.d.u.stex.sampler = sampler; + b.d.u.stex.count = count; + for (int i = 0; i < count; ++i) + b.d.u.stex.texSamplers[i] = texSamplers[i]; return b; } @@ -3084,10 +3125,14 @@ bool operator==(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBind } break; case QRhiShaderResourceBinding::SampledTexture: - if (da->u.stex.tex != db->u.stex.tex - || da->u.stex.sampler != db->u.stex.sampler) - { + if (da->u.stex.count != db->u.stex.count) return false; + for (int i = 0; i < da->u.stex.count; ++i) { + if (da->u.stex.texSamplers[i].tex != db->u.stex.texSamplers[i].tex + || da->u.stex.texSamplers[i].sampler != db->u.stex.texSamplers[i].sampler) + { + return false; + } } break; case QRhiShaderResourceBinding::ImageLoad: @@ -3162,10 +3207,13 @@ QDebug operator<<(QDebug dbg, const QRhiShaderResourceBinding &b) << ')'; break; case QRhiShaderResourceBinding::SampledTexture: - dbg.nospace() << " SampledTexture(" - << "texture=" << d->u.stex.tex - << " sampler=" << d->u.stex.sampler - << ')'; + dbg.nospace() << " SampledTextures(" + << "count=" << d->u.stex.count; + for (int i = 0; i < d->u.stex.count; ++i) { + dbg.nospace() << " texture=" << d->u.stex.texSamplers[i].tex + << " sampler=" << d->u.stex.texSamplers[i].sampler; + } + dbg.nospace() << ')'; break; case QRhiShaderResourceBinding::ImageLoad: dbg.nospace() << " ImageLoad(" diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h index 17c911a5ff..9d906d7bbd 100644 --- a/src/gui/rhi/qrhi_p.h +++ b/src/gui/rhi/qrhi_p.h @@ -348,6 +348,12 @@ public: static QRhiShaderResourceBinding sampledTexture(int binding, StageFlags stage, QRhiTexture *tex, QRhiSampler *sampler); + struct TextureAndSampler { + QRhiTexture *tex; + QRhiSampler *sampler; + }; + static QRhiShaderResourceBinding sampledTextures(int binding, StageFlags stage, int count, const TextureAndSampler *texSamplers); + static QRhiShaderResourceBinding imageLoad(int binding, StageFlags stage, QRhiTexture *tex, int level); static QRhiShaderResourceBinding imageStore(int binding, StageFlags stage, QRhiTexture *tex, int level); static QRhiShaderResourceBinding imageLoadStore(int binding, StageFlags stage, QRhiTexture *tex, int level); @@ -370,9 +376,10 @@ public: int maybeSize; bool hasDynamicOffset; }; + static const int MAX_TEX_SAMPLER_ARRAY_SIZE = 16; struct SampledTextureData { - QRhiTexture *tex; - QRhiSampler *sampler; + int count; + TextureAndSampler texSamplers[MAX_TEX_SAMPLER_ARRAY_SIZE]; }; struct StorageImageData { QRhiTexture *tex; diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp index 7b583e6fd2..7c53925aca 100644 --- a/src/gui/rhi/qrhid3d11.cpp +++ b/src/gui/rhi/qrhid3d11.cpp @@ -627,18 +627,25 @@ void QRhiD3D11::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind break; case QRhiShaderResourceBinding::SampledTexture: { - QD3D11Texture *texD = QRHI_RES(QD3D11Texture, b->u.stex.tex); - QD3D11Sampler *samplerD = QRHI_RES(QD3D11Sampler, b->u.stex.sampler); - if (texD->generation != bd.stex.texGeneration - || texD->m_id != bd.stex.texId - || samplerD->generation != bd.stex.samplerGeneration - || samplerD->m_id != bd.stex.samplerId) - { + const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex; + if (bd.stex.count != data->count) { + bd.stex.count = data->count; srbUpdate = true; - bd.stex.texId = texD->m_id; - bd.stex.texGeneration = texD->generation; - bd.stex.samplerId = samplerD->m_id; - bd.stex.samplerGeneration = samplerD->generation; + } + for (int elem = 0; elem < data->count; ++elem) { + QD3D11Texture *texD = QRHI_RES(QD3D11Texture, data->texSamplers[elem].tex); + QD3D11Sampler *samplerD = QRHI_RES(QD3D11Sampler, data->texSamplers[elem].sampler); + if (texD->generation != bd.stex.d[elem].texGeneration + || texD->m_id != bd.stex.d[elem].texId + || samplerD->generation != bd.stex.d[elem].samplerGeneration + || samplerD->m_id != bd.stex.d[elem].samplerId) + { + srbUpdate = true; + bd.stex.d[elem].texId = texD->m_id; + bd.stex.d[elem].texGeneration = texD->generation; + bd.stex.d[elem].samplerId = samplerD->m_id; + bd.stex.d[elem].samplerGeneration = samplerD->generation; + } } } break; @@ -1894,31 +1901,38 @@ void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD, break; case QRhiShaderResourceBinding::SampledTexture: { - QD3D11Texture *texD = QRHI_RES(QD3D11Texture, b->u.stex.tex); - QD3D11Sampler *samplerD = QRHI_RES(QD3D11Sampler, b->u.stex.sampler); - bd.stex.texId = texD->m_id; - bd.stex.texGeneration = texD->generation; - bd.stex.samplerId = samplerD->m_id; - bd.stex.samplerGeneration = samplerD->generation; - if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { - QPair nativeBinding = mapBinding(b->binding, RBM_VERTEX, nativeResourceBindingMaps); - if (nativeBinding.first >= 0 && nativeBinding.second >= 0) { - res[RBM_VERTEX].textures.append({ nativeBinding.first, texD->srv }); - res[RBM_VERTEX].samplers.append({ nativeBinding.second, samplerD->samplerState }); + const QRhiShaderResourceBinding::Data::SampledTextureData *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); + const QPair nativeBindingComp = mapBinding(b->binding, RBM_COMPUTE, nativeResourceBindingMaps); + // if SPIR-V binding b is mapped to tN and sN in HLSL, and it + // is an array, then it will use tN, tN+1, tN+2, ..., and sN, + // sN+1, sN+2, ... + for (int elem = 0; elem < data->count; ++elem) { + QD3D11Texture *texD = QRHI_RES(QD3D11Texture, data->texSamplers[elem].tex); + QD3D11Sampler *samplerD = QRHI_RES(QD3D11Sampler, data->texSamplers[elem].sampler); + bd.stex.d[elem].texId = texD->m_id; + bd.stex.d[elem].texGeneration = texD->generation; + bd.stex.d[elem].samplerId = samplerD->m_id; + bd.stex.d[elem].samplerGeneration = samplerD->generation; + if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { + if (nativeBindingVert.first >= 0 && nativeBindingVert.second >= 0) { + res[RBM_VERTEX].textures.append({ nativeBindingVert.first + elem, texD->srv }); + res[RBM_VERTEX].samplers.append({ nativeBindingVert.second + elem, samplerD->samplerState }); + } } - } - if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - QPair nativeBinding = mapBinding(b->binding, RBM_FRAGMENT, nativeResourceBindingMaps); - if (nativeBinding.first >= 0 && nativeBinding.second >= 0) { - res[RBM_FRAGMENT].textures.append({ nativeBinding.first, texD->srv }); - res[RBM_FRAGMENT].samplers.append({ nativeBinding.second, samplerD->samplerState }); + if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { + if (nativeBindingFrag.first >= 0 && nativeBindingFrag.second >= 0) { + res[RBM_FRAGMENT].textures.append({ nativeBindingFrag.first + elem, texD->srv }); + res[RBM_FRAGMENT].samplers.append({ nativeBindingFrag.second + elem, samplerD->samplerState }); + } } - } - if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - QPair nativeBinding = mapBinding(b->binding, RBM_COMPUTE, nativeResourceBindingMaps); - if (nativeBinding.first >= 0 && nativeBinding.second >= 0) { - res[RBM_COMPUTE].textures.append({ nativeBinding.first, texD->srv }); - res[RBM_COMPUTE].samplers.append({ nativeBinding.second, samplerD->samplerState }); + if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { + if (nativeBindingComp.first >= 0 && nativeBindingComp.second >= 0) { + res[RBM_COMPUTE].textures.append({ nativeBindingComp.first + elem, texD->srv }); + res[RBM_COMPUTE].samplers.append({ nativeBindingComp.second + elem, samplerD->samplerState }); + } } } } @@ -3529,11 +3543,15 @@ static pD3DCompile resolveD3DCompile() static QByteArray compileHlslShaderSource(const QShader &shader, QShader::Variant shaderVariant, QString *error, QShaderKey *usedShaderKey) { - QShaderCode dxbc = shader.shader({ QShader::DxbcShader, 50, shaderVariant }); - if (!dxbc.shader().isEmpty()) + QShaderKey key = { QShader::DxbcShader, 50, shaderVariant }; + QShaderCode dxbc = shader.shader(key); + if (!dxbc.shader().isEmpty()) { + if (usedShaderKey) + *usedShaderKey = key; return dxbc.shader(); + } - const QShaderKey key = { QShader::HlslShader, 50, shaderVariant }; + key = { QShader::HlslShader, 50, shaderVariant }; QShaderCode hlslSource = shader.shader(key); if (hlslSource.shader().isEmpty()) { qWarning() << "No HLSL (shader model 5.0) code found in baked shader" << shader; diff --git a/src/gui/rhi/qrhid3d11_p_p.h b/src/gui/rhi/qrhid3d11_p_p.h index f749b612b5..33412b8011 100644 --- a/src/gui/rhi/qrhid3d11_p_p.h +++ b/src/gui/rhi/qrhid3d11_p_p.h @@ -210,10 +210,13 @@ struct QD3D11ShaderResourceBindings : public QRhiShaderResourceBindings uint generation; }; struct BoundSampledTextureData { - quint64 texId; - uint texGeneration; - quint64 samplerId; - uint samplerGeneration; + int count; + struct { + quint64 texId; + uint texGeneration; + quint64 samplerId; + uint samplerGeneration; + } d[QRhiShaderResourceBinding::Data::MAX_TEX_SAMPLER_ARRAY_SIZE]; }; struct BoundStorageImageData { quint64 id; diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index feeb65137a..3ba83464d2 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -919,10 +919,12 @@ void QRhiGles2::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind hasDynamicOffsetInSrb = true; break; case QRhiShaderResourceBinding::SampledTexture: - trackedRegisterTexture(&passResTracker, - QRHI_RES(QGles2Texture, b->u.stex.tex), - QRhiPassResourceTracker::TexSample, - QRhiPassResourceTracker::toPassTrackerTextureStage(b->stage)); + for (int elem = 0; elem < b->u.stex.count; ++elem) { + trackedRegisterTexture(&passResTracker, + QRHI_RES(QGles2Texture, b->u.stex.texSamplers[elem].tex), + QRhiPassResourceTracker::TexSample, + QRhiPassResourceTracker::toPassTrackerTextureStage(b->stage)); + } break; case QRhiShaderResourceBinding::ImageLoad: case QRhiShaderResourceBinding::ImageStore: @@ -2574,36 +2576,37 @@ void QRhiGles2::bindShaderResources(QRhiGraphicsPipeline *maybeGraphicsPs, QRhiC break; case QRhiShaderResourceBinding::SampledTexture: { - QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.stex.tex); - QGles2Sampler *samplerD = QRHI_RES(QGles2Sampler, b->u.stex.sampler); QVector &samplers(maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->samplers : QRHI_RES(QGles2ComputePipeline, maybeComputePs)->samplers); - - for (QGles2SamplerDescription &sampler : samplers) { - if (sampler.binding == b->binding) { - f->glActiveTexture(GL_TEXTURE0 + uint(texUnit)); - f->glBindTexture(texD->target, texD->texture); - - if (texD->samplerState != samplerD->d) { - 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)); - // 3D textures not supported by GLES 2.0 or by us atm... - //f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_R, 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); + for (int elem = 0; elem < b->u.stex.count; ++elem) { + QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.stex.texSamplers[elem].tex); + QGles2Sampler *samplerD = QRHI_RES(QGles2Sampler, b->u.stex.texSamplers[elem].sampler); + for (QGles2SamplerDescription &sampler : samplers) { + if (sampler.binding == b->binding) { + f->glActiveTexture(GL_TEXTURE0 + uint(texUnit)); + f->glBindTexture(texD->target, texD->texture); + + if (texD->samplerState != samplerD->d) { + 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)); + // 3D textures not supported by GLES 2.0 or by us atm... + //f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_R, 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; } - texD->samplerState = samplerD->d; - } - f->glUniform1i(sampler.glslLocation, texUnit); - ++texUnit; + f->glUniform1i(sampler.glslLocation + elem, texUnit); + ++texUnit; + } } } } diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index 314c58b0b7..0806c8a052 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -748,30 +748,33 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD break; case QRhiShaderResourceBinding::SampledTexture: { - QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex); - QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler); - if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { - const int nativeBindingTexture = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture); - const int nativeBindingSampler = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Sampler); - if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) { - res[VERTEX].textures.append({ nativeBindingTexture, texD->d->tex }); - res[VERTEX].samplers.append({ nativeBindingSampler, samplerD->d->samplerState }); + const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex; + for (int elem = 0; elem < data->count; ++elem) { + QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.texSamplers[elem].tex); + QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.texSamplers[elem].sampler); + if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { + const int nativeBindingTexture = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture); + const int nativeBindingSampler = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Sampler); + if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) { + res[VERTEX].textures.append({ nativeBindingTexture + elem, texD->d->tex }); + res[VERTEX].samplers.append({ nativeBindingSampler + elem, samplerD->d->samplerState }); + } } - } - if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - const int nativeBindingTexture = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture); - const int nativeBindingSampler = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Sampler); - if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) { - res[FRAGMENT].textures.append({ nativeBindingTexture, texD->d->tex }); - res[FRAGMENT].samplers.append({ nativeBindingSampler, samplerD->d->samplerState }); + 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 }); + } } - } - if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - const int nativeBindingTexture = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture); - const int nativeBindingSampler = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Sampler); - if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) { - res[COMPUTE].textures.append({ nativeBindingTexture, texD->d->tex }); - res[COMPUTE].samplers.append({ nativeBindingSampler, samplerD->d->samplerState }); + 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 }); + } } } } @@ -1020,21 +1023,28 @@ void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind break; case QRhiShaderResourceBinding::SampledTexture: { - QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex); - QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler); - if (texD->generation != bd.stex.texGeneration - || texD->m_id != bd.stex.texId - || samplerD->generation != bd.stex.samplerGeneration - || samplerD->m_id != bd.stex.samplerId) - { + const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex; + if (bd.stex.count != data->count) { + bd.stex.count = data->count; resNeedsRebind = true; - bd.stex.texId = texD->m_id; - bd.stex.texGeneration = texD->generation; - bd.stex.samplerId = samplerD->m_id; - bd.stex.samplerGeneration = samplerD->generation; } - texD->lastActiveFrameSlot = currentFrameSlot; - samplerD->lastActiveFrameSlot = currentFrameSlot; + for (int elem = 0; elem < data->count; ++elem) { + QMetalTexture *texD = QRHI_RES(QMetalTexture, data->texSamplers[elem].tex); + QMetalSampler *samplerD = QRHI_RES(QMetalSampler, data->texSamplers[elem].sampler); + if (texD->generation != bd.stex.d[elem].texGeneration + || texD->m_id != bd.stex.d[elem].texId + || samplerD->generation != bd.stex.d[elem].samplerGeneration + || samplerD->m_id != bd.stex.d[elem].samplerId) + { + resNeedsRebind = true; + bd.stex.d[elem].texId = texD->m_id; + bd.stex.d[elem].texGeneration = texD->generation; + bd.stex.d[elem].samplerId = samplerD->m_id; + bd.stex.d[elem].samplerGeneration = samplerD->generation; + } + texD->lastActiveFrameSlot = currentFrameSlot; + samplerD->lastActiveFrameSlot = currentFrameSlot; + } } break; case QRhiShaderResourceBinding::ImageLoad: @@ -2981,12 +2991,16 @@ bool QMetalShaderResourceBindings::build() break; case QRhiShaderResourceBinding::SampledTexture: { - QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex); - QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler); - bd.stex.texId = texD->m_id; - bd.stex.texGeneration = texD->generation; - bd.stex.samplerId = samplerD->m_id; - bd.stex.samplerGeneration = samplerD->generation; + const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex; + bd.stex.count = data->count; + for (int elem = 0; elem < data->count; ++elem) { + QMetalTexture *texD = QRHI_RES(QMetalTexture, data->texSamplers[elem].tex); + QMetalSampler *samplerD = QRHI_RES(QMetalSampler, data->texSamplers[elem].sampler); + bd.stex.d[elem].texId = texD->m_id; + bd.stex.d[elem].texGeneration = texD->generation; + bd.stex.d[elem].samplerId = samplerD->m_id; + bd.stex.d[elem].samplerGeneration = samplerD->generation; + } } break; case QRhiShaderResourceBinding::ImageLoad: @@ -3241,8 +3255,12 @@ static inline MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c) id QRhiMetalData::createMetalLib(const QShader &shader, QShader::Variant shaderVariant, QString *error, QByteArray *entryPoint, QShaderKey *activeKey) { - QShaderKey key = { QShader::MetalLibShader, 12, shaderVariant }; + QShaderKey key = { QShader::MetalLibShader, 20, shaderVariant }; QShaderCode mtllib = shader.shader(key); + if (mtllib.shader().isEmpty()) { + key.setSourceVersion(12); + mtllib = shader.shader(key); + } if (!mtllib.shader().isEmpty()) { dispatch_data_t data = dispatch_data_create(mtllib.shader().constData(), size_t(mtllib.shader().size()), @@ -3261,16 +3279,20 @@ id QRhiMetalData::createMetalLib(const QShader &shader, QShader::Var } } - key = { QShader::MslShader, 12, shaderVariant }; + key = { QShader::MslShader, 20, shaderVariant }; QShaderCode mslSource = shader.shader(key); if (mslSource.shader().isEmpty()) { - qWarning() << "No MSL 1.2 code found in baked shader" << shader; + key.setSourceVersion(12); + mslSource = shader.shader(key); + } + if (mslSource.shader().isEmpty()) { + qWarning() << "No MSL 2.0 or 1.2 code found in baked shader" << shader; return nil; } NSString *src = [NSString stringWithUTF8String: mslSource.shader().constData()]; MTLCompileOptions *opts = [[MTLCompileOptions alloc] init]; - opts.languageVersion = MTLLanguageVersion1_2; + opts.languageVersion = key.sourceVersion() == 20 ? MTLLanguageVersion2_0 : MTLLanguageVersion1_2; NSError *err = nil; id lib = [dev newLibraryWithSource: src options: opts error: &err]; [opts release]; diff --git a/src/gui/rhi/qrhimetal_p_p.h b/src/gui/rhi/qrhimetal_p_p.h index a5af5611a6..cb4b777d88 100644 --- a/src/gui/rhi/qrhimetal_p_p.h +++ b/src/gui/rhi/qrhimetal_p_p.h @@ -197,10 +197,13 @@ struct QMetalShaderResourceBindings : public QRhiShaderResourceBindings uint generation; }; struct BoundSampledTextureData { - quint64 texId; - uint texGeneration; - quint64 samplerId; - uint samplerGeneration; + int count; + struct { + quint64 texId; + uint texGeneration; + quint64 samplerId; + uint samplerGeneration; + } d[QRhiShaderResourceBinding::Data::MAX_TEX_SAMPLER_ARRAY_SIZE]; }; struct BoundStorageImageData { quint64 id; diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index a92c3e14e9..ca913475a5 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -2487,7 +2487,8 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, srb); QVarLengthArray bufferInfos; - QVarLengthArray imageInfos; + using ArrayOfImageDesc = QVarLengthArray; + QVarLengthArray imageInfos; QVarLengthArray writeInfos; QVarLengthArray, 12> infoIndices; @@ -2530,17 +2531,22 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i break; case QRhiShaderResourceBinding::SampledTexture: { - QVkTexture *texD = QRHI_RES(QVkTexture, b->u.stex.tex); - QVkSampler *samplerD = QRHI_RES(QVkSampler, b->u.stex.sampler); + const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex; + writeInfo.descriptorCount = data->count; // arrays of combined image samplers are supported writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - bd.stex.texId = texD->m_id; - bd.stex.texGeneration = texD->generation; - bd.stex.samplerId = samplerD->m_id; - bd.stex.samplerGeneration = samplerD->generation; - VkDescriptorImageInfo imageInfo; - imageInfo.sampler = samplerD->sampler; - imageInfo.imageView = texD->imageView; - imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + ArrayOfImageDesc imageInfo(data->count); + for (int elem = 0; elem < data->count; ++elem) { + QVkTexture *texD = QRHI_RES(QVkTexture, data->texSamplers[elem].tex); + QVkSampler *samplerD = QRHI_RES(QVkSampler, data->texSamplers[elem].sampler); + bd.stex.d[elem].texId = texD->m_id; + bd.stex.d[elem].texGeneration = texD->generation; + bd.stex.d[elem].samplerId = samplerD->m_id; + bd.stex.d[elem].samplerGeneration = samplerD->generation; + imageInfo[elem].sampler = samplerD->sampler; + imageInfo[elem].imageView = texD->imageView; + imageInfo[elem].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + } + bd.stex.count = data->count; imageInfoIndex = imageInfos.count(); imageInfos.append(imageInfo); } @@ -2555,10 +2561,10 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; bd.simage.id = texD->m_id; bd.simage.generation = texD->generation; - VkDescriptorImageInfo imageInfo; - imageInfo.sampler = VK_NULL_HANDLE; - imageInfo.imageView = view; - imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + ArrayOfImageDesc imageInfo(1); + imageInfo[0].sampler = VK_NULL_HANDLE; + imageInfo[0].imageView = view; + imageInfo[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL; imageInfoIndex = imageInfos.count(); imageInfos.append(imageInfo); } @@ -2596,7 +2602,7 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i if (bufferInfoIndex >= 0) writeInfos[i].pBufferInfo = &bufferInfos[bufferInfoIndex]; else if (imageInfoIndex >= 0) - writeInfos[i].pImageInfo = &imageInfos[imageInfoIndex]; + writeInfos[i].pImageInfo = imageInfos[imageInfoIndex].constData(); } df->vkUpdateDescriptorSets(dev, uint32_t(writeInfos.count()), writeInfos.constData(), 0, nullptr); @@ -4210,24 +4216,30 @@ void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBin break; case QRhiShaderResourceBinding::SampledTexture: { - QVkTexture *texD = QRHI_RES(QVkTexture, b->u.stex.tex); - QVkSampler *samplerD = QRHI_RES(QVkSampler, b->u.stex.sampler); - texD->lastActiveFrameSlot = currentFrameSlot; - samplerD->lastActiveFrameSlot = currentFrameSlot; - trackedRegisterTexture(&passResTracker, texD, - QRhiPassResourceTracker::TexSample, - QRhiPassResourceTracker::toPassTrackerTextureStage(b->stage)); - - if (texD->generation != bd.stex.texGeneration - || texD->m_id != bd.stex.texId - || samplerD->generation != bd.stex.samplerGeneration - || samplerD->m_id != bd.stex.samplerId) - { + const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex; + if (bd.stex.count != data->count) { + bd.stex.count = data->count; rewriteDescSet = true; - bd.stex.texId = texD->m_id; - bd.stex.texGeneration = texD->generation; - bd.stex.samplerId = samplerD->m_id; - bd.stex.samplerGeneration = samplerD->generation; + } + 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) + { + 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; + } } } break; @@ -6065,7 +6077,10 @@ bool QVkShaderResourceBindings::build() memset(&vkbinding, 0, sizeof(vkbinding)); vkbinding.binding = uint32_t(b->binding); vkbinding.descriptorType = toVkDescriptorType(b); - vkbinding.descriptorCount = 1; // no array support yet + if (b->type == QRhiShaderResourceBinding::SampledTexture) + vkbinding.descriptorCount = b->u.stex.count; + else + vkbinding.descriptorCount = 1; vkbinding.stageFlags = toVkShaderStageFlags(b->stage); vkbindings.append(vkbinding); } diff --git a/src/gui/rhi/qrhivulkan_p_p.h b/src/gui/rhi/qrhivulkan_p_p.h index fd65417e75..62516e268d 100644 --- a/src/gui/rhi/qrhivulkan_p_p.h +++ b/src/gui/rhi/qrhivulkan_p_p.h @@ -254,10 +254,13 @@ struct QVkShaderResourceBindings : public QRhiShaderResourceBindings uint generation; }; struct BoundSampledTextureData { - quint64 texId; - uint texGeneration; - quint64 samplerId; - uint samplerGeneration; + int count; + struct { + quint64 texId; + uint texGeneration; + quint64 samplerId; + uint samplerGeneration; + } d[QRhiShaderResourceBinding::Data::MAX_TEX_SAMPLER_ARRAY_SIZE]; }; struct BoundStorageImageData { quint64 id; diff --git a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat index 5b8a77b833..0cfeaaaff3 100644 --- a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat +++ b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat @@ -44,5 +44,6 @@ qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.vert.qsb simple.vert qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.frag.qsb simple.frag qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured.vert.qsb simpletextured.vert qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured.frag.qsb simpletextured.frag +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 20 -o simpletextured_array.frag.qsb simpletextured_array.frag qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.vert.qsb textured.vert qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.frag.qsb textured.frag diff --git a/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb index c235108d39..43edbdffd9 100644 Binary files a/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb and b/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb differ diff --git a/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb b/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb index 68cfeb8f1d..06af5df492 100644 Binary files a/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb and b/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb differ diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb index 397961c238..7749f3caad 100644 Binary files a/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb and b/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb differ diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb b/tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb index a9067949a5..c87d4b2fc1 100644 Binary files a/tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb and b/tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb differ diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured_array.frag b/tests/auto/gui/rhi/qrhi/data/simpletextured_array.frag new file mode 100644 index 0000000000..c5ee2057d8 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simpletextured_array.frag @@ -0,0 +1,17 @@ +#version 440 + +layout(location = 0) in vec2 uv; +layout(location = 0) out vec4 fragColor; + +layout(binding = 0) uniform sampler2D tex[3]; + +void main() +{ + vec4 c0 = texture(tex[0], uv); + vec4 c1 = texture(tex[1], uv); + vec4 c2 = texture(tex[2], uv); + vec4 cc = c0 + c1 + c2; + vec4 c = vec4(clamp(cc.r, 0.0, 1.0), clamp(cc.g, 0.0, 1.0), clamp(cc.b, 0.0, 1.0), clamp(cc.a, 0.0, 1.0)); + c.rgb *= c.a; + fragColor = c; +} diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured_array.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simpletextured_array.frag.qsb new file mode 100644 index 0000000000..362e220d25 Binary files /dev/null and b/tests/auto/gui/rhi/qrhi/data/simpletextured_array.frag.qsb differ diff --git a/tests/auto/gui/rhi/qrhi/data/textured.frag.qsb b/tests/auto/gui/rhi/qrhi/data/textured.frag.qsb index 018d732e2f..f669152c9c 100644 Binary files a/tests/auto/gui/rhi/qrhi/data/textured.frag.qsb and b/tests/auto/gui/rhi/qrhi/data/textured.frag.qsb differ diff --git a/tests/auto/gui/rhi/qrhi/data/textured.vert.qsb b/tests/auto/gui/rhi/qrhi/data/textured.vert.qsb index 44454d226e..d4ba474777 100644 Binary files a/tests/auto/gui/rhi/qrhi/data/textured.vert.qsb and b/tests/auto/gui/rhi/qrhi/data/textured.vert.qsb differ diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp index 549481aa21..c1793d7d4a 100644 --- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp +++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp @@ -91,6 +91,8 @@ private slots: void renderToTextureSimple(); void renderToTextureTexturedQuad_data(); void renderToTextureTexturedQuad(); + void renderToTextureArrayOfTexturedQuad_data(); + void renderToTextureArrayOfTexturedQuad(); void renderToTextureTexturedQuadAndUniformBuffer_data(); void renderToTextureTexturedQuadAndUniformBuffer(); void renderToWindowSimple_data(); @@ -1468,6 +1470,147 @@ void tst_QRhi::renderToTextureTexturedQuad() QVERIFY(qGreen(result.pixel(214, 191)) > 2 * qBlue(result.pixel(214, 191))); } +void tst_QRhi::renderToTextureArrayOfTexturedQuad_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToTextureArrayOfTexturedQuad() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer 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->build()); + + QScopedPointer rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + + QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); + + static const float verticesUvs[] = { + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f + }; + QScopedPointer vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(verticesUvs))); + QVERIFY(vbuf->build()); + updates->uploadStaticBuffer(vbuf.data(), verticesUvs); + + // In this test we pass 3 textures (and samplers) to the fragment shader in + // form of an array of combined image samplers. + + QScopedPointer inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size())); + QVERIFY(inputTexture->build()); + updates->uploadTexture(inputTexture.data(), inputImage); + + QImage redImage(inputImage.size(), QImage::Format_RGBA8888); + redImage.fill(Qt::red); + + QScopedPointer redTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size())); + QVERIFY(redTexture->build()); + updates->uploadTexture(redTexture.data(), redImage); + + QImage greenImage(inputImage.size(), QImage::Format_RGBA8888); + greenImage.fill(Qt::green); + + QScopedPointer greenTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size())); + QVERIFY(greenTexture->build()); + updates->uploadTexture(greenTexture.data(), greenImage); + + QScopedPointer sampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge)); + QVERIFY(sampler->build()); + + QScopedPointer srb(rhi->newShaderResourceBindings()); + QRhiShaderResourceBinding::TextureAndSampler texSamplers[3] = { + { inputTexture.data(), sampler.data() }, + { redTexture.data(), sampler.data() }, + { greenTexture.data(), sampler.data() } + }; + srb->setBindings({ + QRhiShaderResourceBinding::sampledTextures(0, QRhiShaderResourceBinding::FragmentStage, 3, texSamplers) + }); + QVERIFY(srb->build()); + + QScopedPointer pipeline(rhi->newGraphicsPipeline()); + pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip); + QShader vs = loadShader(":/data/simpletextured.vert.qsb"); + QVERIFY(vs.isValid()); + QShader fs = loadShader(":/data/simpletextured_array.frag.qsb"); + QVERIFY(fs.isValid()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 4 * sizeof(float) } }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) } + }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + + QVERIFY(pipeline->build()); + + cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, updates); + cb->setGraphicsPipeline(pipeline.data()); + cb->setShaderResources(); + cb->setViewport({ 0, 0, float(texture->pixelSize().width()), float(texture->pixelSize().height()) }); + QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbindings); + cb->draw(4); + + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888_Premultiplied); + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult); + cb->endPass(readbackBatch); + + rhi->endOffscreenFrame(); + + QVERIFY(!result.isNull()); + + if (impl == QRhi::Null) + return; + + // Flip with D3D and Metal because these have Y down in images. Vulkan does + // not need this because there Y is down both in images and in NDC, which + // just happens to give correct results with our OpenGL-targeted vertex and + // UV data. + if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC()) + result = std::move(result).mirrored(); + + // we added the input image + red + green together, so red and green must be all 1 + for (int y = 0; y < result.height(); ++y) { + for (int x = 0; x < result.width(); ++x) { + const QRgb pixel = result.pixel(x, y); + QCOMPARE(qRed(pixel), 255); + QCOMPARE(qGreen(pixel), 255); + } + } +} + void tst_QRhi::renderToTextureTexturedQuadAndUniformBuffer_data() { rhiTestData(); -- cgit v1.2.3