Merge branch 'master' into q3k/clickity
This commit is contained in:
commit
940886f9fa
@ -351,7 +351,7 @@ int main(int argc, char **argv)
|
||||
break;
|
||||
case TOK_REF:
|
||||
if (s.tokenComments[i].empty())
|
||||
printf("ref %s %s\n", labelNames[v].c_str());
|
||||
printf("ref %s\n", labelNames[v].c_str());
|
||||
else
|
||||
printf("ref %-26s %s\n", labelNames[v].c_str(), s.tokenComments[i].c_str());
|
||||
break;
|
||||
|
@ -145,21 +145,25 @@ struct GraphicElement
|
||||
{
|
||||
enum type_t
|
||||
{
|
||||
G_NONE,
|
||||
G_LINE,
|
||||
G_ARROW,
|
||||
G_BOX,
|
||||
G_CIRCLE,
|
||||
G_LABEL
|
||||
} type = G_NONE;
|
||||
TYPE_NONE,
|
||||
TYPE_LINE,
|
||||
TYPE_ARROW,
|
||||
TYPE_BOX,
|
||||
TYPE_CIRCLE,
|
||||
TYPE_LABEL,
|
||||
|
||||
TYPE_MAX
|
||||
} type = TYPE_NONE;
|
||||
|
||||
enum style_t
|
||||
{
|
||||
G_FRAME, // Static "frame". Contrast between G_INACTIVE and G_ACTIVE
|
||||
G_HIDDEN, // Only display when object is selected or highlighted
|
||||
G_INACTIVE, // Render using low-contrast color
|
||||
G_ACTIVE, // Render using high-contast color
|
||||
} style = G_FRAME;
|
||||
STYLE_FRAME, // Static "frame". Contrast between STYLE_INACTIVE and STYLE_ACTIVE
|
||||
STYLE_HIDDEN, // Only display when object is selected or highlighted
|
||||
STYLE_INACTIVE, // Render using low-contrast color
|
||||
STYLE_ACTIVE, // Render using high-contast color
|
||||
|
||||
STYLE_MAX
|
||||
} style = STYLE_FRAME;
|
||||
|
||||
float x1 = 0, y1 = 0, x2 = 0, y2 = 0, z = 0;
|
||||
std::string text;
|
||||
|
@ -31,220 +31,11 @@
|
||||
|
||||
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), lineShader_(this), zoom_(500.f), ctx_(nullptr), paintTimer_(this),
|
||||
rendererData_(new FPGAViewWidget::RendererData), rendererArgs_(new FPGAViewWidget::RendererArgs)
|
||||
FPGAViewWidget::FPGAViewWidget(QWidget *parent) :
|
||||
QOpenGLWidget(parent), ctx_(nullptr), paintTimer_(this),
|
||||
lineShader_(this), zoom_(500.0f),
|
||||
rendererData_(new FPGAViewWidget::RendererData),
|
||||
rendererArgs_(new FPGAViewWidget::RendererArgs)
|
||||
{
|
||||
colors_.background = QColor("#000000");
|
||||
colors_.grid = QColor("#333");
|
||||
@ -311,68 +102,49 @@ void FPGAViewWidget::initializeGL()
|
||||
0.0);
|
||||
}
|
||||
|
||||
void FPGAViewWidget::drawDecal(LineShaderData &out, const DecalXY &decal)
|
||||
void FPGAViewWidget::drawGraphicElement(LineShaderData &out, const GraphicElement &el, float x, float y)
|
||||
{
|
||||
const float scale = 1.0;
|
||||
|
||||
if (el.type == GraphicElement::TYPE_BOX) {
|
||||
auto line = PolyLine(true);
|
||||
line.point(x + scale * el.x1, y + scale * el.y1);
|
||||
line.point(x + scale * el.x2, y + scale * el.y1);
|
||||
line.point(x + scale * el.x2, y + scale * el.y2);
|
||||
line.point(x + scale * el.x1, y + scale * el.y2);
|
||||
line.build(out);
|
||||
}
|
||||
|
||||
if (el.type == GraphicElement::TYPE_LINE || el.type == GraphicElement::TYPE_ARROW) {
|
||||
PolyLine(x + scale * el.x1, y + scale * el.y1, x + scale * el.x2, y + scale * el.y2)
|
||||
.build(out);
|
||||
}
|
||||
}
|
||||
|
||||
void FPGAViewWidget::drawDecal(LineShaderData &out, const DecalXY &decal)
|
||||
{
|
||||
float offsetX = decal.x;
|
||||
float offsetY = decal.y;
|
||||
|
||||
for (auto &el : ctx_->getDecalGraphics(decal.decal)) {
|
||||
|
||||
if (el.type == GraphicElement::G_BOX) {
|
||||
auto line = PolyLine(true);
|
||||
line.point(offsetX + scale * el.x1, offsetY + scale * el.y1);
|
||||
line.point(offsetX + scale * el.x2, offsetY + scale * el.y1);
|
||||
line.point(offsetX + scale * el.x2, offsetY + scale * el.y2);
|
||||
line.point(offsetX + scale * el.x1, offsetY + scale * el.y2);
|
||||
line.build(out);
|
||||
}
|
||||
|
||||
if (el.type == GraphicElement::G_LINE || el.type == GraphicElement::G_ARROW) {
|
||||
PolyLine(offsetX + scale * el.x1, offsetY + scale * el.y1, offsetX + scale * el.x2, offsetY + scale * el.y2)
|
||||
.build(out);
|
||||
}
|
||||
drawGraphicElement(out, el, offsetX, offsetY);
|
||||
}
|
||||
}
|
||||
|
||||
void FPGAViewWidget::drawDecal(LineShaderData out[], const DecalXY &decal)
|
||||
void FPGAViewWidget::drawArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], const DecalXY &decal)
|
||||
{
|
||||
const float scale = 1.0;
|
||||
float offsetX = 0.0, offsetY = 0.0;
|
||||
float offsetX = decal.x;
|
||||
float offsetY = decal.y;
|
||||
|
||||
for (auto &el : ctx_->getDecalGraphics(decal.decal)) {
|
||||
offsetX = decal.x;
|
||||
offsetY = decal.y;
|
||||
|
||||
if (el.type == GraphicElement::G_BOX) {
|
||||
auto line = PolyLine(true);
|
||||
line.point(offsetX + scale * el.x1, offsetY + scale * el.y1);
|
||||
line.point(offsetX + scale * el.x2, offsetY + scale * el.y1);
|
||||
line.point(offsetX + scale * el.x2, offsetY + scale * el.y2);
|
||||
line.point(offsetX + scale * el.x1, offsetY + scale * el.y2);
|
||||
switch (el.style) {
|
||||
case GraphicElement::G_FRAME:
|
||||
case GraphicElement::G_INACTIVE:
|
||||
case GraphicElement::G_ACTIVE:
|
||||
line.build(out[el.style]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (el.type == GraphicElement::G_LINE || el.type == GraphicElement::G_ARROW) {
|
||||
auto line = PolyLine(offsetX + scale * el.x1, offsetY + scale * el.y1, offsetX + scale * el.x2,
|
||||
offsetY + scale * el.y2);
|
||||
switch (el.style) {
|
||||
case GraphicElement::G_FRAME:
|
||||
case GraphicElement::G_INACTIVE:
|
||||
case GraphicElement::G_ACTIVE:
|
||||
line.build(out[el.style]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (el.style) {
|
||||
case GraphicElement::STYLE_FRAME:
|
||||
case GraphicElement::STYLE_INACTIVE:
|
||||
case GraphicElement::STYLE_ACTIVE:
|
||||
drawGraphicElement(out[el.style], el, offsetX, offsetY);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -402,24 +174,28 @@ void FPGAViewWidget::paintGL()
|
||||
float thick1Px = mouseToWorldDimensions(1, 0).x();
|
||||
float thick11Px = mouseToWorldDimensions(1.1, 0).x();
|
||||
|
||||
// Draw grid.
|
||||
// Render grid.
|
||||
auto grid = LineShaderData();
|
||||
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);
|
||||
}
|
||||
// Draw grid.
|
||||
lineShader_.draw(grid, colors_.grid, thick1Px, matrix);
|
||||
|
||||
rendererDataLock_.lock();
|
||||
lineShader_.draw(rendererData_->decals[0], colors_.frame, thick11Px, matrix);
|
||||
lineShader_.draw(rendererData_->decals[1], colors_.hidden, thick11Px, matrix);
|
||||
lineShader_.draw(rendererData_->decals[2], colors_.inactive, thick11Px, matrix);
|
||||
lineShader_.draw(rendererData_->decals[3], colors_.active, thick11Px, matrix);
|
||||
|
||||
// Render Arch graphics.
|
||||
lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_FRAME], colors_.frame, thick11Px, matrix);
|
||||
lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_HIDDEN], colors_.hidden, thick11Px, matrix);
|
||||
lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_INACTIVE], colors_.inactive, thick11Px, matrix);
|
||||
lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_ACTIVE], colors_.active, thick11Px, matrix);
|
||||
|
||||
// Draw highlighted items.
|
||||
for (int i = 0; i < 8; i++)
|
||||
lineShader_.draw(rendererData_->highlighted[i], colors_.highlight[i], thick11Px, matrix);
|
||||
lineShader_.draw(rendererData_->gfxHighlighted[i], colors_.highlight[i], thick11Px, matrix);
|
||||
|
||||
lineShader_.draw(rendererData_->selected, colors_.selected, thick11Px, matrix);
|
||||
lineShader_.draw(rendererData_->gfxSelected, colors_.selected, thick11Px, matrix);
|
||||
rendererDataLock_.unlock();
|
||||
}
|
||||
|
||||
@ -430,125 +206,151 @@ void FPGAViewWidget::renderLines(void)
|
||||
if (ctx_ == nullptr)
|
||||
return;
|
||||
|
||||
ctx_->lock_ui();
|
||||
|
||||
// For now, collapse any decal changes into change of all decals.
|
||||
// TODO(q3k): fix this
|
||||
bool decalsChanged = false;
|
||||
if (ctx_->allUiReload) {
|
||||
ctx_->allUiReload = false;
|
||||
decalsChanged = true;
|
||||
}
|
||||
if (ctx_->frameUiReload) {
|
||||
ctx_->frameUiReload = false;
|
||||
decalsChanged = true;
|
||||
}
|
||||
if (ctx_->belUiReload.size() > 0) {
|
||||
ctx_->belUiReload.clear();
|
||||
decalsChanged = true;
|
||||
}
|
||||
if (ctx_->wireUiReload.size() > 0) {
|
||||
ctx_->wireUiReload.clear();
|
||||
decalsChanged = true;
|
||||
}
|
||||
if (ctx_->pipUiReload.size() > 0) {
|
||||
ctx_->pipUiReload.clear();
|
||||
decalsChanged = true;
|
||||
}
|
||||
if (ctx_->groupUiReload.size() > 0) {
|
||||
ctx_->groupUiReload.clear();
|
||||
decalsChanged = true;
|
||||
}
|
||||
|
||||
// Local copy of decals, taken as fast as possible to not block the P&R.
|
||||
std::vector<std::pair<BelId, DecalXY>> belDecals;
|
||||
// Data from Context needed to render all decals.
|
||||
std::vector<DecalXY> belDecals;
|
||||
std::vector<DecalXY> wireDecals;
|
||||
std::vector<DecalXY> pipDecals;
|
||||
std::vector<DecalXY> groupDecals;
|
||||
bool decalsChanged = false;
|
||||
{
|
||||
// Take the UI/Normal mutex on the Context, copy over all we need as
|
||||
// fast as we can.
|
||||
std::lock_guard<std::mutex> lock_ui(ctx_->ui_mutex);
|
||||
std::lock_guard<std::mutex> lock(ctx_->mutex);
|
||||
|
||||
if (decalsChanged) {
|
||||
for (auto bel : ctx_->getBels()) {
|
||||
belDecals.push_back(std::pair<BelId, DecalXY>(bel, ctx_->getBelDecal(bel)));
|
||||
// For now, collapse any decal changes into change of all decals.
|
||||
// TODO(q3k): fix this
|
||||
if (ctx_->allUiReload) {
|
||||
ctx_->allUiReload = false;
|
||||
decalsChanged = true;
|
||||
}
|
||||
for (auto wire : ctx_->getWires()) {
|
||||
wireDecals.push_back(ctx_->getWireDecal(wire));
|
||||
if (ctx_->frameUiReload) {
|
||||
ctx_->frameUiReload = false;
|
||||
decalsChanged = true;
|
||||
}
|
||||
for (auto pip : ctx_->getPips()) {
|
||||
pipDecals.push_back(ctx_->getPipDecal(pip));
|
||||
if (ctx_->belUiReload.size() > 0) {
|
||||
ctx_->belUiReload.clear();
|
||||
decalsChanged = true;
|
||||
}
|
||||
for (auto group : ctx_->getGroups()) {
|
||||
groupDecals.push_back(ctx_->getGroupDecal(group));
|
||||
if (ctx_->wireUiReload.size() > 0) {
|
||||
ctx_->wireUiReload.clear();
|
||||
decalsChanged = true;
|
||||
}
|
||||
if (ctx_->pipUiReload.size() > 0) {
|
||||
ctx_->pipUiReload.clear();
|
||||
decalsChanged = true;
|
||||
}
|
||||
if (ctx_->groupUiReload.size() > 0) {
|
||||
ctx_->groupUiReload.clear();
|
||||
decalsChanged = true;
|
||||
}
|
||||
|
||||
// Local copy of decals, taken as fast as possible to not block the P&R.
|
||||
if (decalsChanged) {
|
||||
for (auto bel : ctx_->getBels()) {
|
||||
belDecals.push_back(ctx_->getBelDecal(bel));
|
||||
}
|
||||
for (auto wire : ctx_->getWires()) {
|
||||
wireDecals.push_back(ctx_->getWireDecal(wire));
|
||||
}
|
||||
for (auto pip : ctx_->getPips()) {
|
||||
pipDecals.push_back(ctx_->getPipDecal(pip));
|
||||
}
|
||||
for (auto group : ctx_->getGroups()) {
|
||||
groupDecals.push_back(ctx_->getGroupDecal(group));
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx_->unlock_ui();
|
||||
|
||||
rendererArgsLock_.lock();
|
||||
auto selectedItems = rendererArgs_->selectedItems;
|
||||
auto highlightedItems = rendererArgs_->highlightedItems;
|
||||
auto highlightedOrSelectedChanged = rendererArgs_->highlightedOrSelectedChanged;
|
||||
rendererArgs_->highlightedOrSelectedChanged = false;
|
||||
rendererArgsLock_.unlock();
|
||||
// Arguments from the main UI thread on what we should render.
|
||||
std::vector<DecalXY> selectedDecals;
|
||||
std::vector<DecalXY> highlightedDecals[8];
|
||||
bool highlightedOrSelectedChanged;
|
||||
{
|
||||
// Take the renderer arguments lock, copy over all we need.
|
||||
QMutexLocker lock(&rendererArgsLock_);
|
||||
selectedDecals = rendererArgs_->selectedDecals;
|
||||
for (int i = 0; i < 8; i++)
|
||||
highlightedDecals[i] = rendererArgs_->highlightedDecals[i];
|
||||
highlightedOrSelectedChanged = rendererArgs_->highlightedOrSelectedChanged;
|
||||
rendererArgs_->highlightedOrSelectedChanged = false;
|
||||
}
|
||||
|
||||
QuadTreeBels::BoundingBox globalBB = QuadTreeBels::BoundingBox(-1000, -1000, 1000, 1000);
|
||||
|
||||
// Render decals if necessary.
|
||||
if (decalsChanged) {
|
||||
auto data = std::unique_ptr<FPGAViewWidget::RendererData>(new FPGAViewWidget::RendererData);
|
||||
// Draw Bels.
|
||||
data->qtBels = std::unique_ptr<QuadTreeBels>(new QuadTreeBels(globalBB));
|
||||
for (auto const &decal : belDecals) {
|
||||
drawDecal(data->decals, decal.second);
|
||||
commitToQuadtree(data->qtBels.get(), decal.second, decal.first);
|
||||
drawArchDecal(data->gfxByStyle, decal);
|
||||
}
|
||||
// Draw Wires.
|
||||
for (auto const &decal : wireDecals) {
|
||||
drawDecal(data->decals, decal);
|
||||
drawArchDecal(data->gfxByStyle, decal);
|
||||
}
|
||||
// Draw Pips.
|
||||
for (auto const &decal : pipDecals) {
|
||||
drawDecal(data->decals, decal);
|
||||
drawArchDecal(data->gfxByStyle, decal);
|
||||
}
|
||||
// Draw Groups.
|
||||
for (auto const &decal : groupDecals) {
|
||||
drawDecal(data->decals, decal);
|
||||
drawArchDecal(data->gfxByStyle, decal);
|
||||
}
|
||||
|
||||
// Swap over.
|
||||
rendererDataLock_.lock();
|
||||
rendererData_ = std::move(data);
|
||||
rendererDataLock_.unlock();
|
||||
{
|
||||
QMutexLocker lock(&rendererDataLock_);
|
||||
|
||||
// If we're not re-rendering any highlights/selections, let's
|
||||
// copy them over from teh current object.
|
||||
if (!highlightedOrSelectedChanged) {
|
||||
data->gfxSelected = rendererData_->gfxSelected;
|
||||
for (int i = 0; i < 8; i++)
|
||||
data->gfxHighlighted[i] = rendererData_->gfxHighlighted[i];
|
||||
}
|
||||
|
||||
rendererData_ = std::move(data);
|
||||
}
|
||||
}
|
||||
|
||||
rendererDataLock_.lock();
|
||||
if (decalsChanged || highlightedOrSelectedChanged) {
|
||||
rendererData_->selected.clear();
|
||||
for (auto &decal : selectedItems) {
|
||||
drawDecal(rendererData_->selected, decal);
|
||||
if (highlightedOrSelectedChanged) {
|
||||
QMutexLocker locker(&rendererDataLock_);
|
||||
|
||||
// Render selected.
|
||||
rendererData_->gfxSelected.clear();
|
||||
for (auto &decal : selectedDecals) {
|
||||
drawDecal(rendererData_->gfxSelected, decal);
|
||||
}
|
||||
|
||||
// Render highlighted.
|
||||
for (int i = 0; i < 8; i++) {
|
||||
rendererData_->highlighted[i].clear();
|
||||
for (auto &decal : highlightedItems[i]) {
|
||||
drawDecal(rendererData_->highlighted[i], decal);
|
||||
rendererData_->gfxHighlighted[i].clear();
|
||||
for (auto &decal : highlightedDecals[i]) {
|
||||
drawDecal(rendererData_->gfxHighlighted[i], decal);
|
||||
}
|
||||
}
|
||||
}
|
||||
rendererDataLock_.unlock();
|
||||
}
|
||||
|
||||
void FPGAViewWidget::onSelectedArchItem(std::vector<DecalXY> decals)
|
||||
{
|
||||
rendererArgsLock_.lock();
|
||||
rendererArgs_->selectedItems = decals;
|
||||
rendererArgs_->highlightedOrSelectedChanged = true;
|
||||
rendererArgsLock_.unlock();
|
||||
{
|
||||
QMutexLocker locker(&rendererArgsLock_);
|
||||
rendererArgs_->selectedDecals = decals;
|
||||
rendererArgs_->highlightedOrSelectedChanged = true;
|
||||
}
|
||||
pokeRenderer();
|
||||
}
|
||||
|
||||
void FPGAViewWidget::onHighlightGroupChanged(std::vector<DecalXY> decals, int group)
|
||||
{
|
||||
rendererArgsLock_.lock();
|
||||
rendererArgs_->highlightedItems[group] = decals;
|
||||
rendererArgs_->highlightedOrSelectedChanged = true;
|
||||
rendererArgsLock_.unlock();
|
||||
{
|
||||
QMutexLocker locker(&rendererArgsLock_);
|
||||
rendererArgs_->highlightedDecals[group] = decals;
|
||||
rendererArgs_->highlightedOrSelectedChanged = true;
|
||||
}
|
||||
pokeRenderer();
|
||||
}
|
||||
|
||||
@ -557,7 +359,7 @@ void FPGAViewWidget::resizeGL(int width, int height) {}
|
||||
void FPGAViewWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->buttons() & Qt::RightButton || event->buttons() & Qt::MidButton) {
|
||||
lastPos_ = event->pos();
|
||||
lastDragPos_ = event->pos();
|
||||
}
|
||||
if (event->buttons() & Qt::LeftButton) {
|
||||
int x = event->x();
|
||||
@ -609,9 +411,9 @@ QVector4D FPGAViewWidget::mouseToWorldDimensions(int x, int y)
|
||||
void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->buttons() & Qt::RightButton || event->buttons() & Qt::MidButton) {
|
||||
const int dx = event->x() - lastPos_.x();
|
||||
const int dy = event->y() - lastPos_.y();
|
||||
lastPos_ = event->pos();
|
||||
const int dx = event->x() - lastDragPos_.x();
|
||||
const int dy = event->y() - lastDragPos_.y();
|
||||
lastDragPos_ = event->pos();
|
||||
|
||||
auto world = mouseToWorldDimensions(dx, dy);
|
||||
viewMove_.translate(world.x(), -world.y());
|
||||
|
@ -34,183 +34,10 @@
|
||||
|
||||
#include "nextpnr.h"
|
||||
#include "quadtree.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<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);
|
||||
};
|
||||
|
||||
class PeriodicRunner : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -268,18 +95,17 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions
|
||||
FPGAViewWidget(QWidget *parent = 0);
|
||||
~FPGAViewWidget();
|
||||
|
||||
QSize minimumSizeHint() const override;
|
||||
QSize sizeHint() const override;
|
||||
|
||||
protected:
|
||||
// Qt callbacks.
|
||||
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);
|
||||
QSize minimumSizeHint() const override;
|
||||
QSize sizeHint() const override;
|
||||
|
||||
|
||||
public Q_SLOTS:
|
||||
void newContext(Context *ctx);
|
||||
@ -295,26 +121,13 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions
|
||||
void clickedBel(BelId bel);
|
||||
|
||||
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);
|
||||
QVector4D mouseToWorldDimensions(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<PeriodicRunner> renderRunner_;
|
||||
|
||||
using QuadTreeBels = QuadTree<float, BelId>;
|
||||
@ -326,12 +139,17 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions
|
||||
float offsetY = decal.y;
|
||||
|
||||
for (auto &el : ctx_->getDecalGraphics(decal.decal)) {
|
||||
if (el.type == GraphicElement::G_BOX) {
|
||||
if (el.type == GraphicElement::TYPE_BOX) {
|
||||
tree->insert(typename T::BoundingBox(offsetX + el.x1, offsetY + el.y1, offsetX + el.x2, offsetY + el.y2), bel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QPoint lastDragPos_;
|
||||
LineShader lineShader_;
|
||||
QMatrix4x4 viewMove_;
|
||||
float zoom_;
|
||||
|
||||
struct
|
||||
{
|
||||
QColor background;
|
||||
@ -346,23 +164,31 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions
|
||||
|
||||
struct RendererData
|
||||
{
|
||||
LineShaderData decals[4];
|
||||
LineShaderData selected;
|
||||
LineShaderData highlighted[8];
|
||||
LineShaderData gfxByStyle[GraphicElement::STYLE_MAX];
|
||||
LineShaderData gfxSelected;
|
||||
LineShaderData gfxHighlighted[8];
|
||||
std::unique_ptr<QuadTreeBels> qtBels;
|
||||
};
|
||||
std::unique_ptr<RendererData> rendererData_;
|
||||
QMutex rendererDataLock_;
|
||||
|
||||
struct RendererArgs
|
||||
{
|
||||
std::vector<DecalXY> selectedItems;
|
||||
std::vector<DecalXY> highlightedItems[8];
|
||||
std::vector<DecalXY> selectedDecals;
|
||||
std::vector<DecalXY> highlightedDecals[8];
|
||||
bool highlightedOrSelectedChanged;
|
||||
};
|
||||
|
||||
std::unique_ptr<RendererData> rendererData_;
|
||||
QMutex rendererDataLock_;
|
||||
std::unique_ptr<RendererArgs> rendererArgs_;
|
||||
QMutex rendererArgsLock_;
|
||||
|
||||
void zoom(int level);
|
||||
void renderLines(void);
|
||||
void drawGraphicElement(LineShaderData &out, const GraphicElement &el, float x, float y);
|
||||
void drawDecal(LineShaderData &out, const DecalXY &decal);
|
||||
void drawArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], const DecalXY &decal);
|
||||
QVector4D mouseToWorldCoordinates(int x, int y);
|
||||
QVector4D mouseToWorldDimensions(int x, int y);
|
||||
QMatrix4x4 getProjection(void);
|
||||
};
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
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
|
@ -654,8 +654,8 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
|
||||
|
||||
if (type == GroupId::TYPE_FRAME) {
|
||||
GraphicElement el;
|
||||
el.type = GraphicElement::G_LINE;
|
||||
el.style = GraphicElement::G_FRAME;
|
||||
el.type = GraphicElement::TYPE_LINE;
|
||||
el.style = GraphicElement::STYLE_FRAME;
|
||||
|
||||
el.x1 = x + 0.01, el.x2 = x + 0.02, el.y1 = y + 0.01, el.y2 = y + 0.01;
|
||||
ret.push_back(el);
|
||||
@ -680,8 +680,8 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
|
||||
|
||||
if (type == GroupId::TYPE_MAIN_SW) {
|
||||
GraphicElement el;
|
||||
el.type = GraphicElement::G_BOX;
|
||||
el.style = GraphicElement::G_FRAME;
|
||||
el.type = GraphicElement::TYPE_BOX;
|
||||
el.style = GraphicElement::STYLE_FRAME;
|
||||
|
||||
el.x1 = x + main_swbox_x1;
|
||||
el.x2 = x + main_swbox_x2;
|
||||
@ -692,8 +692,8 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
|
||||
|
||||
if (type == GroupId::TYPE_LOCAL_SW) {
|
||||
GraphicElement el;
|
||||
el.type = GraphicElement::G_BOX;
|
||||
el.style = GraphicElement::G_FRAME;
|
||||
el.type = GraphicElement::TYPE_BOX;
|
||||
el.style = GraphicElement::STYLE_FRAME;
|
||||
|
||||
el.x1 = x + local_swbox_x1;
|
||||
el.x2 = x + local_swbox_x2;
|
||||
@ -707,7 +707,7 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
|
||||
int n = chip_info->wire_data[decal.index].num_segments;
|
||||
const WireSegmentPOD *p = chip_info->wire_data[decal.index].segments.get();
|
||||
|
||||
GraphicElement::style_t style = decal.active ? GraphicElement::G_ACTIVE : GraphicElement::G_INACTIVE;
|
||||
GraphicElement::style_t style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
gfxTileWire(ret, p[i].x, p[i].y, GfxTileWireId(p[i].index), style);
|
||||
@ -715,7 +715,7 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
|
||||
|
||||
if (decal.type == DecalId::TYPE_PIP) {
|
||||
const PipInfoPOD &p = chip_info->pip_data[decal.index];
|
||||
GraphicElement::style_t style = decal.active ? GraphicElement::G_ACTIVE : GraphicElement::G_HIDDEN;
|
||||
GraphicElement::style_t style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_HIDDEN;
|
||||
gfxTilePip(ret, p.x, p.y, GfxTileWireId(p.src_seg), GfxTileWireId(p.dst_seg), style);
|
||||
}
|
||||
|
||||
@ -727,8 +727,8 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
|
||||
|
||||
if (bel_type == TYPE_ICESTORM_LC) {
|
||||
GraphicElement el;
|
||||
el.type = GraphicElement::G_BOX;
|
||||
el.style = decal.active ? GraphicElement::G_ACTIVE : GraphicElement::G_INACTIVE;
|
||||
el.type = GraphicElement::TYPE_BOX;
|
||||
el.style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
|
||||
el.x1 = chip_info->bel_data[bel.index].x + logic_cell_x1;
|
||||
el.x2 = chip_info->bel_data[bel.index].x + logic_cell_x2;
|
||||
el.y1 = chip_info->bel_data[bel.index].y + logic_cell_y1 +
|
||||
@ -740,8 +740,8 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
|
||||
|
||||
if (bel_type == TYPE_SB_IO) {
|
||||
GraphicElement el;
|
||||
el.type = GraphicElement::G_BOX;
|
||||
el.style = decal.active ? GraphicElement::G_ACTIVE : GraphicElement::G_INACTIVE;
|
||||
el.type = GraphicElement::TYPE_BOX;
|
||||
el.style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
|
||||
el.x1 = chip_info->bel_data[bel.index].x + logic_cell_x1;
|
||||
el.x2 = chip_info->bel_data[bel.index].x + logic_cell_x2;
|
||||
el.y1 = chip_info->bel_data[bel.index].y + logic_cell_y1 +
|
||||
@ -754,8 +754,8 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
|
||||
if (bel_type == TYPE_ICESTORM_RAM) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
GraphicElement el;
|
||||
el.type = GraphicElement::G_BOX;
|
||||
el.style = decal.active ? GraphicElement::G_ACTIVE : GraphicElement::G_INACTIVE;
|
||||
el.type = GraphicElement::TYPE_BOX;
|
||||
el.style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
|
||||
el.x1 = chip_info->bel_data[bel.index].x + logic_cell_x1;
|
||||
el.x2 = chip_info->bel_data[bel.index].x + logic_cell_x2;
|
||||
el.y1 = chip_info->bel_data[bel.index].y + logic_cell_y1 + i;
|
||||
|
@ -24,7 +24,7 @@ NEXTPNR_NAMESPACE_BEGIN
|
||||
void gfxTileWire(std::vector<GraphicElement> &g, int x, int y, GfxTileWireId id, GraphicElement::style_t style)
|
||||
{
|
||||
GraphicElement el;
|
||||
el.type = GraphicElement::G_LINE;
|
||||
el.type = GraphicElement::TYPE_LINE;
|
||||
el.style = style;
|
||||
|
||||
// Horizontal Span-4 Wires
|
||||
@ -647,7 +647,7 @@ void pipGfx(std::vector<GraphicElement> &g, int x, int y, float x1, float y1, fl
|
||||
float ty = 0.5 * (y1 + y2);
|
||||
|
||||
GraphicElement el;
|
||||
el.type = GraphicElement::G_ARROW;
|
||||
el.type = GraphicElement::TYPE_ARROW;
|
||||
el.style = style;
|
||||
|
||||
if (fabsf(x1 - swx1) < 0.001 && fabsf(x2 - swx1) < 0.001) {
|
||||
@ -704,7 +704,7 @@ void gfxTilePip(std::vector<GraphicElement> &g, int x, int y, GfxTileWireId src,
|
||||
|
||||
if (src == TILE_WIRE_CARRY_IN && dst == TILE_WIRE_CARRY_IN_MUX) {
|
||||
GraphicElement el;
|
||||
el.type = GraphicElement::G_ARROW;
|
||||
el.type = GraphicElement::TYPE_ARROW;
|
||||
el.style = style;
|
||||
el.x1 = x + logic_cell_x1 + 0.005 * 3;
|
||||
el.x2 = el.x1;
|
||||
|
@ -52,13 +52,13 @@ void svg_dump_decal(const Context *ctx, const DecalXY &decal)
|
||||
const std::string style = "stroke=\"black\" stroke-width=\"0.1\" fill=\"none\"";
|
||||
|
||||
for (auto &el : ctx->getDecalGraphics(decal.decal)) {
|
||||
if (el.type == GraphicElement::G_BOX) {
|
||||
if (el.type == GraphicElement::TYPE_BOX) {
|
||||
std::cout << "<rect x=\"" << (offset + scale * (decal.x + el.x1)) << "\" y=\""
|
||||
<< (offset + scale * (decal.y + el.y1)) << "\" height=\"" << (scale * (el.y2 - el.y1))
|
||||
<< "\" width=\"" << (scale * (el.x2 - el.x1)) << "\" " << style << "/>\n";
|
||||
}
|
||||
|
||||
if (el.type == GraphicElement::G_LINE) {
|
||||
if (el.type == GraphicElement::TYPE_LINE) {
|
||||
std::cout << "<line x1=\"" << (offset + scale * (decal.x + el.x1)) << "\" y1=\""
|
||||
<< (offset + scale * (decal.y + el.y1)) << "\" x2=\"" << (offset + scale * (decal.x + el.x2))
|
||||
<< "\" y2=\"" << (offset + scale * (decal.y + el.y2)) << "\" " << style << "/>\n";
|
||||
|
Loading…
Reference in New Issue
Block a user