Skip to content

gpu: use 1-byte pixel row alignment for texture upload and read back - #500

Merged
DichenZhang1 merged 1 commit into
google:mainfrom
alepacheco:fix/gles-pixel-alignment
Oct 16, 2026
Merged

DichenZhang1 merged 1 commit into
google:mainfrom
alepacheco:fix/gles-pixel-alignment

Conversation

@alepacheco

Copy link
Copy Markdown
Contributor

The image buffers that uhdr_opengl_ctxt uploads with glTexImage2D() are tightly packed: each row is w * bytes per pixel long. OpenGL ES reads them with the default GL_UNPACK_ALIGNMENT of 4, which assumes every row starts on a 4-byte boundary. For the 8-bit YCbCr formats (uploaded as GL_R8) and for RGB888, a width whose row size is not a multiple of 4 makes the driver read past the end of the buffer and shears the texture. Only 8bppYCbCr400 already set the alignment to 1 around its upload and read back.

With GPU acceleration enabled this is reached by decoding, for example, a 4:4:4 base JPEG whose width is not a multiple of 4: applyGainMapGLES() uploads the contiguous YUV planes and the driver reads beyond the allocation.

This PR sets GL_UNPACK_ALIGNMENT and GL_PACK_ALIGNMENT to 1 once, right after the library's own EGL context is made current in init_opengl_ctxt(). Every texture upload and read back in the library goes through that context, so all formats now use packed rows and no call site has to remember to set it; the per-call toggles for 8bppYCbCr400 are removed. Formats whose rows are always a multiple of 4 bytes are unaffected.

Test: OpenGLContextTest.TextureUploadOfTightlyPackedRows uploads 400, 420, 422, 444 and RGB888 buffers at widths 1 to 10, reads the textures back and checks they hold exactly the source bytes. Built with -DUHDR_ENABLE_GLES=1 and AddressSanitizer on Mesa llvmpipe, the original code reports a heap-buffer-overflow in create_texture() on this test; with the change it passes, along with the rest of the unit tests.

Side note, not changed here: under GLES the existing EditorHelperTest cases for 12bppYCbCr420 pass planes[0] of an image whose Y, U and V planes are separate allocations to create_texture(), which expects the three planes in one buffer, so those cases read past the Y plane with or without this change.

Image buffers handed to the GLES path are tightly packed (each row is
width * bytes-per-pixel), but create_texture() uploaded the YCbCr 420,
422, 444 and RGB888 formats with the default GL_UNPACK_ALIGNMENT of 4.
For widths whose rows are not a multiple of 4 bytes, the driver then
pads every row and reads past the end of the caller's buffer, and the
texture content is skewed.

Set GL_UNPACK_ALIGNMENT and GL_PACK_ALIGNMENT to 1 once when the
context is created, so every upload and read back matches the buffer
layout, and drop the per-call toggles the 400 format used.

Add OpenGLContextTest.TextureUploadOfTightlyPackedRows, which uploads
each format at widths 1 to 10 and checks the texture reads back exactly
the source bytes.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request configures the OpenGL context globally to use 1-byte row alignment for unpacking and packing during context initialization, and removes local alignment toggles in create_texture and read_texture. It also adds a new unit test TextureUploadOfTightlyPackedRows to verify texture upload and readback of tightly packed rows for various formats and widths. I have no feedback to provide as there are no review comments.

@DichenZhang1
DichenZhang1 merged commit b04e3bd into google:main Oct 16, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants