gallery/vendor/i-slint-core/gdb_pretty_printers.py
Greg Pomerantz 2f4768f354 Fix 120Hz scroll jank end-to-end: render-ahead present + full-cache eviction off the UI thread
Three root causes, all fixed and verified on the Pixel 9 Pro:

1. Present phase (backend + renderer): VSYNC_LOCKED_RENDER defers the
   render to the vsync wake; RENDER_AHEAD presents the previous frame at
   the start of render() (phase = wake + ~1.1 ms) and holds the new
   frame one slot. Crash fix: a held wgpu frame must be PRESENTED, never
   merely dropped, before the next acquire (AlreadyAcquired panic at
   launch otherwise); discard_pending_frame presents too.

2. Full-res prefetch churn (src/lib.rs): the 8-slot full cache cycled
   through a ~20-cell viewport forever (137 decodes/20 s) and each
   eviction free()'d a 50 MB Vec on the UI thread (20-52 ms CPU spikes,
   ~7/s = the residual SLOWEV NonMain jank). Fix: 60 s tombstone
   cooldown on evicted paths (idle prefetch skips, urgent tap bypasses)
   + off-thread reaper for the freed blobs (insert: 21 ms -> 4 us).

3. (earlier) Skia final flush + Mailbox present mode (vendor, M3).

Instrumentation (all behind --features perf): OTHR/INVK/INVK_TAG/FLCK
lines for cross-thread closure timing and push-site tagging.

Warm measured swipe, 8580-file grid: cadence median 8.33 ms (exact
120 Hz), p99 15.5 ms, 5/552 gaps >16.7 ms (0.9%, max 28.3 ms, zero
>33 ms); PRES phase median ~994 us, zero late frames. Docs updated
(docs/BUGS/scroll-jank-120hz.md, full scorecard + line reference).

Also: vendored i-slint-core + wgpu-hal (vkshim interposition patch),
vkshim profiling tooling, backend made a non-optional dep, debuggable
flag for simpleperf (REMOVE before shipping).
2026-10-30 02:49:23 -04:00

159 lines
4.8 KiB
Python

# Copyright © Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure <david.faure@kdab.com>
# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
# GDB pretty printers for types defined in i_slint_core
import gdb
import gdb.printing
class SharedVectorProvider:
def __init__(self, val):
self.val = val
def to_string(self):
try:
size = int(self.val["inner"]["pointer"]["header"]["size"])
return f"<SharedVector, len={size}>"
except Exception as e:
return f"<SharedVector: error reading size: {e}>"
def children(self):
try:
size = int(self.val["inner"]["pointer"]["header"]["size"])
if size == 0:
return
inner_struct = self.val["inner"]["pointer"].dereference()
maybe_uninit = inner_struct["data"]
elem_type = self._element_type(maybe_uninit)
data_ptr = maybe_uninit.address.cast(elem_type.pointer())
for i in range(size):
yield f"[{i}]", (data_ptr + i).dereference()
except Exception as e:
yield "<error>", f"error reading elements: {e}"
def display_hint(self):
return "array"
@staticmethod
def _element_type(maybe_uninit_val):
ty = maybe_uninit_val.type
for field in ty.fields():
if field.name == "value":
val_type = field.type
if val_type.code == gdb.TYPE_CODE_STRUCT:
for sub in val_type.fields():
if sub.name in ("value", "__0", "0"):
return sub.type
return val_type
raise RuntimeError(
"unsupported MaybeUninit layout for SharedVector element type"
)
class SharedVectorSubPrinter(gdb.printing.SubPrettyPrinter):
def __init__(self):
super().__init__("SharedVector")
def __call__(self, val):
if not self.enabled:
return None
t = val.type.strip_typedefs()
if t.code == gdb.TYPE_CODE_PTR: # also support reference to SharedVector
try:
val = val.dereference()
t = val.type.strip_typedefs()
except gdb.error:
return None
if (
t.code == gdb.TYPE_CODE_STRUCT
and t.tag
and t.tag.startswith("i_slint_core::sharedvector::SharedVector<")
):
return SharedVectorProvider(val)
return None
class SliceProvider:
def __init__(self, val):
self.val = val
def to_string(self):
try:
return f"<Slice, len={int(self.val['len'])}>"
except Exception as e:
return f"<Slice: error reading len: {e}>"
def children(self):
try:
length = int(self.val["len"])
if length == 0:
return
data_ptr = self._data_pointer()
elem_type = data_ptr.type.target()
for i in range(length):
yield f"[{i}]", (data_ptr + i).dereference()
except Exception as e:
yield "<error>", f"error reading elements: {e}"
def display_hint(self):
return "array"
def _data_pointer(self):
nn = self.val["ptr"]
if nn.type.code == gdb.TYPE_CODE_PTR:
return nn
for field in nn.type.fields():
if field.name in ("pointer", "__0", "0"):
candidate = nn[field]
if candidate.type.code == gdb.TYPE_CODE_PTR:
return candidate
raise RuntimeError("unsupported NonNull layout in Slice")
class SliceSubPrinter(gdb.printing.SubPrettyPrinter):
def __init__(self):
super().__init__("Slice")
def __call__(self, val):
if not self.enabled:
return None
t = val.type.strip_typedefs()
if t.code == gdb.TYPE_CODE_PTR: # also support reference to Slice
try:
val = val.dereference()
t = val.type.strip_typedefs()
except gdb.error:
return None
if (
t.code == gdb.TYPE_CODE_STRUCT
and t.tag
and t.tag.startswith("i_slint_core::slice::Slice<")
):
return SliceProvider(val)
return None
class SlintCorePrettyPrinter(gdb.printing.PrettyPrinter):
def __init__(self):
super().__init__("i_slint_core", [])
self.subprinters = [SharedVectorSubPrinter(), SliceSubPrinter()]
def __call__(self, val):
for sp in self.subprinters:
pp = sp(val)
if pp is not None:
return pp
return None
printer = SlintCorePrettyPrinter()
def register_printers(objfile=None):
gdb.printing.register_pretty_printer(objfile, printer, replace=True)
register_printers()