Drawing now takes the style and scale too, like measuring, so the text
drawers have no font state left. UIContext keeps its current style and
passes it, and fills it in from the theme if nothing has set one yet.
PPGe passes its one font.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measuring took the current font and scale, so UIContext switched to the
style being measured and back. On first draw the font to switch back to
could be an empty one, which the UWP drawer asserts on.
MeasureString and MeasureStringRect now take the style and scale, and the
backends' MeasureStringInternal measures in the style it's given.
SetOrCreateFont, which sets the font drawing uses, is shared, and the
backends only implement EnsureFont, which creates a font if needed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A RoundCorners mask picks the corners to round. A square corner is drawn
from the corner image's edge profiles, split along the diagonal, so its
antialiasing matches the straight edges and it needs no extra draw call.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
UIAtlas generates an antialiased white corner for each radius in
ROUND_RECT_RADII (8, 16, 24, 32 UI units) and each style (filled, and
thin/medium/thick outlines from ROUND_RECT_THICKNESS) at the atlas DPI.
DrawBuffer::DrawRoundRect mirrors the corner into all four corners and
stretches the corner's last texel row/column along the edges, so a
round rect is 9 quads (8 for outlines).
Other radii use the nearest corner, scaled. The LogoScreen temporarily
draws some for testing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CommonWindows.h defines WIN32_LEAN_AND_MEAN, which leaves out what the
Direct2D/DirectWrite headers in draw_text_uwp.h need. Include
<windows.h> with just NOMINMAX instead, and keep std::max out of
DropShadow.h so it doesn't depend on which order windows.h comes in.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Text and the SVG button images are mixed on screen, so they now share
UIDropShadowParams (3 UI units of blur, 0.66 intensity, no offset, the
atlas's existing look) instead of the text having its own.
draw_text.cpp included <windows.h> directly, whose min/max macros broke
the std::max calls added here. Include CommonWindows.h instead.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CompositeDropShadow in Common/Render/DropShadow.cpp blurs a premultiplied
RGBA image's alpha (with an optional offset) and composites the image
over it. The atlas's AddDropShadow and TextDrawer::AddDropShadow are now
thin wrappers that pick the parameters.
The blur scatters only the non-transparent texels, which makes atlas
shadows 3-7x faster than the old gather-with-clamping version. Atlas
output is unchanged apart from rounding (alpha differs by at most 1).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The backend rasterizes coverage (or color, for emoji) as usual, then a
shared CPU pass pads the bitmap, blurs an offset copy of the coverage
and composites the text over it as RGBA8888. Blur and offset scale with
the font size and with DPI. The flag is part of the string cache key,
and the text is still aligned by its own size, with the quad grown to
fit the shadow.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
nanosvg walks every scanline of the target for each visible shape, so
rasterizing the whole SVG and cropping the images out of it cost
shapes * canvas area. Each image is now rasterized into a buffer the size
of its bounding box, from a list of copies of its own shapes, so the
images can be rasterized on the thread pool.
Atlas generation on a Redmi 9A (32-bit) goes from about 165 ms to about
95 ms, and on a desktop from about 10 ms to 6 ms. Parsing the SVG is now
the largest part. The atlas layout is unchanged; a few edge pixels differ
by at most 2/255 from the different subpixel offsets.
Also copy whole rows in Image::copyfrom, and fix a leak of the SVG file
data when parsing fails.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MeasureString was slow mostly because of wrapping: DrawStringRect and
MeasureStringRect re-ran the word wrapper every frame, and it measures
every prefix of every word. Wrapped strings are now cached. The caches
are unordered_maps with an xxhash of the key, and lookups reuse a key
buffer so a hit doesn't allocate. UIContext also skips switching fonts
when measuring in the current style. In a Mac debug build, 200 cached
measures go from 0.26 to 0.06 ms and 10 wrapped paragraphs from 2.1 to
0.01 ms per frame.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#22675
Since 3aa4714ce0 desktop Windows draws text with DirectWrite, which looked
up every font in a collection of only our bundled fonts, so an override
like "Font = 微软雅黑" silently fell back to Roboto. Fall back to the
system font collection when the family isn't one of ours.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Scoped storage no longer flips on while no activity is registered: whether
legacy storage was preserved is fixed per install, so it's asked once.
- Free space reads as unknown (-1), not 0 bytes, without an activity.
- Strings from Java (file listings and info, crash history, the camera list,
the input debug string) go through GetJavaString, so names with characters
outside the BMP aren't mangled, and a null string can't crash the crash
history.
- Requests (inputbox, browse_*) whose command can't be delivered are failed
instead of left waiting forever.
- A font that fails to load uses the default typeface (-1) instead of font 0,
and is retried later if there was no activity yet. Missing exception checks
added in the text renderer.
- Method IDs and the storage callbacks are looked up once in JNI_OnLoad.
- Unknown permissions are logged correctly.
Also removes what was left of Moga support, the unused backbuffer format and
a stray MessageBox macro, and tidies System_GetCameraDeviceList.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SDL3_ttf shapes a whole string in one direction, guessed from its first
character, so a Latin run inside Hebrew text (or a Hebrew run inside
English) came out reversed - "PPSSPP Gold" rendered as "PPSSPP dloG".
When a string mixes directions, reorder each line into visual order with
FriBidi and render it left-to-right. Strings in a single direction are
left to SDL3_ttf, which already handles pure Hebrew and shapes Arabic
better than FriBidi's presentation forms.
FriBidi is optional; without it, mixed strings render as before.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
VertexDecoderRiscV.cpp got <cfloat> through SmallDataConvert.h, and three
headers used malloc/free without <cstdlib>, which libc++ no longer brings in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each TextDrawerAndroid took a global ref to the TextRenderer class and
never released it (the release had been commented out as crashing), plus
leaked the local ref it was made from. A TextDrawer is created every time
graphics come up, so that was one of each per resume.
Look the class up once and keep it. Also initialize the method IDs, which
IsReady() read uninitialized if the class was missing, and don't walk into
a null array if renderText throws.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
registerCallbacks took a new global ref to the activity on every onCreate
without releasing the previous one, and unregisterCallbacks only runs on a
real shutdown. Since rotation and resizing recreate the activity, each of
those leaked the old one, view hierarchy and all.
Releasing it means other threads can no longer use the raw global ref,
which had been copied into AndroidStorage and TextDrawerAndroid and was
read from the emu thread and IO threads. They now take a local ref under a
mutex (Android_GetActivity, and the equivalent inside AndroidStorage for
the shortcut activity's sake), so a call in flight keeps its activity
alive while the UI thread swaps in the new one.
The "no activity" bail is a null check on that local ref rather than a
separate HasActivity() test beforehand. Between such a test and the call
the UI thread can swap the activity out, and we'd hand null to a Java
method that wants a Context. The one remaining HasActivity() caller never
passes the activity on, so it has nothing to race with.
The ContentUri class ref and method IDs are now only looked up once,
instead of leaking a class ref per activity.
Headless and the unit test executable link Common but not app-android.cpp, so
they get a stub for this the same way they already do for getEnv and findClass.
Without it the Android CI build fails at link time, where a normal gradle build
is perfectly happy.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01152igCdjTNhT29qvphPyPC
These own GPU objects, memory or refcounts in their destructors (or assert
there that they were torn down), so a copy would double-free. Nothing copies
them today; this keeps it that way. The manager base classes cover every
backend's subclass.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The only guard against writing past the fixed 65536-entry verts_
array was _dbg_assert_msg_, which is compiled out entirely outside
_DEBUG - a large enough single batch (e.g. Circle()/CircleSegment()
with a big segment count, or a large UI list without an intervening
Flush()) silently corrupted the heap in release builds. Added a real
bounds check that drops the vertex instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4QAoxV2KY7ek4PcZw3WvY
TextureLoadTask::Run() only freed the VFS-read file buffer on the
success path - a corrupt/truncated image in a texture pack or
replacement texture directory (valid magic, bad payload) leaked the
whole file's bytes on every load attempt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4QAoxV2KY7ek4PcZw3WvY
Our Qt backend has long been left behind and doesn't even support Vulkan
currently. There would be a lot of work to make it viable, and I don't
think anyone is really interested.
ImGui on SDL will soon fulfill the need for a more classic user interface
with a menu bar on Linux, and on Mac we already have a native UI.
pngLoadPtr allocated the decoded buffer directly from attacker-controlled
PNG IHDR dimensions with no upper bound, so browsing a crafted game icon
or savedata could trigger a multi-gigabyte allocation.
- Add maxWidth/maxHeight parameters to pngLoadPtr (default 8192x8192)
and reject images larger than the limits.
- Thread the limits through LoadTextureLevelsFromFileData,
CreateTextureFromFileData, and CreateTextureFromFile.
- Limit game icons to 256x128 in GameInfoCache and IconCache.
The D2D-based text renderer requires a D3D11 context, which confuses
RenderDoc (it doesn't permit multiple 3D APIs in the same process).
So, let's restore the old GDI renderer and use it if RenderDoc is attached.
See #21638