aboutsummaryrefslogtreecommitdiffstats
path: root/gui/fpgaviewwidget.cc
diff options
context:
space:
mode:
authorSergiusz Bazanski <q3k@q3k.org>2018-06-21 19:12:20 +0100
committerSergiusz Bazanski <q3k@q3k.org>2018-06-22 14:27:04 +0100
commit98b1f0c041b01d07f64e8e04503f8eccb05a93de (patch)
tree4775cd11552fd1da22d1da7824ccbe41a64f14bb /gui/fpgaviewwidget.cc
parent4e480a9a61e62c5e335d8736afa72e108ebaedfb (diff)
downloadnextpnr-98b1f0c041b01d07f64e8e04503f8eccb05a93de.tar.gz
nextpnr-98b1f0c041b01d07f64e8e04503f8eccb05a93de.tar.bz2
nextpnr-98b1f0c041b01d07f64e8e04503f8eccb05a93de.zip
Reimplement FPGAWidget in modern OpenGL.
Diffstat (limited to 'gui/fpgaviewwidget.cc')
-rw-r--r--gui/fpgaviewwidget.cc392
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