Skip to content

Commit 2f4ce7b

Browse files
committed
(GL1) Fix font atlas corruption from GL_UNPACK_ROW_LENGTH state leak
gl1_draw_tex sets glPixelStorei(GL_UNPACK_ROW_LENGTH, pot_width) before its glTexImage2D and never resets it. GL_UNPACK_ROW_LENGTH is global pixel-store state that persists until something changes it, so every subsequent glTexImage2D in the frame inherits it as the source row stride. When this leaked stride does not match the source data's actual row stride, glTexImage2D reads source rows with the wrong pitch and writes the texture with glyphs landing at offsets that are shifted relative to where atlas_offset_x/atlas_offset_y say they should be. In ozone this manifests as the title and sidebar fonts (whichever fonts are uploaded while a stale ROW_LENGTH is in effect) rendering as horizontal stripe patterns instead of letters: the texcoords baked into the vertex block point at slot positions, but those positions in the actual texture contain the wrong glyph data — typically the codepoint-0 fallback bitmap from atlas (0,0), which freetype draws as horizontal bars. The corruption is invisible to glGetTexImage readback because the same ROW_LENGTH state that skewed the upload would skew the readback symmetrically, so a 2D dump comes out looking correct as an image even though the GPU sampler reading specific (s,t) coordinates hits different texels than the bake math expects. A context reset masks the bug because re-running font init under a GL state where ROW_LENGTH happens to already be 0 produces a clean upload. Fix in two places, defense-in-depth: 1. gl1_raster_font_upload_atlas now explicitly sets GL_UNPACK_ALIGNMENT=1 and GL_UNPACK_ROW_LENGTH=0 immediately before glTexImage2D, so the upload is robust regardless of what state it inherits. This matches the existing pattern in gl3_raster_font_upload_atlas. 2. gl1_draw_tex now resets GL_UNPACK_ROW_LENGTH=0 after its glTexImage2D, so it stops leaking the value to other uploads in the first place. Either fix alone resolves the symptom. Both together also protect against future code paths that may set ROW_LENGTH and forget to reset it. Bug appears to be long-standing — present at least as far back as April 2026 (commit c10115b), likely much older. Goes unnoticed because gl1 + ozone is rarely used in practice; gl/glcore/Vulkan are the common paths. VITA is excluded from the new glPixelStorei calls, matching the existing #ifndef VITA guards on the other glPixelStorei sites in this file.
1 parent 3d687a7 commit 2f4ce7b

1 file changed

Lines changed: 26 additions & 0 deletions

File tree

gfx/drivers/gl1.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,25 @@ static void gl1_raster_font_upload_atlas(gl1_raster_t *font)
471471
break;
472472
}
473473

474+
/* The temp buffer is a tightly packed POT-sized GL_LUMINANCE_ALPHA
475+
* image: each row is exactly font->tex_width * 2 bytes with no
476+
* padding. Force the pixel-unpack state to match that before
477+
* uploading. Without this, the upload inherits whatever state the
478+
* GL context happens to be in at the time of the first font init.
479+
* In practice on Windows/NVIDIA, GL_UNPACK_ROW_LENGTH can come up
480+
* non-zero from the WGL/driver setup, which makes glTexImage2D
481+
* read source rows at the wrong stride. The texture ends up with
482+
* glyphs shifted into wrong slots — visually the title and sidebar
483+
* fonts (the first ones uploaded) render as horizontal stripe
484+
* patterns instead of letters, while later fonts that re-upload
485+
* after gl1_draw_tex has reset state happen to come out correct.
486+
*
487+
* gl3 follows the same pattern in gl3_raster_font_upload_atlas. */
488+
#ifndef VITA
489+
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
490+
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
491+
#endif
492+
474493
glTexImage2D(GL_TEXTURE_2D, 0, gl_internal, font->tex_width, font->tex_height,
475494
0, gl_format, GL_UNSIGNED_BYTE, tmp);
476495

@@ -1446,6 +1465,13 @@ static void gl1_draw_tex(gl1_t *gl1, int pot_width, int pot_height, int width, i
14461465
if (frame_rgba)
14471466
free(frame_rgba);
14481467

1468+
#ifndef VITA
1469+
/* Restore default row length so subsequent uploads (e.g. font atlas
1470+
* uploads, or any other glTexImage2D in the rest of the frame path)
1471+
* don't inherit pot_width as the source stride. */
1472+
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
1473+
#endif
1474+
14491475
if (tex == gl1->tex)
14501476
{
14511477
if (gl1->flags & GL1_FLAG_SMOOTH)

0 commit comments

Comments
 (0)