Ω-W1-T3 review fixes: skip meter invalidate when covered; push fast-path predicates into core

Fixes the critical finding — Browse/curve-popup no longer trigger 60 Hz
whole-client repaints when the meter is invisible. Pushes the fast-path and
self-containment geometry into master_meter with tests; batches the four minor
findings.
This commit is contained in:
2026-08-03 13:31:50 -04:00
parent 4320bfdb08
commit dd067a192e
9 changed files with 102 additions and 21 deletions
+12 -12
View File
@@ -1,9 +1,8 @@
// reasampler_editor.h — VST3 IPlugView LICE editor for the ReaSampler 9000 UI. Thin shell:
// hosts a LICE child window, routing host paint/mouse into the pure geometry modules
// (sample_bands, sample_chrome, capture_browser, keyboard_strip, sample_map). The Sample
// face is a three-band stack — chrome, waveform, decks — and the shell TUs split on that
// same axis; Browse is a modal picker over it. All layout/hit-test/drag math lives in the
// pure modules; every edit commits off the audio thread via reloadInstrument.
// hosts a LICE child window, routing host paint/mouse into the pure geometry modules. The
// Sample face is a three-band stack — chrome, waveform, decks — and the shell TUs split on
// that same axis; Browse is a modal picker over it. All layout/hit-test/drag math lives in
// the pure modules; every edit commits off the audio thread via reloadInstrument.
#pragma once
@@ -65,6 +64,9 @@ public:
// always supplies one).
explicit ReaSamplerEditor(ReaSamplerProcessor* processor);
~ReaSamplerEditor() override;
// backBuffer_ owns a raw LICE bitmap with no refcount behind it — a copy would double-free.
ReaSamplerEditor(const ReaSamplerEditor&) = delete;
ReaSamplerEditor& operator=(const ReaSamplerEditor&) = delete;
Steinberg::tresult PLUGIN_API isPlatformTypeSupported(
Steinberg::FIDString type) override;
@@ -302,8 +304,8 @@ private:
struct ChannelPcm {
std::vector<AudioSample> interleaved; // frame-interleaved source frames
int channelCount = 0; // 0 = nothing decoded
Envelope heroEnv; // binned from `interleaved`, so every path that drops the
std::size_t heroBins = 0; // decode drops it too. 0 = not computed
Envelope heroEnv; // binned from `interleaved`; cleared whenever it is
std::size_t heroBins = 0; // 0 = not computed
std::int64_t frameCount() const {
return channelCount > 0
? static_cast<std::int64_t>(interleaved.size()) / channelCount
@@ -467,9 +469,7 @@ private:
// up. One note at a time — a fresh press releases the prior.
int previewingNote_ = -1;
// Resample bake. The click only ARMS it; the sync tick runs it. Running it inline
// would nest a synchronous REAPER action — which re-points this very instance — inside
// a mouse handler with the capture held.
// Resample bake — the click only ARMS it (see this directory's CLAUDE.md Gotchas for why).
bool bakePending_ = false;
// Whether the extension's bake action is registered, resolved on the same tick that
// governs the button's paint, so the control is never enabled and then refusing.
@@ -494,8 +494,8 @@ private:
// The MASTER deck's meter, advanced from the published block magnitudes on its own timer
// (see onMeterTimer for why it runs mid-drag too, and why the first read is discarded).
// A default-constructed meterTick_ means never ticked. meterRects_ is the column interior
// resolved at the last FULL paint; an empty field means "not on screen" (no layout yet, the
// empty state, a modal sheet over it) and sends the tick down the whole-client path.
// resolved at the last FULL paint; an empty field is ambiguous alone (no layout yet, or
// genuinely off screen) — invalidateMeter asks view_/curvePopup_/selectedId_ to tell which.
instrument::ui::MasterMeterUi masterMeter_;
std::chrono::steady_clock::time_point meterTick_{};
instrument::ui::MeterRects meterRects_;