Chrome · ANGLE › D3D11
CVE-2026-113285
Memory Corruption in ANGLE (D3D11)
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
src/tests/gl_tests/CopyTexImageTest.cppsrc/libANGLE/renderer/d3d/TextureD3D.cppsrc/libANGLE/renderer/d3d/TextureD3D.h
Patch
From b5bab166e6a7c94bd545babe61c3d9b82c56de3a Mon Sep 17 00:00:00 2001 From: Ran Wang <[email protected]> Date: Wed, 23 Sep 2026 20:42:06 -0400 Subject: [PATCH] D3D11: Sync image from storage before CPU CopyTexSubImage Check getBaseLevel() in canCreateRenderTargetForImage and sync clean staging images from mTexStorage via getImageAndSyncFromStorageIfNeeded before copyFromFramebuffer so partial copies preserve existing storage contents. Test: angle_end2end_tests --gtest_filter="*CopyTexImageTestES3.*Preserves**" Bug: b/563355257 Change-Id: I3ad952bbd73af45e6938e43c9e1e31ce8d67437b Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8452764 Reviewed-by: Shahbaz Youssefi <[email protected]> Reviewed-by: Yuxin Hu <[email protected]> Commit-Queue: Ran Wang <[email protected]> --- diff --git a/src/libANGLE/renderer/d3d/TextureD3D.cpp b/src/libANGLE/renderer/d3d/TextureD3D.cpp index ba6813e..4496850 100644 --- a/src/libANGLE/renderer/d3d/TextureD3D.cpp +++ b/src/libANGLE/renderer/d3d/TextureD3D.cpp @@ -155,7 +155,7 @@ } ImageD3D *destImage = nullptr; - ANGLE_TRY(getImageAndSyncFromStorage(context, destIndex, &destImage)); + ANGLE_TRY(getImageAndSyncFromStorageIfNeeded(context, destIndex, &destImage)); const gl::Box sourceBox(0, 0, 0, clippedArea.width, clippedArea.height, 1); ANGLE_TRY(mRenderer->copyImage(context, destImage, sourceCopy.get(), sourceBox, destOffset, @@ -184,13 +184,18 @@ return angle::Result::Continue; } -angle::Result TextureD3D::getImageAndSyncFromStorage(const gl::Context *context, - const gl::ImageIndex &index, - ImageD3D **outImage) +angle::Result TextureD3D::getImageAndSyncFromStorageIfNeeded(const gl::Context *context, + const gl::ImageIndex &index, + ImageD3D **outImage) { ImageD3D *image = getImage(index); - if (mTexStorage && mTexStorage->isRenderTarget() && isValidIndex(index) && - isImageComplete(index)) + // Syncing from mTexStorage is needed only when mTexStorage exists/is complete and + // !image->isDirty(). In TextureD3D, !image->isDirty() means mTexStorage holds the latest + // authoritative data (e.g. from FBO rendering/clears or direct TextureStorage11::setData + // uploads) while the CPU staging buffer does not. Conversely, when image->isDirty() is true, + // the CPU staging image already holds newer uncommitted data, so we must not overwrite it from + // mTexStorage (and we defer flushing staging to mTexStorage until commitRegion/updateStorage). + if (mTexStorage && isValidIndex(index) && isImageComplete(index) && !image->isDirty()) { ANGLE_TRY(image->copyFromTexStorage(context, index, mTexStorage)); image->markClean(); @@ -870,10 +875,42 @@ ImageD3D *image = getImage(index); ASSERT(image); - bool levelsComplete = (isImageComplete(index) && isImageComplete(getImageIndex(0, 0))); + bool levelsComplete = + (isImageComplete(index) && isImageComplete(getImageIndex(getBaseLevel(), 0))); return (image->isRenderableFormat() && levelsComplete); } +angle::Result TextureD3D::copyImageFromFramebufferToStaging(const gl::Context *context, + const gl::ImageIndex &index, + const gl::Offset &destOffset, + const gl::Rectangle &clippedArea, + gl::Framebuffer *source) +{ + ASSERT(!canCreateRenderTargetForImage(index)); + // canCreateRenderTargetForImage(index) checks isRenderableFormat(), isImageComplete(index), + // and base-level completeness. Because all formats valid for CopyTexImage/CopyTexSubImage are + // renderable and isImageComplete(index) implies the base level is also complete, + // !canCreateRenderTargetForImage(index) means !isImageComplete(index). + // + // Since mTexStorage only stores mip-complete levels (and updateStorage() skips incomplete + // levels), mTexStorage never holds newer data for an incomplete index, so no sync from + // mTexStorage to the staging image is needed. + // + // onStateChange(DirtyBitsFlagged) is not needed here: CopyTexImage already signals + // SubjectChanged via signalDirtyStorage(), and for CopyTexSubImage an incomplete level cannot + // be uploaded to mTexStorage or sampled until a subsequent level redefinition or texture + // parameter change (e.g. GL_TEXTURE_BASE_LEVEL) makes it complete, which itself signals + // SubjectChanged. + ASSERT(!isImageComplete(index)); + + ImageD3D *image = getImage(index); + ASSERT(image); + gl::Offset stagingOffset(destOffset.x, destOffset.y, 0); + ANGLE_TRY(image->copyFromFramebuffer(context, stagingOffset, clippedArea, source)); + mDirtyImages = true; + return angle::Result::Continue; +} + angle::Result TextureD3D::commitRegion(const gl::Context *context, const gl::ImageIndex &index, const gl::Box ®ion) @@ -1414,9 +1451,8 @@ if (!canCreateRenderTargetForImage(index)) { - ANGLE_TRY(mImageArray[index.getLevelIndex()]->copyFromFramebuffer(context, destOffset, - clippedArea, source)); - mDirtyImages = true; + ANGLE_TRY( + copyImageFromFramebufferToStaging(context, index, destOffset, clippedArea, source)); } else { @@ -1452,16 +1488,14 @@ const gl::Offset clippedOffset(destOffset.x + clippedArea.x - sourceArea.x, destOffset.y + clippedArea.y - sourceArea.y, 0); - // can only make our texture storage to a render target if level 0 is defined (with a width & - // height) and the current level we're copying to is defined (with appropriate format, width & - // height) + // can only make our texture storage to a render target if the base level is defined (with a + // width & height) and the current level we're copying to is defined (with appropriate format, + // width & height) if (!canCreateRenderTargetForImage(index)) { - ANGLE_TRY(mImageArray[index.getLevelIndex()]->copyFromFramebuffer(context, clippedOffset, - clippedArea, source)); - mDirtyImages = true; - onStateChange(angle::SubjectMessage::DirtyBitsFlagged); + ANGLE_TRY( + copyImageFromFramebufferToStaging(context, index, clippedOffset, clippedArea, source)); } else { @@ -1522,10 +1556,11 @@ gl::ImageIndex sourceImageIndex = gl::ImageIndex::Make2D(sourceLevel.get()); TextureD3D *sourceD3D = GetImplAs<TextureD3D>(source); ImageD3D *sourceImage = nullptr; - ANGLE_TRY(sourceD3D->getImageAndSyncFromStorage(context, sourceImageIndex, &sourceImage)); + ANGLE_TRY( + sourceD3D->getImageAndSyncFromStorageIfNeeded(context, sourceImageIndex, &sourceImage)); ImageD3D *destImage = nullptr; - ANGLE_TRY(getImageAndSyncFromStorage(context, index, &destImage)); + ANGLE_TRY(getImageAndSyncFromStorageIfNeeded(context, index, &destImage)); ANGLE_TRY(mRenderer->copyImage(context, destImage, sourceImage, sourceBox, destOffset, unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha)); @@ -1570,10 +1605,11 @@ gl::ImageIndex sourceImageIndex = gl::ImageIndex::Make2D(sourceLevel.get()); TextureD3D *sourceD3D = GetImplAs<TextureD3D>(source); ImageD3D *sourceImage = nullptr; - ANGLE_TRY(sourceD3D->getImageAndSyncFromStorage(context, sourceImageIndex, &sourceImage)); + ANGLE_TRY( + sourceD3D->getImageAndSyncFromStorageIfNeeded(context, sourceImageIndex, &sourceImage)); ImageD3D *destImage = nullptr; - ANGLE_TRY(getImageAndSyncFromStorage(context, index, &destImage)); + ANGLE_TRY(getImageAndSyncFromStorageIfNeeded(context, index, &destImage)); ANGLE_TRY(mRenderer->copyImage(context, destImage, sourceImage, sourceBox, destOffset, unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha)); @@ -2140,10 +2176,8 @@ if (!canCreateRenderTargetForImage(index)) { - ANGLE_TRY(mImageArray[faceIndex][index.getLevelIndex()]->copyFromFramebuffer( - context, destOffset, clippedArea, source)); - mDirtyImages = true; - onStateChange(angle::SubjectMessage::DirtyBitsFlagged); + ANGLE_TRY( + copyImageFromFramebufferToStaging(context, index, destOffset, clippedArea, source)); } else { @@ -2182,10 +2216,8 @@ if (!canCreateRenderTargetForImage(index)) { - ANGLE_TRY(mImageArray[faceIndex][index.getLevelIndex()]->copyFromFramebuffer( - context, clippedOffset, clippedArea, source)); - mDirtyImages = true; - onStateChange(angle::SubjectMessage::DirtyBitsFlagged); + ANGLE_TRY( + copyImageFromFramebufferToStaging(context, index, clippedOffset, clippedArea, source)); } else { @@ -2245,10 +2277,11 @@ gl::ImageIndex sourceImageIndex = gl::ImageIndex::Make2D(sourceLevel.get()); TextureD3D *sourceD3D = GetImplAs<TextureD3D>(source); ImageD3D *sourceImage = nullptr; - ANGLE_TRY(sourceD3D->getImageAndSyncFromStorage(context, sourceImageIndex, &sourceImage)); + ANGLE_TRY( + sourceD3D->getImageAndSyncFromStorageIfNeeded(context, sourceImageIndex, &sourceImage)); ImageD3D *destImage = nullptr; - ANGLE_TRY(getImageAndSyncFromStorage(context, index, &destImage)); + ANGLE_TRY(getImageAndSyncFromStorageIfNeeded(context, index, &destImage)); ANGLE_TRY(mRenderer->copyImage(context, destImage, sourceImage, sourceBox, destOffset, unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha)); @@ -2295,10 +2328,11 @@ gl::ImageIndex sourceImageIndex = gl::ImageIndex::Make2D(sourceLevel.get()); TextureD3D *sourceD3D = GetImplAs<TextureD3D>(source); ImageD3D *sourceImage = nullptr; - ANGLE_TRY(sourceD3D->getImageAndSyncFromStorage(context, sourceImageIndex, &sourceImage)); + ANGLE_TRY( + sourceD3D->getImageAndSyncFromStorageIfNeeded(context, sourceImageIndex, &sourceImage)); ImageD3D *destImage = nullptr; - ANGLE_TRY(getImageAndSyncFromStorage(context, index, &destImage)); + ANGLE_TRY(getImageAndSyncFromStorageIfNeeded(context, index, &destImage)); ANGLE_TRY(mRenderer->copyImage(context, destImage, sourceImage, sourceBox, destOffset, unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha)); @@ -2877,18 +2911,14 @@ // date before the copy and then copy back to the storage afterwards if needed. // TODO: Investigate 3D blits in D3D11. - bool syncTexStorage = mTexStorage && isLevelComplete(index.getLevelIndex()); - if (syncTexStorage) - { - ANGLE_TRY( - mImageArray[index.getLevelIndex()]->copyFromTexStorage(context, index, mTexStorage)); - } - ANGLE_TRY(mImageArray[index.getLevelIndex()]->copyFromFramebuffer(context, clippedDestOffset, - clippedSourceArea, source)); + gl::ImageIndex levelIndex = gl::ImageIndex::Make3D(index.getLevelIndex()); + ImageD3D *image = nullptr; + ANGLE_TRY(getImageAndSyncFromStorageIfNeeded(context, levelIndex, &image)); + ANGLE_TRY(image->copyFromFramebuffer(context, clippedDestOffset, clippedSourceArea, source)); mDirtyImages = true; onStateChange(angle::SubjectMessage::DirtyBitsFlagged); - if (syncTexStorage) + if (mTexStorage && isLevelComplete(index.getLevelIndex())) { ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex())); } @@ -2944,8 +2974,9 @@ ImageD3D *destImage = nullptr; TextureD3D *sourceD3D = GetImplAs<TextureD3D>(source); - ANGLE_TRY(getImageAndSyncFromStorage(context, destIndex, &destImage)); - ANGLE_TRY(sourceD3D->getImageAndSyncFromStorage(context, sourceIndex, &sourceImage)); + ANGLE_TRY(getImageAndSyncFromStorageIfNeeded(context, destIndex, &destImage)); + ANGLE_TRY( + sourceD3D->getImageAndSyncFromStorageIfNeeded(context, sourceIndex, &sourceImage)); ANGLE_TRY(mRenderer->copyImage(context, destImage, sourceImage, sourceBox, destOffset, unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha)); @@ -2991,10 +3022,11 @@ gl::ImageIndex sourceImageIndex = gl::ImageIndex::Make3D(sourceLevel.get()); TextureD3D *sourceD3D = GetImplAs<TextureD3D>(source); ImageD3D *sourceImage = nullptr; - ANGLE_TRY(sourceD3D->getImageAndSyncFromStorage(context, sourceImageIndex, &sourceImage)); + ANGLE_TRY( + sourceD3D->getImageAndSyncFromStorageIfNeeded(context, sourceImageIndex, &sourceImage)); ImageD3D *destImage = nullptr; - ANGLE_TRY(getImageAndSyncFromStorage(context, destIndex, &destImage)); + ANGLE_TRY(getImageAndSyncFromStorageIfNeeded(context, destIndex, &destImage)); ANGLE_TRY(mRenderer->copyImage(context, destImage, sourceImage, sourceBox, destOffset, unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha)); @@ -3540,11 +3572,8 @@ if (!canCreateRenderTargetForImage(index)) { - gl::Offset destLayerOffset(clippedDestOffset.x, clippedDestOffset.y, 0); - ANGLE_TRY(mImageArray[index.getLevelIndex()][clippedDestOffset.z]->copyFromFramebuffer( - context, destLayerOffset, clippedSourceArea, source)); - mDirtyImages = true; - onStateChange(angle::SubjectMessage::DirtyBitsFlagged); + ANGLE_TRY(copyImageFromFramebufferToStaging(context, index, clippedDestOffset, + clippedSourceArea, source)); } else { @@ -3618,9 +3647,10 @@ ImageD3D *destImage = nullptr; TextureD3D *sourceD3D = GetImplAs<TextureD3D>(source); - ANGLE_TRY(getImageAndSyncFromStorage(context, currentDestDepthIndex, &destImage)); - ANGLE_TRY(sourceD3D->getImageAndSyncFromStorage(context, currentSourceDepthIndex, - &sourceImage)); + ANGLE_TRY( + getImageAndSyncFromStorageIfNeeded(context, currentDestDepthIndex, &destImage)); + ANGLE_TRY(sourceD3D->getImageAndSyncFromStorageIfNeeded( + context, currentSourceDepthIndex, &sourceImage)); gl::Box imageBox(sourceBox.x, sourceBox.y, 0, sourceBox.width, sourceBox.height, 1); ANGLE_TRY(mRenderer->copyImage(context, destImage, sourceImage, imageBox, destOffset, unpackFlipY, unpackPremultiplyAlpha, @@ -3680,11 +3710,11 @@ TextureD3D *sourceD3D = GetImplAs<TextureD3D>(source); ImageD3D *sourceImage = nullptr; - ANGLE_TRY( - sourceD3D->getImageAndSyncFromStorage(context, currentSourceIndex, &sourceImage)); + ANGLE_TRY(sourceD3D->getImageAndSyncFromStorageIfNeeded(context, currentSourceIndex, + &sourceImage)); ImageD3D *destImage = nullptr; - ANGLE_TRY(getImageAndSyncFromStorage(context, currentDestIndex, &destImage)); + ANGLE_TRY(getImageAndSyncFromStorageIfNeeded(context, currentDestIndex, &destImage)); ANGLE_TRY(mRenderer->copyImage(context, destImage, sourceImage, currentLayerBox, destOffset, unpackFlipY, unpackPremultiplyAlpha, diff --git a/src/libANGLE/renderer/d3d/TextureD3D.h b/src/libANGLE/renderer/d3d/TextureD3D.h index 2ef651b..c95f12e 100644 --- a/src/libANGLE/renderer/d3d/TextureD3D.h +++ b/src/libANGLE/renderer/d3d/TextureD3D.h @@ -48,9 +48,12 @@ virtual ImageD3D *getImage(const gl::ImageIndex &index) const = 0; virtual GLsizei getLayerCount(int level) const = 0; - angle::Result getImageAndSyncFromStorage(const gl::Context *context, - const gl::ImageIndex &index, - ImageD3D **outImage); + // Returns the ImageD3D for |index|, syncing its CPU staging buffer from |mTexStorage| if + // |mTexStorage| holds newer data (i.e. when |mTexStorage| is valid/complete and the image does + // not already have uncommitted CPU-side modifications). + angle::Result getImageAndSyncFromStorageIfNeeded(const gl::Context *context, + const gl::ImageIndex &index, + ImageD3D **outImage); GLint getBaseLevelWidth() const; GLint getBaseLevelHeight() const; @@ -183,6 +186,11 @@ virtual bool isImageComplete(const gl::ImageIndex &index) const = 0; bool canCreateRenderTargetForImage(const gl::ImageIndex &index) const; + angle::Result copyImageFromFramebufferToStaging(const gl::Context *context, + const gl::ImageIndex &index, + const gl::Offset &destOffset, + const gl::Rectangle &clippedArea, + gl::Framebuffer *source); angle::Result ensureBindFlags(const gl::Context *context, BindFlags bindFlags); angle::Result ensureRenderTarget(const gl::Context *context); diff --git a/src/tests/gl_tests/CopyTexImageTest.cpp b/src/tests/gl_tests/CopyTexImageTest.cpp index 8a89b3e..bfe6b9e 100644 --- a/src/tests/gl_tests/CopyTexImageTest.cpp +++ b/src/tests/gl_tests/CopyTexImageTest.cpp @@ -1459,7 +1459,8 @@ kTexSize / 2); ASSERT_GL_NO_ERROR(); - // Verify it. + // Verify both regions. + verifyResults(dstColor, kExpected.data(), kTexSize, 0, 0, kTexSize / 2, kTexSize / 2, 1.0); verifyResults(dstColor, kExpected.data(), kTexSize, kTexSize / 2, kTexSize / 2, kTexSize, kTexSize, 1.0); } @@ -2142,6 +2143,222 @@ EXPECT_GL_NO_ERROR(); } +// Verify that partial glCopyTexSubImage2D into a cleared non-zero base level (with level 0 +// undefined) preserves the rest of the cleared level. +TEST_P(CopyTexImageTestES3, CopyTexSubImagePreservesClearedNonZeroBaseLevel2D) +{ + constexpr GLsizei kSize = 64; + + GLTexture dstTex; + glBindTexture(GL_TEXTURE_2D, dstTex); + glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + + GLFramebuffer dstFbo; + glBindFramebuffer(GL_FRAMEBUFFER, dstFbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dstTex, 1); + ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + GLRenderbuffer srcRbo; + glBindRenderbuffer(GL_RENDERBUFFER, srcRbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 4, 4); + GLFramebuffer srcFbo; + glBindFramebuffer(GL_FRAMEBUFFER, srcFbo); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, srcRbo); + ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); + glClearColor(1.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo); + glCopyTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, 0, 0, 1, 1); + ASSERT_GL_NO_ERROR(); + + glClearColor(0.0f, 0.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glCopyTexSubImage2D(GL_TEXTURE_2D, 1, 1, 0, 0, 0, 1, 1); + ASSERT_GL_NO_ERROR(); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, dstFbo); + EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); + EXPECT_PIXEL_COLOR_EQ(1, 0, GLColor::blue); ... (file diff truncated)
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/src/tests/gl_tests/CopyTexImageTest.cpp b/src/tests/gl_tests/CopyTexImageTest.cpp
index 8a89b3e..bfe6b9e 100644
--- a/src/tests/gl_tests/CopyTexImageTest.cpp
+++ b/src/tests/gl_tests/CopyTexImageTest.cpp
@@ -1459,7 +1459,8 @@
kTexSize / 2);
ASSERT_GL_NO_ERROR();
- // Verify it.
+ // Verify both regions.
+ verifyResults(dstColor, kExpected.data(), kTexSize, 0, 0, kTexSize / 2, kTexSize / 2, 1.0);
verifyResults(dstColor, kExpected.data(), kTexSize, kTexSize / 2, kTexSize / 2, kTexSize,
kTexSize, 1.0);
}
@@ -2142,6 +2143,222 @@
EXPECT_GL_NO_ERROR();
}
+// Verify that partial glCopyTexSubImage2D into a cleared non-zero base level (with level 0
+// undefined) preserves the rest of the cleared level.
+TEST_P(CopyTexImageTestES3, CopyTexSubImagePreservesClearedNonZeroBaseLevel2D)
+{
+ constexpr GLsizei kSize = 64;
+
+ GLTexture dstTex;
+ glBindTexture(GL_TEXTURE_2D, dstTex);
+ glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
+
+ GLFramebuffer dstFbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, dstFbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dstTex, 1);
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+ glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ GLRenderbuffer srcRbo;
+ glBindRenderbuffer(GL_RENDERBUFFER, srcRbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 4, 4);
+ GLFramebuffer srcFbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, srcFbo);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, srcRbo);
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+ glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo);
+ glCopyTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, 0, 0, 1, 1);
+ ASSERT_GL_NO_ERROR();
+
+ glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glCopyTexSubImage2D(GL_TEXTURE_2D, 1, 1, 0, 0, 0, 1, 1);
+ ASSERT_GL_NO_ERROR();
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, dstFbo);
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
+ EXPECT_PIXEL_COLOR_EQ(1, 0, GLColor::blue);
+ EXPECT_PIXEL_RECT_EQ(2, 0, kSize - 2, 1, GLColor::green);
+ EXPECT_PIXEL_RECT_EQ(0, 1, kSize, kSize - 1, GLColor::green);
+}
+
+// Verify that partial glCopyTexSubImage2D into a cleared non-zero base level of a cubemap face
+// (with level 0 undefined) preserves the rest of the cleared face.
+TEST_P(CopyTexImageTestES3, CopyTexSubImagePreservesClearedNonZeroBaseLevelCube)
+{
+ constexpr GLsizei kSize = 64;
+
+ GLTexture dstTex;
+ glBindTexture(GL_TEXTURE_CUBE_MAP, dstTex);
+ for (GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X; face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
+ ++face)
+ {
+ glTexImage2D(face, 1, GL_RGBA8, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ }
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 1);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, 1);
+
+ GLFramebuffer dstFbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, dstFbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+ dstTex, 1);
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+ glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ GLRenderbuffer srcRbo;
+ glBindRenderbuffer(GL_RENDERBUFFER, srcRbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 4, 4);
+ GLFramebuffer srcFbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, srcFbo);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, srcRbo);
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+ glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo);
+ glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 1, 0, 0, 0, 0, 1, 1);
+ ASSERT_GL_NO_ERROR();
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, dstFbo);
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
+ EXPECT_PIXEL_RECT_EQ(1, 0, kSize - 1, 1, GLColor::green);
+ EXPECT_PIXEL_RECT_EQ(0, 1, kSize, kSize - 1, GLColor::green);
+}
+
+// Verify that partial glCopyTexSubImage3D into a cleared non-zero base level of a 2D array texture
+// (with level 0 undefined) preserves the rest of the cleared layer.
+TEST_P(CopyTexImageTestES3, CopyTexSubImagePreservesClearedNonZeroBaseLevel2DArray)
+{
+ constexpr GLsizei kSize = 64;
+
+ GLTexture dstTex;
+ glBindTexture(GL_TEXTURE_2D_ARRAY, dstTex);
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, kSize, kSize, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+ nullptr);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BASE_LEVEL, 1);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 1);
+
+ GLFramebuffer dstFbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, dstFbo);
+ glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, dstTex, 1, 0);
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+ glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ GLRenderbuffer srcRbo;
+ glBindRenderbuffer(GL_RENDERBUFFER, srcRbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 4, 4);
+ GLFramebuffer srcFbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, srcFbo);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, srcRbo);
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+ glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo);
+ glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, 0, 0, 1, 1);
+ ASSERT_GL_NO_ERROR();
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, dstFbo);
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
+ EXPECT_PIXEL_RECT_EQ(1, 0, kSize - 1, 1, GLColor::green);
+ EXPECT_PIXEL_RECT_EQ(0, 1, kSize, kSize - 1, GLColor::green);
+}
+
+// Verify that partial glCopyTexSubImage3D into a cleared 3D texture (which always uses the CPU
+// fallback via getImageAndSyncFromStorageIfNeeded in TextureD3D_3D::copySubImage) preserves the
+// cleared slice across multiple sub-region copies.
+TEST_P(CopyTexImageTestES3, CopyTexSubImagePreservesCleared3D)
+{
+ constexpr GLsizei kSize = 16;
+
+ GLTexture dstTex;
+ glBindTexture(GL_TEXTURE_3D, dstTex);
+ glTexStorage3D(GL_TEXTURE_3D, 1, GL_RGBA8, kSize, kSize, 2);
+
+ GLFramebuffer dstFbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, dstFbo);
+ glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, dstTex, 0, 0);
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+ glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ GLRenderbuffer srcRbo;
+ glBindRenderbuffer(GL_RENDERBUFFER, srcRbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 4, 4);
+ GLFramebuffer srcFbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, srcFbo);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, srcRbo);
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+ glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 0, 1, 1);
+ ASSERT_GL_NO_ERROR();
+
+ glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 1, 0, 0, 0, 0, 1, 1);
+ ASSERT_GL_NO_ERROR();
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, dstFbo);
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
+ EXPECT_PIXEL_COLOR_EQ(1, 0, GLColor::blue);
+ EXPECT_PIXEL_RECT_EQ(2, 0, kSize - 2, 1, GLColor::green);
+ EXPECT_PIXEL_RECT_EQ(0, 1, kSize, kSize - 1, GLColor::green);
+}
+
+// Verify that partial glCopySubTextureCHROMIUM into a GL_SRGB8_ALPHA8 texture (which forces the
+// CPU fallback in TextureD3D_2D::copySubTexture even at base level 0) preserves storage data
+// uploaded via setData when the storage is not a render target.
+TEST_P(CopyTexImageTestES3, CopySubTexturePreservesStorageDataSRGB)
+{
+ ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_CHROMIUM_copy_texture"));
+
+ constexpr GLsizei kSize = 16;
+ std::vector<GLColor> greenData(kSize * kSize, GLColor::green);
+ std::vector<GLColor> redData(4 * 4, GLColor::red);
+ std::vector<GLColor> blueData(4 * 4, GLColor::blue);
+
+ GLTexture dstTex;
+ glBindTexture(GL_TEXTURE_2D, dstTex);
+ glTexStorage2D(GL_TEXTURE_2D, 1, GL_SRGB8_ALPHA8, kSize, kSize);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kSize, kSize, GL_RGBA, GL_UNSIGNED_BYTE,
+ greenData.data());
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ GLTexture srcTex;
+ glBindTexture(GL_TEXTURE_2D, srcTex);
+ glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, redData.data());
+
+ glCopySubTextureCHROMIUM(srcTex, 0, GL_TEXTURE_2D, dstTex, 0, 0, 0, 0, 0, 1, 1, GL_FALSE,
+ GL_FALSE, GL_FALSE);
+ ASSERT_GL_NO_ERROR();
+
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, blueData.data());
+ glCopySubTextureCHROMIUM(srcTex, 0, GL_TEXTURE_2D, dstTex, 0, 1, 0, 0, 0, 1, 1, GL_FALSE,
+ GL_FALSE, GL_FALSE);
+ ASSERT_GL_NO_ERROR();
+
+ glBindTexture(GL_TEXTURE_2D, dstTex);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glViewport(0, 0, kSize, kSize);
+ draw2DTexturedQuad(0.5f, 1.0f, true);
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
+ EXPECT_PIXEL_COLOR_EQ(1, 0, GLColor::blue);
+ EXPECT_PIXEL_RECT_EQ(2, 0, kSize - 2, 1, GLColor::green);
+ EXPECT_PIXEL_RECT_EQ(0, 1, kSize, kSize - 1, GLColor::green);
+}
+
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND(
CopyTexImageTest,
ES2_D3D11_PRESENT_PATH_FAST(),
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page