From c3ae30085e4047ee7d5a945c36e2055c2de5197d Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Tue, 3 Mar 2020 14:24:11 +0100 Subject: [PATCH] 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 | 59 ++++---- 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 .../gui/rhi/qrhi/data/simpletextured.frag.qsb | Bin 1208 -> 1206 bytes .../gui/rhi/qrhi/data/simpletextured.vert.qsb | Bin 964 -> 983 bytes .../rhi/qrhi/data/simpletextured_array.frag | 17 +++ .../qrhi/data/simpletextured_array.frag.qsb | Bin 0 -> 1693 bytes .../auto/gui/rhi/qrhi/data/textured.frag.qsb | Bin 1574 -> 1590 bytes .../auto/gui/rhi/qrhi/data/textured.vert.qsb | Bin 1342 -> 1368 bytes tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 143 ++++++++++++++++++ 19 files changed, 450 insertions(+), 167 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 (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); - 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); + 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 c235108d39efc5b4850365823fc9d3ff9606c99f..43edbdffd90541ace686be3142c07ae6f1a98719 100644 GIT binary patch literal 740 zcmVPbf8A;FSvN?L@pshXs%SgSNa0wKoj z?j{-8?1tS<5sQ$M2ah72y-F|Q$q&%a;3w!!Po6{&eQ$R*yNyLD_?F+yyf^RPzL|N1 zkT@Y^lK3P6E|4~{i9$N0L@Z(e>_Fp}AaXjI5}w~iWRFvOy6tewWVt1oQfuI{JzdSS zwyktZmSNciO7|?Tv7W-sbcXIz1~l4btDbr~mzC$4JQ{$_6zB^|F=Mn8ku@9AbCQ+ofpaj*XIbLA^P+WqqC2@EuT0%(H;%qgG9^JGx5_4yv1#I_C0F>^)5oNeA_BY|hhP23Ns);uem> zwb1Kutq9?&wcE8fxF3lpk>fPZ|BFtm{}&3*7b$Wy(v#Rk>I_Coz;{+i&%<9}>}hz6 zIB!7pvyQ7}^Gh=BpA(hDy;;z~=Z*+sBmr}vgEDXqtC&}pPu`uGJf<6m5A{cn72|;h z%C3rhE`l}MU-LZqHNFHZY!wV}8T2vEl*og@m>~Xnqr4#aq>$YJ9(AebeYh6z(<0tH zSfSmF(3uv%^)lo;LBb}8{=S0*-$D_%q_F!5)w$`PU{L!>5?*hlA1Q9C#z52AN>6tc zqXi{4_->Jga)BUms?Izs=roRN4^-Fd0CEeOW6=MwJ4Ko# zYcy1x({iTSgoh87xYyIoh^@aGHIh({SY`WQg>vAs(f;bkI=RE3zOB0h+eEXV7Y%>F3_#dui& literal 730 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 V5&Bcf{g*$L$nV9&*iR34C!Vffm8<{& diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb index 397961c2388f94eb8dfe0b00727127c6f1f1b958..7749f3caad10f73e1aeb31449e9d01152cbd78e3 100644 GIT binary patch literal 1206 zcmV;n1WEe<01Pd7oYhuKZyQw$La?g%0jwhB4|IpbqD%IOKY-L-g-|zD`Oe%k_SB_mS+L;R z$M?MNIp4i=$3&DMqJB_cFt@0UIR>J~GU&;{ey3@NJTj?Dd2*=+@+8Z%qIxq7kWUuv zflEEdJ30nrmE)0Qe@xjeJ(lF`*S?Rw=+{>khv8AhvVJ5}YF zb*tGh%a#hF<_)9id#$ps>Za{vOK!_IZW{_?r{>s!WUOHfoBrTZRf5GCDLBCQGBJOshQ| z@x7!&A0Pseo7zwymo2)v)}LE`R#tzU>FW!`Ual`{mHi}lplkRL%&v7 zUb!2AiEs4g{gBH?AuwiBQd&@0JC z5O{jmE_-g%-SJgwZCYKwQM{pwyJiI!R^F{QY`(c>vY8n=JUo1-cb|u}_8{b`hwmN#WIM|LUykq$^Qd&y@%4CYD#>ON0zh&)RAx#IXSsdYf|PY55{&kLWF{O2Vf>#hLvnDkDfdk9TJd>M-& ziCu)uJ)M+(7NwWdQtyX|N(Pu~AJ#9)zO(Ks>|;TVI_Ie5;9AE;zbN~0R(w`b!9;+u z)}4)D&vpNRoNPZQKI4%buXb__1@ffm&Pl(C2u@+0lacrf63;dz@*}J~r*}U3&gl7b U{M1q5QgcVx<44*520s(;nYbu)+W-In literal 1208 zcmV;p1V{S-01M%GoYhuOZyQAvf3a&j+5U-JNa~c5P6-F6SjKVFD#Ed)Vkd2-LQw2L zM6k?uy|x$GyJmN7l9nRG4IwyHd;m_7@&$TB;=qwR;tN#jtwN|51n=#>@or2A<-mc7 zUgrJ%&3kWlJOcnHz^4gs9>5Ui2on6k1A;+5=b!~PsL+NoSkOhX8R4i17>kaB3p(r~ zmp;ZbJmMVrZAkai!ceyz!!pIvwVa|1OrvGlJ>jUmepk2i72)c;1w}DTG1RrZ7z_)F zHE>B-7A;$Cm#wa4qr=cLnxdx~W_nS%uLvBTizbSM;0|m(jdKP877NmRRW$M|=%8hH z_wI=+IDg{XD2l?zd(7 z9i!dpi&ocC-NIkghKb5iSA5-gZ-%P9XPE7vLC?{QeC0oAN4wit$BtA~SKZ0g|E?#y zq22#6=t=&V-Txm|7)8nl1xEHO+i#Z7f0x<+e)DST<6oXU{P{^vp*HN}$&BH=jPxrV zB2<2WbPDyGm9_Opp*GPWUL_`m=L~(m2J%^2B3%keP(0GBNT)dFQ%>fnqf7K9<5CWE z#F(Ml(=H zYthI;f>ui4&b-3fvzR%peG-4xX}*acMAmVe+2w0FvY+8yBil4a%F9WvaTfeN#x3#m z30evNs2`6e=({{|$Ula*DV#Ot=^Lfbl6b-z3`rmJFh{@3loz6qugqJ@mqRke&xd3Y zl3&1#BA$QLtBCknzFdGx2;<6NJ>|C3w~(Hv&2Kydkmhm>DJypuR9952r>=?W~L zW*x0J&3F;^PmcQR`zN`-q?x;Y}7uaV5Gnn&w?CUOsdQW+OgP3H$$UdnM#>*p&DNmkf-9@f97V4ACb3PP*nd8YO zSpFSXUh#Kco;QM$TTYohUNB6pJ80@+wWqskw}Hp1Yuqg z=ytkmX=V^afIZy|E$DM;34_lWjC5SJt)o=0XGWKGbJfExkpf-hlZ~)5@;N}ZDsq%B zt2_t!0ti+mDvJ9L3gL43ZIJat(-JI$No7Zm1kd-_XM&IRPY(smAUpOtB(<$$SteQK WyCLRf4f4@`=tDUCd+{em4DTuca%uwr diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb b/tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb index a9067949a5739fb18ecb73192d15979555126eae..c87d4b2fc1e611356bb13ff261e9987caced0a21 100644 GIT binary patch literal 983 zcmV;|11S6e014%IoXu5RYZFlrKG`I--L@vRwJj)eq&}nwF)6hadcoTCBB&`%kU}YC zo87iYlHIU3Dy86W5b?R_V|~|0e}Ld0@yRD&yv&?4+ua1J^g(dKWarF$b2)ov&oIWa zOpykhXFjvo5o<69el9afpZZ0_Iw{ZEfb*CDKLL^rx(pgFz^9}=U?Ii52=_PYp;&Jc zx|b87<$A)gdHH@x*PX!Uq1Bk;K{%%iTgkq4)NmZvj5O1{?Rdfmr#W2@9bxj8A?(7W zeynq#Amj@PA5XogB%by;I>OlU>+xF}`qx#|VP25slQ_VdMd~x)fP<<%B5nNJ*uretx&`F0?#ILrsDSaxTM>!2bePwI8THUO$v$L}* z#M<~vtTYt3L~<$-E5{PCYR}H!HO8&2uWU8Jz5&P^@O<9OQoG>#EuWWhP@V!A<5(zj zr9Xgk`NowVc?RBKWLqUX>LSeQEoEB-c&)xvf1RisDbsZ(DRw#1E{XpGMg4}9Xcg^! zTAR3nmG#1JoU}ub8Efr_s|lZL&<#b;H;dEvOK87JyHw}UrxE~1Ir^k8UZ{+XFnlLz zxfkr!EFhEEEZ7hqgeYgFU-TgrpP`@vCuvP?@D8FpQHXeXz-p|pnVTe?Td8I17sVN^{CiO;4v!qrfR)L z_%Xu#fb6sC>xud}!{Vz8t2><#cB2`X7N2ifzR}!;c5RBiMLpJgqGjI0zvAJdt_L1Y z0o!OB2!Y+0WH?(vPIDo`64k51P2d<6CXy#>Q9_o{>!6&KfP$g;IxZmB+ FqBUT1_A~$h literal 964 zcmV;#13UZx012^poXu5VYZE~bpSvVhudRu#)`C)2>O+bUlTf8lTdYlMg=$I@N}-f; z=Favc$%Xq9DkAt9M0_s#Sl{*04y#|9F*F?u};8h(iTFOw1>5pTm3ryUm?7+uN``G^%2h9@yskPT;t0 z8863ribwf7k&$Y_?38bUF*G0KIn}d*K2hAk9wsGqI^!=V{6|qs`Wv!Jg&9MM7F%Gy ziueIud|m3&xY8i8q@U&a1=f+>U@no}U@jWR8|HXv$wP4)X5TEwxyZg*-UrfM!TBZT zMX_%2e1vNu|53ILv;QdXp~m(xjz#vnxK=vE9{}lppeBRk9M?U>+~;|phuAhO`KZ_n z=rJa1Q(Q+Ff0Z#mG5d^U0BJrqX%59rJD7CZjb>=s;$F)R%;pXr*QWDy-iYU zUtw?FFhbvH?~1nBvi*+Pu!Z!RH4H!S!bTt{O>xH!1LLJ3usv(KW>6MkS8Q#CzU}3k zZlhhC+TNax&C?=r5D^z0I|98r9@=RqBTtJ?>}Ljn=R6GqJAW)eSwvq(AF4DJW$4P! zBPOq;e3Vf*6y6WaT^ncH+fz&-##5gYFJGyoI4fYqbLdxyGRKx=u@VxMpdg+dgkGDF z$jzW5oRN9>EWL|24_;3qai8Lp?i3A!NObK6t@f;el%*W!SDJXmsBYJ>WEJ_SBuTrU zb3fnMTak+n7ICw9r1mD}Rzz-H(|bV&r^m0i4aaHr8U7{P{lrQp*p~l4wsb#~W3qa^ zviV@~%YCOhl>7F+GxNc$e)@WyN~-H1FOg~N2iU)3uW0H4S+6WDCtfhw?$KZ7*Kb-6 zlGiJx5{=`0+2yg{OxzfcordT7?z2GT*Qdk^K5wG7XIi!=7Ts3IX`)an7E17__V7ss z)WuDg^h_dWipOkL>9vHO+wRTFZ)DT^Ue5EB~(IwuCRojSP zJB(XfS=wx(eGQSkLk%Bg3N$0k`Q{Vwjykx+W-cX0(H+cM)x7h#qs mlx-g2wfaK+ZGtzoH1szO&errZ;{Vc;U&pbB0^kp=)}hEcm+V~t 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 0000000000000000000000000000000000000000..362e220d25dd6e9f6354f4ed71a9daf1a180396f GIT binary patch literal 1693 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 literal 1574 zcmV+>2HE)l01)1IoZVQ>ZyQAvAKP(0wzNr0)3hbfZPO3Caa_l^RfUr%#ZDTeLQtJT zXd9W0y-pU{YiqqWNvoo&5+|gJ1LDL5sTUOW#(^V(D;F*(e@BH7LZI?{J8!((kdGpv z9$0BS^WMDodvD&%ynSmSM3;~)sNSQAKKo>^Zrqi_QwmyC_rD;vqX_Z`kxmD5bYC4{p;b!Q^vDUI4qf)IK zu3m2#CCgpUYWK35*0QZi-Kmk5-oj6S)ii32s_CTjx@!VJvuwR>meP8woz?0smxzpB zag3E*y;^r5(5_o$*t6_doIUVFO8O)rxGl$wp-%uHF8wJbKi1DrdUlf?uUPMOy_gw? zW+}P0en&q~vaAFq;l?nZ1R8bgvb&QYF$V8=Mj%+MrqRxjdmPHLANJ4(ekfBZt>Ks@ z%cEAUmDPrfTlODp%Lem554*(P8vM55tXuYqzXQ(^?R5S{#(Sk&oJGj(FAW`6O_Tw>TsKbpzi{Ql7&KmPLb)2}Jcn~;p6&!GJZ zMhxlgHrfRE*Yh*8--YCUe9NE{1Q$gr0`v0_$&8N^?f7O zAu}rZKea@%vy%*hq3CeT@rjO#JYSqq_WtcXoYsk@+PLcS7gmk3>FBw7tzjV&$WF;|@mAsLI|+_cuaNtr9IbJr;gx{;PEI<2+#8KFn;r0WTd4qfHzVxF@ z&i6Qs|64y}gk=xkqYw0Fb*B)D!orn>FL)hlXTsdnv)g-lyScsI*N48uW4hCHO=(P6t+wP3VoX=J8`L2C%GavHBcIfDAv;4t-XUox z>iN}qo737LL-tNFo}y1O7SZIxYdk*(TG@_zwC~GEN5<#%2wCOL1^gqqX%AbjDa>|; zw)io`ekgh@;K$3*3VZVrZ#QUs1S6{LbhIJJlkINa8y&yB><_hLh`1*FS}^!1`W4_& z)vB?+%GbopGQFR9WUrs`!Yn@^)ESZTL?2`xUGKx}OP9AU8P~&%LHb7+YmoJicy<)8 ztMfh~yiul)F-`i%S+<+yUlo3?8fBW|8DaSo>`ynZ=PZxae1gY4><`gn%)5mB4GX_q zC*JSiL|K+%-^aR>pecq2IDV5HyF(nG_o3C}?Xi?0{uJL4;$6Y~ac_TQJdQC2`8v#e zk~zWa?c;SSnUl=_6f#7gV%~5-ZzP~cwntb_&7a-|_YCtV`RtuznR%?9Vi{xJIgU|? z_vaxnX+F*}X95`KI~aW)J<7Z|$2uC=BNe03fPRwoX>N?^zhL>YJmo4c5|ldLHUk% zAl@n9m0#2*^}F}7!GLg`os_CE1(tx@$-N714`kPpTbkLc=!R>_E#Hd7c1*0-rdd^k YN4@0;^)JOUhtIDWwtmn54X9|l;5Mx%ivR!s diff --git a/tests/auto/gui/rhi/qrhi/data/textured.vert.qsb b/tests/auto/gui/rhi/qrhi/data/textured.vert.qsb index 44454d226ead62d5bc16f34c14892076b867bc81..d4ba4747772dcc0f69bd0cd82c66498d1760cb1b 100644 GIT binary patch literal 1368 zcmV-e1*iG|01no8oaI-|Pa8)RpY__tEa5ABHl$%vs>(ExYa=I$WIzziN2D|$j1Uz? z)Yxm-%HBomUCSuqPiWOjRnI;3f_km`A2feJ)pL(k)dS*C_0p#A&Ajoh9UzKS_0q9s zciz|Uy?Og~_L(sjV{$}b4ls|ItjS8ufuGCD#MfTZVC`do9fMw-Szu?u$_8E(1y|^6 zLhiE$;a;Z~X9u8JfmH(TYMl>hA;Jw-V@2@pg8wGaJtSLbi;_kkgm?_yb<44NW+rVIb~|URiSoR@rn57x+H*!YN1Be6PGbPJ2nF&KX&St z2jL)wW0k?RWhbX87{9^iLZ-+%tnP1+3CPfUN=D1y4`H1e1||%_;I6}*2~wb+=XV}2 zO-9FlynpYD-@cpu{>f)A`fW_qxh_jB3-cRb{0=h@PjCzPoB8FHvL+My2_<$G;bMQk zL}FG$olFLK<1jhQB+Pe30m$+36%R_I3A+(G5qfAFA)Oa=Um*D}M61eF_o&uhE#und zdYHO>nK~w(akiOdXJ=;-NLTclTKh@aQb|Z-c7|!zn0BSzAc#J+O0H9PDjrX6PV=>= zg{Qo5R4kh=Uvg^4RuvYRbSlH16jq<*X`cz0k3u*)-TV6`UkmYZt;1q#d1q;TeKU{y zSqH3**I?=JfEH=3g|NUyfK}LTRUYCN)|Ph?8a^Y9JqEo`6HNOE(!TOJ7dV)~eSo?c zkgqnbXJC!ECa$^UAA;%yA86IU5rm5OhfrBcXNBw__QVAp726{uBY!A|A~%#gKF}@~ z>psZ~>wSBxT`$t9N3s1e*{-LD^ya~RVQX>gD;69&DhJ_8y1F_pA8)FE%}{3nj&i`( z&-KVu7kWy-j}vzgvVgvOU^jSQg-T7;y>cr1AdT`qsvKD(-O>R}<)fR)Lmsm;fnMN; zdHq4d^CjA*8v_~Y`wd*jgq=F_q&yD(pibc6Gu>^|1tId|#AhT^rHOdzV}k2pCeM{P zP{YKh*S^KWeW=$N;~Qo29hVp}u?KayB5xIRFy=dihjb6oh_wbbDz=`MjxKl+A&<6V ziIKjdr|2H$bx|(YLh+*B0^tr5?kdIpPAD#JW*Va!U}7c!yE(s zE+%U_B6v}8hUgSij_7XUq1>SOl-!^kXoS~Gcu^6D%t96Q(YMG~FX)Qi}-Vtx7+0ZORz!uZPK|-I2g|) zUwYs1y|6jXhvC^xj?w zDBIQ=?e41g6_s9rw(PdRTAdTvwfeJUhl8MrYtzb1D-R8EHU+`URZ@B67Q)xP;-LxD z-rjv)a%>=Nk*N1XF9!;6t-ub^`E;u%Rfss}`14a47EmeCB3gE;y-P&Nb)6y@1kEBE zxTfd3HrlD?F($lHv40&@3Hv$dpdb8;9W-vO)jdP1I@)H*`EUAV+;N!&pWo1bhV=jG aKSRg=hM*mx9=hZFctsw$&i)_c_;T9mcdLQ` literal 1342 zcmV-E1;P3N01k?HoaI-`Pa8)VpY_@XmWLrc8&a5*sxnRFVnm`yUbb|N>H%@6dTG<|oB77OCO}%L>ZOCm z`@J8(nVlUNV{s-&3}!#`nZ=r{$XxjISc&A?OByU3{j3Hyb!LN)4V1$u)+M%@&;_hP z@vgE-vO};c1JwynMdu?{=-tDr%!I5SS**qX*Dn}ib78)(Gm+A|Jx%; zPCb!wx~6QWSP4oNU#MEXSvjctrHXyHXoT{*T`j$bKbhl2!wBlObHp99YSn9I(c;2u z-Z1LE7ZiPd7?h1?28U6uxTZhXoa0r~_w43;DC919+WyJB@jP^_nzl1@;0AuweU=9e z{`6^3x4d-4EjlyP`}>hVbNr}su;tcm-*z3as}YGtuY|Upw!(NV^fP^^@G$VL^o0bo z+_#!V*Y!$mR@rHlp<#Jo5f(40w93eB3}5%nBMZ3w{ky#AI>0&tsCRe(zhD5rW#|x- zPg9X$M4UPP?71upxC~&DgUA_GTd0Y`krUA~5`F$II;fVVgJ=1H=b#_-Vi>R+5+W;Y zu`eS#=f^2%w2o1;_hv3x>nZ~Jnsp8wyX<;Z^b(bU2wQ7dMO@{11E3Og*p-xoD}{Kw zc1d89RzL|b+L5>pg)o+EIgY`o;}Sh_{2X>X^DlPXFufCee_CC)Hr2)E+Z6vD2F=K- zM=+TEzhI!loDCt+&+>Z@S0>}5-`~CS*)QKLeEaB=7bypUIs?lDJm+A31%h8;=HYbT z0r_@*b*-dHKtCd27cgAn&zHz7XlRqoqHGK%hna@?cI0sW@QOoRqY2#zn;0$HR>bB7 zJ(o!T6Y=VcrJixE{Yx^YU2cca9Z2YyI@a0g>1hn$ieK{``a${9mXOZu6k*m{uiC3) z095bWMbE9fWuK?Fr}@U?!ed@IHcOVrSKMmNt^kpp$z<81!updu_1OvYK_re&&s0jv zwTK*P9f+OPy_L<)?L79g39+_b6HA8$S|(bH#DWxItio<<=V9E!#_C?DhHIp;hhX<{ zC(}Lv*w;SgA`T*W0HMzNo7_RWV@Xnv0H@ng`MS{FPOZLs5ObR z_8ZOByW{flPwlT6>MX!f3dQ=F9-HdGOdYU0I7tTp1Y|DC5R>Oh61X9f(`(<7VIS&s#`;E>e8**sgy=yXt|(gv8?1SP z;vwHnJjU7pA5~j-%SIQnn9#>twIs-1@l*7S$~p+kjYz#{w?uJ=DDFDd{#K+ekjcZsFyL|2Pu|}EioRXd>DV2`WT}4!=m4?w^5=8 ziN8s?ddVL7agw2af@(wkeOQ|q3*UE?{|*vd-yGrCLs%@*o=1KeEJm69W{yxlIqKyN zlIIAwTa^EE@In4fvbjZZu%1c6>;~nTB)dPL>Id>fiQbJa{O=SVIryFY3sw 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();