Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
frame_cost_probe.hpp
Go to the documentation of this file.
1#pragma once
2
37
39#include <pulp/view/view.hpp>
40
41#include <algorithm>
42#include <chrono>
43#include <cstdint>
44#include <limits>
45#include <stdexcept>
46#include <string>
47#include <utility>
48#include <vector>
49
50namespace pulp::view {
51
53 public:
54 struct Frame {
55 double ms = 0.0;
56 bool full_damage = false;
57 bool has_bounds = false;
61 std::uint64_t layout_passes = 0;
62 };
63
64 struct Summary {
65 int frames = 0;
66 double p50_ms = 0.0, p95_ms = 0.0, max_ms = 0.0;
68 int layout_frames = 0;
70 double mean_damage_area = 0.0;
72 };
73
74 struct Budget {
77 double max_p95_ms = std::numeric_limits<double>::infinity();
80 double max_mean_damage_area = std::numeric_limits<double>::infinity();
84 };
85
88 explicit FrameCostProbe(View& root, PluginViewHost::Size surface = {})
89 : root_(root), recorder_(surface) {
90 if (root.window_host() != nullptr)
91 throw std::logic_error(
92 "FrameCostProbe: the root has a WindowHost; its damage would not "
93 "reach the probe");
94 previous_ = root.plugin_view_host();
95 root.set_plugin_view_host(&recorder_);
96 // Attaching is not a frame: settle the layout it may have dirtied and
97 // start from no damage (PendingDamage starts full, by design, for a
98 // real host's first frame).
100 recorder_.prime();
101 }
103 root_.set_plugin_view_host(previous_);
104 }
107
109 template <typename Fn> const Frame& measure(Fn&& frame) {
110 recorder_.reset();
111 const auto layouts = View::layout_pass_count();
112 const auto t0 = std::chrono::steady_clock::now();
113 std::forward<Fn>(frame)();
114 const auto t1 = std::chrono::steady_clock::now();
115 Frame f;
116 f.ms = std::chrono::duration<double, std::milli>(t1 - t0).count();
117 f.full_damage = recorder_.full > 0;
118 f.has_bounds = recorder_.have;
119 f.damage = recorder_.uni;
120 f.repaint_requests = recorder_.full + recorder_.bounded;
121 f.frame_requests = recorder_.frames_only;
122 f.layout_passes = View::layout_pass_count() - layouts;
123 frames_.push_back(f);
124 return frames_.back();
125 }
126
127 const std::vector<Frame>& frames() const {
128 return frames_;
129 }
130 void clear() {
131 frames_.clear();
132 }
133
134 Summary summary() const {
135 Summary s;
136 s.frames = static_cast<int>(frames_.size());
137 if (frames_.empty())
138 return s;
139 std::vector<double> ms;
140 ms.reserve(frames_.size());
141 double area = 0.0;
142 int area_frames = 0;
143 bool have_union = false;
144 for (const auto& f : frames_) {
145 ms.push_back(f.ms);
146 if (f.full_damage)
147 ++s.full_damage_frames;
148 if (f.layout_passes > 0)
149 ++s.layout_frames;
150 if (f.repaint_requests > 0)
151 ++s.painted_frames;
152 if (f.has_bounds) {
153 area += static_cast<double>(f.damage.width) * f.damage.height;
154 ++area_frames;
155 s.damage_union = have_union ? unite(s.damage_union, f.damage) : f.damage;
156 have_union = true;
157 }
158 }
159 std::sort(ms.begin(), ms.end());
160 s.p50_ms = ms[ms.size() / 2];
161 s.p95_ms = ms[std::min(ms.size() - 1, ms.size() * 95 / 100)];
162 s.max_ms = ms.back();
163 s.mean_damage_area = area_frames > 0 ? area / area_frames : 0.0;
164 return s;
165 }
166
168 static std::vector<std::string> check(const Summary& s, const Budget& budget,
169 const Summary* baseline = nullptr) {
170 std::vector<std::string> breaches;
171 if (s.painted_frames < budget.min_painted_frames)
172 breaches.push_back("positive control: only " + std::to_string(s.painted_frames) +
173 " frames painted (need " +
174 std::to_string(budget.min_painted_frames) +
175 "); the run measured nothing");
176 if (s.full_damage_frames > budget.max_full_damage_frames)
177 breaches.push_back(std::to_string(s.full_damage_frames) + "/" +
178 std::to_string(s.frames) +
179 " frames requested a whole-surface repaint");
180 if (s.layout_frames > budget.max_layout_frames)
181 breaches.push_back(std::to_string(s.layout_frames) + "/" + std::to_string(s.frames) +
182 " frames ran a layout pass");
183 if (s.mean_damage_area > budget.max_mean_damage_area)
184 breaches.push_back("mean bounded damage " + std::to_string(s.mean_damage_area) +
185 " px2 exceeds " + std::to_string(budget.max_mean_damage_area));
186 const double grown = baseline ? s.p95_ms - baseline->p95_ms : s.p95_ms;
187 if (grown > budget.max_p95_ms)
188 breaches.push_back(std::string(baseline ? "p95 grew " : "p95 ") +
189 std::to_string(grown) + " ms (budget " +
190 std::to_string(budget.max_p95_ms) + " ms)");
191 return breaches;
192 }
193
194 private:
195 static Rect unite(const Rect& a, const Rect& b) {
196 const float x0 = std::min(a.x, b.x), y0 = std::min(a.y, b.y);
197 return {x0, y0, std::max(a.x + a.width, b.x + b.width) - x0,
198 std::max(a.y + a.height, b.y + b.height) - y0};
199 }
200
201 class Recorder final : public PluginViewHost {
202 public:
203 explicit Recorder(Size size) : size_(size) {}
204 int full = 0, bounded = 0, frames_only = 0;
205 // A frame asked for without damage (View::request_frame()) is not a
206 // repaint request: whatever that frame changes marks its own.
207 void request_frame() override {
208 ++frames_only;
209 }
210 bool have = false;
211 Rect uni{};
213 return {};
214 }
215 void attach_to_parent(NativeViewHandle) override {}
216 void detach() override {}
217 // Every request ends in exactly one repaint() call, so taking the
218 // damage here classifies each request on its own: a bounded mark
219 // arrives with its rect in the damage, a whole-surface one as full.
220 void repaint() override {
221 const auto snap = damage_.take();
222 if (snap.is_bounded()) {
223 ++bounded;
224 uni = have ? unite(uni, snap.bounds()) : snap.bounds();
225 have = true;
226 } else {
227 ++full;
228 }
229 }
230 void set_size(std::uint32_t w, std::uint32_t h) override {
231 size_ = {w, h};
232 }
233 Size get_size() const override {
234 return size_;
235 }
236 void prime() {
237 damage_.clear();
238 reset();
239 }
240 void reset() {
241 full = bounded = frames_only = 0;
242 have = false;
243 uni = {};
244 }
245
246 private:
247 Size size_;
248 };
249
250 View& root_;
251 Recorder recorder_;
252 PluginViewHost* previous_ = nullptr;
253 std::vector<Frame> frames_;
254};
255
256} // namespace pulp::view
FrameCostProbe & operator=(const FrameCostProbe &)=delete
FrameCostProbe(View &root, PluginViewHost::Size surface={})
Attach a recording host to root for the probe's lifetime.
Definition frame_cost_probe.hpp:88
Summary summary() const
Definition frame_cost_probe.hpp:134
const std::vector< Frame > & frames() const
Definition frame_cost_probe.hpp:127
FrameCostProbe(const FrameCostProbe &)=delete
static std::vector< std::string > check(const Summary &s, const Budget &budget, const Summary *baseline=nullptr)
One human-readable line per breach of budget; empty when within it.
Definition frame_cost_probe.hpp:168
~FrameCostProbe()
Definition frame_cost_probe.hpp:102
const Frame & measure(Fn &&frame)
Run one frame's work and record what it cost.
Definition frame_cost_probe.hpp:109
void clear()
Definition frame_cost_probe.hpp:130
virtual void detach()=0
virtual void repaint()=0
virtual Size get_size() const =0
virtual void request_frame()
A frame without damage (see WindowHost::request_frame()).
Definition plugin_view_host.hpp:244
virtual NativeViewHandle native_handle()=0
PendingDamage damage_
Definition plugin_view_host.hpp:254
Definition view.hpp:73
WindowHost * window_host() const
Definition view.hpp:2454
static std::uint64_t layout_pass_count() noexcept
Definition view.hpp:883
void layout_children_if_needed()
Lay out only when a geometry mutation has happened since this root's last completed pass.
Definition view.hpp:369
PluginViewHost * plugin_view_host() const
Definition view.hpp:2461
virtual void set_plugin_view_host(PluginViewHost *host)
Back-reference to the PluginViewHost that owns this editor tree.
Hover-cursor tracking for platform window hosts.
Definition fwd.hpp:27
@ s
Definition input_events.hpp:41
@ a
Definition input_events.hpp:39
@ h
Definition input_events.hpp:39
@ f
Definition input_events.hpp:39
@ b
Definition input_events.hpp:39
@ w
Definition input_events.hpp:41
@ bounds
Definition waveform_editor_primitives.hpp:192
@ baseline
Definition geometry.hpp:370
void * NativeViewHandle
Definition native_view_host.hpp:19
Definition frame_cost_probe.hpp:74
double max_mean_damage_area
Definition frame_cost_probe.hpp:80
int max_layout_frames
Definition frame_cost_probe.hpp:79
int max_full_damage_frames
Definition frame_cost_probe.hpp:78
int min_painted_frames
Positive control: a run that painted fewer frames than this measured nothing, and is reported as a br...
Definition frame_cost_probe.hpp:83
double max_p95_ms
Absolute p95 ceiling, or (with a baseline) the most the p95 may grow over the baseline's p95.
Definition frame_cost_probe.hpp:77
Definition frame_cost_probe.hpp:54
Rect damage
union of the bounded requests
Definition frame_cost_probe.hpp:58
int frame_requests
frames requested without damage
Definition frame_cost_probe.hpp:60
std::uint64_t layout_passes
layout_children() calls the frame ran
Definition frame_cost_probe.hpp:61
bool has_bounds
bounded damage was requested
Definition frame_cost_probe.hpp:57
int repaint_requests
damage-marking requests the frame caused
Definition frame_cost_probe.hpp:59
double ms
wall time of the frame's work
Definition frame_cost_probe.hpp:55
bool full_damage
some request repainted the whole surface
Definition frame_cost_probe.hpp:56
Definition frame_cost_probe.hpp:64
double p50_ms
Definition frame_cost_probe.hpp:66
double max_ms
Definition frame_cost_probe.hpp:66
Rect damage_union
union of every frame's bounded damage
Definition frame_cost_probe.hpp:71
double mean_damage_area
mean bounded area over painted frames
Definition frame_cost_probe.hpp:70
int full_damage_frames
frames with any whole-surface request
Definition frame_cost_probe.hpp:67
int layout_frames
frames that ran at least one layout pass
Definition frame_cost_probe.hpp:68
int painted_frames
frames that requested any repaint at all
Definition frame_cost_probe.hpp:69
double p95_ms
Definition frame_cost_probe.hpp:66
int frames
Definition frame_cost_probe.hpp:65
Definition plugin_view_host.hpp:66
Axis-aligned rectangle in continuous (float) coordinates.
Definition geometry.hpp:52