Add GLES support for the same pixel formats for copying texture -> buffer as when copying buffer -> texture#183428
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors and centralizes pixel format conversion logic for GLES, expanding support for texture-to-buffer copies by introducing ToPixelFormatGLES and removing the duplicated TexImage2DData struct. However, it introduces several heap buffer overflow and over-read vulnerabilities. This is due to a mismatch in how the kS8UInt (Stencil 8) format is handled, where Impeller treats it as 1-byte-per-pixel, but the GLES backend maps it to a 4-byte depth-stencil format (GL_UNSIGNED_INT_24_8). This discrepancy leads to out-of-bounds memory access in BlitCopyTextureToBufferCommandGLES, BlitCopyBufferToTextureCommandGLES, and TextureGLES::OnSetContents. These critical security issues must be addressed by ensuring consistent byte-size handling or performing explicit format conversions in the GLES backend. Additionally, consider improving the new utility function by making it aware of GPU capabilities for better correctness (specifically for SRGB formats) and to enhance debuggability.
| gl.ReadPixels(source_region.GetX(), source_region.GetY(), | ||
| source_region.GetWidth(), source_region.GetHeight(), | ||
| format, type, data + destination_offset); |
There was a problem hiding this comment.
There is a mismatch between the pixel format mapping in ToPixelFormatGLES and the byte size calculation in BytesPerPixelForPixelFormat for PixelFormat::kS8UInt.
In formats_gles.cc, kS8UInt is mapped to GL_DEPTH_STENCIL with GL_UNSIGNED_INT_24_8 (4 bytes per pixel). However, BytesPerPixelForPixelFormat in impeller/core/formats.h returns 1 byte per pixel for kS8UInt.
In BlitCopyTextureToBufferCommandGLES::Encode, gl.ReadPixels will write 4 bytes per pixel based on the GLES format, but the destination buffer size and the FlipImage call (line 314) rely on bytes_per_pixel which is 1. This leads to a heap buffer overflow when copying stencil data from a texture to a buffer.
To remediate this, ensure that the GLES format used for gl.ReadPixels matches the expected byte size of the destination buffer, or perform a manual conversion from the 4-byte depth-stencil format to the 1-byte stencil format.
| gles_format->external_format, // format | ||
| gles_format->type, // type | ||
| tex_data); // data |
There was a problem hiding this comment.
Similar to the issue in BlitCopyTextureToBufferCommandGLES::Encode, there is a mismatch for PixelFormat::kS8UInt that leads to a heap buffer over-read here.
gl.TexSubImage2D will read 4 bytes per pixel from tex_data because gles_format->type is GL_UNSIGNED_INT_24_8. However, the source buffer source may have been allocated based on 1 byte per pixel (the core definition of kS8UInt). This can result in reading past the end of the source buffer.
Ensure the source buffer contains sufficient data for the 4-byte GLES format, or handle the conversion from 1-byte stencil data to the 4-byte depth-stencil format required by GLES.
| gl.TexImage2D(texture_target, // target | ||
| 0u, // LOD level | ||
| format.internal_format, // internal format | ||
| size.width, // width | ||
| size.height, // height | ||
| 0u, // border | ||
| format.external_format, // format | ||
| format.type, // type | ||
| tex_data); // data |
There was a problem hiding this comment.
In TextureGLES::OnSetContents, gl.TexImage2D uses the format and type from ToPixelFormatGLES. For PixelFormat::kS8UInt, this results in a 4-byte per pixel read (GL_UNSIGNED_INT_24_8).
However, the validation check on line 234 only ensures that the input mapping buffer is at least GetByteSizeOfBaseMipLevel() bytes. For kS8UInt, this is typically 1 byte per pixel. This leads to a heap buffer over-read when gl.TexImage2D is called, as it will attempt to read 4 bytes per pixel from a buffer that may only be sized for 1 byte per pixel.
Validate that the input buffer size is sufficient for the mapped GLES format and type before calling gl.TexImage2D.
| std::optional<PixelFormatGLES> ToPixelFormatGLES(PixelFormat format, | ||
| bool supports_bgra); |
There was a problem hiding this comment.
To improve this new utility function, consider changing its signature to accept const CapabilitiesGLES& instead of bool supports_bgra.
This would allow you to check for other capabilities, like SRGB support, which is currently not handled correctly for SRGB pixel formats (e.g., kR8G8B8A8UNormIntSRGB). The supports_bgra flag can then be derived from the capabilities object within the function. This would centralize capability checking and fix a color correctness issue.
| std::optional<PixelFormatGLES> ToPixelFormatGLES(PixelFormat format, | |
| bool supports_bgra); | |
| std::optional<PixelFormatGLES> ToPixelFormatGLES( | |
| PixelFormat format, | |
| const CapabilitiesGLES& capabilities); |
| case PixelFormat::kR8G8B8A8UNormInt: | ||
| format.internal_format = GL_RGBA; | ||
| format.external_format = GL_RGBA; | ||
| format.type = GL_UNSIGNED_BYTE; | ||
| break; | ||
| case PixelFormat::kR8G8B8A8UNormIntSRGB: | ||
| format.internal_format = GL_RGBA; | ||
| format.external_format = GL_RGBA; | ||
| format.type = GL_UNSIGNED_BYTE; | ||
| break; |
There was a problem hiding this comment.
These two case statements for kR8G8B8A8UNormInt and kR8G8B8A8UNormIntSRGB have identical bodies and can be combined to reduce duplication.
Additionally, this is a good opportunity to correctly handle kR8G8B8A8UNormIntSRGB. Its internal_format should be GL_SRGB8_ALPHA8 on platforms that support it to ensure correct gamma conversion. Currently, it's treated as a linear GL_RGBA format, which can lead to incorrect colors. This could be fixed by passing CapabilitiesGLES to this function as suggested in formats_gles.h.
| case PixelFormat::kR8G8B8A8UNormInt: | |
| format.internal_format = GL_RGBA; | |
| format.external_format = GL_RGBA; | |
| format.type = GL_UNSIGNED_BYTE; | |
| break; | |
| case PixelFormat::kR8G8B8A8UNormIntSRGB: | |
| format.internal_format = GL_RGBA; | |
| format.external_format = GL_RGBA; | |
| format.type = GL_UNSIGNED_BYTE; | |
| break; | |
| case PixelFormat::kR8G8B8A8UNormInt: | |
| case PixelFormat::kR8G8B8A8UNormIntSRGB: | |
| format.internal_format = GL_RGBA; | |
| format.external_format = GL_RGBA; | |
| format.type = GL_UNSIGNED_BYTE; | |
| break; |
There was a problem hiding this comment.
The duplication removal is a good suggestion. It also begs the question if the differences between those formats is being respected and if it's important. I think the difference between those is linear space versus gamma space. If this is just used for blitting, i don't think it matters.
There was a problem hiding this comment.
Removed the duplication. I'm unsure about the implications of those two formats either. But for the purposes of this PR, I'm just preserving existing behavior here.
| case PixelFormat::kR8G8B8A8UNormInt: | ||
| format.internal_format = GL_RGBA; | ||
| format.external_format = GL_RGBA; | ||
| format.type = GL_UNSIGNED_BYTE; | ||
| break; | ||
| case PixelFormat::kR8G8B8A8UNormIntSRGB: | ||
| format.internal_format = GL_RGBA; | ||
| format.external_format = GL_RGBA; | ||
| format.type = GL_UNSIGNED_BYTE; | ||
| break; |
There was a problem hiding this comment.
The duplication removal is a good suggestion. It also begs the question if the differences between those formats is being respected and if it's important. I think the difference between those is linear space versus gamma space. If this is just used for blitting, i don't think it matters.
|
Thanks for the review, @gaaclarke. I was wrangling with Gemini's comments about PixelFormat::kS8UInt and was planning on dealing with that before actually sending this out for review. I think Gemini is correct about PixelFormat::kS8UInt having inconsistencies and not returning the correct values with Fixing this doesn't seem trivial, and this PR does preserve existing behavior (except for |
Yea, it's fine to maintain the existing behavior. A hypothetical bug is not something we should chase after. We can defer to reported behavior for stuff like this. That's the interesting thing between supporting something like impeller versus flutter_gpu. In impeller we just have to support what flutter uses, in flutter_gpu we have to support anything anyone could possibly do =) lgtm |
…ture -> buffer as when copying buffer -> texture (flutter/flutter#183428)
flutter/flutter@195ae7b...3f400d7 2026-03-11 [email protected] Roll Clang to 80743bd43fd5b38fedc503308e7a652e23d3ec93 (flutter/flutter#182919) 2026-03-11 [email protected] Roll Fuchsia Linux SDK from 8C_qfgWgoNhkV0_Mn... to QD887D4OanteB7UKM... (flutter/flutter#183492) 2026-03-11 [email protected] Roll Dart SDK from fecf806be5d0 to 8531f7c2bdae (2 revisions) (flutter/flutter#183489) 2026-03-11 [email protected] [impeller] Use the GLES3 shaders in the embedder if supported (flutter/flutter#180072) 2026-03-11 [email protected] Roll Dart SDK from ae2be9700800 to fecf806be5d0 (1 revision) (flutter/flutter#183482) 2026-03-11 [email protected] Add GLES support for the same pixel formats for copying texture -> buffer as when copying buffer -> texture (flutter/flutter#183428) 2026-03-11 [email protected] Roll Skia from 257d04225d0c to 0cab3e4ee34b (8 revisions) (flutter/flutter#183476) 2026-03-10 [email protected] Fix GitHub workflows to use the `flutteractionsbot` mirror for PR branches. (flutter/flutter#183470) 2026-03-10 [email protected] [material/menu_anchor.dart] Ensure positioned menus always begin animating at the target position (flutter/flutter#182932) 2026-03-10 [email protected] Roll pub packages (flutter/flutter#183467) 2026-03-10 [email protected] Roll Dart SDK from ebef6c849489 to ae2be9700800 (4 revisions) (flutter/flutter#183460) 2026-03-10 [email protected] Roll Skia from 4b35832cc7ea to 257d04225d0c (5 revisions) (flutter/flutter#183457) 2026-03-10 [email protected] dev: Use a super-parameter in several missed cases (flutter/flutter#182251) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC [email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
BlitCopyBufferToTextureCommandGLES(and initializing/setting texture contents inTextureGLES) supports a bunch of different pixel formats, as defined in theTexImage2DDataconstructors inblit_command_gles.ccandtexture_gles.cc.But
BlitCopyTextureToBufferCommandGLESonly has support specifically forkR8G8B8A8UNormIntandkB8G8R8A8UNormInt.This makes
BlitCopyTextureToBufferCommandGLESsupport the same pixel formats asBlitCopyBufferToTextureCommandGLESandTextureGLES.Fixes #183462
Changes:
Share
ToPixelFormatGLESblit_command_gles.ccandtexture_gles.cchave almost-duplicate logic to convertimpeller::PixelFormatto aGLint internal_format, aGLenum external_format, and aGLenum type. Factor this out into a sharedToPixelFormatGLESfunction informats_gles.h/cc.Where there were differences between the
blit_command_gles.ccandtexture_gles.cc:kR32G32B32A32Float:blit_command_gles.cchasinternal_format = GL_RGBA,external_format = GL_RGBAtexture_gles.cchasinternal_format = GL_RGBA32F,external_format = GL_RGBAinternal_format = GL_RGBA32F,external_format = GL_RGBAkR16G16B16A16Float:blit_command_gles.cchasinternal_format = GL_RGBA,external_format = GL_RGBAtexture_gles.cchasinternal_format = GL_RGBA16F,external_format = GL_RGBAinternal_format = GL_RGBA16F,external_format = GL_RGBAkR32Float:blit_command_gles.cchasinternal_format = GL_RED,external_format = GL_REDtexture_gles.cchasinternal_format = GL_R32F,external_format = GL_RGBAinternal_format = GL_R32F,external_format = GL_REDAll the other PixelFormat cases were the same between the two files and copied over directly.
Remove
TexImage2DDatablit_command_gles.ccandtexture_gles.cceach define aTexImage2DDatastruct which just contains the pixel format data and sometimes an optional data buffer. I removed both of these. Instead, we just use theToPixelFormatGLESreturn value directly, and use the source data buffer directly.Improve pixel format support in
BlitCopyTextureToBufferCommandGLES::Encode()In
BlitCopyTextureToBufferCommandGLES::Encode(), useToPixelFormatGLES()to determine the format and type used forgl.ReadPixels(). This expands the existing functionality that only supported two specific pixel formats.Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.