diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc index 3ab35d34303cd..9abf48f76ce0d 100644 --- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc +++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc @@ -625,6 +625,112 @@ TEST_P(GLES2DecoderTest, CopyTexImage2DGLError) { texture->GetLevelSize(GL_TEXTURE_2D, level, &width, &height, nullptr)); } +// Demonstrates decoder/driver level-dimension desync in DoCopyTexImage2D's +// clipped branch. When the source rect is clipped, the decoder issues +// glTexImage2D (which redefines the driver-side level) followed by +// glCopyTexSubImage2D inside a single COPY/PEEK error window. If the second +// call errors (e.g. GL_INVALID_OPERATION from a multisampled read FBO per +// ES3 8.6), SetLevelInfo is skipped, but the driver-side level has already +// been redefined to the smaller dimensions. The decoder then forwards a +// subsequent oversized TexSubImage2D to the driver. +TEST_P(GLES2DecoderTest, CopyTexImage2DClippedDriverErrorLevelInfoDesync) { + const GLenum kTarget = GL_TEXTURE_2D; + const GLint kLevel = 0; + const GLint kLargeW = 16; + const GLint kLargeH = 16; + const GLint kSmallW = 2; + const GLint kSmallH = 2; + + DoBindTexture(kTarget, client_texture_id_, kServiceTextureId); + + // Step 1: define level 0 as 16x16. Decoder records LevelInfo{16,16}. + DoTexImage2D(kTarget, kLevel, GL_RGBA, kLargeW, kLargeH, 0, GL_RGBA, + GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset); + + TextureManager* manager = group().texture_manager(); + TextureRef* texture_ref = manager->GetTexture(client_texture_id_); + ASSERT_TRUE(texture_ref != nullptr); + Texture* texture = texture_ref->texture(); + GLint width = 0, height = 0; + ASSERT_TRUE( + texture->GetLevelSize(kTarget, kLevel, &width, &height, nullptr)); + ASSERT_EQ(kLargeW, width); + ASSERT_EQ(kLargeH, height); + + // Step 2: CopyTexImage2D with x=-1 so the source rect is clipped against + // the default 128x64 backbuffer. This enters the clipped branch: + // (a) glTexImage2D(2x2) -> driver level redefined to 2x2 (succeeds) + // (b) glCopyTexSubImage2D -> driver returns GL_INVALID_OPERATION + // (mocked; on a real ES3 driver this is + // what a multisampled read FBO produces) + // LOCAL_PEEK_GL_ERROR != NO_ERROR -> SetLevelInfo SKIPPED. + { + InSequence s; + // LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER + EXPECT_CALL(*gl_, GetError()) + .WillOnce(Return(GL_NO_ERROR)) + .RetiresOnSaturation(); + // ScopedPixelUnpackState push + EXPECT_CALL(*gl_, PixelStorei(GL_UNPACK_ALIGNMENT, _)) + .Times(1) + .RetiresOnSaturation(); + // (a) Driver-side level redefined to 2x2 -- this SUCCEEDS. + EXPECT_CALL(*gl_, TexImage2D(kTarget, kLevel, GL_RGBA, kSmallW, kSmallH, 0, + GL_RGBA, GL_UNSIGNED_BYTE, _)) + .Times(1) + .RetiresOnSaturation(); + // ScopedPixelUnpackState restore + EXPECT_CALL(*gl_, PixelStorei(GL_UNPACK_ALIGNMENT, _)) + .Times(1) + .RetiresOnSaturation(); + // (b) src=(-1,0,2,2) clipped to (0,0,1,2); destX=0-(-1)=1. + EXPECT_CALL(*gl_, + CopyTexSubImage2D(kTarget, kLevel, 1, 0, 0, 0, 1, kSmallH)) + .Times(1) + .RetiresOnSaturation(); + // LOCAL_PEEK_GL_ERROR -- driver reports the CopyTexSubImage2D failure. + EXPECT_CALL(*gl_, GetError()) + .WillOnce(Return(GL_INVALID_OPERATION)) + .RetiresOnSaturation(); + } + cmds::CopyTexImage2D copy_cmd; + copy_cmd.Init(kTarget, kLevel, GL_RGBA, /*x=*/-1, /*y=*/0, kSmallW, kSmallH); + EXPECT_EQ(error::kNoError, ExecuteCmd(copy_cmd)); + // The driver error is surfaced to the client. + EXPECT_EQ(GL_INVALID_OPERATION, GetGLError()); + + // Step 3: BUG -- decoder LevelInfo is still 16x16 even though the driver + // level was redefined to 2x2 by the glTexImage2D call above. + width = 0; + height = 0; + ASSERT_TRUE( + texture->GetLevelSize(kTarget, kLevel, &width, &height, nullptr)); + EXPECT_EQ(kLargeW, width) + << "decoder LevelInfo width is stale (driver level is " << kSmallW + << "x" << kSmallH << ")"; + EXPECT_EQ(kLargeH, height); + + // Step 4: TexSubImage2D with xoffset=1,width=15,height=16 passes + // ValidForTexture (which compares against the stale 16x16 LevelInfo) and + // is FORWARDED to the driver as glTexSubImage2D. On a native ES driver + // that does not re-validate sub-image bounds against the actual 2x2 level, + // this is a 15x16 OOB write into a 2x2 allocation. (A full-level 16x16 + // sub-image is rewritten by the decoder into a glTexImage2D, which would + // happen to redefine the driver level back to 16x16; a partial sub-image + // is required to reach the OOB condition.) + EXPECT_CALL(*gl_, TexSubImage2D(kTarget, kLevel, 1, 0, kLargeW - 1, kLargeH, + GL_RGBA, GL_UNSIGNED_BYTE, + shared_memory_address_.get())) + .Times(1) + .RetiresOnSaturation(); + cmds::TexSubImage2D sub_cmd; + sub_cmd.Init(kTarget, kLevel, 1, 0, kLargeW - 1, kLargeH, GL_RGBA, + GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, + GL_FALSE); + EXPECT_EQ(error::kNoError, ExecuteCmd(sub_cmd)); + EXPECT_EQ(GL_NO_ERROR, GetGLError()); +} + TEST_P(GLES2DecoderManualInitTest, CopyTexImage2DUnsizedInternalFormat) { InitState init; init.gl_version = "OpenGL ES 3.0";