From 9e608193e32085fafbd53e21b79ad6cb06686406 Mon Sep 17 00:00:00 2001 From: baketnk Date: Thu, 24 Sep 2026 13:12:53 -0400 Subject: [PATCH] Stabilize raw overlay pointer rendering and trace click delivery --- docs/overlay.md | 8 +++++--- src/main.cpp | 8 +++++--- src/overlay.cpp | 25 +++++++++++++++++++------ src/overlay.hpp | 1 + src/panel_surface.cpp | 35 +++++++++++++---------------------- tests/panel_test.cpp | 8 ++++++-- 6 files changed, 49 insertions(+), 36 deletions(-) diff --git a/docs/overlay.md b/docs/overlay.md index 25b52bb..a27bb02 100644 --- a/docs/overlay.md +++ b/docs/overlay.md @@ -24,9 +24,11 @@ preview, status and control surfaces use independently rasterized antialiased ed and restrained baked neon halos rather than GPU bloom. The recording indicator and selected controls remain distinguishable by their labels, not color alone. Rounded control hit areas exclude their clipped corners. -Rendering/uploads occur only for changed content, page, settings, or pointer -feedback; static frames are reused. The caller may call `draw(Panel)` at 10 ms -intervals. Tracking transforms do not require repainting the canvas. +Rendering/uploads occur only for changed content, page or settings; laser hover +and button down/up are hit-tested without a raw-texture upload, to reduce +compositor flicker reported during hover. Static frames are reused. +The caller may call `draw(Panel)` at 10 ms intervals. Tracking transforms do +not require repainting the canvas. The complete transcript preview is paginated by glyph width and four-line height; Previous and Next navigate it without changing the source transcript. diff --git a/src/main.cpp b/src/main.cpp index aa65438..ab82ede 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -89,6 +89,7 @@ int main(int argc, char** argv) { const auto end = std::chrono::steady_clock::now() + std::chrono::seconds(30); std::string status; bool held = false; + bool quit_check = false; std::string last_action = "Press panel controls or tap then hold right grip. No mic or typing."; auto action_name = [](frameyap::UiAction action) { switch (action) { @@ -103,9 +104,9 @@ int main(int argc, char** argv) { } return "Unknown"; }; - while (std::chrono::steady_clock::now() < end) { + while (!quit_check && std::chrono::steady_clock::now() < end) { for (auto action : overlay.poll()) { - if (action == frameyap::UiAction::Quit) return 0; + if (action == frameyap::UiAction::Quit) { quit_check = true; break; } if (action == frameyap::UiAction::BeginRecord) held = true; if (action == frameyap::UiAction::EndRecord || action == frameyap::UiAction::Cancel) held = false; last_action = std::string(action_name(action)) + " received; diagnostic only."; @@ -113,10 +114,11 @@ int main(int argc, char** argv) { } auto next = overlay.controls_status(); if (next != status) { status = next; std::cout << status << std::endl; } - overlay.draw({"Controls ONLY - " + std::string(held ? "HOLD recognized" : "no microphone or typing"), + overlay.draw({overlay.pointer_status(), status, last_action, true, held}); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } + std::cout << "Final " << overlay.pointer_status() << std::endl; } else { const auto end = std::chrono::steady_clock::now() + std::chrono::seconds(5); while (std::chrono::steady_clock::now() < end) { diff --git a/src/overlay.cpp b/src/overlay.cpp index 69f67b7..8ad75ab 100644 --- a/src/overlay.cpp +++ b/src/overlay.cpp @@ -66,6 +66,8 @@ struct Overlay::Impl { bool focus = true; bool persist_mount = true; vr::EVRInputError action_update_error = vr::VRInputError_None; + unsigned pointer_downs = 0, pointer_ups = 0, pointer_actions = 0, pointer_resets = 0; + std::string last_pointer_event = "none"; Impl(const std::string& assets, const std::string& font, std::optional requested, bool persist) : settings_path(default_mount_settings_path()), @@ -236,22 +238,28 @@ struct Overlay::Impl { case vr::VREvent_OverlayShown: focus = true; reset_input(result); break; case vr::VREvent_OverlayGamepadFocusLost: + surface.reset_pointers(); ++pointer_resets; + last_pointer_event = "gamepad focus lost"; break; case vr::VREvent_OverlayFocusChanged: - // Dashboard laser/gamepad focus can change between the two grip - // squeezes. It is not action activity or controller tracking loss: - // invalidate pointer presses, but leave a physical grip gesture - // armed. Hidden overlays and true OpenVR input capture still reset. - surface.reset_pointers(); break; + // This global focus notification also fires when the dashboard + // laser enters our overlay. Do not erase a press between down/up; + // a release must still hit the same enabled control. + last_pointer_event = "overlay focus changed"; break; case vr::VREvent_MouseMove: surface.pointer_move(event.data.mouse.cursorIndex, event.data.mouse.x, H - event.data.mouse.y); break; case vr::VREvent_MouseButtonDown: + ++pointer_downs; + last_pointer_event = "down button=" + std::to_string(event.data.mouse.button); if (event.data.mouse.button == vr::VRMouseButton_Left) surface.pointer_down(event.data.mouse.cursorIndex, event.data.mouse.x, H - event.data.mouse.y); break; case vr::VREvent_MouseButtonUp: + ++pointer_ups; + last_pointer_event = "up button=" + std::to_string(event.data.mouse.button); if (event.data.mouse.button == vr::VRMouseButton_Left) { auto event_result = surface.pointer_up(event.data.mouse.cursorIndex, event.data.mouse.x, H - event.data.mouse.y); + if (event_result.action || event_result.mount || event_result.recenter) ++pointer_actions; if (event_result.action) result.push_back(*event_result.action); if (event_result.mount) { mount = *event_result.mount; @@ -328,13 +336,18 @@ std::string Overlay::controls_status() { vr::VRControllerState_t raw{}; const bool raw_ok = device < impl_->poses.size() && impl_->system->GetControllerState(device, &raw, sizeof(raw)); result += std::string(i == 0 ? "\nLeft:" : "\nRight:") + - " err=" + std::to_string(int(error)) + " bound=" + (data.bActive ? "Y" : "N") + + " err=" + std::to_string(int(error)) + " active=" + (data.bActive ? "Y" : "N") + " down=" + (data.bState ? "Y" : "N") + " pose=" + (tracked ? "Y" : "N") + " origin=" + (origin_ok ? "Y" : "N") + " raw=" + (raw_ok ? ((raw.ulButtonPressed & vr::ButtonMaskFromId(vr::k_EButton_Grip)) ? "down" : "up") : "n/a"); } return result; } +std::string Overlay::pointer_status() const { + return "Pointer down=" + std::to_string(impl_->pointer_downs) + " up=" + std::to_string(impl_->pointer_ups) + + " hits=" + std::to_string(impl_->pointer_actions) + " resets=" + std::to_string(impl_->pointer_resets) + + " last=" + impl_->last_pointer_event; +} int registration(const std::string& manifest, bool remove, bool autostart) { try { diff --git a/src/overlay.hpp b/src/overlay.hpp index 4c0fad1..63bbbee 100644 --- a/src/overlay.hpp +++ b/src/overlay.hpp @@ -25,6 +25,7 @@ public: std::vector poll(); void draw(const Panel& panel); std::string controls_status(); // diagnostic only, no input delivery + std::string pointer_status() const; // diagnostic counters, no input delivery private: struct Impl; std::unique_ptr impl_; diff --git a/src/panel_surface.cpp b/src/panel_surface.cpp index 9fc3082..61d8dd9 100644 --- a/src/panel_surface.cpp +++ b/src/panel_surface.cpp @@ -103,7 +103,7 @@ struct PanelSurface::Impl { Mount mount; bool settings = false, dirty = true; std::string placement_note; - std::array pressed{{-1, -1}}, hovered{{-1, -1}}; + std::array pressed{{-1, -1}}; std::vector lines; size_t page = 0; static constexpr size_t lines_per_page = 4; @@ -241,9 +241,7 @@ struct PanelSurface::Impl { return -1; } void reset() { - if (std::any_of(pressed.begin(), pressed.end(), [](int i) { return i >= 0; }) || - std::any_of(hovered.begin(), hovered.end(), [](int i) { return i >= 0; })) dirty = true; - pressed.fill(-1); hovered.fill(-1); + pressed.fill(-1); } bool render(const Panel& p) { if (p.recording != panel.recording || p.enabled != panel.enabled || @@ -296,16 +294,14 @@ struct PanelSurface::Impl { bool on = enabled(b.id); bool selected = (b.id == Control::Review && !settings) || (b.id == Control::Settings && settings) || (mounting(b.id) && *mounting(b.id) == mount); - bool hover = std::find(hovered.begin(), hovered.end(), int(i)) != hovered.end(); - bool down = std::find(pressed.begin(), pressed.end(), int(i)) != pressed.end(); - const Color fill = !on ? Color{17, 23, 33, 255} : down ? Color{35, 77, 88, 255} : - hover || selected ? Color{26, 55, 68, 255} : card; + const Color fill = !on ? Color{17, 23, 33, 255} : + selected ? Color{26, 55, 68, 255} : card; const Color accent = b.id == Control::Record && panel.recording ? pink : cyan; - const bool highlighted = on && (hover || selected || b.id == Control::Record || + const bool highlighted = on && (selected || b.id == Control::Record || (b.id == Control::Insert && panel.transcript.size())); rounded(b.r, std::min(16, b.r.h / 3), fill, !on ? Color{35, 46, 59, 255} : highlighted ? accent : Color{67, 93, 112, 255}, - highlighted ? (hover || down ? .40f : .23f) : 0.f, highlighted ? 2 : 1); + highlighted ? .23f : 0.f, highlighted ? 2 : 1); const auto label = b.id == Control::Record && panel.recording ? "Stop" : b.label; text(label, b.r.x + 16, b.r.y + b.r.h / 2 + 9, 27, on ? ink : Color{81, 96, 113, 255}, b.r.x + b.r.w - 8); if (mounting(b.id) && selected) text("ON", b.r.x + b.r.w - 56, b.r.y + 38, 23, cyan, b.r.x + b.r.w - 12); @@ -319,32 +315,27 @@ PanelSurface::~PanelSurface() = default; bool PanelSurface::render(const Panel& p) { return impl_->render(p); } const std::vector& PanelSurface::pixels() const { return impl_->pixels; } bool PanelSurface::available(UiAction a) const { return impl_->available(a); } -void PanelSurface::pointer_move(unsigned cursor, float x, float y) { - if (cursor >= impl_->hovered.size()) return; - const int next = impl_->hit(x, y); - if (next != impl_->hovered[cursor]) { impl_->hovered[cursor] = next; impl_->dirty = true; } +void PanelSurface::pointer_move(unsigned, float, float) { + // Hit-test on down/up only. SetOverlayRaw can flicker in SteamVR when each + // laser hover frame causes another full RGBA upload. } void PanelSurface::pointer_down(unsigned cursor, float x, float y) { if (cursor >= impl_->pressed.size()) return; impl_->pressed[cursor] = impl_->hit(x, y); - pointer_move(cursor, x, y); - impl_->dirty = true; } SurfaceEvent PanelSurface::pointer_up(unsigned cursor, float x, float y) { SurfaceEvent result; if (cursor >= impl_->pressed.size()) return result; int index = std::exchange(impl_->pressed[cursor], -1); - pointer_move(cursor, x, y); - impl_->dirty = true; if (index < 0 || impl_->hit(x, y) != index) return result; auto c = buttons[index].id; if (c == Control::Record) result.action = impl_->panel.recording ? UiAction::EndRecord : UiAction::BeginRecord; else if (auto a = action(c)) result.action = a; - else if (auto m = mounting(c)) { impl_->mount = *m; result.mount = *m; impl_->reset(); } + else if (auto m = mounting(c)) { impl_->mount = *m; result.mount = *m; impl_->reset(); impl_->dirty = true; } else if (c == Control::Recenter) { result.recenter = true; impl_->reset(); } - else if (c == Control::Review || c == Control::Settings) { impl_->settings = c == Control::Settings; impl_->reset(); } - else if (c == Control::Prev) --impl_->page; - else if (c == Control::Next) ++impl_->page; + else if (c == Control::Review || c == Control::Settings) { impl_->settings = c == Control::Settings; impl_->reset(); impl_->dirty = true; } + else if (c == Control::Prev) { --impl_->page; impl_->dirty = true; } + else if (c == Control::Next) { ++impl_->page; impl_->dirty = true; } return result; } void PanelSurface::reset_pointers() { impl_->reset(); } diff --git a/tests/panel_test.cpp b/tests/panel_test.cpp index e55d455..69031d6 100644 --- a/tests/panel_test.cpp +++ b/tests/panel_test.cpp @@ -131,8 +131,12 @@ int main(int argc, char** argv) { assert(surface.pixels() == replaced); p.status = std::string(4096, 's'); p.detail = std::string(4096, 'd'); assert(surface.render(p)); assert(!surface.render(p)); - surface.pointer_move(0, 900, 610); assert(surface.render(p)); + // Laser motion and a press do not re-upload raw pixels. A completed + // action still reaches the caller; meaningful panel changes redraw. surface.pointer_move(0, 900, 610); assert(!surface.render(p)); - assert(click(surface, 900, 610).action == UiAction::Quit); + surface.pointer_move(0, 900, 610); assert(!surface.render(p)); + surface.pointer_down(0, 900, 610); assert(!surface.render(p)); + assert(surface.pointer_up(0, 900, 610).action == UiAction::Quit); + assert(!surface.render(p)); std::cout << "panel checks passed (no OpenVR, microphone or input injection)\n"; }