From 16acc6ea43f4c45dfb0e9550d731a2fba3f32618 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Thu, 26 Jul 2018 17:37:24 +0100 Subject: [PATCH] gui: move polyline/lineshader to gui/lineshader.{h,cc} --- gui/fpgaviewwidget.cc | 211 ------------------------------------------ gui/fpgaviewwidget.h | 175 +---------------------------------- 2 files changed, 1 insertion(+), 385 deletions(-) diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index d87c26a3..de73e27b 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -31,217 +31,6 @@ 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(); -} - FPGAViewWidget::FPGAViewWidget(QWidget *parent) : QOpenGLWidget(parent), ctx_(nullptr), paintTimer_(this), lineShader_(this), zoom_(500.0f), diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index 6505c555..260ebf05 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -33,183 +33,10 @@ #include #include "nextpnr.h" +#include "lineshader.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 vertices; - std::vector normals; - std::vector miters; - std::vector 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 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); -}; - class PeriodicRunner : public QThread { Q_OBJECT