mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (Pipe): bind incomplete image units as no image
This commit is contained in:
@@ -72,6 +72,36 @@ namespace MobileGL::MG_Pipe {
|
||||
}
|
||||
}
|
||||
|
||||
// GL 4.6 core 8.26: an image access is INVALID - loads return zero, stores are dropped - when
|
||||
// the bound texture is incomplete or the bound level lies outside [level_base, q]. Image
|
||||
// units ignore the sampler, so "complete" here is the level's own: the base level needs
|
||||
// base-level completeness and any other level a complete mip chain up to it, the reading
|
||||
// the conformance suite applies (a mutable texture with only level 0 defined, bound at
|
||||
// level 2). Such a binding travels as an EMPTY unit, which both backends already answer
|
||||
// with "no image" - an ES driver would otherwise happily address the undefined level.
|
||||
inline Bool MGPipeImageBindingIsAccessible(const MG_State::GLState::ImageTextureBinding& binding) {
|
||||
using namespace MG_State::GLState;
|
||||
const ITextureObject* texture = binding.Texture.get();
|
||||
if (texture == nullptr) return false;
|
||||
const TextureObjectMipmap* mipmapTexture = AsMipmapTexture(texture);
|
||||
// Buffer and multisample storage have exactly one level and no level range to leave.
|
||||
if (mipmapTexture == nullptr) return true;
|
||||
if (binding.Level < 0) return false;
|
||||
const Uint level = static_cast<Uint>(binding.Level);
|
||||
const UintVec2& range = texture->GetLevelRange();
|
||||
if (level < range.x() || level > range.y()) return false;
|
||||
if (texture->IsImmutable() || texture->IsTextureView()) {
|
||||
// Every level of immutable storage exists, so the range against the storage is all.
|
||||
return level < texture->GetAddressableLevelCount();
|
||||
}
|
||||
if (level >= mipmapTexture->GetMipmapLevelCount()) return false;
|
||||
for (const auto uploadTarget : texture->GetUploadTargets()) {
|
||||
const IntVec3 size = mipmapTexture->GetMipmapTexelSize(uploadTarget, level);
|
||||
if (size.x() <= 0 || size.y() <= 0 || size.z() <= 0) return false;
|
||||
}
|
||||
return IsMipmapCompleteForFilter(texture, /*mipmapped=*/level != range.x());
|
||||
}
|
||||
|
||||
// D-G3. Over the tail with Start and Count mixed in, the same shape the two sampler sets
|
||||
// use - and it covers InternalFormat and Access because those are live glBindImageTexture
|
||||
// state that the format-less image bake keys on, not decoration.
|
||||
@@ -128,9 +158,9 @@ namespace MobileGL::MG_Pipe {
|
||||
MGPImageView& entry = m_entries[unit];
|
||||
entry = MGPImageView{};
|
||||
entry.Unit = unit;
|
||||
entry.Res = binding.Texture ? MGPipeSlots().Acquire(MGPipeKind::Texture,
|
||||
binding.Texture->GetLifetimeId())
|
||||
: kMGPipeNullHandle;
|
||||
entry.Res = MGPipeImageBindingIsAccessible(binding)
|
||||
? MGPipeSlots().Acquire(MGPipeKind::Texture, binding.Texture->GetLifetimeId())
|
||||
: kMGPipeNullHandle;
|
||||
// D-A4: a texture named in an emitted MGPImageView is SHADER-IMAGE-bound from
|
||||
// then on - the bit ImageBindableHint is derived from. The bind itself noted it
|
||||
// first (TextureState.h, so the hint precedes the first sync); this is the
|
||||
|
||||
@@ -607,6 +607,34 @@ void main() { imageStore(img, ivec2(0, 0), vec4(1.0)); }
|
||||
GL::BindImageTexture(0, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8);
|
||||
}
|
||||
|
||||
// GL 4.6 core 8.26: a level the texture does not have as a complete image - here a mutable
|
||||
// texture with only level 0 defined and the default MAX_LEVEL, bound at level 2 - is an
|
||||
// invalid access, so the unit travels EMPTY and the backend binds no image (loads read zero,
|
||||
// stores are dropped). The same texture bound at its base level stays a real binding.
|
||||
TEST(ImageEmit, AnIncompleteLevelTravelsAsAnEmptyUnit) {
|
||||
EmitterScope scope;
|
||||
const GLuint program = MakeComputeProgram(kImageCompute);
|
||||
GL::UseProgram(program);
|
||||
GLuint name = 0;
|
||||
GL::GenTextures(1, &name);
|
||||
GL::BindTexture(GL_TEXTURE_2D, name);
|
||||
GL::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
|
||||
GL::BindImageTexture(1, name, 2, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA8);
|
||||
ASSERT_TRUE(Ctx().GetImageTextureBinding(1).Texture != nullptr) << "the bind itself is legal";
|
||||
Emitter().EmitShaderImages(Ctx());
|
||||
ASSERT_GE(Emitter().Window(), 2u);
|
||||
EXPECT_TRUE(MGPipeHandleIsNull(Emitter().LastImageViews()[1].Res))
|
||||
<< "an undefined, mip-incomplete level must not reach the backend as an image";
|
||||
|
||||
GL::BindImageTexture(1, name, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA8);
|
||||
Emitter().EmitShaderImages(Ctx());
|
||||
EXPECT_FALSE(MGPipeHandleIsNull(Emitter().LastImageViews()[1].Res))
|
||||
<< "the base level is base-complete and must stay bound";
|
||||
GL::UseProgram(0);
|
||||
GL::BindImageTexture(1, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8);
|
||||
}
|
||||
|
||||
// P5d round 3, package C: glBindImageTexture IS THE PRODUCER OF THE IMAGE-UNIT HIGH-WATER
|
||||
// MARK, and this case is the reason a reader may bound a walk with it. The split client's
|
||||
// per-draw GPU-write sweep (MG_Remote/Client/GpuWritePending.cpp) stops at the mark instead
|
||||
|
||||
Reference in New Issue
Block a user