blob: 5760693cfaca18f10875ad38cf4bbef86a26950e [file] [log] [blame]
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "tools/render/axis_renderer.h"
#include <array>
#include "tools/render/layout_constants.h"
namespace quic_trace {
namespace render {
namespace {
// The following shader adjusts window coordinates to GL coordinates. It also
// shifts the coordinates so that (0, 0) is the starting point at which the axis
// should be drawn.
const char* kVertexShader = R"(
uniform vec2 axis_offset;
in vec2 coord;
void main(void) {
gl_Position = windowToGl(coord + axis_offset);
}
)";
// Color everything black.
const char* kFragmentShader = R"(
out vec4 color;
void main(void) {
color = vec4(0, 0, 0, 1);
}
)";
// A line as loaded into the vertex buffer.
struct Line {
float x0, y0, x1, y1;
};
// A tick on the axis.
struct Tick {
// |offset| here is the offset along x- or y-axis, reported in pixels.
float offset;
// The text of the tick.
std::string text;
};
// Generate tick text for the X axis.
std::string TimeToString(float time) {
char buffer[16];
snprintf(buffer, sizeof(buffer), "%gs", time / 1e6);
return buffer;
}
// Generate tick text for the Y axis.
std::string DataOffsetToString(float offset) {
char buffer[16];
snprintf(buffer, sizeof(buffer), "%gM", offset / 1e6f);
return buffer;
}
// Finds an appropriate placement of ticks along either axis. |offset| and
// |viewport_size| indicate which portion of the trace is currently being
// rendered, in axis units (microseconds or bytes), |axis_size| is the size of
// the axis in pixels, and |max_text_size| is an estimate of how big the text
// label can be in the dimension colinear with the axis.
template <std::string (*ToString)(float)>
std::vector<Tick> Ticks(float offset,
float viewport_size,
size_t axis_size,
size_t max_text_size) {
size_t max_ticks = axis_size / (max_text_size + 16);
// Try to find the distance between ticks. We start by rounding down
// |viewport_size| to a fairly small value and then go over potential
// predefined candidates until we find the one that fits (i.e. if
// viewport_size is 0.23s, we'd try 0.01, 0.02, 0.05, 0.1, 0.2, etc)
float scale_base = std::pow(10.f, std::floor(std::log10(viewport_size)) - 2);
const std::array<float, 9> scale_factors = {1.f, 2.f, 5.f, 10.f, 20.f,
50.f, 100.f, 200.f, 500.f};
float scale = 1.f;
for (float scale_factor : scale_factors) {
scale = scale_base * scale_factor;
if (viewport_size / scale <= max_ticks) {
break;
}
}
if (viewport_size / scale > max_ticks) {
LOG(ERROR) << "Failed to determine the correct number and distance between "
"ticks in the axis";
return std::vector<Tick>();
}
std::vector<Tick> ticks;
for (float current = offset - std::fmod(offset, scale);
current < offset + viewport_size; current += scale) {
// Since offset is non-negative, we have to allow some small room to render
// the zero in most cases.
if (current < offset - 1) {
continue;
}
ticks.push_back(
{(current - offset) / viewport_size * axis_size, ToString(current)});
}
return ticks;
}
} // namespace
AxisRenderer::AxisRenderer(TextRenderer* text_factory,
const ProgramState* state)
: shader_(kVertexShader, kFragmentShader),
state_(state),
text_renderer_(text_factory) {
// Use a simple reference text to determine which spacing should be used
// between the ticks on the axis.
std::shared_ptr<const Text> reference_text =
text_renderer_->RenderText("12.345s");
reference_label_width_ = reference_text->width();
reference_label_height_ = reference_text->height();
}
void AxisRenderer::Render() {
const float distance_between_axis_and_trace = state_->ScaleForDpi(10);
const vec2 axis_offset =
TraceMargin(state_->dpi_scale()) -
vec2(distance_between_axis_and_trace, distance_between_axis_and_trace);
const float tick_size = state_->ScaleForDpi(10);
const vec2 axis_size = state_->window() - 2 * axis_offset;
// Note that the coordinates of the objects in this array are w.r.t the origin
// of the axis lines that we are drawing.
std::vector<Line> lines = {
{0, 0, axis_size.x, 0},
{0, 0, 0, axis_size.y},
};
for (const Tick& tick :
Ticks<TimeToString>(state_->offset().x, state_->viewport().x,
axis_size.x, reference_label_width_)) {
lines.push_back({tick.offset, 0, tick.offset, -tick_size});
std::shared_ptr<const Text> text = text_renderer_->RenderText(tick.text);
text_renderer_->AddText(
text,
axis_offset.x + tick.offset - text->width() / 2, // Center on X
axis_offset.y - text->height() - tick_size); // Shift on Y
}
for (const Tick& tick :
Ticks<DataOffsetToString>(state_->offset().y, state_->viewport().y,
axis_size.y, reference_label_height_)) {
lines.push_back({0, tick.offset, -tick_size, tick.offset});
std::shared_ptr<const Text> text = text_renderer_->RenderText(tick.text);
text_renderer_->AddText(
text,
axis_offset.x - text->width() - tick_size * 1.6, // Shift on X
axis_offset.y + tick.offset - text->height() / 2); // Center on Y
}
GlVertexBuffer buffer;
glBindBuffer(GL_ARRAY_BUFFER, *buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(*lines.data()) * lines.size(),
lines.data(), GL_STATIC_DRAW);
GlVertexArray array_;
glBindVertexArray(*array_);
glUseProgram(*shader_);
state_->Bind(shader_);
shader_.SetUniform("axis_offset", axis_offset.x, axis_offset.y);
GlVertexArrayAttrib coord(shader_, "coord");
glVertexAttribPointer(*coord, 2, GL_FLOAT, GL_FALSE, 0, 0);
glDrawArrays(GL_LINES, 0, lines.size() * 2);
}
} // namespace render
} // namespace quic_trace