• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "tools/render/trace_program.h"
16 
17 #include "absl/flags/flag.h"
18 #include "absl/time/clock.h"
19 #include "tools/render/layout_constants.h"
20 
21 ABSL_FLAG(bool, show_fps, false, "Show the current framerate of the program");
22 ABSL_FLAG(bool, vsync, true, "Enables vsync");
23 
24 ABSL_FLAG(double,
25           mouseover_threshold,
26           3.0,
27           "The minimum size of a single packet (in fractional pixels) that "
28           "causes the packet information box being showed");
29 
30 namespace quic_trace {
31 namespace render {
32 
TraceProgram()33 TraceProgram::TraceProgram()
34     : window_(SDL_CreateWindow(
35           "QUIC trace viewer",
36           0,
37           0,
38           state_.window.x,
39           state_.window.y,
40           SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI)),
41       context_(*window_) {
42   UpdateWindowSize();
43   state_buffer_ = std::make_unique<ProgramState>(&state_);
44   renderer_ = std::make_unique<TraceRenderer>(state_buffer_.get());
45   text_renderer_ = std::make_unique<TextRenderer>(state_buffer_.get());
46   axis_renderer_ = std::make_unique<AxisRenderer>(text_renderer_.get(),
47                                                    state_buffer_.get());
48   rectangle_renderer_ =
49       std::make_unique<RectangleRenderer>(state_buffer_.get());
50 
51   SDL_GL_SetSwapInterval(absl::GetFlag(FLAGS_vsync) ? 1 : 0);
52   SDL_SetWindowMinimumSize(*window_, 640, 480);
53 
54   glEnable(GL_BLEND);
55   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
56 }
57 
LoadTrace(std::unique_ptr<Trace> trace)58 void TraceProgram::LoadTrace(std::unique_ptr<Trace> trace) {
59   std::stable_sort(
60       trace->mutable_events()->begin(), trace->mutable_events()->end(),
61       [](const Event& a, const Event& b) { return a.time_us() < b.time_us(); });
62   trace_ = std::make_unique<ProcessedTrace>(std::move(trace), renderer_.get());
63   state_.viewport.x = renderer_->max_x();
64   state_.viewport.y = renderer_->max_y();
65 }
66 
Loop()67 void TraceProgram::Loop() {
68   while (!quit_) {
69     absl::Time frame_start = absl::Now();
70     PollEvents();
71     PollKeyboard();
72     PollMouse();
73     EnsureBounds();
74 
75     state_buffer_->Refresh();
76 
77     // Render.
78     glClearColor(1.f, 1.f, 1.f, 1.f);
79     glClear(GL_COLOR_BUFFER_BIT);
80     // Note that the order of calls below determines what is drawn on top of
81     // what.
82     renderer_->Render();
83     axis_renderer_->Render();
84     MaybeShowFramerate();
85     DrawRightSideTables();
86     // The batch object renderers should be called last.
87     rectangle_renderer_->Render();
88     text_renderer_->DrawAll();
89     SDL_GL_SwapWindow(*window_);
90 
91     absl::Time frame_end = absl::Now();
92     frame_duration_ = 0.25 * (frame_end - frame_start) + 0.75 * frame_duration_;
93   }
94 }
95 
ScaleAdditiveFactor(float x)96 float TraceProgram::ScaleAdditiveFactor(float x) {
97   return x * absl::ToDoubleSeconds(frame_duration_) /
98          absl::ToDoubleSeconds(kReferenceFrameDuration);
99 }
100 
ScaleMultiplicativeFactor(float k)101 float TraceProgram::ScaleMultiplicativeFactor(float k) {
102   return std::pow(k, absl::ToDoubleSeconds(frame_duration_) /
103                          absl::ToDoubleSeconds(kReferenceFrameDuration));
104 }
105 
Zoom(float zoom)106 void TraceProgram::Zoom(float zoom) {
107   float zoom_factor = std::abs(zoom);
108   float sign = std::copysign(1.f, zoom);
109 
110   // Ensure that the central point doesn't move.
111   state_.offset.x += sign * (1 - zoom_factor) * state_.viewport.x / 2;
112   state_.offset.y += sign * (1 - zoom_factor) * state_.viewport.y / 2;
113 
114   state_.viewport.x *= std::pow(zoom_factor, sign);
115   state_.viewport.y *= std::pow(zoom_factor, sign);
116 }
117 
UpdateWindowSize()118 void TraceProgram::UpdateWindowSize() {
119   int width, height;
120   SDL_GL_GetDrawableSize(*window_, &width, &height);
121   state_.window = vec2(width, height);
122   glViewport(0, 0, width, height);
123 
124   int input_width, input_height;
125   SDL_GetWindowSize(*window_, &input_width, &input_height);
126   input_scale_ = vec2((float)width / input_width, (float)height / input_height);
127 
128   const float kReferenceDpi = 100.f;
129   float dpi;
130   int result = SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(*window_), &dpi,
131                                  nullptr, nullptr);
132   if (result < 0) {
133     LOG(WARNING) << "Failed to retrieve window DPI";
134   }
135   state_.dpi_scale = input_scale_.x * dpi / kReferenceDpi;
136 }
137 
PollEvents()138 void TraceProgram::PollEvents() {
139   SDL_Event event;
140   while (SDL_PollEvent(&event)) {
141     switch (event.type) {
142       case SDL_QUIT:
143         quit_ = true;
144         break;
145 
146       case SDL_WINDOWEVENT:
147         if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
148           UpdateWindowSize();
149         }
150         break;
151 
152       case SDL_KEYDOWN:
153         if (event.key.keysym.scancode == SDL_SCANCODE_H) {
154           show_online_help_ = !show_online_help_;
155         }
156         break;
157 
158       case SDL_MOUSEWHEEL: {
159         int wheel_offset = event.wheel.y;
160         if (wheel_offset == 0) {
161           break;
162         }
163         if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) {
164           wheel_offset *= -1;
165         }
166         // Note that this does not need to be scaled with framerate since
167         // mousewheel events are discrete.
168         Zoom(std::copysign(0.95f, wheel_offset));
169         break;
170       }
171     }
172   }
173 }
174 
PollKeyboard()175 void TraceProgram::PollKeyboard() {
176   const uint8_t* state = SDL_GetKeyboardState(nullptr);
177   if (state[SDL_SCANCODE_Q]) {
178     quit_ = true;
179   }
180 
181   // Zoom handling.
182   const float zoom_factor = ScaleMultiplicativeFactor(0.98);
183   if (state[SDL_SCANCODE_Z]) {
184     Zoom(+zoom_factor);
185   }
186   if (state[SDL_SCANCODE_X]) {
187     Zoom(-zoom_factor);
188   }
189   if (state[SDL_SCANCODE_UP]) {
190     state_.offset.y += ScaleAdditiveFactor(state_.viewport.y * 0.03);
191   }
192   if (state[SDL_SCANCODE_DOWN]) {
193     state_.offset.y -= ScaleAdditiveFactor(state_.viewport.y * 0.03);
194   }
195   if (state[SDL_SCANCODE_LEFT]) {
196     state_.offset.x -= ScaleAdditiveFactor(state_.viewport.x * 0.03);
197   }
198   if (state[SDL_SCANCODE_RIGHT]) {
199     state_.offset.x += ScaleAdditiveFactor(state_.viewport.x * 0.03);
200   }
201   if (state[SDL_SCANCODE_R]) {
202     absl::optional<Box> new_viewport =
203         trace_->BoundContainedPackets(Box{state_.offset, state_.viewport});
204     if (new_viewport) {
205       state_.offset = new_viewport->origin;
206       state_.viewport = new_viewport->size;
207     }
208   }
209 }
210 
PollMouse()211 void TraceProgram::PollMouse() {
212   int x, y;
213   uint32_t buttons = SDL_GetMouseState(&x, &y);
214   const uint8_t* state = SDL_GetKeyboardState(nullptr);
215   bool shift = state[SDL_SCANCODE_LSHIFT] || state[SDL_SCANCODE_RSHIFT];
216   x *= input_scale_.x;
217   y *= input_scale_.y;
218 
219   HandlePanning((buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) && !shift, x,
220                 state_.window.y - y);
221   HandleSummary((buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) && shift, x);
222   HandleZooming(buttons & SDL_BUTTON(SDL_BUTTON_RIGHT), x, state_.window.y - y);
223   HandleMouseover(x, state_.window.y - y);
224 }
225 
HandlePanning(bool pressed,int x,int y)226 void TraceProgram::HandlePanning(bool pressed, int x, int y) {
227   if (!pressed) {
228     panning_ = false;
229     return;
230   }
231 
232   if (!panning_) {
233     panning_ = true;
234     panning_last_pos_ = vec2(x, y);
235     return;
236   }
237 
238   state_.offset += WindowToTraceRelative(panning_last_pos_ - vec2(x, y));
239 
240   panning_last_pos_ = vec2(x, y);
241 }
242 
HandleZooming(bool pressed,int x,int y)243 void TraceProgram::HandleZooming(bool pressed, int x, int y) {
244   if (!pressed && !zooming_) {
245     return;
246   }
247 
248   if (pressed && !zooming_) {
249     zooming_ = true;
250     zoom_start_x_ = x;
251     zoom_start_y_ = y;
252     return;
253   }
254 
255   Box window_box = BoundingBox(vec2(x, y), vec2(zoom_start_x_, zoom_start_y_));
256   // Ensure that the selection does not go out of the bounds of the trace view.
257   window_box = IntersectBoxes(window_box, TraceBounds());
258 
259   if (pressed && zooming_) {
260     // User is still selecting the area to zoom into.  Draw a transparent grey
261     // rectangle to indicate the currently picked area.
262     rectangle_renderer_->AddRectangle(window_box, 0x00000033);
263     return;
264   }
265 
266   // Actually zoom in.
267   zooming_ = false;
268   // Discard all attempts to zoom in into something smaller than 16x16 pixels,
269   // as those are more liekly to be accidental.
270   if (window_box.size.x < 16 || window_box.size.y < 16) {
271     return;
272   }
273 
274   const Box trace_box = WindowToTraceCoordinates(window_box);
275   state_.viewport = trace_box.size;
276   state_.offset = trace_box.origin;
277 }
278 
HandleSummary(bool pressed,int x)279 void TraceProgram::HandleSummary(bool pressed, int x) {
280   if (!pressed) {
281     summary_ = false;
282     return;
283   }
284 
285   if (!summary_) {
286     summary_ = true;
287     summary_start_x_ = x;
288     return;
289   }
290 
291   Box window_box = BoundingBox(vec2(x, 0), vec2(summary_start_x_, 99999.f));
292   // Ensure that the selection does not go out of the bounds of the trace view.
293   window_box = IntersectBoxes(window_box, TraceBounds());
294   if (window_box.size.x < 1) {
295     return;
296   }
297   rectangle_renderer_->AddRectangle(window_box, 0x00000033);
298 
299   const Box selected = WindowToTraceCoordinates(window_box);
300 
301   summary_table_.emplace(state_buffer_.get(), text_renderer_.get(),
302                          rectangle_renderer_.get());
303   if (!trace_->SummaryTable(&*summary_table_, selected.origin.x,
304                             selected.origin.x + selected.size.x)) {
305     summary_table_ = absl::nullopt;
306   }
307 }
308 
HandleMouseover(int x,int y)309 void TraceProgram::HandleMouseover(int x, int y) {
310   vec2 window_pos(x, y);
311   if (!IsInside(window_pos, TraceBounds())) {
312     return;
313   }
314 
315   vec2 trace_pos = WindowToTraceCoordinates(window_pos);
316 
317   float packet_size_in_pixels =
318       kSentPacketDurationMs / state_.viewport.x * state_.window.x;
319   if (packet_size_in_pixels < absl::GetFlag(FLAGS_mouseover_threshold)) {
320     renderer_->set_highlighted_packet(-1);
321     return;
322   }
323 
324   constexpr int kPixelMargin = 64;
325   const vec2 margin = WindowToTraceRelative(vec2(kPixelMargin, kPixelMargin));
326   ProcessedTrace::PacketSearchResult hovered_packet =
327       trace_->FindPacketContainingPoint(trace_pos, margin);
328   renderer_->set_highlighted_packet(hovered_packet.index);
329   if (hovered_packet.event == nullptr) {
330     return;
331   }
332 
333   constexpr vec2 kMouseoverOffset = vec2(32, 32);
334   Table table(state_buffer_.get(), text_renderer_.get(),
335               rectangle_renderer_.get());
336   trace_->FillTableForPacket(&table, hovered_packet.as_rendered,
337                              hovered_packet.event);
338   vec2 table_size = table.Layout();
339   table.Draw(vec2(x, y) + kMouseoverOffset + 0 * table_size);
340 }
341 
GenerateOnlineHelp()342 Table TraceProgram::GenerateOnlineHelp() {
343   Table table(state_buffer_.get(), text_renderer_.get(),
344               rectangle_renderer_.get());
345   table.AddHeader("Help");
346   table.AddRow("h", "Toggle help");
347   table.AddRow("z", "Zoom in");
348   table.AddRow("x", "Zoom out");
349   table.AddRow("r", "Rescale");
350   table.AddRow("Arrows", "Move");
351   table.AddRow("LMouse", "Move");
352   table.AddRow("RMouse", "Zoom");
353   table.AddRow("Shift+LM", "Summary");
354   return table;
355 }
356 
EnsureBounds()357 void TraceProgram::EnsureBounds() {
358   constexpr float kTimeMargin = 3000.f;
359   constexpr float kOffsetMargin = 10 * 1350.f;
360   state_.viewport.x =
361       std::min(state_.viewport.x, renderer_->max_x() + 2 * kTimeMargin);
362   state_.viewport.y =
363       std::min(state_.viewport.y, renderer_->max_y() + 2 * kOffsetMargin);
364 
365   const float min_x = -kTimeMargin;
366   const float min_y = -kOffsetMargin;
367   const float max_x = renderer_->max_x() + kTimeMargin;
368   const float max_y = renderer_->max_y() + kOffsetMargin;
369 
370   state_.offset.x = std::max(min_x, state_.offset.x);
371   state_.offset.x = std::min(max_x - state_.viewport.x, state_.offset.x);
372   state_.offset.y = std::max(min_y, state_.offset.y);
373   state_.offset.y = std::min(max_y - state_.viewport.y, state_.offset.y);
374 }
375 
WindowToTraceRelative(vec2 vector)376 vec2 TraceProgram::WindowToTraceRelative(vec2 vector) {
377   const vec2 pixel_viewport = state_.window - 2 * TraceMargin(state_.dpi_scale);
378   return vector * state_.viewport / pixel_viewport;
379 }
380 
WindowToTraceCoordinates(vec2 point)381 vec2 TraceProgram::WindowToTraceCoordinates(vec2 point) {
382   return state_.offset +
383          WindowToTraceRelative(point - TraceMargin(state_.dpi_scale));
384 }
385 
WindowToTraceCoordinates(Box box)386 Box TraceProgram::WindowToTraceCoordinates(Box box) {
387   return BoundingBox(WindowToTraceCoordinates(box.origin),
388                      WindowToTraceCoordinates(box.origin + box.size));
389 }
390 
TraceBounds()391 Box TraceProgram::TraceBounds() {
392   return BoundingBox(TraceMargin(state_.dpi_scale),
393                      state_.window - TraceMargin(state_.dpi_scale));
394 }
395 
MaybeShowFramerate()396 void TraceProgram::MaybeShowFramerate() {
397   if (!absl::GetFlag(FLAGS_show_fps)) {
398     return;
399   }
400 
401   char buffer[16];
402   snprintf(buffer, sizeof(buffer), "%.2ffps",
403            1. / absl::ToDoubleSeconds(frame_duration_));
404   std::shared_ptr<const Text> framerate = text_renderer_->RenderText(buffer);
405   text_renderer_->AddText(framerate, 0, state_.window.y - framerate->height());
406 }
407 
DrawRightSideTables()408 void TraceProgram::DrawRightSideTables() {
409   float distance = 20.f * state_.dpi_scale;
410   vec2 offset = state_.window - vec2(distance, distance);
411 
412   if (summary_table_.has_value()) {
413     vec2 table_size = summary_table_->Layout();
414     summary_table_->Draw(offset - table_size);
415     offset.y -= table_size.y + distance;
416   }
417 
418   if (show_online_help_) {
419     Table online_help = GenerateOnlineHelp();
420     vec2 table_size = online_help.Layout();
421     online_help.Draw(offset - table_size);
422     offset.y -= table_size.y + distance;
423   }
424 
425   summary_table_ = absl::nullopt;
426 }
427 
428 }  // namespace render
429 }  // namespace quic_trace
430