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/axis_renderer.h"
16 
17 #include <array>
18 
19 #include "tools/render/layout_constants.h"
20 
21 namespace quic_trace {
22 namespace render {
23 namespace {
24 // The following shader adjusts window coordinates to GL coordinates.  It also
25 // shifts the coordinates so that (0, 0) is the starting point at which the axis
26 // should be drawn.
27 const char* kVertexShader = R"(
28 uniform vec2 axis_offset;
29 
30 in vec2 coord;
31 void main(void) {
32   gl_Position = windowToGl(coord + axis_offset);
33 }
34 )";
35 
36 // Color everything black.
37 const char* kFragmentShader = R"(
38 out vec4 color;
39 void main(void) {
40   color = vec4(0, 0, 0, 1);
41 }
42 )";
43 
44 // A line as loaded into the vertex buffer.
45 struct Line {
46   float x0, y0, x1, y1;
47 };
48 
49 // A tick on the axis.
50 struct Tick {
51   // |offset| here is the offset along x- or y-axis, reported in pixels.
52   float offset;
53   // The text of the tick.
54   std::string text;
55 };
56 
57 // Generate tick text for the X axis.
TimeToString(float time)58 std::string TimeToString(float time) {
59   char buffer[16];
60   snprintf(buffer, sizeof(buffer), "%gs", time / 1e6);
61   return buffer;
62 }
63 
64 // Generate tick text for the Y axis.
DataOffsetToString(float offset)65 std::string DataOffsetToString(float offset) {
66   char buffer[16];
67   snprintf(buffer, sizeof(buffer), "%gM", offset / 1e6f);
68   return buffer;
69 }
70 
71 // Finds an appropriate placement of ticks along either axis.  |offset| and
72 // |viewport_size| indicate which portion of the trace is currently being
73 // rendered, in axis units (microseconds or bytes), |axis_size| is the size of
74 // the axis in pixels, and |max_text_size| is an estimate of how big the text
75 // label can be in the dimension colinear with the axis.
76 template <std::string (*ToString)(float)>
Ticks(float offset,float viewport_size,size_t axis_size,size_t max_text_size)77 std::vector<Tick> Ticks(float offset,
78                         float viewport_size,
79                         size_t axis_size,
80                         size_t max_text_size) {
81   size_t max_ticks = axis_size / (max_text_size + 16);
82   // Try to find the distance between ticks.  We start by rounding down
83   // |viewport_size| to a fairly small value and then go over potential
84   // predefined candidates until we find the one that fits (i.e. if
85   // viewport_size is 0.23s, we'd try 0.01, 0.02, 0.05, 0.1, 0.2, etc)
86   float scale_base = std::pow(10.f, std::floor(std::log10(viewport_size)) - 2);
87   const std::array<float, 9> scale_factors = {1.f,  2.f,   5.f,   10.f, 20.f,
88                                               50.f, 100.f, 200.f, 500.f};
89   float scale = 1.f;
90   for (float scale_factor : scale_factors) {
91     scale = scale_base * scale_factor;
92     if (viewport_size / scale <= max_ticks) {
93       break;
94     }
95   }
96   if (viewport_size / scale > max_ticks) {
97     LOG(ERROR) << "Failed to determine the correct number and distance between "
98                   "ticks in the axis";
99     return std::vector<Tick>();
100   }
101 
102   std::vector<Tick> ticks;
103   for (float current = offset - std::fmod(offset, scale);
104        current < offset + viewport_size; current += scale) {
105     // Since offset is non-negative, we have to allow some small room to render
106     // the zero in most cases.
107     if (current < offset - 1) {
108       continue;
109     }
110 
111     ticks.push_back(
112         {(current - offset) / viewport_size * axis_size, ToString(current)});
113   }
114   return ticks;
115 }
116 
117 }  // namespace
118 
AxisRenderer(TextRenderer * text_factory,const ProgramState * state)119 AxisRenderer::AxisRenderer(TextRenderer* text_factory,
120                            const ProgramState* state)
121     : shader_(kVertexShader, kFragmentShader),
122       state_(state),
123       text_renderer_(text_factory) {
124   // Use a simple reference text to determine which spacing should be used
125   // between the ticks on the axis.
126   std::shared_ptr<const Text> reference_text =
127       text_renderer_->RenderText("12.345s");
128   reference_label_width_ = reference_text->width();
129   reference_label_height_ = reference_text->height();
130 }
131 
Render()132 void AxisRenderer::Render() {
133   const float distance_between_axis_and_trace = state_->ScaleForDpi(10);
134   const vec2 axis_offset =
135       TraceMargin(state_->dpi_scale()) -
136       vec2(distance_between_axis_and_trace, distance_between_axis_and_trace);
137   const float tick_size = state_->ScaleForDpi(10);
138   const vec2 axis_size = state_->window() - 2 * axis_offset;
139 
140   // Note that the coordinates of the objects in this array are w.r.t the origin
141   // of the axis lines that we are drawing.
142   std::vector<Line> lines = {
143       {0, 0, axis_size.x, 0},
144       {0, 0, 0, axis_size.y},
145   };
146   for (const Tick& tick :
147        Ticks<TimeToString>(state_->offset().x, state_->viewport().x,
148                            axis_size.x, reference_label_width_)) {
149     lines.push_back({tick.offset, 0, tick.offset, -tick_size});
150     std::shared_ptr<const Text> text = text_renderer_->RenderText(tick.text);
151     text_renderer_->AddText(
152         text,
153         axis_offset.x + tick.offset - text->width() / 2,  // Center on X
154         axis_offset.y - text->height() - tick_size);      // Shift on Y
155   }
156   for (const Tick& tick :
157        Ticks<DataOffsetToString>(state_->offset().y, state_->viewport().y,
158                                  axis_size.y, reference_label_height_)) {
159     lines.push_back({0, tick.offset, -tick_size, tick.offset});
160     std::shared_ptr<const Text> text = text_renderer_->RenderText(tick.text);
161     text_renderer_->AddText(
162         text,
163         axis_offset.x - text->width() - tick_size * 1.6,    // Shift on X
164         axis_offset.y + tick.offset - text->height() / 2);  // Center on Y
165   }
166 
167   GlVertexBuffer buffer;
168   glBindBuffer(GL_ARRAY_BUFFER, *buffer);
169   glBufferData(GL_ARRAY_BUFFER, sizeof(*lines.data()) * lines.size(),
170                lines.data(), GL_STATIC_DRAW);
171 
172   GlVertexArray array_;
173   glBindVertexArray(*array_);
174 
175   glUseProgram(*shader_);
176   state_->Bind(shader_);
177   shader_.SetUniform("axis_offset", axis_offset.x, axis_offset.y);
178 
179   GlVertexArrayAttrib coord(shader_, "coord");
180   glVertexAttribPointer(*coord, 2, GL_FLOAT, GL_FALSE, 0, 0);
181   glDrawArrays(GL_LINES, 0, lines.size() * 2);
182 }
183 
184 }  // namespace render
185 }  // namespace quic_trace
186