blob: b10a1ded81face32ec564d724f9c23eaf4d8251b [file] [log] [blame]
James Kuszmaul6271cf72019-09-18 20:10:08 -07001#include "frc971/analysis/plotting/webgl2_plotter.h"
2
3#include <assert.h>
4#include <stdlib.h>
5
6#include <iostream>
7
8#include <emscripten/emscripten.h>
9#include <emscripten/html5.h>
10
11namespace frc971 {
12namespace plotting {
13
14namespace {
15// Shader and program construction taken from examples at
16// https://github.com/emscripten-core/emscripten/blob/incoming/tests/webgl2_draw_packed_triangle.c
17GLuint compile_shader(GLenum shaderType, const char *src) {
18 GLuint shader = glCreateShader(shaderType);
19 glShaderSource(shader, 1, &src, nullptr);
20 glCompileShader(shader);
21
22 GLint isCompiled = 0;
23 glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
24 if (!isCompiled) {
25 GLint maxLength = 0;
26 glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
27 char *buf = (char *)malloc(maxLength + 1);
28 glGetShaderInfoLog(shader, maxLength, &maxLength, buf);
29 printf("%s\n", buf);
30 free(buf);
31 return 0;
32 }
33
34 return shader;
35}
36
37GLuint create_program(GLuint vertexShader, GLuint fragmentShader,
38 GLuint attribute_location) {
39 GLuint program = glCreateProgram();
40 glAttachShader(program, vertexShader);
41 glAttachShader(program, fragmentShader);
42 glBindAttribLocation(program, attribute_location, "apos");
43 glLinkProgram(program);
44 return program;
45}
46
47GLuint CreateLineProgram(GLuint attribute_location) {
48 // Create a shader program which will take in:
49 // -A series of points to plot
50 // -Scale/offset parameters for choosing how to zoom/pan
51 // -Point size/color information
52 //
53 // The vertex shader then takes in the series of points (apos) and
54 // transforms the points by the scale/offset to determine their on-screen
55 // position. We aren't doing any funny with 3D or perspective, so we leave
56 // the z and w components untouched.
57 //
58 // We set the color of the line in the fragment shader.
59 const char vertex_shader[] =
60 "#version 100\n"
61 "attribute vec2 apos;"
62 "uniform vec2 scale;"
63 "uniform vec2 offset;"
64 "uniform float point_size;"
65 "void main() {"
66 "gl_Position.xy = apos.xy * scale.xy + offset.xy;"
67 "gl_Position.z = 0.0;"
68 "gl_Position.w = 1.0;"
69 "gl_PointSize = point_size;"
70 "}";
71 GLuint vs = compile_shader(GL_VERTEX_SHADER, vertex_shader);
72
73 const char fragment_shader[] =
74 "#version 100\n"
75 "precision lowp float;"
76 "uniform vec4 color;"
77 "void main() {"
78 "gl_FragColor = color;"
79 "}";
80 GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fragment_shader);
81 return create_program(vs, fs, attribute_location);
82}
83
84const Eigen::Vector2d Vector2dInfinity() {
85 return Eigen::Vector2d::Ones() * std::numeric_limits<double>::infinity();
86}
87
88} // namespace
89
90class WebglLine : public Line {
91 public:
92 WebglLine(GLuint program, size_t buffer_size = 1000000)
93 : color_uniform_location_(glGetUniformLocation(program, "color")),
94 point_size_uniform_location_(
95 glGetUniformLocation(program, "point_size")),
96 line_size_(0) {}
97 virtual ~WebglLine() {}
98 void SetPoints(const std::vector<Eigen::Vector2d> &pts) override {
99 updated_ = true;
100 max_values_ = -Vector2dInfinity();
101 min_values_ = Vector2dInfinity();
102 line_size_ = 0;
103 buffer_.clear();
104 for (const auto &pt : pts) {
105 buffer_.push_back(pt.x());
106 buffer_.push_back(pt.y());
107 max_values_ = max_values_.cwiseMax(pt);
108 min_values_ = min_values_.cwiseMin(pt);
109 ++line_size_;
110 }
111 }
112 void Draw() override {
113 updated_ = false;
114 if (buffer_.empty()) {
115 return;
116 }
117 // TODO(james): Flushing and rewriting the buffer on every line draw seems
118 // like it should be inefficient, but in practice it seems to actually be
119 // fine for the amounts of data that we are dealing with (i.e., doing a few
120 // tens of MB of copies at the most is not actually that expensive).
121 glBufferData(GL_ARRAY_BUFFER, buffer_.size() * sizeof(float),
122 buffer_.data(), GL_STATIC_DRAW);
123 glUniform4f(color_uniform_location_, color_.r, color_.g, color_.b, 1.0);
124 glUniform1f(point_size_uniform_location_, point_size_);
125 if (point_size_ != 0) {
126 glDrawArrays(GL_POINTS, 0, line_size_);
127 }
128 if (line_width_ != 0) {
129 glDrawArrays(GL_LINE_STRIP, 0, line_size_);
130 }
131 assert(GL_NO_ERROR == glGetError() && "glDrawArray failed");
132 }
133 void SetColor(const Color &color) override {
134 updated_ = true;
135 color_ = color;
136 }
137
138 Eigen::Vector2d MaxValues() const override { return max_values_; }
139 Eigen::Vector2d MinValues() const override { return min_values_; }
140
141 void SetLineWidth(const float width) override {
142 updated_ = true;
143 line_width_ = width;
144 }
145 void SetPointSize(const float point_size) override {
146 updated_ = true;
147 point_size_ = point_size;
148 }
149
150 bool HasUpdate() override { return updated_; }
151
152 private:
153 const GLuint color_uniform_location_;
154 const GLuint point_size_uniform_location_;
155 std::vector<float> buffer_;
156 size_t line_size_;
157 Color color_;
158 Eigen::Vector2d max_values_ = -Vector2dInfinity();
159 Eigen::Vector2d min_values_ = Vector2dInfinity();
160 float line_width_ = 1.0;
161 float point_size_ = 3.0;
162 bool updated_ = true;
163};
164
165WebglCanvasPlotter::WebglCanvasPlotter(const std::string &canvas_id,
166 GLuint attribute_location) {
167 EmscriptenWebGLContextAttributes attr;
168 emscripten_webgl_init_context_attributes(&attr);
169 assert(attr.antialias && "Antialiasing should be enabled by default.");
170 attr.majorVersion = 2;
171 EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context("#canvas", &attr);
172 assert(ctx && "Failed to create WebGL2 context");
173 emscripten_webgl_make_context_current(ctx);
174
175 program_ = CreateLineProgram(attribute_location);
176 scale_uniform_location_ = glGetUniformLocation(program_, "scale");
177 offset_uniform_location_ = glGetUniformLocation(program_, "offset");
178
179 glGenBuffers(1, &gl_buffer_);
180 glUseProgram(program_);
181 glBindBuffer(GL_ARRAY_BUFFER, gl_buffer_);
182 glVertexAttribPointer(attribute_location, 2, GL_FLOAT, GL_FALSE, 8, 0);
183 glEnableVertexAttribArray(attribute_location);
184
185 zoom_rectangle_ = std::make_unique<WebglLine>(program_);
186 zoom_rectangle_->SetColor({.r = 1.0, .g = 1.0, .b = 1.0});
187 zoom_rectangle_->SetLineWidth(2.0);
188 zoom_rectangle_->SetPointSize(0.0);
189}
190
191Line *WebglCanvasPlotter::AddLine() {
192 lines_.push_back(std::make_unique<WebglLine>(program_));
193 return lines_.back().get();
194}
195void WebglCanvasPlotter::Undo() {
196 if (old_scales_.empty() || old_offsets_.empty()) {
197 return;
198 }
199 scale_ = old_scales_.back();
200 old_scales_.pop_back();
201 offset_ = old_offsets_.back();
202 old_offsets_.pop_back();
203}
204
205void WebglCanvasPlotter::RecordState() {
206 old_scales_.push_back(scale_);
207 old_offsets_ .push_back(offset_);
208}
209
210void WebglCanvasPlotter::SetScale(const Eigen::Vector2d &scale) {
211 scale_ = scale;
212}
213
214Eigen::Vector2d WebglCanvasPlotter::GetScale() const {
215 return scale_;
216}
217
218void WebglCanvasPlotter::SetOffset(const Eigen::Vector2d &offset) {
219 offset_ = offset;
220}
221
222Eigen::Vector2d WebglCanvasPlotter::GetOffset() const {
223 return offset_;
224}
225
226void WebglCanvasPlotter::Redraw() {
227 const bool scaling_update = last_scale_ != scale_ || last_offset_ != offset_;
228 bool data_update = zoom_rectangle_->HasUpdate();
229 for (const auto &line : lines_) {
230 data_update = line->HasUpdate() || data_update;
231 }
232 if (!scaling_update && !data_update) {
233 return;
234 }
235 glUseProgram(program_);
236 glBindBuffer(GL_ARRAY_BUFFER, gl_buffer_);
237 glUniform2f(scale_uniform_location_, scale_.x(), scale_.y());
238 glUniform2f(offset_uniform_location_, offset_.x(), offset_.y());
239 for (const auto &line : lines_) {
240 line->Draw();
241 }
242 zoom_rectangle_->Draw();
243 last_scale_ = scale_;
244 last_offset_ = offset_;
245}
246
247Eigen::Vector2d WebglCanvasPlotter::MaxValues() const {
248 Eigen::Vector2d max = -Vector2dInfinity();
249 for (const auto &line : lines_) {
250 max = max.cwiseMax(line->MaxValues());
251 }
252 return max;
253}
254Eigen::Vector2d WebglCanvasPlotter::MinValues() const {
255 Eigen::Vector2d min = Vector2dInfinity();
256 for (const auto &line : lines_) {
257 min = min.cwiseMin(line->MinValues());
258 }
259 return min;
260}
261
262void WebglCanvasPlotter::ClearZoomRectangle() {
263 zoom_rectangle_->SetPoints({});
264}
265
266
267void WebglCanvasPlotter::SetZoomRectangle(const Eigen::Vector2d &corner1,
268 const Eigen::Vector2d &corner2) {
269 zoom_rectangle_->SetPoints({corner1,
270 {corner1.x(), corner2.y()},
271 corner2,
272 {corner2.x(), corner1.y()},
273 corner1});
274}
275
276} // namespace plotting
277} // namespace frc971