diff --git a/src/tests/gl_tests/PbufferTest.cpp b/src/tests/gl_tests/PbufferTest.cpp --- a/src/tests/gl_tests/PbufferTest.cpp +++ b/src/tests/gl_tests/PbufferTest.cpp @@ -1249,8 +1249,68 @@ eglDestroySurface(display, pbufferSurface); } +// ES3 fixture so that GL_TEXTURE_BASE_LEVEL is accepted by validation. +class PbufferBaseLevelTestES3 : public PbufferTest +{}; + +// Regression test: setting GL_TEXTURE_BASE_LEVEL > 0 on a pbuffer-bound +// (non-immutable) GL_TEXTURE_2D must not pass an out-of-range mip NSRange to +// -[MTLTexture newTextureViewWithPixelFormat:textureType:levels:slices:]. +// +// Preconditions on the Metal backend: +// - eglBindTexImage -> TextureMtl::bindTexImage sets mBoundSurface and a +// 1-mip mNativeTextureStorage with baseGLLevel=0. +// - The texture is not immutable, so getEffectiveBaseLevel() == mBaseLevel. +// - onBaseMaxLevelsChanged takes the isImmutableOrPBuffer() short path and +// calls createViewFromBaseToMaxLevel() without rebuilding storage. +// +// With baseLevel=5 and a 1-mip storage (getMaxSupportedGLLevel()==0), +// createViewFromBaseToMaxLevel() computes maxLevel=max(min(0,..),5)=5, +// baseToMaxLevels=1, then calls createMipsView(5,1) -> +// [parent newTextureView... levels:NSMakeRange(5,1)] on a parent with +// mipmapLevelCount==1. With Metal API validation enabled (MTL_DEBUG_LAYER=1) +// this asserts; in release it is driver-level UB. +TEST_P(PbufferBaseLevelTestES3, BaseLevelGreaterThanPbufferMipCount) +{ + ANGLE_SKIP_TEST_IF(!mSupportsPbuffers || !mSupportsBindTexImage); + ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3); + + EGLWindow *window = getEGLWindow(); + + GLTexture texture; + glBindTexture(GL_TEXTURE_2D, texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + EXPECT_GL_NO_ERROR(); + + // Bind the 1-mip pbuffer surface to the texture (non-immutable). + EXPECT_EGL_TRUE(eglBindTexImage(window->getDisplay(), mPbuffer, EGL_BACK_BUFFER)); + glViewport(0, 0, getWindowWidth(), getWindowHeight()); + EXPECT_GL_NO_ERROR(); + + // Trigger TextureMtl::onBaseMaxLevelsChanged -> createViewFromBaseToMaxLevel + // with EffectiveBaseLevel(5) > getMaxSupportedGLLevel(0). + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 5); + EXPECT_GL_NO_ERROR(); + + // Exercise the resulting view: sample it from a fragment shader so that the + // (potentially out-of-range) MTLTexture view is bound to a render encoder. + glUseProgram(mTextureProgram); + glUniform1i(mTextureUniformLocation, 0); + drawQuad(mTextureProgram, "position", 0.5f); + glFinish(); + EXPECT_GL_NO_ERROR(); + + // Restore and release. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + eglReleaseTexImage(window->getDisplay(), mPbuffer, EGL_BACK_BUFFER); +} + ANGLE_INSTANTIATE_TEST_ES2(PbufferTest); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PbufferBaseLevelTestES3); +ANGLE_INSTANTIATE_TEST_ES3(PbufferBaseLevelTestES3); + GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PbufferColorspaceTest); ANGLE_INSTANTIATE_TEST_ES3_AND(PbufferColorspaceTest, ES3_VULKAN().enable(Feature::EglColorspaceAttributePassthrough));