CVE-2026-79040
Overview
Background
- Packed depth-stencil image
- A single renderbuffer or texture with format such as
GL_DEPTH24_STENCIL8that stores both depth and stencil in one allocation, but can be attached at onlyGL_DEPTH_ATTACHMENTor onlyGL_STENCIL_ATTACHMENT. - `ClearUnclearedAttachments`
- The decoder routine that lazily clears any not-yet-initialized framebuffer attachments before a draw/clear, selecting
glClearbits per attachment point. - `MarkAttachmentsAsCleared` / `cleared()` flag
- Bookkeeping that records an image as initialized; a packed depth-stencil image carries a single per-image cleared flag covering both components.
- `BindAttachmentToPoint`
- New helper that binds a given
Attachment(renderbuffer, 2D/3D/multisample texture) at an arbitrary attachment point on the currently bound framebuffer, or detaches when passed null.
Root Cause Analysis
In the attachment-clearing path of gpu/command_buffer/service/gles2_cmd_decoder.cc, clear bits were chosen by attachment-point key: HasUnclearedAttachment(GL_STENCIL_ATTACHMENT) gated GL_STENCIL_BUFFER_BIT and HasUnclearedAttachment(GL_DEPTH_ATTACHMENT) gated GL_DEPTH_BUFFER_BIT. When a packed GL_DEPTH24_STENCIL8 image was attached at only one point (say depth only), only that component’s bit was set, so glClear wrote only the depth component, yet MarkAttachmentsAsCleared then flipped the single per-image cleared flag, marking the entire packed image — including its never-written stencil component — as initialized. This violated the invariant that an image is marked cleared only after all of its memory has been written, leaving the unbound component holding uninitialized/stale GPU memory.
The fix detects a packed image present at only one point whose format also contains the other channel (via GLES2Util::GetChannelsForFormat(...) & kStencil/kDepth), temporarily binds that image at the empty point with Framebuffer::BindAttachmentToPoint, sets both clear_depth and clear_stencil so glClear writes both GL_DEPTH_BUFFER_BIT and GL_STENCIL_BUFFER_BIT, then restores the empty point to null afterward.
Attack Path
- Allocate a packed depth-stencil image
A renderer creates a
GL_DEPTH24_STENCIL8renderbuffer (or texture) via the command buffer. - Attach at only one point
It attaches the image at only
GL_DEPTH_ATTACHMENT(or onlyGL_STENCIL_ATTACHMENT), leaving the complementary component’s point empty. - Trigger the implicit clear
On a draw/clear,
ClearUnclearedAttachmentscleared only the attached component’s bit butMarkAttachmentsAsClearedmarked the whole image cleared, so the other component retained uninitialized device memory. - Read back stale memory (inferred)
The renderer subsequently attaches or samples the previously-empty component to observe uninitialized graphics memory; the specific read-back primitive is not shown in the diff and is
(inferred).
Impact Assessment
GL_DEPTH24_STENCIL8 image and attach it at only one of the depth/stencil points via the command buffer. The advisory classifies this as an uninitialized resource in GPU with no exploited-in-the-wild status stated; any concrete cross-context memory disclosure beyond the missing initialization is (inferred).Changed Functions
| Function | Change | Notes |
|---|---|---|
ifgpu/command_buffer/service/framebuffer_manager.cc |
modified | |
ifgpu/command_buffer/service/gles2_cmd_decoder.cc |
modified | |
TEST_Pgpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc |
modified |
Files Changed
gpu/command_buffer/service/framebuffer_manager.ccgpu/command_buffer/service/gles2_cmd_decoder.ccgpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.ccgpu/command_buffer/service/framebuffer_manager.h
Audit Directions
- Per-key vs. per-image stateLook for lazy-clear or initialization logic keyed on an attachment point or view while the underlying resource’s cleared/initialized flag covers the whole allocation, where partial writes can prematurely mark the resource complete.
- Multi-component/aliased formatsAudit handling of formats that pack multiple channels (depth+stencil, or multi-plane) to ensure every component is written before the resource is considered cleared.
- Attach/detach around clearsReview other places that clear framebuffer or texture memory to confirm all sub-resources reachable by the client are covered by the clear, not just those currently bound at their canonical point.
Patch
From 427dfe1b0756fe81fb374503e3230ef2610e1bba Mon Sep 17 00:00:00 2001 From: Tzarial <[email protected]> Date: Wed, 15 Jul 2026 16:23:19 -0700 Subject: [PATCH] [agy][gpu] Clear both components of packed D/S When a packed depth-stencil renderbuffer or texture is attached at only GL_DEPTH_ATTACHMENT or only GL_STENCIL_ATTACHMENT, ClearUnclearedAttachments selected glClear bits by attachment-point key and so wrote only one component before MarkAttachmentsAsCleared flipped the single per-image cleared flag. Detect this case, temporarily bind the packed image at the empty depth/stencil point so the driver has both buffers to write, clear with both GL_DEPTH_BUFFER_BIT and GL_STENCIL_BUFFER_BIT, then restore the empty point. Fixed: 516950646 Test: gpu_unittests Change-Id: I100bc653d8dbf5f9707fd35c4d113872c274b462 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8086561 Reviewed-by: Kai Ninomiya <[email protected]> Commit-Queue: Kai Ninomiya <[email protected]> Cr-Commit-Position: refs/heads/main@{#1662933} --- diff --git a/gpu/command_buffer/service/framebuffer_manager.cc b/gpu/command_buffer/service/framebuffer_manager.cc index 26a8aef..213b97f 100644 --- a/gpu/command_buffer/service/framebuffer_manager.cc +++ b/gpu/command_buffer/service/framebuffer_manager.cc @@ -1081,6 +1081,37 @@ RestoreDrawBuffers(); } +// static +void Framebuffer::BindAttachmentToPoint(GLenum target, + GLenum attachment_point, + const Attachment* attachment) { + if (!attachment) { + glFramebufferRenderbufferEXT(target, attachment_point, GL_RENDERBUFFER, 0); + return; + } + if (attachment->IsRenderbufferAttachment()) { + const RenderbufferAttachment* rb = + static_cast<const RenderbufferAttachment*>(attachment); + glFramebufferRenderbufferEXT(target, attachment_point, GL_RENDERBUFFER, + rb->renderbuffer()->service_id()); + } else if (attachment->IsTextureAttachment()) { + const TextureAttachment* tex = + static_cast<const TextureAttachment*>(attachment); + if (tex->Is3D()) { + glFramebufferTextureLayer(target, attachment_point, + tex->texture()->service_id(), tex->level(), + tex->layer()); + } else if (tex->samples() > 0) { + glFramebufferTexture2DMultisampleEXT( + target, attachment_point, tex->target(), tex->texture()->service_id(), + tex->level(), tex->samples()); + } else { + glFramebufferTexture2DEXT(target, attachment_point, tex->target(), + tex->texture()->service_id(), tex->level()); + } + } +} + void Framebuffer::OnInsertUpdateLastColorAttachmentId(GLenum attachment) { if (attachment >= GL_COLOR_ATTACHMENT0 && attachment < GL_COLOR_ATTACHMENT0 + manager_->max_color_attachments_) { diff --git a/gpu/command_buffer/service/framebuffer_manager.h b/gpu/command_buffer/service/framebuffer_manager.h index 376149ba..6b750cf2 100644 --- a/gpu/command_buffer/service/framebuffer_manager.h +++ b/gpu/command_buffer/service/framebuffer_manager.h @@ -120,6 +120,13 @@ // Re-attaches all current attachments for recreateFbo workaround. void ReattachAttachments(GLenum framebuffer_target); + // Binds |attachment| at |attachment_point| on the framebuffer currently + // bound at |target|, or detaches whatever is at |attachment_point| if + // |attachment| is null. Only modifies driver-side state. + static void BindAttachmentToPoint(GLenum target, + GLenum attachment_point, + const Attachment* attachment); + // Attaches a renderbuffer to a particlar attachment. // Pass null to detach. void AttachRenderbuffer( diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc index 1e076c33..cfaf482 100644 --- a/gpu/command_buffer/service/gles2_cmd_decoder.cc +++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc @@ -7275,14 +7275,42 @@ } } - if (framebuffer->HasUnclearedAttachment(GL_STENCIL_ATTACHMENT)) { + const Framebuffer::Attachment* depth_attachment = + framebuffer->GetAttachment(GL_DEPTH_ATTACHMENT); + const Framebuffer::Attachment* stencil_attachment = + framebuffer->GetAttachment(GL_STENCIL_ATTACHMENT); + bool clear_depth = depth_attachment && !depth_attachment->cleared(); + bool clear_stencil = stencil_attachment && !stencil_attachment->cleared(); + + // A packed depth-stencil image attached at only one of the depth/stencil + // points must be bound and cleared at both points so that both components + // are initialized before the image is marked as cleared. + GLenum filled_depth_stencil_point = 0; + if (clear_depth && !stencil_attachment && + (GLES2Util::GetChannelsForFormat(depth_attachment->internal_format()) & + GLES2Util::kStencil) != 0) { + filled_depth_stencil_point = GL_STENCIL_ATTACHMENT; + Framebuffer::BindAttachmentToPoint(target, GL_STENCIL_ATTACHMENT, + depth_attachment); + clear_stencil = true; + } else if (clear_stencil && !depth_attachment && + (GLES2Util::GetChannelsForFormat( + stencil_attachment->internal_format()) & + GLES2Util::kDepth) != 0) { + filled_depth_stencil_point = GL_DEPTH_ATTACHMENT; + Framebuffer::BindAttachmentToPoint(target, GL_DEPTH_ATTACHMENT, + stencil_attachment); + clear_depth = true; + } + + if (clear_stencil) { api()->glClearStencilFn(0); state_.SetDeviceStencilMaskSeparate(GL_FRONT, kDefaultStencilMask); state_.SetDeviceStencilMaskSeparate(GL_BACK, kDefaultStencilMask); clear_bits |= GL_STENCIL_BUFFER_BIT; } - if (framebuffer->HasUnclearedAttachment(GL_DEPTH_ATTACHMENT)) { + if (clear_depth) { api()->glClearDepthFn(1.0f); state_.SetDeviceDepthMask(GL_TRUE); clear_bits |= GL_DEPTH_BUFFER_BIT; @@ -7304,6 +7332,11 @@ } } + if (filled_depth_stencil_point) { + Framebuffer::BindAttachmentToPoint(target, filled_depth_stencil_point, + nullptr); + } + if (cleared_int_renderbuffers || clear_bits) { if (reset_draw_buffers) framebuffer->RestoreDrawBuffers(); diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc index 134e4404..977afca 100644 --- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc +++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc @@ -3951,6 +3951,140 @@ } } +TEST_P(GLES3DecoderTest, ClearDepthStencilRenderbufferAttachedAtDepthOnly) { + // A DEPTH24_STENCIL8 renderbuffer attached at GL_DEPTH_ATTACHMENT only must + // have both its depth and stencil components cleared before being marked as + // cleared. + DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_, + kServiceRenderbufferId); + DoRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1, 1, + GL_NO_ERROR); + + GLuint color_renderbuffer = client_renderbuffer_id_ + 1; + GLuint color_renderbuffer_service = kServiceRenderbufferId + 1; + EXPECT_CALL(*gl_, GenRenderbuffersEXT(1, _)) + .WillOnce(SetArgPointee<1>(color_renderbuffer_service)) + .RetiresOnSaturation(); + GenHelper<cmds::GenRenderbuffersImmediate>(color_renderbuffer); + DoBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer, + color_renderbuffer_service); + DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1, GL_NO_ERROR); + + DoBindFramebuffer(GL_FRAMEBUFFER, client_framebuffer_id_, + kServiceFramebufferId); + DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, color_renderbuffer, + color_renderbuffer_service, GL_NO_ERROR); + DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER, client_renderbuffer_id_, + kServiceRenderbufferId, GL_NO_ERROR); + + // The packed renderbuffer is bound at the stencil point for the duration of + // the implicit clear so that glClear writes both components. + EXPECT_CALL(*gl_, FramebufferRenderbufferEXT( + GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, kServiceRenderbufferId)) + .Times(1) + .RetiresOnSaturation(); + EXPECT_CALL( + *gl_, FramebufferRenderbufferEXT( + GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0)) + .Times(1) + .RetiresOnSaturation(); + SetupExpectationsForFramebufferClearing( + GL_DRAW_FRAMEBUFFER, // target + GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | + GL_STENCIL_BUFFER_BIT, // clear bits + 0, 0, 0, 0, // color + 0, // stencil + 1.0f, // depth + false, // scissor test + 0, 0, 128, 64); + SetupExpectationsForApplyingDirtyState(false, // Framebuffer is RGB + true, // Framebuffer has depth + false, // Framebuffer has stencil + 0x1111, // color bits + true, // depth mask + false, // depth enabled + 0, // front stencil mask + 0, // back stencil mask + false); // stencil enabled + EXPECT_CALL(*gl_, DrawBuffersARB(_, _)).Times(1).RetiresOnSaturation(); + EXPECT_CALL(*gl_, Clear(GL_COLOR_BUFFER_BIT)).Times(1).RetiresOnSaturation(); + + cmds::Clear cmd; + cmd.Init(GL_COLOR_BUFFER_BIT); + EXPECT_EQ(error::kNoError, ExecuteCmd(cmd)); + EXPECT_EQ(GL_NO_ERROR, GetGLError()); +} + +TEST_P(GLES3DecoderTest, ClearDepthStencilRenderbufferAttachedAtStencilOnly) { + // A DEPTH24_STENCIL8 renderbuffer attached at GL_STENCIL_ATTACHMENT only + // must have both its depth and stencil components cleared before being + // marked as cleared. + DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_, + kServiceRenderbufferId); + DoRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1, 1, + GL_NO_ERROR); + + GLuint color_renderbuffer = client_renderbuffer_id_ + 1; + GLuint color_renderbuffer_service = kServiceRenderbufferId + 1; + EXPECT_CALL(*gl_, GenRenderbuffersEXT(1, _)) + .WillOnce(SetArgPointee<1>(color_renderbuffer_service)) + .RetiresOnSaturation(); + GenHelper<cmds::GenRenderbuffersImmediate>(color_renderbuffer); + DoBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer, + color_renderbuffer_service); + DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1, GL_NO_ERROR); + + DoBindFramebuffer(GL_FRAMEBUFFER, client_framebuffer_id_, + kServiceFramebufferId); + DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, color_renderbuffer, + color_renderbuffer_service, GL_NO_ERROR); + DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, client_renderbuffer_id_, + kServiceRenderbufferId, GL_NO_ERROR); + + // The packed renderbuffer is bound at the depth point for the duration of + // the implicit clear so that glClear writes both components. + EXPECT_CALL( + *gl_, FramebufferRenderbufferEXT(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER, kServiceRenderbufferId)) + .Times(1) + .RetiresOnSaturation(); + EXPECT_CALL( + *gl_, FramebufferRenderbufferEXT(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER, 0)) + .Times(1) + .RetiresOnSaturation(); + SetupExpectationsForFramebufferClearing( + GL_DRAW_FRAMEBUFFER, // target + GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | + GL_STENCIL_BUFFER_BIT, // clear bits + 0, 0, 0, 0, // color + 0, // stencil + 1.0f, // depth + false, // scissor test + 0, 0, 128, 64); + SetupExpectationsForApplyingDirtyState(false, // Framebuffer is RGB + false, // Framebuffer has depth + true, // Framebuffer has stencil + 0x1111, // color bits + false, // depth mask + false, // depth enabled + 0xFFFFFFFFU, // front stencil mask + 0xFFFFFFFFU, // back stencil mask + false); // stencil enabled + EXPECT_CALL(*gl_, DrawBuffersARB(_, _)).Times(1).RetiresOnSaturation(); + EXPECT_CALL(*gl_, Clear(GL_COLOR_BUFFER_BIT)).Times(1).RetiresOnSaturation(); + + cmds::Clear cmd; + cmd.Init(GL_COLOR_BUFFER_BIT); + EXPECT_EQ(error::kNoError, ExecuteCmd(cmd)); + EXPECT_EQ(GL_NO_ERROR, GetGLError()); +} + TEST_P(GLES2DecoderManualInitTest, MESAFramebufferFlipYExtensionEnabled) { InitState init; init.gl_version = "OpenGL ES 3.1";
Regression Test / PoC
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc
index 134e4404..977afca 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc
@@ -3951,6 +3951,140 @@
}
}
+TEST_P(GLES3DecoderTest, ClearDepthStencilRenderbufferAttachedAtDepthOnly) {
+ // A DEPTH24_STENCIL8 renderbuffer attached at GL_DEPTH_ATTACHMENT only must
+ // have both its depth and stencil components cleared before being marked as
+ // cleared.
+ DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_,
+ kServiceRenderbufferId);
+ DoRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1, 1,
+ GL_NO_ERROR);
+
+ GLuint color_renderbuffer = client_renderbuffer_id_ + 1;
+ GLuint color_renderbuffer_service = kServiceRenderbufferId + 1;
+ EXPECT_CALL(*gl_, GenRenderbuffersEXT(1, _))
+ .WillOnce(SetArgPointee<1>(color_renderbuffer_service))
+ .RetiresOnSaturation();
+ GenHelper<cmds::GenRenderbuffersImmediate>(color_renderbuffer);
+ DoBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer,
+ color_renderbuffer_service);
+ DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1, GL_NO_ERROR);
+
+ DoBindFramebuffer(GL_FRAMEBUFFER, client_framebuffer_id_,
+ kServiceFramebufferId);
+ DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_RENDERBUFFER, color_renderbuffer,
+ color_renderbuffer_service, GL_NO_ERROR);
+ DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ GL_RENDERBUFFER, client_renderbuffer_id_,
+ kServiceRenderbufferId, GL_NO_ERROR);
+
+ // The packed renderbuffer is bound at the stencil point for the duration of
+ // the implicit clear so that glClear writes both components.
+ EXPECT_CALL(*gl_, FramebufferRenderbufferEXT(
+ GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, kServiceRenderbufferId))
+ .Times(1)
+ .RetiresOnSaturation();
+ EXPECT_CALL(
+ *gl_, FramebufferRenderbufferEXT(
+ GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0))
+ .Times(1)
+ .RetiresOnSaturation();
+ SetupExpectationsForFramebufferClearing(
+ GL_DRAW_FRAMEBUFFER, // target
+ GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |
+ GL_STENCIL_BUFFER_BIT, // clear bits
+ 0, 0, 0, 0, // color
+ 0, // stencil
+ 1.0f, // depth
+ false, // scissor test
+ 0, 0, 128, 64);
+ SetupExpectationsForApplyingDirtyState(false, // Framebuffer is RGB
+ true, // Framebuffer has depth
+ false, // Framebuffer has stencil
+ 0x1111, // color bits
+ true, // depth mask
+ false, // depth enabled
+ 0, // front stencil mask
+ 0, // back stencil mask
+ false); // stencil enabled
+ EXPECT_CALL(*gl_, DrawBuffersARB(_, _)).Times(1).RetiresOnSaturation();
+ EXPECT_CALL(*gl_, Clear(GL_COLOR_BUFFER_BIT)).Times(1).RetiresOnSaturation();
+
+ cmds::Clear cmd;
+ cmd.Init(GL_COLOR_BUFFER_BIT);
+ EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
+ EXPECT_EQ(GL_NO_ERROR, GetGLError());
+}
+
+TEST_P(GLES3DecoderTest, ClearDepthStencilRenderbufferAttachedAtStencilOnly) {
+ // A DEPTH24_STENCIL8 renderbuffer attached at GL_STENCIL_ATTACHMENT only
+ // must have both its depth and stencil components cleared before being
+ // marked as cleared.
+ DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_,
+ kServiceRenderbufferId);
+ DoRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1, 1,
+ GL_NO_ERROR);
+
+ GLuint color_renderbuffer = client_renderbuffer_id_ + 1;
+ GLuint color_renderbuffer_service = kServiceRenderbufferId + 1;
+ EXPECT_CALL(*gl_, GenRenderbuffersEXT(1, _))
+ .WillOnce(SetArgPointee<1>(color_renderbuffer_service))
+ .RetiresOnSaturation();
+ GenHelper<cmds::GenRenderbuffersImmediate>(color_renderbuffer);
+ DoBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer,
+ color_renderbuffer_service);
+ DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1, GL_NO_ERROR);
+
+ DoBindFramebuffer(GL_FRAMEBUFFER, client_framebuffer_id_,
+ kServiceFramebufferId);
+ DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_RENDERBUFFER, color_renderbuffer,
+ color_renderbuffer_service, GL_NO_ERROR);
+ DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, client_renderbuffer_id_,
+ kServiceRenderbufferId, GL_NO_ERROR);
+
+ // The packed renderbuffer is bound at the depth point for the duration of
+ // the implicit clear so that glClear writes both components.
+ EXPECT_CALL(
+ *gl_, FramebufferRenderbufferEXT(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ GL_RENDERBUFFER, kServiceRenderbufferId))
+ .Times(1)
+ .RetiresOnSaturation();
+ EXPECT_CALL(
+ *gl_, FramebufferRenderbufferEXT(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ GL_RENDERBUFFER, 0))
+ .Times(1)
+ .RetiresOnSaturation();
+ SetupExpectationsForFramebufferClearing(
+ GL_DRAW_FRAMEBUFFER, // target
+ GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |
+ GL_STENCIL_BUFFER_BIT, // clear bits
+ 0, 0, 0, 0, // color
+ 0, // stencil
+ 1.0f, // depth
+ false, // scissor test
+ 0, 0, 128, 64);
+ SetupExpectationsForApplyingDirtyState(false, // Framebuffer is RGB
+ false, // Framebuffer has depth
+ true, // Framebuffer has stencil
+ 0x1111, // color bits
+ false, // depth mask
+ false, // depth enabled
+ 0xFFFFFFFFU, // front stencil mask
+ 0xFFFFFFFFU, // back stencil mask
+ false); // stencil enabled
+ EXPECT_CALL(*gl_, DrawBuffersARB(_, _)).Times(1).RetiresOnSaturation();
+ EXPECT_CALL(*gl_, Clear(GL_COLOR_BUFFER_BIT)).Times(1).RetiresOnSaturation();
+
+ cmds::Clear cmd;
+ cmd.Init(GL_COLOR_BUFFER_BIT);
+ EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
+ EXPECT_EQ(GL_NO_ERROR, GetGLError());
+}
+
TEST_P(GLES2DecoderManualInitTest, MESAFramebufferFlipYExtensionEnabled) {
InitState init;
init.gl_version = "OpenGL ES 3.1";