Repository navigation
Draw sharp text on scaled (HiDPI) displays - #2979
Merged
Merged
Conversation
Arcade's windows use pyglet's "stretch" scaling, so text was rasterized at the window's size and stretched to fill the larger framebuffer, which made strokes uneven and blocky. The switch to Liberation Sans made it more noticeable than Calibri did. On a scaled display, Text now lays its pyglet label out at the framebuffer's resolution (dpi and positions multiplied by the pixel ratio) and draws it back at the same size with a pyglet State on the label's group that scales the view matrix. The state travels with the group, so text in a user's Batch is scaled too. Text's positions and sizes are still in window units. At a ratio of 1 nothing changes. create_text_sprite keeps laying text out in window units, since its texture is that size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pvcraven
force-pushed
the
crisp-hidpi-text
branch
from
October 9, 2026 21:59
49ed371 to
54bb0a3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Text looked blocky on scaled displays, such as Windows at 125% or 150%. This makes
Text,draw_textand GUI text sharp there, at the same size and position.Why it was blocky
Arcade sets
pyglet.options.dpi_scaling = "stretch"(since #2361). pyglet's docs for that mode say content is drawn at the window's size and stretched to fill the larger framebuffer, "at the cost of blurry content". At 125%, a 640×320 window has an 800×400 framebuffer, so every glyph rasterized at 96 DPI was scaled 1.25× on the GPU. That gives strokes 2 or 3 pixels wide in the same word, and stair-stepped diagonals.It got more noticeable two weeks ago. I rendered the same scene from five checkouts: September 12 (pyglet dev8), October 1 (pyglet dev11), before #2926, before #2957, and today. They look alike until #2957 switched the default font from Calibri to Liberation Sans. Calibri is heavily hinted for small screen sizes and hides the stretching. Liberation Sans, like Arial, shows it. In
"platform"mode, which isn't stretched, all three fonts are smooth.The change (
arcade/text.py)On a display whose pixel ratio isn't 1,
Textcreates its pyglet label:dpi = 96 × ratio(or the user's owndpi× ratio), so glyphs are rasterized at the screen's resolution;x,y,widthandheightmultiplied by the ratio;Group(parented to the user's group, if any) holding a_ScaleViewState. That pyglet 3Statemultipliesctx.view_matrixbyscale(1/ratio)while the label draws and restores it afterwards.The state belongs to the label's group, so it also applies when the label is drawn through the user's
Batch. Equal states compare equal, so a batch can draw labels with the same scale together.Text'sx,y,position,width,height,size,content_width,content_height,content_size,left,right,topandbottomconvert back to window units. OnlyText.label, the pyglet object, uses framebuffer pixels.pixel_perfect=True) nothing changes: no extra group, and the same DPI.create_text_spritekeeps laying text out in window units through a small_UnscaledTextsubclass. Its texture is the text's size in window units, so a higher resolution would just be scaled back down, which looked worse in testing. Sharp text sprites would need a higher-resolution texture and a sprite scale, which I left for a separate decision.Results at 125% (Windows)
Rendered with Arcade's default
"stretch"scaling.draw_textat 10, 14 and 24 pt,arcade.Text, aUILabeland aUIFlatButtonall have even strokes and smooth curves, at the same positions.create_text_spriteoutput is pixel-identical todevelopment.Tests
tests/unit/text/test_text_hidpi.pypatches the scaleTextlays out at to 2, since CI runs at 1:Batch, lands in the same place and at the same size as unscaled text. With the state turned off, these two fail;create_text_spritesizes are unchanged;Also run:
make.py docs-full;"stretch"scaling at 125%, without errors.Not tested: macOS Retina, where the ratio is 2. The same code path applies there, but I couldn't check it.
🤖 Generated with Claude Code