diff options
Diffstat (limited to 'gui/fpgaviewwidget.cc')
-rw-r--r-- | gui/fpgaviewwidget.cc | 392 |
1 files changed, 295 insertions, 97 deletions
diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index 000b4cf0..4e60c56b 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -1,13 +1,221 @@ -#include "fpgaviewwidget.h" +/* + * 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 <cstdio> +#include <math.h> + #include <QApplication> #include <QCoreApplication> #include <QMouseEvent> #include <QWidget> -#include <math.h> + +#include "fpgaviewwidget.h" +#include "log.h" #include "mainwindow.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. + QVector2D tangent_normal = QVector2D(-tangent.y(), tangent.x()); + + // Calculate normal to one of the lines. + 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) + float miter = QVector2D::dotProduct(tangent_normal, dprev_normal); + + float x = cur->x(); + float y = cur->y(); + float mx = tangent_normal.x(); + 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, built to 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; + } + 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"); + + return true; +} + +void LineShader::draw(const LineShaderData &line, const QMatrix4x4 &projection) +{ + auto gl = QOpenGLContext::currentContext()->functions(); + program_->bind(); + + program_->setUniformValue(uniforms_.projection, projection); + program_->setUniformValue(uniforms_.thickness, line.thickness); + program_->setUniformValue(uniforms_.color, line.color.r, line.color.g, + line.color.b, line.color.a); + + gl->glVertexAttribPointer(attributes_.position, 2, GL_FLOAT, GL_FALSE, 0, + &line.vertices[0]); + gl->glVertexAttribPointer(attributes_.normal, 2, GL_FLOAT, GL_FALSE, 0, + &line.normals[0]); + gl->glVertexAttribPointer(attributes_.miter, 1, GL_FLOAT, GL_FALSE, 0, + &line.miters[0]); + + gl->glEnableVertexAttribArray(0); + gl->glEnableVertexAttribArray(1); + gl->glEnableVertexAttribArray(2); + + gl->glDrawElements(GL_TRIANGLES, line.indices.size(), GL_UNSIGNED_INT, + &line.indices[0]); + + gl->glDisableVertexAttribArray(2); + gl->glDisableVertexAttribArray(1); + gl->glDisableVertexAttribArray(0); + program_->release(); +} + FPGAViewWidget::FPGAViewWidget(QWidget *parent) - : QOpenGLWidget(parent), m_xMove(0), m_yMove(0), m_zDistance(1.0) + : QOpenGLWidget(parent), moveX_(0), moveY_(0), zoom_(10.0f), + lineShader_(this) { ctx = qobject_cast<MainWindow *>(getMainWindow())->getContext(); } @@ -29,139 +237,127 @@ QSize FPGAViewWidget::sizeHint() const { return QSize(640, 480); } void FPGAViewWidget::setXTranslation(float t_x) { - if (t_x != m_xMove) { - m_xMove = t_x; - update(); - } + if (t_x == moveX_) + return; + + moveX_ = t_x; + update(); } void FPGAViewWidget::setYTranslation(float t_y) { - if (t_y != m_yMove) { - m_yMove = t_y; - update(); - } + if (t_y == moveY_) + return; + + moveY_ = t_y; + update(); } void FPGAViewWidget::setZoom(float t_z) { - if (t_z != m_zDistance) { - m_zDistance -= t_z; - if (m_zDistance < 0.1f) - m_zDistance = 0.1f; - if (m_zDistance > 10.0f) - m_zDistance = 10.0f; - - update(); - } + if (t_z == zoom_) + return; + zoom_ = t_z; + + if (zoom_ < 1.0f) + zoom_ = 1.0f; + if (zoom_ > 100.f) + zoom_ = 100.0f; + + update(); } void FPGAViewWidget::initializeGL() { + if (!lineShader_.compile()) { + log_error("Could not compile shader.\n"); + } initializeOpenGLFunctions(); glClearColor(1.0, 1.0, 1.0, 0.0); } -void FPGAViewWidget::drawElement(const GraphicElement &el) +void FPGAViewWidget::drawElement(LineShaderData &out, const GraphicElement &el) { float scale = 1.0, offset = 0.0; - if (el.type == GraphicElement::G_BOX) { - glBegin(GL_LINES); - glVertex3f((offset + scale * el.x1), (offset + scale * el.y1), 0.0f); - glVertex3f((offset + scale * el.x2), (offset + scale * el.y1), 0.0f); - - glVertex3f((offset + scale * el.x2), (offset + scale * el.y1), 0.0f); - glVertex3f((offset + scale * el.x2), (offset + scale * el.y2), 0.0f); - glVertex3f((offset + scale * el.x2), (offset + scale * el.y2), 0.0f); - glVertex3f((offset + scale * el.x1), (offset + scale * el.y2), 0.0f); - - glVertex3f((offset + scale * el.x1), (offset + scale * el.y2), 0.0f); - glVertex3f((offset + scale * el.x1), (offset + scale * el.y1), 0.0f); - glEnd(); + if (el.type == GraphicElement::G_BOX) { + auto line = PolyLine(true); + line.point(offset + scale * el.x1, offset + scale * el.y1); + line.point(offset + scale * el.x2, offset + scale * el.y1); + line.point(offset + scale * el.x2, offset + scale * el.y2); + line.point(offset + scale * el.x1, offset + scale * el.y2); + line.build(out); } if (el.type == GraphicElement::G_LINE) { - glBegin(GL_LINES); - glVertex3f((offset + scale * el.x1), (offset + scale * el.y1), 0.0f); - glVertex3f((offset + scale * el.x2), (offset + scale * el.y2), 0.0f); - glEnd(); + PolyLine(offset + scale * el.x1, offset + scale * el.y1, + offset + scale * el.x2, offset + scale * el.y2) + .build(out); } } void FPGAViewWidget::paintGL() { - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glLoadIdentity(); - - glTranslatef(m_xMove, m_yMove, -10.0); - glScalef(m_zDistance, m_zDistance, 0.0f); - - // Grid - glColor3f(0.9, 0.9, 0.9); - glBegin(GL_LINES); - for (float i = -100; i <= 100; i += 0.1) { - glVertex3f((float)i, -100.0f, 0.0f); - glVertex3f((float)i, 100.0f, 0.0f); - glVertex3f(-100.0f, (float)i, 0.0f); - glVertex3f(100.0f, (float)i, 0.0f); - } - glColor3f(0.7, 0.7, 0.7); - for (int i = -100; i <= 100; i += 1) { - glVertex3f((float)i, -100.0f, 0.0f); - glVertex3f((float)i, 100.0f, 0.0f); - glVertex3f(-100.0f, (float)i, 0.0f); - glVertex3f(100.0f, (float)i, 0.0f); + auto gl = QOpenGLContext::currentContext()->functions(); + const qreal retinaScale = devicePixelRatio(); + gl->glViewport(0, 0, width() * retinaScale, height() * retinaScale); + gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + float aspect = float(width()) / float(height()); + + QMatrix4x4 matrix; + matrix.ortho(QRectF(-aspect / 2.0, -0.5, aspect, 1.0f)); + matrix.translate(moveX_, moveY_, -0.5); + matrix.scale(zoom_ * 0.01f, zoom_ * 0.01f, 0); + + // Draw grid. + auto grid = LineShaderData(0.01f, QColor("#DDD")); + for (float i = -100.0f; i < 100.0f; i += 1.0f) { + PolyLine(-100.0f, i, 100.0f, i).build(grid); + PolyLine(i, -100.0f, i, 100.0f).build(grid); } - glEnd(); + lineShader_.draw(grid, matrix); - glColor3f(0.1, 0.1, 0.1); - glLineWidth(0.1); - // Draw Bels + // Draw Bels. + auto bels = LineShaderData(0.02f, QColor("#b000ba")); for (auto bel : ctx->getBels()) { for (auto &el : ctx->getBelGraphics(bel)) - drawElement(el); + drawElement(bels, el); } - // Draw Frame Graphics - for (auto &el : ctx->getFrameGraphics()) - drawElement(el); -} + lineShader_.draw(bels, matrix); -void FPGAViewWidget::resizeGL(int width, int height) -{ - m_windowWidth = width; - m_windowHeight = height; - glViewport(0, 0, m_windowWidth, m_windowHeight); - - float aspect = width * 1.0 / height; - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(-1.0 * aspect, +1.0 * aspect, -1.0, +1.0, 1.0, 15.0); - glMatrixMode(GL_MODELVIEW); + // Draw Frame Graphics. + auto frames = LineShaderData(0.02f, QColor("#0066ba")); + for (auto &el : ctx->getFrameGraphics()) { + drawElement(frames, el); + } + lineShader_.draw(frames, matrix); } +void FPGAViewWidget::resizeGL(int width, int height) {} + void FPGAViewWidget::mousePressEvent(QMouseEvent *event) { - m_lastPos = event->pos(); + startDragX_ = moveX_; + startDragY_ = moveY_; + lastPos_ = event->pos(); } void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event) { - int dx = event->x() - m_lastPos.x(); - int dy = event->y() - m_lastPos.y(); - float dx_scale = dx * (1 / (float)640); - float dy_scale = -dy * (1 / (float)480); - - if (event->buttons() & Qt::LeftButton) { - float xpos = m_xMove + dx_scale; - float ypos = m_yMove + dy_scale; - if (m_xMove / m_zDistance <= 100.0 && m_xMove / m_zDistance >= -100.0) - setXTranslation(xpos); - if (m_yMove / m_zDistance <= 100.0 && m_yMove / m_zDistance >= -100.0) - setYTranslation(ypos); - } - m_lastPos = event->pos(); + const int dx = event->x() - lastPos_.x(); + const int dy = event->y() - lastPos_.y(); + + const qreal retinaScale = devicePixelRatio(); + float aspect = float(width()) / float(height()); + const float dx_scale = dx * (1 / (float)width() * retinaScale * aspect); + const float dy_scale = dy * (1 / (float)height() * retinaScale); + + float xpos = dx_scale + startDragX_; + float ypos = dy_scale + startDragY_; + + setXTranslation(xpos); + setYTranslation(ypos); } void FPGAViewWidget::wheelEvent(QWheelEvent *event) @@ -169,7 +365,9 @@ void FPGAViewWidget::wheelEvent(QWheelEvent *event) QPoint degree = event->angleDelta() / 8; if (!degree.isNull()) { - QPoint step = degree / 15; - setZoom(step.y() * -0.1f); + float steps = degree.y() / 15.0; + setZoom(zoom_ + steps); } } + +NEXTPNR_NAMESPACE_END |