CVE-2026-19170
Overview
Background
- ANGLE
- Chrome’s translation layer that implements the WebGL/GLES API on top of native GPU drivers (here the PowerVR Android GL backend).
- Layered texture
- a multi-layer texture object such as
GL_TEXTURE_2D_ARRAYwhose individual layers can be bound to a framebuffer viaglFramebufferTextureLayer. - FBO attachment
- a texture layer referenced by a
Framebuffer, which the driver resolves to a concrete backing storage allocation at attach time. - Subject/Observer messaging
- ANGLE’s
SubjectMessagemechanism by which aTexture(subject) notifies an observingFramebufferabout relevant state changes.
Root Cause Analysis
When a layer of a GL_TEXTURE_2D_ARRAY is attached to a framebuffer and the application subsequently calls TexImage3D on that texture with a larger layer count, ANGLE reallocates the underlying texture storage to hold the new dimensions. On PowerVR Android GPUs the driver-side framebuffer attachment continues to reference the old, now-freed storage rather than the newly allocated backing, because the driver does not re-resolve the attachment on a storage reallocation. Because ANGLE previously issued no re-attachment, any subsequent draw or read against the framebuffer targeted freed GPU memory, producing a use-after-free.
The fix detects a layer-count increase inside TextureGL::setImageHelper, emits a new SubjectMessage::TextureLayerCountIncreased before the reallocation, and has Framebuffer::onSubjectStateChange respond by detaching the stale attachment (glFramebufferTextureLayer with texture id 0) and marking the attachment dirty so it is lazily re-attached to the fresh storage on the next bind. This is gated behind the reattachTextureToFboAfterLayerIncrease feature flag so only the affected PowerVR Android configuration pays the extra state-sync cost.
Attack Path
- Create a layered render target
Allocate a
GL_TEXTURE_2D_ARRAYand attach one of its layers to a framebuffer withglFramebufferTextureLayer. - Grow the layer count
Call
TexImage3Don the same texture with a larger depth, forcing ANGLE to free the old storage and allocate new backing. - Leave the stale attachment On the vulnerable driver the framebuffer still points at the freed allocation, which the GPU may recycle for other data.
- Draw or read
Issue a draw call or
readPixelsagainst the framebuffer, causing the GPU to write to or read from freed memory. - Groom for exploitation Reclaim the freed region with attacker-controlled contents to leverage the UAF toward information disclosure or corruption.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
FramebufferGLsrc/libANGLE/renderer/gl/FramebufferGL.h |
modified | |
Texture2DArrayTestES3_ReattachTextureToFbosrc/tests/gl_tests/TextureTest.cpp |
modified |
Files Changed
src/libANGLE/renderer/gl/FramebufferGL.hutil/autogen/angle_features_autogen.hsrc/libANGLE/renderer/gl/renderergl_utils.cppinclude/platform/autogen/FeaturesGL_autogen.hsrc/libANGLE/Texture.cpputil/autogen/angle_features_autogen.cppsrc/libANGLE/renderer/gl/TextureGL.cppsrc/libANGLE/Observer.hsrc/libANGLE/renderer/gl/ContextGL.cppsrc/tests/capture_replay_tests/capture_replay_expectations.txtinclude/platform/gl_features.jsonsrc/libANGLE/renderer/gl/FramebufferGL.cppsrc/tests/gl_tests/TextureTest.cppsrc/libANGLE/Framebuffer.cppsrc/libANGLE/renderer/FramebufferImpl.h
Audit Directions
- Storage reallocation vs. live attachmentsAudit every path that frees or reallocates texture/renderbuffer storage (
TexImage*,TexStorage*, resize, respecify) for framebuffer attachments that may still reference the old allocation. - Driver-specific attachment refreshReview other GL backend workarounds where the native driver caches attachment bindings, ensuring ANGLE re-issues attach calls after any operation that changes the underlying resource identity.
- Subject/Observer coverageCheck that all state changes affecting an FBO attachment (layer count, format, depth) emit a
SubjectMessageobserved byFramebuffer::onSubjectStateChange, so no stale binding survives silently.
Patch
From b8352abce0383b26af32415c1d0f4a6912f665ae Mon Sep 17 00:00:00 2001 From: Zhenyao Mo <[email protected]> Date: Thu, 30 Jul 2026 19:25:57 -0700 Subject: [PATCH] ANGLE: Reattach texture layer to FBO after layer count increase On PowerVR Android GPUs, when a layer of a layered texture (e.g., GL_TEXTURE_2D_ARRAY) is attached to a framebuffer and later TexImage3D is called on the texture to increase its layer count, the framebuffer remains attached to the old memory instead of the newly allocated texture storage. This CL adds the reattachTextureToFboAfterLayerIncrease workaround, enabled on PowerVR Android. When enabled: - When TextureGL::setImageHelper detects a layer count increase on a GL_TEXTURE_2D_ARRAY texture, it sends a TextureLayerCountIncreased subject message before reallocating texture storage via TexImage3D. - When Framebuffer::onSubjectStateChange receives this message, it calls FramebufferGL::onAttachmentLayerCountChange to detach the attachment point on the GL driver (calling glFramebufferTextureLayer with texture id 0) and sets the attachment dirty bit. - Re-attachment happens lazily: when the Framebuffer is next bound and used, ANGLE's normal state synchronization processes the dirty bit and automatically re-attaches the texture layer to the GL framebuffer. - Adds unit tests verifying correct layer re-attachment behavior across single-context and shared multi-context scenarios, instantiated with the workaround enabled and disabled. Bug: chromium:499602793,chromium:537729021 Change-Id: I84899b963d8e8683b5998d281c12263415c709a8 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8162364 Auto-Submit: Zhenyao Mo <[email protected]> Reviewed-by: Geoff Lang <[email protected]> Reviewed-by: Shahbaz Youssefi <[email protected]> Commit-Queue: Shahbaz Youssefi <[email protected]> Commit-Queue: Geoff Lang <[email protected]> --- diff --git a/include/platform/gl_features.json b/include/platform/gl_features.json index 32dffc1..f6cd33d 100644 --- a/include/platform/gl_features.json +++ b/include/platform/gl_features.json @@ -985,6 +985,14 @@ "object on ARM Mali Valhall/Avalon GPUs to avoid driver crash." ], "issue": "http://crbug.com/534468209" + }, + { + "name": "reattach_texture_to_fbo_after_layer_increase", + "category": "Workarounds", + "description": [ + "Reattach texture layer to framebuffer after calling TexImage3D to increase layer count on PowerVR Android." + ], + "issue": "http://crbug.com/499602793" } ] } diff --git a/src/libANGLE/Framebuffer.cpp b/src/libANGLE/Framebuffer.cpp index afa7711..5261ffd 100644 --- a/src/libANGLE/Framebuffer.cpp +++ b/src/libANGLE/Framebuffer.cpp @@ -2327,6 +2327,18 @@ return; } + if (message == angle::SubjectMessage::TextureLayerCountIncreased) + { + FramebufferAttachment *attachment = getAttachmentFromSubjectIndex(index); + if (attachment) + { + (void)mImpl->onAttachmentLayerCountChange(attachment); + } + mDirtyBits.set(index); + onStateChange(angle::SubjectMessage::DirtyBitsFlagged); + return; + } + // This can be triggered by the GL back-end TextureGL class. ASSERT(message == angle::SubjectMessage::DirtyBitsFlagged || message == angle::SubjectMessage::TextureIDDeleted); diff --git a/src/libANGLE/Observer.h b/src/libANGLE/Observer.h index 526f82b..2e0ddea 100644 --- a/src/libANGLE/Observer.h +++ b/src/libANGLE/Observer.h @@ -88,6 +88,9 @@ // Indicates the underlying object storage has been reallocated. ObjectReallocated, + // Indicates a layered texture's layer count has increased. + TextureLayerCountIncreased, + // Indicates a change in foveated rendering state in the subject. FoveatedRenderingStateChanged, }; diff --git a/src/libANGLE/Texture.cpp b/src/libANGLE/Texture.cpp index 3611262..6b05a48 100644 --- a/src/libANGLE/Texture.cpp +++ b/src/libANGLE/Texture.cpp @@ -2749,6 +2749,9 @@ case angle::SubjectMessage::ObjectReallocated: onStateChange(angle::SubjectMessage::ObjectReallocated); break; + case angle::SubjectMessage::TextureLayerCountIncreased: + onStateChange(angle::SubjectMessage::TextureLayerCountIncreased); + break; case angle::SubjectMessage::DirtyBitsFlagged: signalDirtyState(DIRTY_BIT_IMPLEMENTATION); diff --git a/src/libANGLE/renderer/FramebufferImpl.h b/src/libANGLE/renderer/FramebufferImpl.h index dbace39..d7ecc89 100644 --- a/src/libANGLE/renderer/FramebufferImpl.h +++ b/src/libANGLE/renderer/FramebufferImpl.h @@ -105,6 +105,8 @@ virtual angle::Result onLabelUpdate(const gl::Context *context); + virtual angle::Result onAttachmentLayerCountChange(gl::FramebufferAttachment *attachment); + const gl::FramebufferState &getState() const { return mState; } protected: @@ -116,6 +118,12 @@ return false; } +inline angle::Result FramebufferImpl::onAttachmentLayerCountChange( + gl::FramebufferAttachment *attachment) +{ + return angle::Result::Continue; +} + // Default implementation returns the format specified in the attachment. inline const gl::InternalFormat &FramebufferImpl::getImplementationColorReadFormat( const gl::Context *context) const diff --git a/src/libANGLE/renderer/gl/ContextGL.cpp b/src/libANGLE/renderer/gl/ContextGL.cpp index c78f216..fd2c023 100644 --- a/src/libANGLE/renderer/gl/ContextGL.cpp +++ b/src/libANGLE/renderer/gl/ContextGL.cpp @@ -143,7 +143,7 @@ funcs->genFramebuffers(1, &fbo); } - return new FramebufferGL(data, fbo, false); + return new FramebufferGL(data, fbo, false, funcs, getStateManager()); } TextureImpl *ContextGL::createTexture(const gl::TextureState &state) diff --git a/src/libANGLE/renderer/gl/FramebufferGL.cpp b/src/libANGLE/renderer/gl/FramebufferGL.cpp index e24641c..625eefc 100644 --- a/src/libANGLE/renderer/gl/FramebufferGL.cpp +++ b/src/libANGLE/renderer/gl/FramebufferGL.cpp @@ -464,11 +464,17 @@ return textureGL->hasEmulatedAlphaChannel(attachment->getTextureImageIndex()); } -FramebufferGL::FramebufferGL(const gl::FramebufferState &data, GLuint id, bool emulatedAlpha) +FramebufferGL::FramebufferGL(const gl::FramebufferState &data, + GLuint id, + bool emulatedAlpha, + const FunctionsGL *functions, + StateManagerGL *stateManager) : FramebufferImpl(data), mFramebufferID(id), mHasEmulatedAlphaAttachment(emulatedAlpha), - mAppliedEnabledDrawBuffers(1) + mAppliedEnabledDrawBuffers(1), + mFunctions(functions), + mStateManager(stateManager) { ASSERT((isDefault() && id == 0) || !isDefault()); } @@ -1377,6 +1383,15 @@ return blitter->clearFramebuffer(context, colorAttachments, depth, stencil, this); } +angle::Result FramebufferGL::onAttachmentLayerCountChange(gl::FramebufferAttachment *attachment) +{ + ASSERT(!isDefault() && attachment && attachment->isAttached() && + attachment->type() == GL_TEXTURE && mFunctions->framebufferTextureLayer); + mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID); + mFunctions->framebufferTextureLayer(GL_FRAMEBUFFER, attachment->getBinding(), 0, 0, 0); + return angle::Result::Continue; +} + angle::Result FramebufferGL::syncState(const gl::Context *context, GLenum binding, const gl::Framebuffer::DirtyBits &dirtyBits, diff --git a/src/libANGLE/renderer/gl/FramebufferGL.h b/src/libANGLE/renderer/gl/FramebufferGL.h index 18cc840..31c5706 100644 --- a/src/libANGLE/renderer/gl/FramebufferGL.h +++ b/src/libANGLE/renderer/gl/FramebufferGL.h @@ -28,7 +28,11 @@ class FramebufferGL : public FramebufferImpl { public: - FramebufferGL(const gl::FramebufferState &data, GLuint id, bool emulatedAlpha); + FramebufferGL(const gl::FramebufferState &data, + GLuint id, + bool emulatedAlpha, + const FunctionsGL *functions, + StateManagerGL *stateManager); ~FramebufferGL() override; void destroy(const gl::Context *context) override; @@ -84,6 +88,8 @@ // The GL back-end requires a full sync state before we call checkStatus. bool shouldSyncStateBeforeCheckStatus() const override; + angle::Result onAttachmentLayerCountChange(gl::FramebufferAttachment *attachment) override; + gl::FramebufferStatus checkStatus(const gl::Context *context) const override; angle::Result ensureAttachmentsInitialized(const gl::Context *context, @@ -152,6 +158,8 @@ GLuint mFramebufferID; bool mHasEmulatedAlphaAttachment; gl::DrawBufferMask mAppliedEnabledDrawBuffers; + const FunctionsGL *mFunctions; + StateManagerGL *mStateManager; }; bool IsEmulatedAlphaChannelTextureAttachment(const gl::FramebufferAttachment *attachment); diff --git a/src/libANGLE/renderer/gl/TextureGL.cpp b/src/libANGLE/renderer/gl/TextureGL.cpp index 697ff2b..7ece705 100644 --- a/src/libANGLE/renderer/gl/TextureGL.cpp +++ b/src/libANGLE/renderer/gl/TextureGL.cpp @@ -278,6 +278,16 @@ onStateChange(angle::SubjectMessage::ObjectReallocated); } + if (features.reattachTextureToFboAfterLayerIncrease.enabled && + getType() == gl::TextureType::_2DArray) + { + const gl::ImageDesc &desc = mState.getImageDesc(target, level); + if (size.depth > desc.size.depth) + { + onStateChange(angle::SubjectMessage::TextureLayerCountIncreased); + } + } + const gl::InternalFormat &originalInternalFormatInfo = gl::GetInternalFormatInfo(internalFormat, type); nativegl::TexImageFormat texImageFormat = diff --git a/src/libANGLE/renderer/gl/renderergl_utils.cpp b/src/libANGLE/renderer/gl/renderergl_utils.cpp index 732f843..46afe19 100644 --- a/src/libANGLE/renderer/gl/renderergl_utils.cpp +++ b/src/libANGLE/renderer/gl/renderergl_utils.cpp @@ -2788,6 +2788,10 @@ ANGLE_FEATURE_CONDITION(features, avoidComplexExpressionsInStructConstructor, IsPowerVR(vendor)); + // http://crbug.com/499602793 + ANGLE_FEATURE_CONDITION(features, reattachTextureToFboAfterLayerIncrease, + IsPowerVR(vendor) && IsAndroid()); + // Mac Intel drivers are unable to allocate buffers larger than ~1gb ANGLE_FEATURE_CONDITION(features, limitMaxBufferSizeTo1gb, isApple && isIntel); } diff --git a/src/tests/capture_replay_tests/capture_replay_expectations.txt b/src/tests/capture_replay_tests/capture_replay_expectations.txt index 3a6a919..e140de3 100644 --- a/src/tests/capture_replay_tests/capture_replay_expectations.txt +++ b/src/tests/capture_replay_tests/capture_replay_expectations.txt @@ -286,3 +286,6 @@ # Crashes on win and linux trace bots 372059358 : MultisampleTestES3.CopyTexImage2DFromMsaaDefaultFbo/* = SKIP_FOR_CAPTURE + +# Fails on win and linux trace bots +499602793 : Texture2DArrayTestES3_ReattachTextureToFbo.IncreaseLayersWithFramebufferAttachedMultiContext/* = SKIP_FOR_CAPTURE diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp index acc3375..8ebcd4d 100644 --- a/src/tests/gl_tests/TextureTest.cpp +++ b/src/tests/gl_tests/TextureTest.cpp @@ -9298,6 +9298,135 @@ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); } +class Texture2DArrayTestES3_ReattachTextureToFbo : public Texture2DArrayTestES3 +{}; + +// Test increasing layer count of a 2D array texture when one of its layers is attached to a +// framebuffer. http://crbug.com/499602793 +TEST_P(Texture2DArrayTestES3_ReattachTextureToFbo, IncreaseLayersWithFramebufferAttached) +{ + // http://crbug.com/499602793 - Metal backend does not support redefining 2D array texture + // layer count without releasing storage. + ANGLE_SKIP_TEST_IF(IsMetal()); + + glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + std::vector<GLColor> pixelsRed(4 * 4 * 1, GLColor::red); + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, + pixelsRed.data()); + ASSERT_GL_NO_ERROR(); + + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 0); + EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::red); + + // Increase layer count to 2. + std::vector<GLColor> pixelsGreen(4 * 4 * 2, GLColor::green); + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, + pixelsGreen.data()); + ASSERT_GL_NO_ERROR(); + + // Verify layer 0 points to the new memory (green). + EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green); + + // Verify layer 1 is also green. + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 1); + EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green); + + // Clear layer 1 to blue and verify. + glClearColor(0.0f, 0.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::blue); + + // Now sample from layer 0 and layer 1 using a shader to ensure texture memory matches FBO + // memory. + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0); + glUseProgram(mProgram); + glUniform1i(mTextureArrayLocation, 0); + + // Verify layer 0 is green. + glUniform1i(mTextureArraySliceUniformLocation, 0); + drawQuad(mProgram, "position", 0.5f); + EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green); + + // Verify layer 1 is blue. + glUniform1i(mTextureArraySliceUniformLocation, 1); + drawQuad(mProgram, "position", 0.5f); + EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::blue); +} + +// Test increasing layer count of a 2D array texture when one of its layers is attached to a +// framebuffer in one context, and the texture is redefined in another shared context. +// http://crbug.com/499602793 +TEST_P(Texture2DArrayTestES3_ReattachTextureToFbo, + IncreaseLayersWithFramebufferAttachedMultiContext) +{ + // http://crbug.com/499602793 - Metal backend does not support redefining 2D array texture + // layer count without releasing storage. + ANGLE_SKIP_TEST_IF(IsMetal()); + + glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + std::vector<GLColor> pixelsRed(4 * 4 * 1, GLColor::red); + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, + pixelsRed.data()); + ASSERT_GL_NO_ERROR(); + + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 0); + EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::red); + + // Set up and switch to a secondary context sharing resources with the current context. + EGLWindow *window = getEGLWindow(); + EGLDisplay display = window->getDisplay(); + EGLConfig config = window->getConfig(); + EGLSurface surface = window->getSurface(); + EGLint contextAttributes[] = { + EGL_CONTEXT_MAJOR_VERSION_KHR, + GetParam().majorVersion, + EGL_CONTEXT_MINOR_VERSION_KHR, + GetParam().minorVersion, + EGL_NONE, + }; + EGLContext context1 = eglGetCurrentContext(); + EGLContext context2 = eglCreateContext(display, config, context1, contextAttributes); + ASSERT_NE(context2, EGL_NO_CONTEXT); + eglMakeCurrent(display, surface, surface, context2); + + // In the secondary context, bind the texture and increase layer count to 2. + glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture); + std::vector<GLColor> pixelsGreen(4 * 4 * 2, GLColor::green); + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, + pixelsGreen.data()); + ASSERT_GL_NO_ERROR(); + + // Switch back to the primary context. + eglMakeCurrent(display, surface, surface, context1); + + // Verify layer 0 points to the new memory (green). + EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green); + + // Attach layer 1 to the FBO in context1 and verify. Explicitly calling + // glFramebufferTextureLayer attaches to the newly allocated texture storage. + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 1); + EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green); + + // Clear layer 1 to blue and verify. + glClearColor(0.0f, 0.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::blue); + ... (file diff truncated)
Regression Test / PoC
diff --git a/src/tests/capture_replay_tests/capture_replay_expectations.txt b/src/tests/capture_replay_tests/capture_replay_expectations.txt
index 3a6a919..e140de3 100644
--- a/src/tests/capture_replay_tests/capture_replay_expectations.txt
+++ b/src/tests/capture_replay_tests/capture_replay_expectations.txt
@@ -286,3 +286,6 @@
# Crashes on win and linux trace bots
372059358 : MultisampleTestES3.CopyTexImage2DFromMsaaDefaultFbo/* = SKIP_FOR_CAPTURE
+
+# Fails on win and linux trace bots
+499602793 : Texture2DArrayTestES3_ReattachTextureToFbo.IncreaseLayersWithFramebufferAttachedMultiContext/* = SKIP_FOR_CAPTURE
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
index acc3375..8ebcd4d 100644
--- a/src/tests/gl_tests/TextureTest.cpp
+++ b/src/tests/gl_tests/TextureTest.cpp
@@ -9298,6 +9298,135 @@
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}
+class Texture2DArrayTestES3_ReattachTextureToFbo : public Texture2DArrayTestES3
+{};
+
+// Test increasing layer count of a 2D array texture when one of its layers is attached to a
+// framebuffer. http://crbug.com/499602793
+TEST_P(Texture2DArrayTestES3_ReattachTextureToFbo, IncreaseLayersWithFramebufferAttached)
+{
+ // http://crbug.com/499602793 - Metal backend does not support redefining 2D array texture
+ // layer count without releasing storage.
+ ANGLE_SKIP_TEST_IF(IsMetal());
+
+ glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ std::vector<GLColor> pixelsRed(4 * 4 * 1, GLColor::red);
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+ pixelsRed.data());
+ ASSERT_GL_NO_ERROR();
+
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 0);
+ EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::red);
+
+ // Increase layer count to 2.
+ std::vector<GLColor> pixelsGreen(4 * 4 * 2, GLColor::green);
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+ pixelsGreen.data());
+ ASSERT_GL_NO_ERROR();
+
+ // Verify layer 0 points to the new memory (green).
+ EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
+
+ // Verify layer 1 is also green.
+ glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 1);
+ EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
+
+ // Clear layer 1 to blue and verify.
+ glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::blue);
+
+ // Now sample from layer 0 and layer 1 using a shader to ensure texture memory matches FBO
+ // memory.
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0);
+ glUseProgram(mProgram);
+ glUniform1i(mTextureArrayLocation, 0);
+
+ // Verify layer 0 is green.
+ glUniform1i(mTextureArraySliceUniformLocation, 0);
+ drawQuad(mProgram, "position", 0.5f);
+ EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
+
+ // Verify layer 1 is blue.
+ glUniform1i(mTextureArraySliceUniformLocation, 1);
+ drawQuad(mProgram, "position", 0.5f);
+ EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::blue);
+}
+
+// Test increasing layer count of a 2D array texture when one of its layers is attached to a
+// framebuffer in one context, and the texture is redefined in another shared context.
+// http://crbug.com/499602793
+TEST_P(Texture2DArrayTestES3_ReattachTextureToFbo,
+ IncreaseLayersWithFramebufferAttachedMultiContext)
+{
+ // http://crbug.com/499602793 - Metal backend does not support redefining 2D array texture
+ // layer count without releasing storage.
+ ANGLE_SKIP_TEST_IF(IsMetal());
+
+ glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ std::vector<GLColor> pixelsRed(4 * 4 * 1, GLColor::red);
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+ pixelsRed.data());
+ ASSERT_GL_NO_ERROR();
+
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 0);
+ EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::red);
+
+ // Set up and switch to a secondary context sharing resources with the current context.
+ EGLWindow *window = getEGLWindow();
+ EGLDisplay display = window->getDisplay();
+ EGLConfig config = window->getConfig();
+ EGLSurface surface = window->getSurface();
+ EGLint contextAttributes[] = {
+ EGL_CONTEXT_MAJOR_VERSION_KHR,
+ GetParam().majorVersion,
+ EGL_CONTEXT_MINOR_VERSION_KHR,
+ GetParam().minorVersion,
+ EGL_NONE,
+ };
+ EGLContext context1 = eglGetCurrentContext();
+ EGLContext context2 = eglCreateContext(display, config, context1, contextAttributes);
+ ASSERT_NE(context2, EGL_NO_CONTEXT);
+ eglMakeCurrent(display, surface, surface, context2);
+
+ // In the secondary context, bind the texture and increase layer count to 2.
+ glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
+ std::vector<GLColor> pixelsGreen(4 * 4 * 2, GLColor::green);
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+ pixelsGreen.data());
+ ASSERT_GL_NO_ERROR();
+
+ // Switch back to the primary context.
+ eglMakeCurrent(display, surface, surface, context1);
+
+ // Verify layer 0 points to the new memory (green).
+ EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
+
+ // Attach layer 1 to the FBO in context1 and verify. Explicitly calling
+ // glFramebufferTextureLayer attaches to the newly allocated texture storage.
+ glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 1);
+ EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
+
+ // Clear layer 1 to blue and verify.
+ glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::blue);
+
+ // Clean up secondary context.
+ eglDestroyContext(display, context2);
+}
+
// Create a 3D texture, use it, then redefine one level without changing dimensions.
TEST_P(Texture3DTestES3, RedefineLevelData)
{
@@ -23208,6 +23337,16 @@
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Texture2DArrayTestES3);
ANGLE_INSTANTIATE_TEST_ES3(Texture2DArrayTestES3);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Texture2DArrayTestES3_ReattachTextureToFbo);
+ANGLE_INSTANTIATE_TEST_ES3_AND(
+ Texture2DArrayTestES3_ReattachTextureToFbo,
+ ES3_OPENGL().enable(Feature::ReattachTextureToFboAfterLayerIncrease),
+ ES3_OPENGL().disable(Feature::ReattachTextureToFboAfterLayerIncrease),
+ ES3_OPENGLES().enable(Feature::ReattachTextureToFboAfterLayerIncrease),
+ ES3_OPENGLES().disable(Feature::ReattachTextureToFboAfterLayerIncrease),
+ ES3_VULKAN().enable(Feature::ReattachTextureToFboAfterLayerIncrease),
+ ES3_VULKAN().disable(Feature::ReattachTextureToFboAfterLayerIncrease));
+
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TextureSizeTextureArrayTest);
ANGLE_INSTANTIATE_TEST_ES3(TextureSizeTextureArrayTest);