/* * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 Serge Bazanski * * 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 MAPGLWIDGET_H #define MAPGLWIDGET_H #include #include #include #include #include #include #include #include #include #include #include #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 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 private: QMutex mutex_; QWaitCondition condition_; bool abort_; std::function target_; QTimer timer_; public: explicit PeriodicRunner(QObject *parent, std::function target) : QThread(parent), abort_(false), target_(target), timer_(this) { connect(&timer_, &QTimer::timeout, this, &PeriodicRunner::poke); } void run(void) override { for (;;) { mutex_.lock(); condition_.wait(&mutex_); if (abort_) { mutex_.unlock(); return; } target_(); mutex_.unlock(); } } void startTimer(int msecs) { timer_.start(msecs); } ~PeriodicRunner() { mutex_.lock(); abort_ = true; condition_.wakeOne(); mutex_.unlock(); wait(); } void poke(void) { condition_.wakeOne(); } }; class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions { Q_OBJECT public: FPGAViewWidget(QWidget *parent = 0); ~FPGAViewWidget(); QSize minimumSizeHint() const override; QSize sizeHint() const override; protected: void initializeGL() Q_DECL_OVERRIDE; void paintGL() Q_DECL_OVERRIDE; void resizeGL(int width, int height) Q_DECL_OVERRIDE; void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE; void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE; void drawDecal(LineShaderData &data, const DecalXY &decal); void drawDecal(LineShaderData out[], const DecalXY &decal); public Q_SLOTS: void newContext(Context *ctx); void onSelectedArchItem(std::vector decals); void onHighlightGroupChanged(std::vector decals, int group); void pokeRenderer(void); void zoomIn(); void zoomOut(); void zoomSelected(); void zoomOutbound(); private: void renderLines(void); void zoom(int level); QPoint lastPos_; LineShader lineShader_; QMatrix4x4 viewMove_; float zoom_; QMatrix4x4 getProjection(void); QVector4D mouseToWorldCoordinates(int x, int y); const float zoomNear_ = 1.0f; // do not zoom closer than this const float zoomFar_ = 10000.0f; // do not zoom further than this const float zoomLvl1_ = 100.0f; const float zoomLvl2_ = 50.0f; Context *ctx_; QTimer paintTimer_; std::unique_ptr renderRunner_; struct { QColor background; QColor grid; QColor frame; QColor hidden; QColor inactive; QColor active; QColor selected; QColor highlight[8]; } colors_; struct RendererData { LineShaderData decals[4]; LineShaderData selected; LineShaderData highlighted[8]; }; struct RendererArgs { std::vector selectedItems; std::vector highlightedItems[8]; bool highlightedOrSelectedChanged; }; std::unique_ptr rendererData_; QMutex rendererDataLock_; std::unique_ptr rendererArgs_; QMutex rendererArgsLock_; }; NEXTPNR_NAMESPACE_END #endif