fix(render): one fence per frame for the vertex ring; two qt6 adapter fixes
The persistent-mapped instance ring had three slots and recorded a fence per upload, so a frame recycled a slot whose fence was recorded a few draws earlier *in that same frame*: from the fourth of the demos' nine uploads on, each one waited on the frame's own work. It is free on a driver that retires draws promptly (the GLFW host, 1.07 ms/frame) and ~4 ms of the Qt widget's 7.11 ms under llvmpipe, and it grows with the trace and cursor counts this library exists for. * The ring grows to cover a frame's upload count, the frame's write cursor is the ring index (so a frame writes 0, 1, 2, ... and never a slot twice), one fence is recorded after the frame's last draw, and the only wait is at the start of the next frame. A wait that times out, or a slot written with no fence ever recorded behind it, drops the store to the orphan path -- the treatment a refused fence already had -- so nothing is ever written into storage the GPU may still be reading. * New test-only counters (PLOT_RING_STATS, the PLOT_PYRAMID_COUNT_* pattern: free functions, no member, so no consumer's layout changes) pin a frame's upload and wait counts. The new tests assert the frame's slot offsets are distinct, that the ring grew past its floor to cover the frame, and that a frame waits at most once per store -- read from the counters and from glClientWaitSync itself. * qt6: print the unresolved entry-point names before the load-failure branch, which used to return first and leave the one failure this widget can have reporting nothing but a generic message. * qt6: the cached pointer anchor is now in logical pixels and a move's delta is the logical difference scaled by the DPR in effect, so a scale change between two move events (the window dragged onto another monitor mid-drag) is no longer a cross-scale jump applied as a pan. The anchor moved into a toolkit-free unit (adapters/qt6/qt_pointer.hpp) with its own test. GLFW demo headless: 1.07 -> 0.73 ms/frame. Qt widget under xvfb/llvmpipe: 7.1 -> 3.8 ms at dpr 1.0, 6.7 -> 3.6 ms at 1.25, with both upload paths still rendering and the demo's scripted pan/box-zoom checks passing at both scales.
This commit is contained in:
1 parent
1ee2dd9c3d
commit
3c0e7763ab
13 files changed
+626
-113
No files matched your search
@@ -1,4 +1,5 @@
|
||||
#include <qt6/qt_plot_widget.hpp>
|
||||
#include <qt6/qt_pointer.hpp>
|
||||
|
||||
#include <plot/gl/loader.hpp>
|
||||
|
||||
@@ -114,21 +115,26 @@ void PlotWidget::initializeGL() {
|
||||
renderer_.reset();
|
||||
|
||||
g_unresolved_count = 0;
|
||||
if (!plot::gl::load(&resolve_via_qt)) {
|
||||
const bool loaded = plot::gl::load(&resolve_via_qt);
|
||||
// Printed before the failure branch, not after it: the names collected here are
|
||||
// precisely what explains a failure -- which 3.3 entry point this context could
|
||||
// not provide -- so a load that failed must report them too, or the one failure
|
||||
// this widget can have says nothing but "missing a required entry point".
|
||||
if (g_unresolved_count > 0) {
|
||||
std::fprintf(stderr,
|
||||
"[plot::qt6] QOpenGLContext::getProcAddress() did not resolve %u required"
|
||||
" entry point(s); plot::gl::load() %s for:",
|
||||
g_unresolved_count, loaded ? "used its dlsym fallback" : "failed");
|
||||
for (u32 i = 0; i < g_unresolved_count; ++i) std::fprintf(stderr, " %s", g_unresolved[i]);
|
||||
std::fprintf(stderr, "\n");
|
||||
}
|
||||
if (!loaded) {
|
||||
init_status_ = Status{ErrorCode::Unsupported,
|
||||
"plot::gl::load() failed: the current context is missing a required"
|
||||
" OpenGL 3.3 entry point"};
|
||||
std::fprintf(stderr, "[plot::qt6] %s\n", init_status_.message);
|
||||
return;
|
||||
}
|
||||
if (g_unresolved_count > 0) {
|
||||
std::fprintf(stderr,
|
||||
"[plot::qt6] QOpenGLContext::getProcAddress() did not resolve %u required"
|
||||
" entry point(s); plot::gl::load() used its dlsym fallback for:",
|
||||
g_unresolved_count);
|
||||
for (u32 i = 0; i < g_unresolved_count; ++i) std::fprintf(stderr, " %s", g_unresolved[i]);
|
||||
std::fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
renderer_.emplace();
|
||||
Status status;
|
||||
@@ -206,19 +212,24 @@ Vec2 PlotWidget::to_device(const QPointF& p) const noexcept {
|
||||
void PlotWidget::refresh_viewport() { controller_.set_viewport(device_viewport()); }
|
||||
|
||||
void PlotWidget::forward_pointer(QMouseEvent* event, bool pressed, bool released) {
|
||||
const Vec2 p = to_device(event->position());
|
||||
// The anchor stays logical (see PointerAnchor): a move that arrives after the
|
||||
// window moved to a display with another scale factor must not measure the gap
|
||||
// between a position taken at the old DPR and one taken at the new one. The
|
||||
// delta is the logical difference scaled by the ratio in effect now, so the same
|
||||
// gesture maps to the same device distance at either scale.
|
||||
const QPointF position = event->position();
|
||||
const f64 dpr = static_cast<f64>(devicePixelRatioF());
|
||||
const Vec2 logical{static_cast<f32>(position.x()), static_cast<f32>(position.y())};
|
||||
const Vec2 p = to_device(position);
|
||||
const Vec2 delta = anchor_.device_delta(logical, dpr);
|
||||
input::PointerEvent e;
|
||||
e.x = p.x;
|
||||
e.y = p.y;
|
||||
if (have_pointer_) {
|
||||
e.dx = p.x - last_x_;
|
||||
e.dy = p.y - last_y_;
|
||||
}
|
||||
e.dx = delta.x;
|
||||
e.dy = delta.y;
|
||||
// A press is also a pointer sample: the next move's delta starts here, or the
|
||||
// first motion of a drag would include the jump to the click.
|
||||
last_x_ = p.x;
|
||||
last_y_ = p.y;
|
||||
have_pointer_ = true;
|
||||
anchor_.set(logical);
|
||||
e.button = button_from(event->button());
|
||||
e.mods = mods_from(event->modifiers());
|
||||
e.down = any_plot_button(event->buttons());
|
||||
|
||||
Reference in new issue
Block a user