aboutsummaryrefslogtreecommitdiffstats
path: root/gui
diff options
context:
space:
mode:
authorSergiusz Bazanski <q3k@q3k.org>2018-07-26 17:39:22 +0100
committerSergiusz Bazanski <q3k@q3k.org>2018-07-26 17:39:22 +0100
commitae6eeb9d810c647ca1684459627b8dd20870f993 (patch)
treed08d1469dafc893c64ea7e1d0a1966eaf97e82e1 /gui
parent16acc6ea43f4c45dfb0e9550d731a2fba3f32618 (diff)
downloadnextpnr-ae6eeb9d810c647ca1684459627b8dd20870f993.tar.gz
nextpnr-ae6eeb9d810c647ca1684459627b8dd20870f993.tar.bz2
nextpnr-ae6eeb9d810c647ca1684459627b8dd20870f993.zip
gui: include linshader.{cc,h}
Diffstat (limited to 'gui')
-rw-r--r--gui/lineshader.cc236
-rw-r--r--gui/lineshader.h209
2 files changed, 445 insertions, 0 deletions
diff --git a/gui/lineshader.cc b/gui/lineshader.cc
new file mode 100644
index 00000000..94a7a010
--- /dev/null
+++ b/gui/lineshader.cc
@@ -0,0 +1,236 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Serge Bazanski <q3k@symbioticeda.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#include "log.h"
+#include "lineshader.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+void PolyLine::buildPoint(LineShaderData *building, const QVector2D *prev, const QVector2D *cur,
+ const QVector2D *next) const
+{
+ // buildPoint emits two vertices per line point, along with normals to move
+ // them the right directio when rendering and miter to compensate for
+ // bends.
+
+ if (cur == nullptr) {
+ // BUG
+ return;
+ }
+
+ if (prev == nullptr && next == nullptr) {
+ // BUG
+ return;
+ }
+
+ // TODO(q3k): fast path for vertical/horizontal lines?
+
+ // TODO(q3k): consider moving some of the linear algebra to the GPU,
+ // they're better at this than poor old CPUs.
+
+ // Build two unit vectors pointing in the direction of the two segments
+ // defined by (prev, cur) and (cur, next)
+ QVector2D dprev, dnext;
+ if (prev == nullptr) {
+ dnext = *next - *cur;
+ dprev = dnext;
+ } else if (next == nullptr) {
+ dprev = *cur - *prev;
+ dnext = dprev;
+ } else {
+ dprev = *cur - *prev;
+ dnext = *next - *cur;
+ }
+ dprev.normalize();
+ dnext.normalize();
+
+ // Calculate tangent unit vector.
+ QVector2D tangent(dprev + dnext);
+ tangent.normalize();
+
+ // Calculate normal to tangent - this is the line on which the vectors need
+ // to be pushed to build a thickened line.
+ const QVector2D tangent_normal = QVector2D(-tangent.y(), tangent.x());
+
+ // Calculate normal to one of the lines.
+ const QVector2D dprev_normal = QVector2D(-dprev.y(), dprev.x());
+ // https://people.eecs.berkeley.edu/~sequin/CS184/IMGS/Sweep_PolyLine.jpg
+ // (the ^-1 is performed in the shader)
+ const float miter = QVector2D::dotProduct(tangent_normal, dprev_normal);
+
+ const float x = cur->x();
+ const float y = cur->y();
+ const float mx = tangent_normal.x();
+ const float my = tangent_normal.y();
+
+ // Push back 'left' vertex.
+ building->vertices.push_back(Vertex2DPOD(x, y));
+ building->normals.push_back(Vertex2DPOD(mx, my));
+ building->miters.push_back(miter);
+
+ // Push back 'right' vertex.
+ building->vertices.push_back(Vertex2DPOD(x, y));
+ building->normals.push_back(Vertex2DPOD(mx, my));
+ building->miters.push_back(-miter);
+}
+
+void PolyLine::build(LineShaderData &target) const
+{
+ if (points_.size() < 2) {
+ return;
+ }
+ const QVector2D *first = &points_.front();
+ const QVector2D *last = &points_.back();
+
+ // Index number of vertices, used to build the index buffer.
+ unsigned int startIndex = target.vertices.size();
+ unsigned int index = startIndex;
+
+ // For every point on the line, call buildPoint with (prev, point, next).
+ // If we're building a closed line, prev/next wrap around. Otherwise
+ // they are passed as nullptr and buildPoint interprets that accordinglu.
+ const QVector2D *prev = nullptr;
+
+ // Loop iterator used to ensure next is valid.
+ unsigned int i = 0;
+ for (const QVector2D &point : points_) {
+ const QVector2D *next = nullptr;
+ if (++i < points_.size()) {
+ next = (&point + 1);
+ }
+
+ // If the line is closed, wrap around. Otherwise, pass nullptr.
+ if (prev == nullptr && closed_) {
+ buildPoint(&target, last, &point, next);
+ } else if (next == nullptr && closed_) {
+ buildPoint(&target, prev, &point, first);
+ } else {
+ buildPoint(&target, prev, &point, next);
+ }
+
+ // If we have a prev point relative to cur, build a pair of triangles
+ // to render vertices into lines.
+ if (prev != nullptr) {
+ target.indices.push_back(index);
+ target.indices.push_back(index + 1);
+ target.indices.push_back(index + 2);
+
+ target.indices.push_back(index + 2);
+ target.indices.push_back(index + 1);
+ target.indices.push_back(index + 3);
+
+ index += 2;
+ }
+ prev = &point;
+ }
+
+ // If we're closed, build two more vertices that loop the line around.
+ if (closed_) {
+ target.indices.push_back(index);
+ target.indices.push_back(index + 1);
+ target.indices.push_back(startIndex);
+
+ target.indices.push_back(startIndex);
+ target.indices.push_back(index + 1);
+ target.indices.push_back(startIndex + 1);
+ }
+}
+
+bool LineShader::compile(void)
+{
+ program_ = new QOpenGLShaderProgram(parent_);
+ program_->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource_);
+ program_->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource_);
+ if (!program_->link()) {
+ printf("could not link program: %s\n", program_->log().toStdString().c_str());
+ return false;
+ }
+
+ if (!vao_.create())
+ log_abort();
+ vao_.bind();
+
+ if (!buffers_.position.create())
+ log_abort();
+ if (!buffers_.normal.create())
+ log_abort();
+ if (!buffers_.miter.create())
+ log_abort();
+ if (!buffers_.index.create())
+ log_abort();
+
+ attributes_.position = program_->attributeLocation("position");
+ attributes_.normal = program_->attributeLocation("normal");
+ attributes_.miter = program_->attributeLocation("miter");
+ uniforms_.thickness = program_->uniformLocation("thickness");
+ uniforms_.projection = program_->uniformLocation("projection");
+ uniforms_.color = program_->uniformLocation("color");
+
+ vao_.release();
+ return true;
+}
+
+void LineShader::draw(const LineShaderData &line, const QColor &color, float thickness, const QMatrix4x4 &projection)
+{
+ auto gl = QOpenGLContext::currentContext()->functions();
+ if (line.vertices.size() == 0)
+ return;
+ vao_.bind();
+ program_->bind();
+
+ buffers_.position.bind();
+ buffers_.position.allocate(&line.vertices[0], sizeof(Vertex2DPOD) * line.vertices.size());
+
+ buffers_.normal.bind();
+ buffers_.normal.allocate(&line.normals[0], sizeof(Vertex2DPOD) * line.normals.size());
+
+ buffers_.miter.bind();
+ buffers_.miter.allocate(&line.miters[0], sizeof(GLfloat) * line.miters.size());
+
+ buffers_.index.bind();
+ buffers_.index.allocate(&line.indices[0], sizeof(GLuint) * line.indices.size());
+
+ program_->setUniformValue(uniforms_.projection, projection);
+ program_->setUniformValue(uniforms_.thickness, thickness);
+ program_->setUniformValue(uniforms_.color, color.redF(), color.greenF(), color.blueF(), color.alphaF());
+
+ buffers_.position.bind();
+ program_->enableAttributeArray("position");
+ gl->glVertexAttribPointer(attributes_.position, 2, GL_FLOAT, GL_FALSE, 0, (void *)0);
+
+ buffers_.normal.bind();
+ program_->enableAttributeArray("normal");
+ gl->glVertexAttribPointer(attributes_.normal, 2, GL_FLOAT, GL_FALSE, 0, (void *)0);
+
+ buffers_.miter.bind();
+ program_->enableAttributeArray("miter");
+ gl->glVertexAttribPointer(attributes_.miter, 1, GL_FLOAT, GL_FALSE, 0, (void *)0);
+
+ buffers_.index.bind();
+ gl->glDrawElements(GL_TRIANGLES, line.indices.size(), GL_UNSIGNED_INT, (void *)0);
+
+ program_->disableAttributeArray("miter");
+ program_->disableAttributeArray("normal");
+ program_->disableAttributeArray("position");
+
+ program_->release();
+ vao_.release();
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/gui/lineshader.h b/gui/lineshader.h
new file mode 100644
index 00000000..3f4c4057
--- /dev/null
+++ b/gui/lineshader.h
@@ -0,0 +1,209 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Serge Bazanski <q3k@symbioticeda.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#ifndef LINESHADER_H
+#define LINESHADER_H
+
+#include <QOpenGLBuffer>
+#include <QOpenGLFunctions>
+#include <QOpenGLShaderProgram>
+#include <QOpenGLVertexArrayObject>
+#include <QOpenGLWidget>
+
+#include "nextpnr.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+// Vertex2DPOD is a structure of X, Y coordinates that can be passed to OpenGL
+// directly.
+NPNR_PACKED_STRUCT(struct Vertex2DPOD {
+ GLfloat x;
+ GLfloat y;
+
+ Vertex2DPOD(GLfloat X, GLfloat Y) : x(X), y(Y) {}
+});
+
+// LineShaderData is a built set of vertices that can be rendered by the
+// LineShader.
+// Each LineShaderData can have its' own color and thickness.
+struct LineShaderData
+{
+ std::vector<Vertex2DPOD> vertices;
+ std::vector<Vertex2DPOD> normals;
+ std::vector<GLfloat> miters;
+ std::vector<GLuint> indices;
+
+ LineShaderData(void) {}
+
+ void clear(void)
+ {
+ vertices.clear();
+ normals.clear();
+ miters.clear();
+ indices.clear();
+ }
+};
+
+// PolyLine is a set of segments defined by points, that can be built to a
+// ShaderLine for GPU rendering.
+class PolyLine
+{
+ private:
+ std::vector<QVector2D> points_;
+ bool closed_;
+
+ void buildPoint(LineShaderData *building, const QVector2D *prev, const QVector2D *cur, const QVector2D *next) const;
+
+ public:
+ // Create an empty PolyLine.
+ PolyLine(bool closed = false) : closed_(closed) {}
+
+ // Create a non-closed polyline consisting of one segment.
+ PolyLine(float x0, float y0, float x1, float y1) : closed_(false)
+ {
+ point(x0, y0);
+ point(x1, y1);
+ }
+
+ // Add a point to the PolyLine.
+ void point(float x, float y) { points_.push_back(QVector2D(x, y)); }
+
+ // Built PolyLine to shader data.
+ void build(LineShaderData &target) const;
+
+ // Set whether line is closed (ie. a loop).
+ void setClosed(bool closed) { closed_ = closed; }
+};
+
+// LineShader is an OpenGL shader program that renders LineShaderData on the
+// GPU.
+// The LineShader expects two vertices per line point. It will push those
+// vertices along the given normal * miter. This is used to 'stretch' the line
+// to be as wide as the given thickness. The normal and miter are calculated
+// by the PolyLine build method in order to construct a constant thickness line
+// with miter edge joints.
+//
+// +------+------+
+//
+// |
+// PolyLine.build()
+// |
+// V
+//
+// ^ ^ ^
+// | | | <--- normal vectors (x2, pointing in the same
+// +/+----+/+----+/+ direction)
+//
+// |
+// vertex shader
+// |
+// V
+//
+// +------+------+ ^ by normal * miter * thickness/2
+// | | |
+// +------+------+ V by normal * miter * thickness/2
+//
+// (miter is flipped for every second vertex generated)
+class LineShader
+{
+ private:
+ QObject *parent_;
+ QOpenGLShaderProgram *program_;
+
+ // GL attribute locations.
+ struct
+ {
+ // original position of line vertex
+ GLuint position;
+ // normal by which vertex should be translated
+ GLuint normal;
+ // scalar defining:
+ // - how stretched the normal vector should be to
+ // compensate for bends
+ // - which way the normal should be applied (+1 for one vertex, -1
+ // for the other)
+ GLuint miter;
+ } attributes_;
+
+ // GL buffers
+ struct
+ {
+ QOpenGLBuffer position;
+ QOpenGLBuffer normal;
+ QOpenGLBuffer miter;
+ QOpenGLBuffer index;
+ } buffers_;
+
+ // GL uniform locations.
+ struct
+ {
+ // combines m/v/p matrix to apply
+ GLuint projection;
+ // desired thickness of line
+ GLuint thickness;
+ // color of line
+ GLuint color;
+ } uniforms_;
+
+ QOpenGLVertexArrayObject vao_;
+
+ public:
+ LineShader(QObject *parent) : parent_(parent), program_(nullptr)
+ {
+ buffers_.position = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
+ buffers_.position.setUsagePattern(QOpenGLBuffer::StaticDraw);
+
+ buffers_.normal = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
+ buffers_.normal.setUsagePattern(QOpenGLBuffer::StaticDraw);
+
+ buffers_.miter = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
+ buffers_.miter.setUsagePattern(QOpenGLBuffer::StaticDraw);
+
+ buffers_.index = QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
+ buffers_.index.setUsagePattern(QOpenGLBuffer::StaticDraw);
+ }
+
+ static constexpr const char *vertexShaderSource_ =
+ "#version 110\n"
+ "attribute highp vec2 position;\n"
+ "attribute highp vec2 normal;\n"
+ "attribute highp float miter;\n"
+ "uniform highp float thickness;\n"
+ "uniform highp mat4 projection;\n"
+ "void main() {\n"
+ " vec2 p = position.xy + vec2(normal * thickness/2.0 / miter);\n"
+ " gl_Position = projection * vec4(p, 0.0, 1.0);\n"
+ "}\n";
+
+ static constexpr const char *fragmentShaderSource_ = "#version 110\n"
+ "uniform lowp vec4 color;\n"
+ "void main() {\n"
+ " gl_FragColor = color;\n"
+ "}\n";
+
+ // Must be called on initialization.
+ bool compile(void);
+
+ // Render a LineShaderData with a given M/V/P transformation.
+ void draw(const LineShaderData &data, const QColor &color, float thickness, const QMatrix4x4 &projection);
+};
+
+NEXTPNR_NAMESPACE_END
+
+#endif