gui: include linshader.{cc,h}
This commit is contained in:
parent
16acc6ea43
commit
ae6eeb9d81
236
gui/lineshader.cc
Normal file
236
gui/lineshader.cc
Normal file
@ -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
|
209
gui/lineshader.h
Normal file
209
gui/lineshader.h
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user