--- a/src/MacSrc/OpenGL.cc 2026-08-22 09:00:00.000000000 +0200 +++ b/src/MacSrc/OpenGL.cc 2026-08-22 09:05:00.000000000 +0200 @@ -562,7 +562,14 @@ static bool opengl_cache_texture(CachedTexture toCache, grs_bitmap *bm) { SDL_GL_MakeCurrent(window, context); - glPixelStorei(GL_UNPACK_ROW_LENGTH, bm->row); + // GL_UNPACK_ROW_LENGTH must describe the buffer actually passed to + // glTexImage2D() below (toCache.converted->pixels, a separately + // allocated SDL_PIXELFORMAT_RGBA32 surface from + // SDL_ConvertSurfaceFormat()) - not bm->row, the original 8-bit + // source bitmap's own unrelated row stride. Using bm->row here fed + // gl4es's GL_UNPACK_ROW_LENGTH emulation the wrong stride whenever + // the two differed, corrupting the texture upload. + glPixelStorei(GL_UNPACK_ROW_LENGTH, toCache.converted->pitch / 4); if (texturesByBitsPtr.size() < MAX_CACHED_TEXTURES) { // We have enough room, generate the new texture @@ -696,7 +703,10 @@ t->lastDrawTime = *tmd_ticks; if (isDirty) { - glPixelStorei(GL_UNPACK_ROW_LENGTH, bm->row); + // Same fix as opengl_cache_texture() above - t->converted->pixels + // is what's actually uploaded, so its own pitch is what + // GL_UNPACK_ROW_LENGTH needs to describe, not bm->row. + glPixelStorei(GL_UNPACK_ROW_LENGTH, t->converted->pitch / 4); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bm->w, bm->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, t->converted->pixels); } }