mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[Fix, Test] (MG_Backend/DirectVulkan, MG_IntegrationTest): a sampler uniform array is one binding with many descriptors too - Magma wrote only element zero, and declines the multi-dimensional shape loudly
This commit is contained in:
@@ -1837,9 +1837,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// A descriptor ARRAY occupies one binding with descriptorCount = N, and is
|
// A descriptor ARRAY occupies one binding with descriptorCount = N, and is
|
||||||
// supported for exactly the kinds that have a per-element resolve path in
|
// supported for exactly the kinds that have a per-element resolve path in
|
||||||
// UniformManager::BindProgramUniformBuffers: UBO instance arrays
|
// UniformManager::BindProgramUniformBuffers: UBO instance arrays
|
||||||
// (uniform Block {...} b[N];), storage-block instance arrays, and image
|
// (uniform Block {...} b[N];), storage-block instance arrays, image uniform
|
||||||
// uniform arrays. Anything else must fail program creation cleanly rather
|
// arrays, and combined-image-sampler arrays (uniform sampler2D s[N];).
|
||||||
// than continue with corrupt state.
|
// Anything else - a uniform TEXEL buffer array is the one remaining kind -
|
||||||
|
// must fail program creation cleanly rather than continue with corrupt state.
|
||||||
//
|
//
|
||||||
// Getting listed here is not cosmetic: a kind that is rejected leaves
|
// Getting listed here is not cosmetic: a kind that is rejected leaves
|
||||||
// GetOrCreateProgram's MOBILEGL_ASSERT(remapOk) as the only complaint, and
|
// GetOrCreateProgram's MOBILEGL_ASSERT(remapOk) as the only complaint, and
|
||||||
@@ -1848,12 +1849,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// unification and the set->0 normalisation this function exists to do. A
|
// unification and the set->0 normalisation this function exists to do. A
|
||||||
// program with an image array plus any second descriptor got aliased
|
// program with an image array plus any second descriptor got aliased
|
||||||
// bindings out of that, and a DEBUG build trapped on the same program.
|
// bindings out of that, and a DEBUG build trapped on the same program.
|
||||||
|
// Which is also why the message below is MGLOG_I: MGLOG_E is compiled out
|
||||||
|
// of an INFO build, so a refusal that only said MGLOG_E said nothing at all
|
||||||
|
// in the builds that ship.
|
||||||
const Bool arraySupportedForKind =
|
const Bool arraySupportedForKind =
|
||||||
kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic ||
|
kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic ||
|
||||||
kind == ProgramFactory::DescriptorBindingKind::StorageBuffer ||
|
kind == ProgramFactory::DescriptorBindingKind::StorageBuffer ||
|
||||||
kind == ProgramFactory::DescriptorBindingKind::StorageImage;
|
kind == ProgramFactory::DescriptorBindingKind::StorageImage ||
|
||||||
|
kind == ProgramFactory::DescriptorBindingKind::CombinedImageSampler;
|
||||||
if (binding->count != 1 && !arraySupportedForKind) {
|
if (binding->count != 1 && !arraySupportedForKind) {
|
||||||
MGLOG_E("ProgramFactory: descriptor arrays are unsupported for this descriptor "
|
MGLOG_I("ProgramFactory: descriptor arrays are unsupported for this descriptor "
|
||||||
"kind (name='%s' count=%u type=%d)",
|
"kind (name='%s' count=%u type=%d)",
|
||||||
binding->name ? binding->name : "<null>", binding->count,
|
binding->name ? binding->name : "<null>", binding->count,
|
||||||
static_cast<Int>(binding->descriptor_type));
|
static_cast<Int>(binding->descriptor_type));
|
||||||
@@ -2386,6 +2391,52 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// How many descriptors to declare for an ARRAY of opaque uniforms (samplers, images) at
|
||||||
|
// one binding - or 0, meaning this binding cannot be described and must be declined.
|
||||||
|
//
|
||||||
|
// Two separate things have to hold, and neither is checkable from the SPIR-V alone:
|
||||||
|
//
|
||||||
|
// * the count has to fit a VkDescriptorSetLayoutBinding this device will accept, and fit
|
||||||
|
// the Uint16 it is stored in (65536 would narrow to 0) and the scratch the bind path
|
||||||
|
// reserves from it;
|
||||||
|
// * the frontend reflection has to have RESERVED that many consecutive uniform locations
|
||||||
|
// for this uniform, because the per-element resolve paths address element k as
|
||||||
|
// baseLocation + k. SPIRV-Reflect's `count` is the FLATTENED element count, while GL
|
||||||
|
// locations follow the OUTER dimension only (ProgramObject::GetUniformArraySizeByTIndex
|
||||||
|
// answers TType::getOuterArraySize()). For a one-dimensional array the two agree; for
|
||||||
|
// `uniform sampler2D g[2][3]` SPIR-V says 6 where the reflection reserved 2, and
|
||||||
|
// elements 2..5 would silently resolve onto whichever uniform got the next locations.
|
||||||
|
//
|
||||||
|
// Asking the reflection whether baseLocation and baseLocation + count - 1 are slots of the
|
||||||
|
// SAME uniform tests exactly that precondition, without this code having to model how
|
||||||
|
// glslang chooses to lay an array of arrays out - if a future glslang reserves all six, the
|
||||||
|
// check passes and the per-element paths are right for it too.
|
||||||
|
static Uint32 DescriptorCountForOpaqueUniformArray(const MG_State::GLState::ProgramObject& program,
|
||||||
|
const String& uniformName, Uint32 binding, Int baseLocation,
|
||||||
|
Uint32 reflectedCount, Uint32 maxBindings,
|
||||||
|
const char* kindLabel) {
|
||||||
|
const Uint32 count = std::max<Uint32>(1u, reflectedCount);
|
||||||
|
if (count == 1) {
|
||||||
|
return 1u;
|
||||||
|
}
|
||||||
|
if (count > maxBindings) {
|
||||||
|
MGLOG_I("ProgramFactory::ReflectLayout: %s array '%s' at binding %u has %u elements, past the %u "
|
||||||
|
"this device can describe - declining the program",
|
||||||
|
kindLabel, uniformName.c_str(), binding, count, maxBindings);
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
if (baseLocation < 0 ||
|
||||||
|
!program.UniformLocationsAliasSameUniform(baseLocation, baseLocation + static_cast<Int>(count - 1u))) {
|
||||||
|
MGLOG_I("ProgramFactory::ReflectLayout: %s array '%s' at binding %u spans %u descriptors but the "
|
||||||
|
"reflection reserved fewer uniform locations for it (base=%d) - a multi-dimensional array "
|
||||||
|
"is the usual cause, and MobileGL declines it rather than resolve elements onto a "
|
||||||
|
"neighbouring uniform",
|
||||||
|
kindLabel, uniformName.c_str(), binding, count, baseLocation);
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
void ProgramFactory::ReflectLayout(const MG_State::GLState::ProgramObject& program,
|
void ProgramFactory::ReflectLayout(const MG_State::GLState::ProgramObject& program,
|
||||||
const Vector<Vector<Uint>>& spirv, VkProgramObject& entry) const {
|
const Vector<Vector<Uint>>& spirv, VkProgramObject& entry) const {
|
||||||
// Initialize layout vectors
|
// Initialize layout vectors
|
||||||
@@ -2608,6 +2659,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
const Int location = program.GetUniformLocation(uniformName);
|
const Int location = program.GetUniformLocation(uniformName);
|
||||||
if (location < 0) {
|
if (location < 0) {
|
||||||
|
// A uniform with no location is ordinarily one GL never made active, and
|
||||||
|
// dropping it is routine. An ARRAY reaching here is not routine: it is the
|
||||||
|
// multi-dimensional case. `uniform sampler2D g[2][3]` arrives from
|
||||||
|
// SPIRV-Reflect as one binding of 6 descriptors named "g", while the frontend
|
||||||
|
// reflection keys an array of arrays by its full "[0]"-terminated spelling
|
||||||
|
// ("g[0][0]"), so no base location resolves and the per-element paths have
|
||||||
|
// nothing to count from. Declining is the honest answer - but it has to SAY
|
||||||
|
// so at a level that survives a release build, because dropping the binding
|
||||||
|
// leaves the shader reading a descriptor the layout never declared.
|
||||||
|
if (sampler->count > 1) {
|
||||||
|
MGLOG_I("ProgramFactory::ReflectLayout: declining '%s' at binding %u - a %u-element "
|
||||||
|
"descriptor array with no frontend uniform location (a multi-dimensional array "
|
||||||
|
"of samplers or images is the known cause)",
|
||||||
|
uniformName.c_str(), binding, sampler->count);
|
||||||
|
}
|
||||||
entry.bindingKinds[binding] = DescriptorBindingKind::None;
|
entry.bindingKinds[binding] = DescriptorBindingKind::None;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -2624,15 +2690,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// a storage BLOCK array, whose elements take consecutive GL binding points
|
// a storage BLOCK array, whose elements take consecutive GL binding points
|
||||||
// from the declared one, each element of an image array carries its own
|
// from the declared one, each element of an image array carries its own
|
||||||
// independently assigned image unit - see ResolveStorageImageDescriptor.
|
// independently assigned image unit - see ResolveStorageImageDescriptor.
|
||||||
// Bounds-checked like the UBO array path above: descriptorCount goes
|
// Bounds- and extent-checked like the UBO array path above; see
|
||||||
// straight into a VkDescriptorSetLayoutBinding, and the bind path reserves
|
// DescriptorCountForOpaqueUniformArray for what "declined" costs and why
|
||||||
// scratch from it, so an absurd array size has to be refused here rather
|
// the reflection's reserved extent - not SPIRV-Reflect's flattened count -
|
||||||
// than narrowed into a Uint16 (where 65536 would become 0).
|
// is what the per-element resolve can actually address.
|
||||||
const Uint32 imageArrayCount = std::max<Uint32>(1u, sampler->count);
|
const Uint32 imageArrayCount = DescriptorCountForOpaqueUniformArray(
|
||||||
if (imageArrayCount > m_maxBindings) {
|
program, uniformName, binding, location, sampler->count, m_maxBindings, "image");
|
||||||
MGLOG_E("ProgramFactory::ReflectLayout: image array '%s' at binding %u has %u elements, "
|
if (imageArrayCount == 0) {
|
||||||
"past the %u this device can describe",
|
|
||||||
uniformName.c_str(), binding, imageArrayCount, m_maxBindings);
|
|
||||||
entry.bindingKinds[binding] = DescriptorBindingKind::None;
|
entry.bindingKinds[binding] = DescriptorBindingKind::None;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -2668,6 +2732,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
"ProgramFactory::ReflectLayout: failed to resolve texture target for '%s'",
|
"ProgramFactory::ReflectLayout: failed to resolve texture target for '%s'",
|
||||||
uniformName.c_str());
|
uniformName.c_str());
|
||||||
if (descriptorKind == DescriptorBindingKind::CombinedImageSampler) {
|
if (descriptorKind == DescriptorBindingKind::CombinedImageSampler) {
|
||||||
|
// An ARRAY of sampler uniforms is ONE binding carrying `count` descriptors,
|
||||||
|
// exactly like the image array above, and for the same reason: GLSL 4.20
|
||||||
|
// gives `layout(binding = 1) uniform sampler2D goku[4]` one declaration
|
||||||
|
// spanning texture units 1..4, each element with its own glUniform1i-assigned
|
||||||
|
// unit. Leaving descriptorCount at 1 declared a single-descriptor binding
|
||||||
|
// while the shader indexed descriptors 1..3 of it, and the bind path wrote
|
||||||
|
// only element 0 - so elements 1..N read a descriptor nobody had written
|
||||||
|
// (KHR-GL42.shading_language_420pack.binding_sampler_array; lavapipe faults
|
||||||
|
// inside the JIT-ed shader rather than reporting).
|
||||||
|
const Uint32 samplerArrayCount = DescriptorCountForOpaqueUniformArray(
|
||||||
|
program, uniformName, binding, location, sampler->count, m_maxBindings, "sampler");
|
||||||
|
if (samplerArrayCount == 0) {
|
||||||
|
entry.bindingKinds[binding] = DescriptorBindingKind::None;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
entry.bindingDescriptorCounts[binding] = static_cast<Uint16>(samplerArrayCount);
|
||||||
|
|
||||||
const SamplerNumericDomain numericDomain = UniformTypeToSamplerNumericDomain(uniformType);
|
const SamplerNumericDomain numericDomain = UniformTypeToSamplerNumericDomain(uniformType);
|
||||||
MOBILEGL_ASSERT(numericDomain != SamplerNumericDomain::Unknown,
|
MOBILEGL_ASSERT(numericDomain != SamplerNumericDomain::Unknown,
|
||||||
"ProgramFactory::ReflectLayout: failed to resolve sampler numeric domain "
|
"ProgramFactory::ReflectLayout: failed to resolve sampler numeric domain "
|
||||||
|
|||||||
@@ -68,6 +68,29 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Uniform location of ELEMENT `element` of the opaque-uniform array at `baseLocation`, or
|
||||||
|
// -1 when the reflection did not reserve that element. DoReflection hands out one location
|
||||||
|
// per array element, so the element's location is the base plus its index - bounded by the
|
||||||
|
// array's real extent so a descriptorCount that outran the reflection cannot walk onto the
|
||||||
|
// next uniform. Element 0 is the ordinary non-array case and costs nothing extra.
|
||||||
|
static Int ResolveDescriptorElementLocation(const MG_State::GLState::ProgramObject& program, Int baseLocation,
|
||||||
|
Uint32 element) {
|
||||||
|
if (baseLocation < 0 || element == 0) {
|
||||||
|
return baseLocation;
|
||||||
|
}
|
||||||
|
const Int location = baseLocation + static_cast<Int>(element);
|
||||||
|
return program.UniformLocationsAliasSameUniform(baseLocation, location) ? location : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// descriptorCount this binding declares in the descriptor set layout (1 for everything that
|
||||||
|
// is not an array). Kept in one place because the layout, the scratch reservation and the
|
||||||
|
// per-element write loops must all agree on it.
|
||||||
|
static Uint32 BindingDescriptorCount(const ProgramFactory::VkProgramObject& programObj, Uint32 binding) {
|
||||||
|
return binding < programObj.bindingDescriptorCounts.size()
|
||||||
|
? std::max<Uint32>(1u, programObj.bindingDescriptorCounts[binding])
|
||||||
|
: 1u;
|
||||||
|
}
|
||||||
|
|
||||||
static Int ResolveSamplerUnitIndex(const MG_State::GLState::ProgramObject& program, Int location, Uint32 binding) {
|
static Int ResolveSamplerUnitIndex(const MG_State::GLState::ProgramObject& program, Int location, Uint32 binding) {
|
||||||
MOBILEGL_ASSERT(location >= -1, "ResolveSamplerUnitIndex: invalid sampler location for binding %u", binding);
|
MOBILEGL_ASSERT(location >= -1, "ResolveSamplerUnitIndex: invalid sampler location for binding %u", binding);
|
||||||
if (location < 0) {
|
if (location < 0) {
|
||||||
@@ -268,15 +291,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Bool UniformManager::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer,
|
Bool UniformManager::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer,
|
||||||
const MG_State::GLState::ProgramObject& program,
|
const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj,
|
const ProgramFactory::VkProgramObject& programObj,
|
||||||
Uint32 binding, VkDescriptorImageInfo& outImageInfo,
|
Uint32 binding, Uint32 element,
|
||||||
|
VkDescriptorImageInfo& outImageInfo,
|
||||||
Bool trustUnchangedHint) const {
|
Bool trustUnchangedHint) const {
|
||||||
MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveSamplerDescriptor: texture manager is null");
|
MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveSamplerDescriptor: texture manager is null");
|
||||||
MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptor: sampler manager is null");
|
MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptor: sampler manager is null");
|
||||||
|
// The whole-descriptor memo below is keyed by binding alone, so it describes a binding
|
||||||
|
// that carries exactly one descriptor. An arrayed binding's elements would overwrite
|
||||||
|
// each other in it (see SamplerResolveMemo::info); they re-resolve instead.
|
||||||
|
const Bool descriptorMemoUsable = BindingDescriptorCount(programObj, binding) == 1u;
|
||||||
// The caller proved every input of this binding's resolution unchanged since the
|
// The caller proved every input of this binding's resolution unchanged since the
|
||||||
// last full resolve (which also filled the cache), so the whole chain below -
|
// last full resolve (which also filled the cache), so the whole chain below -
|
||||||
// texture/sampler resolution, completeness probe, sync, layout handling, sampler
|
// texture/sampler resolution, completeness probe, sync, layout handling, sampler
|
||||||
// and view lookups - would recompute the identical descriptor.
|
// and view lookups - would recompute the identical descriptor.
|
||||||
if (trustUnchangedHint && binding < m_samplerResolveMemo.size() &&
|
if (trustUnchangedHint && descriptorMemoUsable && binding < m_samplerResolveMemo.size() &&
|
||||||
m_samplerResolveMemo[binding].infoValid) {
|
m_samplerResolveMemo[binding].infoValid) {
|
||||||
outImageInfo = m_samplerResolveMemo[binding].info;
|
outImageInfo = m_samplerResolveMemo[binding].info;
|
||||||
return true;
|
return true;
|
||||||
@@ -286,9 +314,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// Raw-pointer resolve to skip the SharedPtr atomic refcount churn: the bound texture stays
|
// Raw-pointer resolve to skip the SharedPtr atomic refcount churn: the bound texture stays
|
||||||
// alive through the draw via GL binding state. Only the fallback path needs a SharedPtr to
|
// alive through the draw via GL binding state. Only the fallback path needs a SharedPtr to
|
||||||
// keep the fallback texture alive for the rest of this call.
|
// keep the fallback texture alive for the rest of this call.
|
||||||
MG_State::GLState::ITextureObject* texture = ResolveSamplerTextureRaw(program, programObj, binding);
|
MG_State::GLState::ITextureObject* texture = ResolveSamplerTextureRaw(program, programObj, binding, element);
|
||||||
|
|
||||||
const Int location = programObj.samplerUniformLocationByBinding[binding];
|
// Per ELEMENT: GLSL 4.20 gives every element of `uniform sampler2D goku[4]` its own
|
||||||
|
// texture unit (consecutive from the declared binding, but glUniform1i may scatter them
|
||||||
|
// afterwards), so the unit - and with it the bound texture, the unit's sampler override
|
||||||
|
// and the fallback decision - is the element's, not the binding's.
|
||||||
|
const Int location =
|
||||||
|
ResolveDescriptorElementLocation(program, programObj.samplerUniformLocationByBinding[binding], element);
|
||||||
|
if (location < 0 && element > 0) {
|
||||||
|
MGLOG_I("ResolveSamplerDescriptor: binding %u element %u is past the end of its sampler array", binding,
|
||||||
|
element);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const Int unit = ResolveSamplerUnitIndex(program, location, binding);
|
const Int unit = ResolveSamplerUnitIndex(program, location, binding);
|
||||||
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
|
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
|
||||||
const auto& samplerOverride = textureUnit.GetSamplerObject();
|
const auto& samplerOverride = textureUnit.GetSamplerObject();
|
||||||
@@ -459,7 +497,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (outImageInfo.sampler == VK_NULL_HANDLE) {
|
if (outImageInfo.sampler == VK_NULL_HANDLE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (binding < m_samplerResolveMemo.size()) {
|
// Only for a binding that carries a single descriptor - an array's elements would
|
||||||
|
// publish each other's descriptors here, and the next hinted draw would hand element
|
||||||
|
// N-1's texture to element 0.
|
||||||
|
if (descriptorMemoUsable && binding < m_samplerResolveMemo.size()) {
|
||||||
m_samplerResolveMemo[binding].info = outImageInfo;
|
m_samplerResolveMemo[binding].info = outImageInfo;
|
||||||
m_samplerResolveMemo[binding].infoValid = true;
|
m_samplerResolveMemo[binding].infoValid = true;
|
||||||
NoteSamplerResolveMemoTouched(binding);
|
NoteSamplerResolveMemoTouched(binding);
|
||||||
@@ -505,37 +546,45 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (programObj.bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::CombinedImageSampler) {
|
if (programObj.bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::CombinedImageSampler) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto* texture = ResolveSamplerTextureRaw(program, programObj, binding);
|
// The rewrite this gates is program-wide, so EVERY sampler the program can read has
|
||||||
if (texture == nullptr) return false;
|
// to qualify - including every element of a sampler array, each of which reaches a
|
||||||
const auto& levelRange = texture->GetLevelRange();
|
// different texture through its own unit.
|
||||||
if (levelRange.x() != levelRange.y()) return false;
|
const Uint32 descriptorCount = BindingDescriptorCount(programObj, binding);
|
||||||
|
for (Uint32 element = 0; element < descriptorCount; ++element) {
|
||||||
|
const auto* texture = ResolveSamplerTextureRaw(program, programObj, binding, element);
|
||||||
|
if (texture == nullptr) return false;
|
||||||
|
const auto& levelRange = texture->GetLevelRange();
|
||||||
|
if (levelRange.x() != levelRange.y()) return false;
|
||||||
|
|
||||||
// An explicit-LOD sample is a single filtered tap, so it also gives up anisotropic
|
// An explicit-LOD sample is a single filtered tap, so it also gives up anisotropic
|
||||||
// filtering - which a single-level view can still have. Resolve the sampler exactly
|
// filtering - which a single-level view can still have. Resolve the sampler exactly
|
||||||
// the way ResolveSamplerDescriptor does and bail if anisotropy would apply.
|
// the way ResolveSamplerDescriptor does and bail if anisotropy would apply.
|
||||||
const Int location = programObj.samplerUniformLocationByBinding[binding];
|
const Int location = ResolveDescriptorElementLocation(
|
||||||
const Int unit = ResolveSamplerUnitIndex(program, location, binding);
|
program, programObj.samplerUniformLocationByBinding[binding], element);
|
||||||
const auto& samplerOverride = MG_State::pGLContext->GetTextureUnitObject(unit).GetSamplerObject();
|
if (location < 0 && element > 0) return false;
|
||||||
const auto* effectiveSampler =
|
const Int unit = ResolveSamplerUnitIndex(program, location, binding);
|
||||||
samplerOverride ? samplerOverride.get() : texture->GetSamplerObject().get();
|
const auto& samplerOverride = MG_State::pGLContext->GetTextureUnitObject(unit).GetSamplerObject();
|
||||||
if (effectiveSampler == nullptr) return false;
|
const auto* effectiveSampler =
|
||||||
if (effectiveSampler->GetMaxAnisotropy() > 1.0f &&
|
samplerOverride ? samplerOverride.get() : texture->GetSamplerObject().get();
|
||||||
effectiveSampler->GetMinFilter() == SamplerFilterMode::Linear &&
|
if (effectiveSampler == nullptr) return false;
|
||||||
effectiveSampler->GetMagFilter() == SamplerFilterMode::Linear) {
|
if (effectiveSampler->GetMaxAnisotropy() > 1.0f &&
|
||||||
return false;
|
effectiveSampler->GetMinFilter() == SamplerFilterMode::Linear &&
|
||||||
}
|
effectiveSampler->GetMagFilter() == SamplerFilterMode::Linear) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// An explicit LOD 0 makes lambda exactly 0, which is the magnification side of the
|
// An explicit LOD 0 makes lambda exactly 0, which is the magnification side of the
|
||||||
// min/mag decision. That only matches the implicit form when lambda could not have been
|
// min/mag decision. That only matches the implicit form when lambda could not have been
|
||||||
// positive anyway (the LOD clamp already pins it at or below 0), or when the two
|
// positive anyway (the LOD clamp already pins it at or below 0), or when the two
|
||||||
// filters are the same and the choice cannot be observed.
|
// filters are the same and the choice cannot be observed.
|
||||||
const Float effectiveMaxLod = effectiveSampler->GetMipmapMode() == SamplerMipmapMode::None
|
const Float effectiveMaxLod = effectiveSampler->GetMipmapMode() == SamplerMipmapMode::None
|
||||||
? 0.0f
|
? 0.0f
|
||||||
: effectiveSampler->GetMaxLod();
|
: effectiveSampler->GetMaxLod();
|
||||||
if (effectiveMaxLod > 0.0f && effectiveSampler->GetMinFilter() != effectiveSampler->GetMagFilter()) {
|
if (effectiveMaxLod > 0.0f && effectiveSampler->GetMinFilter() != effectiveSampler->GetMagFilter()) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
sawSampler = true;
|
||||||
}
|
}
|
||||||
sawSampler = true;
|
|
||||||
}
|
}
|
||||||
return sawSampler;
|
return sawSampler;
|
||||||
}
|
}
|
||||||
@@ -568,14 +617,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
MG_State::GLState::ITextureObject* UniformManager::ResolveSamplerTextureRaw(
|
MG_State::GLState::ITextureObject* UniformManager::ResolveSamplerTextureRaw(
|
||||||
const MG_State::GLState::ProgramObject& program, const ProgramFactory::VkProgramObject& programObj,
|
const MG_State::GLState::ProgramObject& program, const ProgramFactory::VkProgramObject& programObj,
|
||||||
Uint32 binding) {
|
Uint32 binding, Uint32 element) {
|
||||||
MOBILEGL_ASSERT(MG_State::pGLContext != nullptr, "ResolveSamplerTextureRaw: GL context is null");
|
MOBILEGL_ASSERT(MG_State::pGLContext != nullptr, "ResolveSamplerTextureRaw: GL context is null");
|
||||||
MOBILEGL_ASSERT(binding < programObj.samplerUniformLocationByBinding.size(),
|
MOBILEGL_ASSERT(binding < programObj.samplerUniformLocationByBinding.size(),
|
||||||
"ResolveSamplerTextureRaw: sampler location binding %u out of range", binding);
|
"ResolveSamplerTextureRaw: sampler location binding %u out of range", binding);
|
||||||
MOBILEGL_ASSERT(binding < programObj.samplerTextureTargetByBinding.size(),
|
MOBILEGL_ASSERT(binding < programObj.samplerTextureTargetByBinding.size(),
|
||||||
"ResolveSamplerTextureRaw: sampler target binding %u out of range", binding);
|
"ResolveSamplerTextureRaw: sampler target binding %u out of range", binding);
|
||||||
|
|
||||||
const Int location = programObj.samplerUniformLocationByBinding[binding];
|
const Int location =
|
||||||
|
ResolveDescriptorElementLocation(program, programObj.samplerUniformLocationByBinding[binding], element);
|
||||||
const Int unit = ResolveSamplerUnitIndex(program, location, binding);
|
const Int unit = ResolveSamplerUnitIndex(program, location, binding);
|
||||||
|
|
||||||
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
|
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
|
||||||
@@ -860,7 +910,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
Bool UniformManager::ResolveSampledBinding(const MG_State::GLState::ProgramObject& program,
|
Bool UniformManager::ResolveSampledBinding(const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj,
|
const ProgramFactory::VkProgramObject& programObj,
|
||||||
Uint32 binding,
|
Uint32 binding, Uint32 element,
|
||||||
MG_State::GLState::ITextureObject*& outTexture,
|
MG_State::GLState::ITextureObject*& outTexture,
|
||||||
const MG_State::GLState::SamplerObject*& outSampler) const {
|
const MG_State::GLState::SamplerObject*& outSampler) const {
|
||||||
// Open-coded ResolveSamplerTextureRaw so the unit is resolved once for both the
|
// Open-coded ResolveSamplerTextureRaw so the unit is resolved once for both the
|
||||||
@@ -871,7 +921,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
"ResolveSampledBinding: sampler location binding %u out of range", binding);
|
"ResolveSampledBinding: sampler location binding %u out of range", binding);
|
||||||
MOBILEGL_ASSERT(binding < programObj.samplerTextureTargetByBinding.size(),
|
MOBILEGL_ASSERT(binding < programObj.samplerTextureTargetByBinding.size(),
|
||||||
"ResolveSampledBinding: sampler target binding %u out of range", binding);
|
"ResolveSampledBinding: sampler target binding %u out of range", binding);
|
||||||
const Int location = programObj.samplerUniformLocationByBinding[binding];
|
const Int location =
|
||||||
|
ResolveDescriptorElementLocation(program, programObj.samplerUniformLocationByBinding[binding], element);
|
||||||
|
if (location < 0 && element > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const Int unit = ResolveSamplerUnitIndex(program, location, binding);
|
const Int unit = ResolveSamplerUnitIndex(program, location, binding);
|
||||||
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
|
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
|
||||||
const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding];
|
const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding];
|
||||||
@@ -915,19 +969,27 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
MG_State::GLState::ITextureObject* texture = nullptr;
|
// Every ELEMENT of a sampler array reaches its own texture through its own unit,
|
||||||
const MG_State::GLState::SamplerObject* sampler = nullptr;
|
// so every element has to be in the sampled set: this walk is what gets those
|
||||||
if (!ResolveSampledBinding(program, programObj, binding, texture, sampler)) {
|
// textures synced and transitioned to a sampled layout BEFORE the render pass
|
||||||
continue;
|
// opens, and a missed element would first be touched by the descriptor resolve
|
||||||
}
|
// inside an active pass.
|
||||||
if (outBindingRecords != nullptr) {
|
const Uint32 descriptorCount = BindingDescriptorCount(programObj, binding);
|
||||||
outBindingRecords->push_back({texture != nullptr ? texture->GetLifetimeId() : 0,
|
for (Uint32 element = 0; element < descriptorCount; ++element) {
|
||||||
sampler != nullptr ? sampler->GetLifetimeId() : 0});
|
MG_State::GLState::ITextureObject* texture = nullptr;
|
||||||
}
|
const MG_State::GLState::SamplerObject* sampler = nullptr;
|
||||||
|
if (!ResolveSampledBinding(program, programObj, binding, element, texture, sampler)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (outBindingRecords != nullptr) {
|
||||||
|
outBindingRecords->push_back({texture != nullptr ? texture->GetLifetimeId() : 0,
|
||||||
|
sampler != nullptr ? sampler->GetLifetimeId() : 0});
|
||||||
|
}
|
||||||
|
|
||||||
auto found = std::find(outTextures.begin(), outTextures.end(), texture);
|
auto found = std::find(outTextures.begin(), outTextures.end(), texture);
|
||||||
if (found == outTextures.end()) {
|
if (found == outTextures.end()) {
|
||||||
outTextures.push_back(texture);
|
outTextures.push_back(texture);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -948,18 +1010,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (programObj.bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::CombinedImageSampler) {
|
if (programObj.bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::CombinedImageSampler) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
MG_State::GLState::ITextureObject* texture = nullptr;
|
// Element-for-element, in the same order CollectSampledTextures recorded them -
|
||||||
const MG_State::GLState::SamplerObject* sampler = nullptr;
|
// the two walks have to visit the identical descriptor sequence or the positional
|
||||||
if (!ResolveSampledBinding(program, programObj, binding, texture, sampler)) {
|
// comparison below drifts.
|
||||||
continue;
|
const Uint32 descriptorCount = BindingDescriptorCount(programObj, binding);
|
||||||
}
|
for (Uint32 element = 0; element < descriptorCount; ++element) {
|
||||||
if (recordIndex >= previousRecords.size()) {
|
MG_State::GLState::ITextureObject* texture = nullptr;
|
||||||
return false;
|
const MG_State::GLState::SamplerObject* sampler = nullptr;
|
||||||
}
|
if (!ResolveSampledBinding(program, programObj, binding, element, texture, sampler)) {
|
||||||
const SampledBindingRecord& record = previousRecords[recordIndex++];
|
continue;
|
||||||
if (record.textureLifetimeId != (texture != nullptr ? texture->GetLifetimeId() : 0) ||
|
}
|
||||||
record.samplerLifetimeId != (sampler != nullptr ? sampler->GetLifetimeId() : 0)) {
|
if (recordIndex >= previousRecords.size()) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
const SampledBindingRecord& record = previousRecords[recordIndex++];
|
||||||
|
if (record.textureLifetimeId != (texture != nullptr ? texture->GetLifetimeId() : 0) ||
|
||||||
|
record.samplerLifetimeId != (sampler != nullptr ? sampler->GetLifetimeId() : 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return recordIndex == previousRecords.size();
|
return recordIndex == previousRecords.size();
|
||||||
@@ -984,26 +1052,40 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Int location = programObj.samplerUniformLocationByBinding[binding];
|
const Int baseLocation = programObj.samplerUniformLocationByBinding[binding];
|
||||||
if (location < 0) {
|
if (baseLocation < 0) {
|
||||||
MGLOG_E("CollectStorageImageTextures: binding %u has no image uniform location", binding);
|
MGLOG_E("CollectStorageImageTextures: binding %u has no image uniform location", binding);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const Int imageUnit = program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
|
// Per ELEMENT, for the same reason the sampled walk above is: an image ARRAY is one
|
||||||
if (imageUnit < 0 || imageUnit >= MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS) {
|
// binding whose elements each carry their own image unit, so each reaches its own
|
||||||
MGLOG_E("CollectStorageImageTextures: image unit %d is invalid for binding %u",
|
// texture. This walk is what puts those textures into the pre-pass sync and layout
|
||||||
imageUnit, binding);
|
// transition; collecting only element 0 left elements 1..N to be first touched by
|
||||||
return false;
|
// the descriptor resolve, which happens with a render pass already open.
|
||||||
}
|
const Uint32 descriptorCount = BindingDescriptorCount(programObj, binding);
|
||||||
|
for (Uint32 element = 0; element < descriptorCount; ++element) {
|
||||||
|
const Int location = ResolveDescriptorElementLocation(program, baseLocation, element);
|
||||||
|
if (location < 0) {
|
||||||
|
MGLOG_E("CollectStorageImageTextures: binding %u element %u is past the end of its image array",
|
||||||
|
binding, element);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const Int imageUnit = program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
|
||||||
|
if (imageUnit < 0 || imageUnit >= MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS) {
|
||||||
|
MGLOG_E("CollectStorageImageTextures: image unit %d is invalid for binding %u element %u",
|
||||||
|
imageUnit, binding, element);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
auto* texture = MG_State::pGLContext->GetImageTextureBinding(imageUnit).Texture.get();
|
auto* texture = MG_State::pGLContext->GetImageTextureBinding(imageUnit).Texture.get();
|
||||||
if (texture == nullptr) {
|
if (texture == nullptr) {
|
||||||
MGLOG_E("CollectStorageImageTextures: image unit %d is unbound for binding %u",
|
MGLOG_E("CollectStorageImageTextures: image unit %d is unbound for binding %u element %u",
|
||||||
imageUnit, binding);
|
imageUnit, binding, element);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (std::find(outTextures.begin(), outTextures.end(), texture) == outTextures.end()) {
|
if (std::find(outTextures.begin(), outTextures.end(), texture) == outTextures.end()) {
|
||||||
outTextures.push_back(texture);
|
outTextures.push_back(texture);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -1437,19 +1519,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
for (const auto& arrayEntry : programObj.arrayedUniformBlockIndicesByBinding) {
|
for (const auto& arrayEntry : programObj.arrayedUniformBlockIndicesByBinding) {
|
||||||
uboArrayExtra += static_cast<Uint32>(arrayEntry.second.size()) - 1u;
|
uboArrayExtra += static_cast<Uint32>(arrayEntry.second.size()) - 1u;
|
||||||
}
|
}
|
||||||
Uint32 ssboArrayExtra = 0;
|
// Surplus descriptors over "one per binding", summed across EVERY arrayed binding
|
||||||
|
// whatever its kind - storage blocks, image arrays and sampler arrays all land here.
|
||||||
|
// One number for all of them because each container below is bounded by the same total.
|
||||||
|
Uint32 arrayDescriptorExtra = 0;
|
||||||
for (const Uint16 count : programObj.bindingDescriptorCounts) {
|
for (const Uint16 count : programObj.bindingDescriptorCounts) {
|
||||||
if (count > 1) ssboArrayExtra += static_cast<Uint32>(count) - 1u;
|
if (count > 1) arrayDescriptorExtra += static_cast<Uint32>(count) - 1u;
|
||||||
}
|
}
|
||||||
writes.reserve(m_maxBindings);
|
writes.reserve(m_maxBindings);
|
||||||
bufferInfos.reserve(m_maxBindings + uboArrayExtra + ssboArrayExtra);
|
bufferInfos.reserve(m_maxBindings + uboArrayExtra + arrayDescriptorExtra);
|
||||||
// ssboArrayExtra sums EVERY arrayed binding's surplus, image arrays included, so it is
|
// Every binding pushes at most descriptorCount image infos, so bindings + surplus is the
|
||||||
// the right worst case for this container too now that a storage-image binding pushes
|
// worst case. Reserving only m_maxBindings here was exact while every binding pushed
|
||||||
// one info per element. Reserving only m_maxBindings here was exact while every binding
|
// exactly one - and reallocates under an image or sampler array, dangling every
|
||||||
// pushed exactly one - and would have let the vector reallocate under an image array,
|
// pImageInfo already recorded in `writes` before vkUpdateDescriptorSets reads them. That
|
||||||
// dangling every pImageInfo already recorded in `writes` (including the sampler
|
// is reachable wherever m_maxBindings is small (it clamps to ~16 on Adreno and Mali),
|
||||||
// branch's &imageInfos.back()) before vkUpdateDescriptorSets reads them.
|
// which is exactly where a 7-element CTS sampler array does not fit the slack.
|
||||||
imageInfos.reserve(m_maxBindings + ssboArrayExtra);
|
imageInfos.reserve(m_maxBindings + arrayDescriptorExtra);
|
||||||
texelBufferViews.reserve(m_maxBindings);
|
texelBufferViews.reserve(m_maxBindings);
|
||||||
dynamicOffsets.reserve(programObj.dynamicBindings.size() + uboArrayExtra);
|
dynamicOffsets.reserve(programObj.dynamicBindings.size() + uboArrayExtra);
|
||||||
|
|
||||||
@@ -1477,10 +1562,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
write.descriptorCount = 1;
|
write.descriptorCount = 1;
|
||||||
|
|
||||||
if (kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic) {
|
if (kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic) {
|
||||||
const Uint32 descriptorCount =
|
const Uint32 descriptorCount = BindingDescriptorCount(programObj, binding);
|
||||||
binding < programObj.bindingDescriptorCounts.size()
|
|
||||||
? std::max<Uint32>(1, programObj.bindingDescriptorCounts[binding])
|
|
||||||
: 1u;
|
|
||||||
dynamicUboDescriptorCount += descriptorCount;
|
dynamicUboDescriptorCount += descriptorCount;
|
||||||
fastRebindUboBinding = binding;
|
fastRebindUboBinding = binding;
|
||||||
const SizeT firstBufferInfoIndex = bufferInfos.size();
|
const SizeT firstBufferInfoIndex = bufferInfos.size();
|
||||||
@@ -1523,10 +1605,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// One write per binding, but `descriptorCount` buffer infos: a GLSL block
|
// One write per binding, but `descriptorCount` buffer infos: a GLSL block
|
||||||
// instance array occupies a single binding whose elements each come from their
|
// instance array occupies a single binding whose elements each come from their
|
||||||
// own GL binding point.
|
// own GL binding point.
|
||||||
const Uint32 descriptorCount =
|
const Uint32 descriptorCount = BindingDescriptorCount(programObj, binding);
|
||||||
binding < programObj.bindingDescriptorCounts.size()
|
|
||||||
? std::max<Uint32>(1, programObj.bindingDescriptorCounts[binding])
|
|
||||||
: 1u;
|
|
||||||
const SizeT firstBufferInfoIndex = bufferInfos.size();
|
const SizeT firstBufferInfoIndex = bufferInfos.size();
|
||||||
for (Uint32 element = 0; element < descriptorCount; ++element) {
|
for (Uint32 element = 0; element < descriptorCount; ++element) {
|
||||||
VkDescriptorBufferInfo bufferInfo{};
|
VkDescriptorBufferInfo bufferInfo{};
|
||||||
@@ -1552,10 +1631,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// never written at all, and a shader that indexes them reads an undefined
|
// never written at all, and a shader that indexes them reads an undefined
|
||||||
// descriptor (lavapipe faults inside the shader; a real driver is free to do
|
// descriptor (lavapipe faults inside the shader; a real driver is free to do
|
||||||
// anything).
|
// anything).
|
||||||
const Uint32 descriptorCount =
|
const Uint32 descriptorCount = BindingDescriptorCount(programObj, binding);
|
||||||
binding < programObj.bindingDescriptorCounts.size()
|
|
||||||
? std::max<Uint32>(1, programObj.bindingDescriptorCounts[binding])
|
|
||||||
: 1u;
|
|
||||||
const SizeT firstImageInfoIndex = imageInfos.size();
|
const SizeT firstImageInfoIndex = imageInfos.size();
|
||||||
for (Uint32 element = 0; element < descriptorCount; ++element) {
|
for (Uint32 element = 0; element < descriptorCount; ++element) {
|
||||||
VkDescriptorImageInfo imageInfo{};
|
VkDescriptorImageInfo imageInfo{};
|
||||||
@@ -1575,32 +1651,60 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
write.pImageInfo = &imageInfos[firstImageInfoIndex];
|
write.pImageInfo = &imageInfos[firstImageInfoIndex];
|
||||||
writes.push_back(write);
|
writes.push_back(write);
|
||||||
} else {
|
} else {
|
||||||
VkDescriptorImageInfo imageInfo{};
|
// One write per binding, but `descriptorCount` image infos: a sampler ARRAY is a
|
||||||
Bool hasImage = false;
|
// single binding whose elements each carry their own texture unit. Writing only
|
||||||
if (samplerBindingOverride != nullptr &&
|
// element 0 - which is all this used to do - left elements 1..N never written,
|
||||||
samplerBindingOverride->binding == binding &&
|
// so a shader indexing them sampled a descriptor nobody had filled in
|
||||||
samplerBindingOverride->texture != nullptr &&
|
// (KHR-GL42.shading_language_420pack.binding_sampler_array).
|
||||||
samplerBindingOverride->sampler != nullptr) {
|
const Uint32 descriptorCount = BindingDescriptorCount(programObj, binding);
|
||||||
hasImage = ResolveSamplerDescriptorOverride(*samplerBindingOverride, imageInfo);
|
// Overrides come only from MobileGL's own blit and depth-mipmap programs, whose
|
||||||
} else {
|
// samplers are scalars; the override replaces THE descriptor at its binding, so
|
||||||
hasImage = ResolveSamplerDescriptor(commandBuffer, program, programObj, binding, imageInfo,
|
// there is no element for it to mean on an arrayed one.
|
||||||
samplerDescriptorsUnchangedHint);
|
const Bool overrideThisBinding = samplerBindingOverride != nullptr &&
|
||||||
|
samplerBindingOverride->binding == binding &&
|
||||||
|
samplerBindingOverride->texture != nullptr &&
|
||||||
|
samplerBindingOverride->sampler != nullptr;
|
||||||
|
MOBILEGL_ASSERT(
|
||||||
|
!overrideThisBinding || descriptorCount == 1,
|
||||||
|
"BindProgramUniformBuffers: sampler override targets arrayed binding %u (%u descriptors)",
|
||||||
|
binding, descriptorCount);
|
||||||
|
const SizeT firstImageInfoIndex = imageInfos.size();
|
||||||
|
for (Uint32 element = 0; element < descriptorCount; ++element) {
|
||||||
|
VkDescriptorImageInfo imageInfo{};
|
||||||
|
Bool hasImage = false;
|
||||||
|
if (overrideThisBinding && element == 0) {
|
||||||
|
hasImage = ResolveSamplerDescriptorOverride(*samplerBindingOverride, imageInfo);
|
||||||
|
} else {
|
||||||
|
hasImage = ResolveSamplerDescriptor(commandBuffer, program, programObj, binding, element,
|
||||||
|
imageInfo, samplerDescriptorsUnchangedHint);
|
||||||
|
}
|
||||||
|
if (!hasImage) {
|
||||||
|
MGLOG_E(
|
||||||
|
"UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u element %u "
|
||||||
|
"has no valid texture descriptor",
|
||||||
|
binding, element);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (imageInfo.sampler == VK_NULL_HANDLE || imageInfo.imageView == VK_NULL_HANDLE) {
|
||||||
|
MGLOG_E(
|
||||||
|
"UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u element %u "
|
||||||
|
"has null sampler or imageView",
|
||||||
|
binding, element);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
imageInfos.push_back(imageInfo);
|
||||||
}
|
}
|
||||||
if (!hasImage) {
|
if (descriptorCount > 1) {
|
||||||
MGLOG_E(
|
// The dynamic-offset-only rebind replays a whole descriptor set on the
|
||||||
"UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u has no valid texture descriptor",
|
// strength of the sampler hint alone, and its eligibility probe was written
|
||||||
binding);
|
// for bindings that carry one descriptor each. An arrayed sampler binding
|
||||||
return false;
|
// also bypasses the per-binding descriptor memo, so there is nothing for it
|
||||||
|
// to win here either.
|
||||||
|
fastRebindKindsEligible = false;
|
||||||
}
|
}
|
||||||
if (imageInfo.sampler == VK_NULL_HANDLE || imageInfo.imageView == VK_NULL_HANDLE) {
|
|
||||||
MGLOG_E(
|
|
||||||
"UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u has null sampler or imageView",
|
|
||||||
binding);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
imageInfos.push_back(imageInfo);
|
|
||||||
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||||
write.pImageInfo = &imageInfos.back();
|
write.descriptorCount = descriptorCount;
|
||||||
|
write.pImageInfo = &imageInfos[firstImageInfoIndex];
|
||||||
writes.push_back(write);
|
writes.push_back(write);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,10 +53,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// caches - a live layout's entry must never be purged (its sets would be
|
// caches - a live layout's entry must never be purged (its sets would be
|
||||||
// unreachable pool slots), so there is deliberately no age-based sweep here.
|
// unreachable pool slots), so there is deliberately no age-based sweep here.
|
||||||
void OnDescriptorSetLayoutDestroyed(VkDescriptorSetLayout descriptorSetLayout);
|
void OnDescriptorSetLayoutDestroyed(VkDescriptorSetLayout descriptorSetLayout);
|
||||||
// One record per visited CombinedImageSampler binding (post fallback substitution,
|
// One record per visited CombinedImageSampler DESCRIPTOR (post fallback substitution,
|
||||||
// in binding order): the resolved texture and effective sampler, as never-reused
|
// in binding order, and within a binding in array-element order): the resolved texture
|
||||||
// lifetime ids so a freed-and-reallocated object at the same heap address can only
|
// and effective sampler, as never-reused lifetime ids so a freed-and-reallocated object
|
||||||
// MISS a comparison, never false-hit it (same ABA rule as SamplerResolveMemo).
|
// at the same heap address can only MISS a comparison, never false-hit it (same ABA
|
||||||
|
// rule as SamplerResolveMemo). An arrayed binding contributes one record per element -
|
||||||
|
// element granularity is required, or swapping the textures of two elements of the same
|
||||||
|
// array would leave the record list identical and the fast path would keep a stale set.
|
||||||
struct SampledBindingRecord {
|
struct SampledBindingRecord {
|
||||||
Uint64 textureLifetimeId = 0;
|
Uint64 textureLifetimeId = 0;
|
||||||
Uint64 samplerLifetimeId = 0;
|
Uint64 samplerLifetimeId = 0;
|
||||||
@@ -143,8 +146,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// texture after the fallback substitution (may still be null when no fallback
|
// texture after the fallback substitution (may still be null when no fallback
|
||||||
// exists), effective sampler = unit override else the texture's own sampler.
|
// exists), effective sampler = unit override else the texture's own sampler.
|
||||||
// False = the binding is skipped (unbound with a non-2D fallback target).
|
// False = the binding is skipped (unbound with a non-2D fallback target).
|
||||||
|
// `element` indexes a sampler array inside the binding; see ResolveSamplerDescriptor.
|
||||||
Bool ResolveSampledBinding(const MG_State::GLState::ProgramObject& program,
|
Bool ResolveSampledBinding(const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 element,
|
||||||
MG_State::GLState::ITextureObject*& outTexture,
|
MG_State::GLState::ITextureObject*& outTexture,
|
||||||
const MG_State::GLState::SamplerObject*& outSampler) const;
|
const MG_State::GLState::SamplerObject*& outSampler) const;
|
||||||
// Raw-pointer variant for the per-draw sampled-texture walk (CollectSampledTextures):
|
// Raw-pointer variant for the per-draw sampled-texture walk (CollectSampledTextures):
|
||||||
@@ -152,14 +156,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// only need the pointer skip the SharedPtr copy's atomic refcount churn.
|
// only need the pointer skip the SharedPtr copy's atomic refcount churn.
|
||||||
static MG_State::GLState::ITextureObject* ResolveSamplerTextureRaw(
|
static MG_State::GLState::ITextureObject* ResolveSamplerTextureRaw(
|
||||||
const MG_State::GLState::ProgramObject& program,
|
const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding);
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 element);
|
||||||
SharedPtr<MG_State::GLState::ITextureObject> GetFallbackTexture(TextureTarget target) const;
|
SharedPtr<MG_State::GLState::ITextureObject> GetFallbackTexture(TextureTarget target) const;
|
||||||
|
// `element` indexes a sampler ARRAY inside one binding; each element carries its own
|
||||||
|
// independently assigned GL texture unit, so it selects the texture, the sampler
|
||||||
|
// override and the fallback separately from its neighbours.
|
||||||
|
//
|
||||||
// trustUnchangedHint: reuse this binding's cached VkDescriptorImageInfo outright
|
// trustUnchangedHint: reuse this binding's cached VkDescriptorImageInfo outright
|
||||||
// (see BindProgramUniformBuffers' samplerDescriptorsUnchangedHint for the proof
|
// (see BindProgramUniformBuffers' samplerDescriptorsUnchangedHint for the proof
|
||||||
// obligations the caller carries).
|
// obligations the caller carries). The cache is keyed by binding alone, so it is
|
||||||
|
// used ONLY for single-descriptor bindings - see m_samplerResolveMemo.
|
||||||
Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program,
|
Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||||
VkDescriptorImageInfo& outImageInfo,
|
Uint32 element, VkDescriptorImageInfo& outImageInfo,
|
||||||
Bool trustUnchangedHint = false) const;
|
Bool trustUnchangedHint = false) const;
|
||||||
Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride,
|
Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride,
|
||||||
VkDescriptorImageInfo& outImageInfo) const;
|
VkDescriptorImageInfo& outImageInfo) const;
|
||||||
@@ -346,6 +355,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// proves every resolve input unchanged; cleared with the per-frame reset
|
// proves every resolve input unchanged; cleared with the per-frame reset
|
||||||
// (the cached VkSampler outlives a frame only via a fresh resolve, which
|
// (the cached VkSampler outlives a frame only via a fresh resolve, which
|
||||||
// also re-stamps it against VkSamplerManager's frame-boundary sweep).
|
// also re-stamps it against VkSamplerManager's frame-boundary sweep).
|
||||||
|
//
|
||||||
|
// This one field is keyed by binding but describes ONE descriptor, so it is
|
||||||
|
// written and read only for single-descriptor bindings. A sampler ARRAY's
|
||||||
|
// elements share the binding and would overwrite each other here - the last
|
||||||
|
// element resolved would then be handed to element 0 on the next hinted draw.
|
||||||
|
// Every other field above is self-validating (each compares its full key
|
||||||
|
// before reuse, and the view-format entry is a pure function of format and
|
||||||
|
// numeric domain), so an arrayed binding may keep using those.
|
||||||
VkDescriptorImageInfo info{};
|
VkDescriptorImageInfo info{};
|
||||||
Bool infoValid = false;
|
Bool infoValid = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -233,19 +233,6 @@ void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); }
|
|||||||
return image.At(gl.Width() / 2, gl.Height() / 2);
|
return image.At(gl.Width() / 2, gl.Height() / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Magma turns a sampler array into ONE descriptor with descriptorCount = N, and
|
|
||||||
// ProgramFactory::ReflectLayout refuses any descriptor array that is not a
|
|
||||||
// dynamic UBO (MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp - "descriptor
|
|
||||||
// arrays are unsupported for this descriptor kind"), so program creation fails
|
|
||||||
// and the draw samples descriptors that were never written. On a hardware driver
|
|
||||||
// that reads back as wrong pixels; under lavapipe it is a segfault in the
|
|
||||||
// rasterizer thread. Supporting it means carrying an element dimension through
|
|
||||||
// UniformManager's per-binding location tables, which is a feature, not a fix -
|
|
||||||
// so this case is SCOPED rather than disabled, because the frontend half it also
|
|
||||||
// covers (the seeded units) is real on both backends and is asserted below
|
|
||||||
// before the draw.
|
|
||||||
bool SamplerArrayDescriptorsAreSupported() const { return Gl().BackendName() != "DirectVulkan"; }
|
|
||||||
|
|
||||||
// Same shape, different gap: with the compile fixed, this shader now links on
|
// Same shape, different gap: with the compile fixed, this shader now links on
|
||||||
// both backends but paints nothing on Magma - the atomic counter becomes a
|
// both backends but paints nothing on Magma - the atomic counter becomes a
|
||||||
// buffer descriptor there and that half is not wired up yet (the conformance
|
// buffer descriptor there and that half is not wired up yet (the conformance
|
||||||
@@ -300,11 +287,6 @@ void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); }
|
|||||||
}
|
}
|
||||||
glUseProgram(0);
|
glUseProgram(0);
|
||||||
|
|
||||||
if (!SamplerArrayDescriptorsAreSupported()) {
|
|
||||||
GTEST_SKIP() << "sampler descriptor arrays are unimplemented on " << Gl().BackendName()
|
|
||||||
<< "; the seeded units above are the half of this case it can answer";
|
|
||||||
}
|
|
||||||
|
|
||||||
const Rgba8 centre = DrawAndRead(program);
|
const Rgba8 centre = DrawAndRead(program);
|
||||||
EXPECT_EQ(FirstGLError(), 0u);
|
EXPECT_EQ(FirstGLError(), 0u);
|
||||||
EXPECT_EQ(centre.r, 0) << "sampler array elements that read the wrong texture: " << BadElements(centre.r);
|
EXPECT_EQ(centre.r, 0) << "sampler array elements that read the wrong texture: " << BadElements(centre.r);
|
||||||
|
|||||||
Reference in New Issue
Block a user