Pace bounded HiW output on the existing presenter clock

This commit is contained in:
Shokunin
2026-10-21 07:34:12 -07:00
committed by Matthew Stanley
parent 779bc43030
commit d0cdcfa81d
11 changed files with 180 additions and 17 deletions
+16
View File
@@ -2055,3 +2055,19 @@ R4 recorded-route production probe improves 53.371 to 57.472 guest Hz;
zero-gap acceptance remains open. Matched GPU completion probes also reject
a separate depth shader (no improvement); that experiment was removed.
See title analysis/render-60fps/SLOWDOWNS.md for receipts and limitations.
### 2026-10-21: Bounded asynchronous presentation pacing
Publish drawable sizes from SDL3 pixel-size events instead of querying window
properties per frame. Batch SPSC queue consumption/accounting on separated
producer/consumer cache lines. For HiW hold output at guest rate, move deadline
waits to the existing presenter; its bounded queue paces the guest without a
second independent timer. Queued scheduling preserves early producer anchors.
Guest device timing and quality settings are unchanged. Hardware image/mask,
resize and scheduling controls pass. R4 Release default Ultra recorded drives
cover 60 seconds at 60.000 guest Hz, p99 emulation busy 52.3% idle / 56.4% with
23.61% total host CPU background load; zero presented gaps >25 ms, maxima
17.286/17.567 ms. Internal callback/completion gaps are separately reported and
are not claimed to pass that limit. See title SLOWDOWNS.md for attribution,
receipts, and clock definitions. No release or main-branch landing.
+5 -1
View File
@@ -365,7 +365,7 @@ if(BUILD_TESTING)
--output "${CMAKE_CURRENT_BINARY_DIR}/gl-render-thread-results")
set_tests_properties(gl_render_thread_test PROPERTIES
LABELS "gpu;opengl;hardware" TIMEOUT 300 SKIP_RETURN_CODE 77)
foreach(_interp_mode normal hiw wide_quality hiw_wide_quality)
foreach(_interp_mode normal hiw wide_quality hiw_wide_quality hiw_wide_quality_present)
set(_interp_args --interpolation)
if(_interp_mode MATCHES "wide_quality")
list(APPEND _interp_args --wide-present --quality)
@@ -384,6 +384,10 @@ if(BUILD_TESTING)
set_tests_properties(gl_render_thread_interpolation_${_interp_mode}_test
PROPERTIES ENVIRONMENT "PSX_GL_HIRES_WINDOW=1")
endif()
if(_interp_mode MATCHES "_present$")
set_tests_properties(gl_render_thread_interpolation_${_interp_mode}_test
PROPERTIES ENVIRONMENT "PSX_GL_HIRES_WINDOW=1;PSX_GL_TEST_PRESENT_THREAD=1")
endif()
endforeach()
# Frame generation ([video] frame_generation): the real presented
# images are identical with it off and on (VRAM and native-wide
+3
View File
@@ -17,6 +17,9 @@ typedef struct FrameInterpolationSchedule {
* plans the whole 0..1 window, which is the historical behaviour. */
double phase_lo;
double phase_hi;
/* Bounded asynchronous producer: preparing several frames ahead is normal,
* so an early arrival must not reset the presentation clock. */
int queued;
} FrameInterpolationSchedule;
/* Plan one stock guest-frame interval. Presentation deadlines remain anchored
+8
View File
@@ -157,6 +157,8 @@ void gl_renderer_stereo_diag(GLRenderStereoDiag *out);
uint64_t gl_renderer_perf_ticks(void);
uint64_t gl_renderer_perf_frequency(void);
int gl_renderer_interpolation_owns_cadence(void);
/* Nonblocking, atomically published readiness for bounded queued pacing. */
int gl_renderer_interpolation_async_cadence(void);
void gl_renderer_interpolation_diag(int *enabled, int *suspended,
int *history_frames,
double *host_hz, double *target_hz,
@@ -576,6 +578,12 @@ typedef struct {
uint64_t prior_ticks[8], prior_max_ticks[8];
} GlRenderWorkStats;
void gl_renderer_work_stats(GlRenderWorkStats *out);
/* Opt-in replay receipt: actual SwapWindow return times, process-clock us.
* Query outside measurement: drains the render/present queues. */
#define GL_SWAP_TIMING_CAP 65536
typedef uint64_t (*GlSwapClockFn)(void);
void gl_renderer_swap_timing_start(GlSwapClockFn clock);
int gl_renderer_swap_timestamps(uint64_t *out, int cap);
/* Native-wide mirror ablation (perf attribution, debug cmd gl_ws_ablate):
* 0 = normal, 1 = skip the whole mirror pass (incl. wide_clear), 2 = full mirror
+1 -1
View File
@@ -45,7 +45,7 @@ int frame_interpolation_schedule_begin_phase(FrameInterpolationSchedule *schedul
* Normal guest work lands after the prior deadline but before the next one;
* retaining the old anchor makes that work consume the pacing budget. */
if (schedule->source_deadline <= 0.0 ||
now_d + source_period < schedule->source_deadline ||
(!schedule->queued && now_d + source_period < schedule->source_deadline) ||
now_d > schedule->source_deadline + source_period * 4.0) {
schedule->frame_start = now_d;
schedule->source_deadline = now_d + source_period;
+65 -6
View File
@@ -746,6 +746,8 @@ static double s_interp_host_hz = 0.0;
static double s_interp_target_hz = 0.0;
static double s_interp_source_hz = 0.0;
static FrameInterpolationSchedule s_interp_schedule;
static uint64_t s_interp_pending_deadline;
static _Atomic int s_interp_async_cadence;
/* Blend source (psx_mod_set_frame_interpolation_source). VBLANK (0, the
* default) treats every guest VBlank present as a new frame, exactly as
* before. FLIP (1) rotates history only on a real flip -- the display origin
@@ -8063,6 +8065,7 @@ void gl_renderer_set_ws_ablate(int mode) {
int gl_renderer_get_ws_ablate(void) { return s_ws_ablate; }
static void interp_reset_history_unlocked(void) {
atomic_store_explicit(&s_interp_async_cadence, 0, memory_order_release);
s_interp_valid = 0;
s_interp_w = s_interp_h = 0;
s_interp_src_w = s_interp_src_h = 0;
@@ -8158,6 +8161,10 @@ int gl_renderer_interpolation_owns_cadence(void) {
return s_ctx && s_interp_enabled && !interp_suspended_now();
}
int gl_renderer_interpolation_async_cadence(void) {
return atomic_load_explicit(&s_interp_async_cadence, memory_order_acquire);
}
void gl_renderer_interpolation_diag(int *enabled, int *suspended,
int *history_frames,
double *host_hz, double *target_hz,
@@ -8332,7 +8339,7 @@ static int interp_present(float alpha) {
s_interp_blend_mode);
}
static void interp_wait_until(uint64_t deadline, uint64_t frequency) {
static void interp_wait_until_ex(uint64_t deadline, uint64_t frequency, int render_idle) {
uint64_t now, start;
if (!deadline || !frequency) return;
start = SDL_GetPerformanceCounter();
@@ -8340,8 +8347,10 @@ static void interp_wait_until(uint64_t deadline, uint64_t frequency) {
now = SDL_GetPerformanceCounter();
if (now >= deadline) {
/* Idle host time feeds the render-pass budget. */
(void)s_idle_ticks_accum_fwd(now - start);
if (s_dyn_on) s_dyn_ledger.idle_ticks += now - start;
if (render_idle) {
(void)s_idle_ticks_accum_fwd(now - start);
if (s_dyn_on) s_dyn_ledger.idle_ticks += now - start;
}
return;
}
uint64_t remain = deadline - now;
@@ -8354,8 +8363,28 @@ static void interp_wait_until(uint64_t deadline, uint64_t frequency) {
}
}
static void interp_wait_until(uint64_t deadline, uint64_t frequency) {
interp_wait_until_ex(deadline, frequency, 1);
}
static int hiw_async_pacing(void) {
static int enabled = -1;
if (enabled < 0) {
const char *e = getenv("PSX_GL_HIW_ASYNC_PACE");
enabled = !(e && e[0] == '0');
}
/* HiW refuses temporal render transactions already. Hold at guest rate
* needs one composed output per VBlank, with its wait on the presenter so
* the renderer can prepare the next scene. Other modes retain their render
* pass/idle-credit schedule unchanged. */
return enabled && s_hiw && s_interp_hold && s_pt_on && s_rth_on &&
rt_on_render_thread() && s_interp_target_hz <= s_interp_source_hz + 1e-6;
}
static void interp_present_source_interval_timed_impl(void) {
static uint64_t diag_start, diag_swaps, diag_captures;
const int asynchronous = hiw_async_pacing();
s_interp_schedule.queued = asynchronous;
atomic_store_explicit(&s_interp_async_cadence, asynchronous, memory_order_release);
uint64_t frequency = SDL_GetPerformanceFrequency();
uint64_t now = SDL_GetPerformanceCounter();
uint64_t deadline;
@@ -8371,14 +8400,16 @@ static void interp_present_source_interval_timed_impl(void) {
while (frame_interpolation_schedule_next(
&s_interp_schedule, SDL_GetPerformanceCounter(),
&deadline, &alpha)) {
interp_wait_until(deadline, frequency);
if (!asynchronous) interp_wait_until(deadline, frequency);
s_interp_pending_deadline = asynchronous ? deadline : 0;
latency_ring_mark(LAT_SWAP_BEGIN);
if (!pass_gen_present(deadline))
(void)interp_present(alpha);
latency_ring_mark(LAT_SWAP_END);
s_interp_pending_deadline = 0;
}
interp_wait_until(frame_interpolation_schedule_end(&s_interp_schedule),
frequency);
if (!asynchronous)
interp_wait_until(frame_interpolation_schedule_end(&s_interp_schedule), frequency);
now = SDL_GetPerformanceCounter();
if (!diag_start) {
@@ -10113,6 +10144,7 @@ static int s_pt_want = 0, s_pt_slots = 3;
static GLenum s_pt_format = GL_RGBA8; /* the window's: no alpha -> GL_RGB8 */
static SDL_GLContext s_pt_ctx = NULL;
static GLuint s_pts_tex[PT_MAX_SLOTS], s_pts_rb[PT_MAX_SLOTS], s_pts_fbo[PT_MAX_SLOTS];
static uint64_t s_pts_deadline[PT_MAX_SLOTS];
static int s_pts_w[PT_MAX_SLOTS], s_pts_h[PT_MAX_SLOTS];
static unsigned s_pts_gen[PT_MAX_SLOTS];
/* present thread's own objects (FBOs are per-context) */
@@ -10181,6 +10213,8 @@ static int pt_cb_ctx(void *user, int current) {
/* The one owned swap call: the composing thread's (direct) or the present
* thread's. */
static uint64_t s_swap_times[GL_SWAP_TIMING_CAP], s_swap_times_n;
static _Atomic(GlSwapClockFn) s_swap_clock;
static void gl_window_swap(void) {
#if defined(PSX_SDL3)
psx_video_telemetry_present(SDL_GL_SwapWindow(s_win) ? 1 : 0);
@@ -10188,6 +10222,27 @@ static void gl_window_swap(void) {
SDL_GL_SwapWindow(s_win);
psx_video_telemetry_present(-1); // SDL2 exposes no swap result.
#endif
GlSwapClockFn clock = atomic_load_explicit(&s_swap_clock, memory_order_acquire);
if (clock) s_swap_times[s_swap_times_n++ % GL_SWAP_TIMING_CAP] = clock();
}
void gl_renderer_swap_timing_start(GlSwapClockFn clock) {
GL_RT_SYNC("swap_timing_start");
if (s_pt_on) pt_drain();
s_swap_times_n = 0;
atomic_store_explicit(&s_swap_clock, clock, memory_order_release);
}
int gl_renderer_swap_timestamps(uint64_t *out, int cap) {
if (!out || cap <= 0) return 0;
GL_RT_SYNC("swap_timestamps");
if (s_pt_on) pt_drain();
/* Context handoff plus presenter drain leave the single swap writer idle. */
uint64_t n = s_swap_times_n < GL_SWAP_TIMING_CAP ? s_swap_times_n : GL_SWAP_TIMING_CAP;
if (n > (uint64_t)cap) n = (uint64_t)cap;
uint64_t first = s_swap_times_n - n;
for (uint64_t i=0; i<n; ++i) out[i] = s_swap_times[(first+i) % GL_SWAP_TIMING_CAP];
return (int)n;
}
static void *pt_cb_present(void *user, int slot, void *ready) {
@@ -10224,6 +10279,9 @@ static void *pt_cb_present(void *user, int slot, void *ready) {
GL_PRESENT_THREAD_TEST_HOOK(slot);
#endif
void *done = p_glFenceSync(PT_SYNC_GPU_COMMANDS_COMPLETE, 0);
/* Submission publishes this slot's deadline under the presenter queue
* mutex. This wait grants no renderer idle credit: it overlaps rendering. */
interp_wait_until_ex(s_pts_deadline[slot], SDL_GetPerformanceFrequency(), 0);
gl_window_swap();
return done;
}
@@ -10303,6 +10361,7 @@ static void pt_end(void) {
static void gl_present_swap(void) {
if (!s_pt_on) { gl_window_swap(); return; }
(void)pt_target_fbo(); /* a frame that never bound 0 still has its slot */
s_pts_deadline[pt_current()] = s_interp_pending_deadline;
void *ready = p_glFenceSync(PT_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
void *done = NULL;
+42 -7
View File
@@ -4070,6 +4070,17 @@ static int present_effective_swap_interval(void) {
}
static int present_should_wall_pace(void) {
static const bool queued_pace = [] {
const char *e = std::getenv("PSX_GL_ASYNC_GUEST_PACE");
return !(e && e[0] == '0');
}();
/* The presenter explicitly waits for each guest-rate deadline. Bounded
* render/present queues carry that clock back to the guest, independently
* of driver vsync; a second guest timer only serializes the pipeline. */
if (queued_pace && g_frame_interpolation && g_gl_active &&
gl_renderer_render_thread_active() && !rt_held() &&
gl_renderer_interpolation_async_cadence())
return 0;
/* While this thread owns the GL context, temporal presentation subdivides
* this same interval. When rendering is asynchronous, cap the guest here:
* the renderer's presentation deadline belongs to its own thread. */
@@ -4993,14 +5004,20 @@ static void openxr_ready_report_tick(uint64_t frames) {
/* Explicit, file-driven production benchmark. No debug server, sampler or
* per-instruction tracing is needed. Capture outside the measured interval;
* export the existing frame ring before requesting the normal quit path. */
struct ReplayWorkSample { uint64_t frame, guest, pacer, queue, completed; };
static std::vector<ReplayWorkSample> g_replay_work;
static void input_replay_frame_completed(uint64_t frame) {
if (g_replay_work.empty()) return;
auto &sample = g_replay_work[frame % HOST_FRAME_STAMP_CAP];
if (sample.frame == frame) sample.completed = host_clock_us();
}
static void input_replay_frontend_tick(uint64_t frame) {
uint64_t relative = input_replay_frame(frame);
if (relative == UINT64_MAX) return;
static uint64_t stop = 0, seconds = 0, deadline_us = 0;
static const char *report = nullptr, *shots = nullptr;
static bool initialized = false, finished = false;
struct WorkSample { uint64_t frame, guest, pacer, queue; };
static std::vector<WorkSample> work;
auto &work = g_replay_work;
if (!initialized) {
if (const char *e = std::getenv("PSX_INPUT_REPLAY_STOP")) stop = std::strtoull(e, nullptr, 10);
if (const char *e = std::getenv("PSX_INPUT_REPLAY_SECONDS")) {
@@ -5011,7 +5028,10 @@ static void input_replay_frontend_tick(uint64_t frame) {
}
report = std::getenv("PSX_INPUT_REPLAY_REPORT");
shots = std::getenv("PSX_INPUT_REPLAY_SHOTS");
if (report && *report) work.resize(HOST_FRAME_STAMP_CAP);
if (report && *report) {
work.resize(HOST_FRAME_STAMP_CAP);
if (g_gl_active) gl_renderer_swap_timing_start(host_clock_us);
}
initialized = true;
}
if (finished) return;
@@ -5024,7 +5044,7 @@ static void input_replay_frontend_tick(uint64_t frame) {
const uint64_t hz = SDL_GetPerformanceFrequency();
work[frame % HOST_FRAME_STAMP_CAP] = {frame, g_runtime_perf.guest_work_ticks * 1000000 / hz,
g_runtime_perf.pacer_ticks * 1000000 / hz,
(rs.backpressure_ns + rs.ring_full_ns + rs.acquire_ns) / 1000};
(rs.backpressure_ns + rs.ring_full_ns + rs.acquire_ns) / 1000, 0};
}
if (shots && (relative == 800 || (stop > 0 && relative == stop+2))) {
char path[512];
@@ -5036,7 +5056,7 @@ static void input_replay_frontend_tick(uint64_t frame) {
if (report && *report) {
FILE *f = std::fopen(report, "w");
if (!f) throw std::runtime_error("cannot write input replay frame report");
std::fputs("frame,relative,us,interp,checks,faults,io_kb,guest_us,pacer_us,queue_us\n", f);
std::fputs("frame,relative,us,interp,checks,faults,io_kb,guest_us,pacer_us,queue_us,completed_us\n", f);
uint64_t total = host_frame_stamp_total();
uint64_t first = total > HOST_FRAME_STAMP_CAP ? total-HOST_FRAME_STAMP_CAP : 0;
for (uint64_t i=first; i<total; i++) {
@@ -5046,10 +5066,10 @@ static void input_replay_frontend_tick(uint64_t frame) {
if (rel == UINT64_MAX) continue;
const auto &w = work[fr % HOST_FRAME_STAMP_CAP];
if (w.frame != fr) continue;
std::fprintf(f, "%u,%llu,%llu,%u,%u,%u,%u,%llu,%llu,%llu\n", fr,
std::fprintf(f, "%u,%llu,%llu,%u,%u,%u,%u,%llu,%llu,%llu,%llu\n", fr,
(unsigned long long)rel, (unsigned long long)us, ii, ck, pf, io,
(unsigned long long)w.guest, (unsigned long long)w.pacer,
(unsigned long long)w.queue);
(unsigned long long)w.queue, (unsigned long long)w.completed);
}
if (std::fclose(f) != 0) throw std::runtime_error("cannot finish input replay frame report");
std::string span_path = std::string(report) + ".spans.csv";
@@ -5077,6 +5097,14 @@ static void input_replay_frontend_tick(uint64_t frame) {
if (std::fclose(f) != 0) throw std::runtime_error("cannot finish input replay render thread report");
GlRenderWorkStats stats{};
gl_renderer_work_stats(&stats);
std::vector<uint64_t> swaps(GL_SWAP_TIMING_CAP);
const int swap_count = gl_renderer_swap_timestamps(swaps.data(), (int)swaps.size());
std::string swap_path = std::string(report) + ".present.csv";
f = std::fopen(swap_path.c_str(), "w");
if (!f) throw std::runtime_error("cannot write input replay presentation report");
std::fputs("us\n", f);
for (int i=0; i<swap_count; ++i) std::fprintf(f,"%llu\n",(unsigned long long)swaps[i]);
if (std::fclose(f) != 0) throw std::runtime_error("cannot finish input replay presentation report");
std::string path = std::string(report) + ".render.csv";
f = std::fopen(path.c_str(), "w");
if (!f) throw std::runtime_error("cannot write input replay render report");
@@ -11251,6 +11279,13 @@ extern "C" int psx_dynres_status_json(char *out, int cap) {
static void sdl_vblank_present(void) {
extern uint64_t s_frame_count;
struct ReplayCompletion {
~ReplayCompletion() {
extern uint64_t s_frame_count;
input_replay_frame_completed(s_frame_count);
}
} completion;
sync_guest_cadence_to_video_standard();
NetplayVblankEpilogue ep = sdl_vblank_present_body();
render_thread_vblank();
+2 -1
View File
@@ -162,7 +162,8 @@ def main():
"PSX_GL_STATE_QUERY_FAST", "PSX_GL_QUEUE_UPLOAD_FAST",
"PSX_GL_BLOOM_FUSE_EXTEND", "PSX_GL_TEXEL_CACHE",
"PSX_GL_HIW_COPY_FAST", "PSX_GL_IDENTITY_COPY_FAST",
"PSX_GL_DRAWABLE_EVENTS", "PSX_RENDER_QUEUE_BATCH")
"PSX_GL_DRAWABLE_EVENTS", "PSX_RENDER_QUEUE_BATCH",
"PSX_GL_HIW_ASYNC_PACE")
controls = [{flag: "0" for flag in flags}]
controls += [{flag: "0"} for flag in flags]
controls += [{"PSX_GL_TEXEL_CACHE_LAYERS": "4"}]
@@ -54,7 +54,9 @@ assert "if (source != s_interp_source) interp_reset_history_unlocked();" in sett
assert "s_interp_source = source;" in setter
# ---- interp_capture's FLIP branch (gpu_gl_renderer.c) -----------------------
capture = function_body(GL, "static int interp_capture(GLuint fbo,")
wrapper = function_body(GL, "static int interp_capture(GLuint fbo,")
assert "interp_capture_timed_impl(" in wrapper, "timed wrapper must invoke the capture implementation"
capture = function_body(GL, "static int interp_capture_timed_impl(GLuint fbo,")
flip_at = capture.index("if (s_interp_source == 1) {")
flip = capture[flip_at:capture.index("} else {", flip_at)]
flat = squash(flip)
@@ -284,6 +284,25 @@ int main(void) {
&schedule, 0u, 1000000u, 60.0, 59.0),
"output below source cadence should be rejected");
frame_interpolation_schedule_reset(&schedule);
schedule.queued = 1;
for (int i = 1; i <= 6; ++i) {
uint64_t deadline;
float alpha;
CHECK(frame_interpolation_schedule_begin(&schedule, 1000000u,
1000000u, 60.0, 60.0), "queued interval accepted");
CHECK(frame_interpolation_schedule_next(&schedule, 1000000u,
&deadline, &alpha), "queued interval has one output");
CHECK(fabs((double)deadline - (1000000.0 + i * 1000000.0 / 60.0)) < 1.0,
"early producer retains every presentation deadline");
CHECK(!frame_interpolation_schedule_next(&schedule, 1000000u,
&deadline, &alpha), "queued interval has no duplicate output");
}
CHECK(frame_interpolation_schedule_begin(&schedule, 2000000u,
1000000u, 60.0, 60.0), "queued clock recovers after sustained stall");
CHECK(frame_interpolation_schedule_end(&schedule) == 2016667u,
"late queued producer reanchors after sustained stall");
printf(failures ? "FAILED (%d)\n" : "ALL PASS\n", failures);
return failures ? 1 : 0;
}
+16
View File
@@ -132,6 +132,10 @@ static void test_live_resolution(int original_scale) {
check(gl_renderer_set_internal_scale_live(original_scale) == original_scale,
"live resolution recovers after refusal");
}
static uint64_t fixture_clock_us(void) {
const uint64_t now = SDL_GetPerformanceCounter(), hz = SDL_GetPerformanceFrequency();
return now / hz * 1000000u + now % hz * 1000000u / hz;
}
static int drawable_worker(void *data) {
int *size = data;
gl_drawable_size(&size[0], &size[1]);
@@ -346,6 +350,7 @@ int main(int argc, char **argv) {
gr_set_scale(scale);
if (dynres) gl_renderer_set_dynamic_resolution(1);
gl_renderer_set_swap_interval(0);
if (getenv("PSX_GL_TEST_PRESENT_THREAD")) gl_renderer_set_present_thread(1, 3);
if (!gl_renderer_init_context(win)) { fprintf(stderr, "FAIL context\n"); return 2; }
check(gr_backend() == GR_BACKEND_OPENGL, "OpenGL backend");
textures();
@@ -353,6 +358,7 @@ int main(int argc, char **argv) {
check(hiw_ensure(0, FRAME_W) != NULL, "first hires buffer");
check(hiw_ensure(512, 512+FRAME_W) != NULL, "second hires buffer");
}
gl_renderer_swap_timing_start(fixture_clock_us);
gr_wide_configure(426, 53);
if (quality_scene) {
gl_renderer_set_bloom(1.0f);
@@ -508,6 +514,16 @@ int main(int argc, char **argv) {
check(st.acquires >= 2 && st.acquires <= (uint64_t)frames + 4, "acquires bounded");
check(st.bytes > ((uint64_t)32 << 20), "ring wrapped (more than its size recorded)");
}
{
uint64_t times[256], tail[2];
int count = gl_renderer_swap_timestamps(times, 256);
check(count > 2, "swap completion timestamps captured");
for (int i=1; i<count; ++i)
check(times[i] >= times[i-1], "swap clock is monotonic");
check(gl_renderer_swap_timestamps(tail, 2) == 2 &&
tail[0] == times[count-2] && tail[1] == times[count-1],
"bounded swap query returns newest entries");
}
if (drawable_events_enabled())
check(s_drawable_queries == drawable_queries_before, "no per-frame drawable queries");
/* Pixel-size events publish resizes without querying the window on a