nextpnr/gui/fpgaviewwidget.cc

881 lines
29 KiB
C++
Raw Normal View History

/*
* 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>
2018-06-11 02:48:52 +08:00
#include <QApplication>
2018-06-06 03:03:06 +08:00
#include <QCoreApplication>
2018-06-07 04:53:52 +08:00
#include <QMouseEvent>
2018-06-11 02:48:52 +08:00
#include <QWidget>
2018-10-21 16:20:50 +08:00
#include "QtImGui.h"
#include "imgui.h"
#include "fpgaviewwidget.h"
#include "log.h"
2018-06-11 00:33:39 +08:00
#include "mainwindow.h"
2018-06-06 03:03:06 +08:00
NEXTPNR_NAMESPACE_BEGIN
2018-07-27 08:22:29 +08:00
FPGAViewWidget::FPGAViewWidget(QWidget *parent)
: QOpenGLWidget(parent), ctx_(nullptr), paintTimer_(this), lineShader_(this), zoom_(10.0f),
rendererArgs_(new FPGAViewWidget::RendererArgs), rendererData_(new FPGAViewWidget::RendererData)
2018-06-06 03:03:06 +08:00
{
2018-07-20 20:19:45 +08:00
colors_.background = QColor("#000000");
2018-07-18 02:16:26 +08:00
colors_.grid = QColor("#333");
colors_.frame = QColor("#808080");
2018-07-18 02:16:26 +08:00
colors_.hidden = QColor("#606060");
colors_.inactive = QColor("#303030");
colors_.active = QColor("#f0f0f0");
colors_.selected = QColor("#ff6600");
2018-07-27 05:40:45 +08:00
colors_.hovered = QColor("#906030");
2018-07-18 02:16:26 +08:00
colors_.highlight[0] = QColor("#6495ed");
colors_.highlight[1] = QColor("#7fffd4");
colors_.highlight[2] = QColor("#98fb98");
colors_.highlight[3] = QColor("#ffd700");
colors_.highlight[4] = QColor("#cd5c5c");
colors_.highlight[5] = QColor("#fa8072");
colors_.highlight[6] = QColor("#ff69b4");
colors_.highlight[7] = QColor("#da70d6");
2018-07-27 05:40:45 +08:00
rendererArgs_->changed = false;
2018-07-27 08:21:50 +08:00
rendererArgs_->flags.zoomOutbound = true;
2018-07-13 17:21:49 +08:00
2018-06-23 03:16:49 +08:00
auto fmt = format();
fmt.setMajorVersion(3);
2018-10-21 16:20:50 +08:00
fmt.setMinorVersion(2);
2018-06-23 03:16:49 +08:00
setFormat(fmt);
fmt = format();
if (fmt.majorVersion() < 3) {
printf("Could not get OpenGL 3.0 context. Aborting.\n");
log_abort();
}
2018-10-21 16:20:50 +08:00
if (fmt.minorVersion() < 2) {
printf("Could not get OpenGL 3.2 context - trying anyway...\n ");
2018-06-23 03:16:49 +08:00
}
2018-07-18 02:16:26 +08:00
2018-07-20 20:19:45 +08:00
connect(&paintTimer_, SIGNAL(timeout()), this, SLOT(update()));
paintTimer_.start(1000 / 20); // paint GL 20 times per second
2018-07-18 02:16:26 +08:00
2018-07-20 17:58:30 +08:00
renderRunner_ = std::unique_ptr<PeriodicRunner>(new PeriodicRunner(this, [this] { renderLines(); }));
renderRunner_->start();
renderRunner_->startTimer(1000 / 2); // render lines 2 times per second
2018-07-27 05:40:45 +08:00
setMouseTracking(true);
2018-06-11 02:48:52 +08:00
}
2018-06-26 21:47:22 +08:00
FPGAViewWidget::~FPGAViewWidget() {}
void FPGAViewWidget::newContext(Context *ctx)
2018-06-11 02:48:52 +08:00
{
2018-06-26 21:47:22 +08:00
ctx_ = ctx;
onSelectedArchItem(std::vector<DecalXY>(), false);
for (int i = 0; i < 8; i++)
onHighlightGroupChanged(std::vector<DecalXY>(), i);
2018-07-27 08:21:50 +08:00
{
QMutexLocker lock(&rendererArgsLock_);
rendererArgs_->flags.zoomOutbound = true;
}
2018-07-18 02:16:26 +08:00
pokeRenderer();
2018-06-06 03:03:06 +08:00
}
2018-06-07 04:53:52 +08:00
QSize FPGAViewWidget::minimumSizeHint() const { return QSize(640, 480); }
2018-06-06 03:03:06 +08:00
2018-06-07 04:53:52 +08:00
QSize FPGAViewWidget::sizeHint() const { return QSize(640, 480); }
2018-06-06 03:03:06 +08:00
void FPGAViewWidget::initializeGL()
{
if (!lineShader_.compile()) {
log_error("Could not compile shader.\n");
}
2018-06-06 03:03:06 +08:00
initializeOpenGLFunctions();
2018-10-21 16:20:50 +08:00
QtImGui::initialize(this);
glClearColor(colors_.background.red() / 255, colors_.background.green() / 255,
colors_.background.blue() / 255, 0.0);
{
QMutexLocker locker(&rendererDataLock_);
// 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);
}
grid.last_render = 1;
lineShader_.update_vbos(GraphicElement::STYLE_GRID, grid);
}
2018-06-06 03:03:06 +08:00
}
2018-07-27 05:40:45 +08:00
float FPGAViewWidget::PickedElement::distance(Context *ctx, float wx, float wy) const
{
// Get DecalXY for this element.
DecalXY dec = decal(ctx);
// Coordinates within decal.
float dx = wx - dec.x;
float dy = wy - dec.y;
auto graphics = ctx->getDecalGraphics(dec.decal);
if (graphics.size() == 0)
return -1;
// TODO(q3k): For multi-line decals, find intersections and also calculate distance to them.
// Go over its' GraphicElements, and calculate the distance to them.
std::vector<float> distances;
2018-07-27 08:22:29 +08:00
std::transform(graphics.begin(), graphics.end(), std::back_inserter(distances),
[&](const GraphicElement &ge) -> float {
switch (ge.type) {
case GraphicElement::TYPE_BOX: {
// If outside the box, return unit distance to closest border.
float outside_x = -1, outside_y = -1;
if (dx < ge.x1 || dx > ge.x2) {
outside_x = std::min(std::abs(dx - ge.x1), std::abs(dx - ge.x2));
}
if (dy < ge.y1 || dy > ge.y2) {
outside_y = std::min(std::abs(dy - ge.y1), std::abs(dy - ge.y2));
}
if (outside_x != -1 && outside_y != -1)
return std::min(outside_x, outside_y);
// If in box, return 0.
return 0;
}
case GraphicElement::TYPE_LINE:
case GraphicElement::TYPE_ARROW: {
// Return somewhat primitively calculated distance to segment.
// TODO(q3k): consider coming up with a better algorithm
QVector2D w(wx, wy);
QVector2D a(ge.x1, ge.y1);
QVector2D b(ge.x2, ge.y2);
float dw = a.distanceToPoint(w) + b.distanceToPoint(w);
float dab = a.distanceToPoint(b);
return std::abs(dw - dab) / dab;
}
default:
// Not close to antyhing.
return -1;
}
});
2018-07-27 05:40:45 +08:00
// Find smallest non -1 distance.
// Find closest element.
return *std::min_element(distances.begin(), distances.end(), [&](float a, float b) {
2018-07-27 08:22:29 +08:00
if (a == -1)
return false;
if (b == -1)
return true;
2018-07-27 05:40:45 +08:00
return a < b;
});
}
void FPGAViewWidget::renderGraphicElement(LineShaderData &out, PickQuadTree::BoundingBox &bb, const GraphicElement &el,
float x, float y)
{
2018-07-27 00:26:26 +08:00
if (el.type == GraphicElement::TYPE_BOX) {
auto line = PolyLine(true);
2018-07-27 01:43:00 +08:00
line.point(x + el.x1, y + el.y1);
line.point(x + el.x2, y + el.y1);
line.point(x + el.x2, y + el.y2);
line.point(x + el.x1, y + el.y2);
2018-07-27 00:26:26 +08:00
line.build(out);
2018-07-27 01:43:00 +08:00
2018-07-27 09:04:24 +08:00
bb.setX0(std::min(bb.x0(), x + el.x1));
bb.setY0(std::min(bb.y0(), y + el.y1));
bb.setX1(std::max(bb.x1(), x + el.x2));
bb.setY1(std::max(bb.y1(), y + el.y2));
out.last_render++;
2018-07-27 01:43:00 +08:00
return;
2018-07-27 00:26:26 +08:00
}
2018-07-27 00:26:26 +08:00
if (el.type == GraphicElement::TYPE_LINE || el.type == GraphicElement::TYPE_ARROW) {
2018-07-27 01:43:00 +08:00
PolyLine(x + el.x1, y + el.y1, x + el.x2, y + el.y2).build(out);
2018-07-27 09:04:24 +08:00
bb.setX0(std::min(bb.x0(), x + el.x1));
bb.setY0(std::min(bb.y0(), y + el.y1));
bb.setX1(std::max(bb.x1(), x + el.x2));
bb.setY1(std::max(bb.y1(), y + el.y2));
out.last_render++;
2018-07-27 01:43:00 +08:00
return;
}
}
2018-07-27 09:04:24 +08:00
void FPGAViewWidget::renderDecal(LineShaderData &out, PickQuadTree::BoundingBox &bb, const DecalXY &decal)
{
if (decal.decal == DecalId())
return;
2018-07-26 23:20:58 +08:00
float offsetX = decal.x;
float offsetY = decal.y;
for (auto &el : ctx_->getDecalGraphics(decal.decal)) {
2018-07-27 09:04:24 +08:00
renderGraphicElement(out, bb, el, offsetX, offsetY);
}
}
void FPGAViewWidget::renderArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], PickQuadTree::BoundingBox &bb,
const DecalXY &decal)
{
2018-07-27 00:26:26 +08:00
float offsetX = decal.x;
float offsetY = decal.y;
for (auto &el : ctx_->getDecalGraphics(decal.decal)) {
2018-07-27 00:26:26 +08:00
switch (el.style) {
case GraphicElement::STYLE_FRAME:
case GraphicElement::STYLE_INACTIVE:
case GraphicElement::STYLE_ACTIVE:
2018-07-27 09:04:24 +08:00
renderGraphicElement(out[el.style], bb, el, offsetX, offsetY);
2018-07-27 00:26:26 +08:00
break;
default:
break;
}
}
}
2018-07-27 05:40:45 +08:00
void FPGAViewWidget::populateQuadTree(RendererData *data, const DecalXY &decal, const PickedElement &element)
2018-07-27 01:43:00 +08:00
{
2018-07-27 05:40:45 +08:00
float x = decal.x;
float y = decal.y;
for (auto &el : ctx_->getDecalGraphics(decal.decal)) {
2018-07-27 09:24:00 +08:00
if (el.style == GraphicElement::STYLE_HIDDEN || el.style == GraphicElement::STYLE_FRAME) {
2018-07-27 05:40:45 +08:00
continue;
}
2018-08-20 06:39:27 +08:00
bool res = true;
2018-07-27 05:40:45 +08:00
if (el.type == GraphicElement::TYPE_BOX) {
// Boxes are bounded by themselves.
2018-08-20 06:39:27 +08:00
res = data->qt->insert(PickQuadTree::BoundingBox(x + el.x1, y + el.y1, x + el.x2, y + el.y2), element);
2018-07-27 05:40:45 +08:00
}
if (el.type == GraphicElement::TYPE_LINE || el.type == GraphicElement::TYPE_ARROW) {
// Lines are bounded by their AABB slightly enlarged.
2018-07-27 08:22:29 +08:00
float x0 = x + el.x1;
float y0 = y + el.y1;
float x1 = x + el.x2;
float y1 = y + el.y2;
2018-07-27 05:40:45 +08:00
if (x1 < x0)
std::swap(x0, x1);
if (y1 < y0)
std::swap(y0, y1);
x0 -= 0.01;
y0 -= 0.01;
x1 += 0.01;
y1 += 0.01;
2018-08-20 06:39:27 +08:00
res = data->qt->insert(PickQuadTree::BoundingBox(x0, y0, x1, y1), element);
}
if (!res) {
NPNR_ASSERT_FALSE("populateQuadTree: could not insert element");
2018-07-27 05:40:45 +08:00
}
}
2018-07-27 01:43:00 +08:00
}
2018-07-13 03:22:53 +08:00
QMatrix4x4 FPGAViewWidget::getProjection(void)
{
QMatrix4x4 matrix;
const float aspect = float(width()) / float(height());
matrix.perspective(90, aspect, zoomNear_, zoomFar_ + 0.1f);
2018-07-13 03:22:53 +08:00
return matrix;
}
2018-06-06 03:03:06 +08:00
void FPGAViewWidget::paintGL()
{
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);
2018-07-13 03:22:53 +08:00
QMatrix4x4 matrix = getProjection();
2018-07-27 08:21:50 +08:00
matrix.translate(0.0f, 0.0f, -zoom_);
2018-07-13 03:22:53 +08:00
matrix *= viewMove_;
// Calculate world thickness to achieve a screen 1px/1.1px line.
2018-07-26 23:20:58 +08:00
float thick1Px = mouseToWorldDimensions(1, 0).x();
float thick11Px = mouseToWorldDimensions(1.1, 0).x();
2018-07-27 05:40:45 +08:00
float thick2Px = mouseToWorldDimensions(2, 0).x();
2018-07-27 01:43:00 +08:00
{
QMutexLocker locker(&rendererDataLock_);
// Must be called from a thread holding the OpenGL context
update_vbos();
}
// Render the grid.
lineShader_.draw(GraphicElement::STYLE_GRID, colors_.grid, thick1Px,
matrix);
// Render Arch graphics.
lineShader_.draw(GraphicElement::STYLE_FRAME, colors_.frame, thick11Px,
matrix);
lineShader_.draw(GraphicElement::STYLE_HIDDEN, colors_.hidden, thick11Px,
matrix);
lineShader_.draw(GraphicElement::STYLE_INACTIVE, colors_.inactive,
thick11Px, matrix);
lineShader_.draw(GraphicElement::STYLE_ACTIVE, colors_.active, thick11Px,
matrix);
// Draw highlighted items.
for (int i = 0; i < 8; i++) {
GraphicElement::style_t style = (GraphicElement::style_t)(
GraphicElement::STYLE_HIGHLIGHTED0 + i);
lineShader_.draw(style, colors_.highlight[i], thick11Px, matrix);
}
2018-07-18 02:16:26 +08:00
lineShader_.draw(GraphicElement::STYLE_SELECTED, colors_.selected,
thick11Px, matrix);
lineShader_.draw(GraphicElement::STYLE_HOVER, colors_.hovered,
thick2Px, matrix);
2018-07-27 08:21:50 +08:00
// Flags from pipeline.
PassthroughFlags flags = rendererData_->flags;
2018-07-27 08:22:29 +08:00
2018-07-27 08:21:50 +08:00
// Check flags passed through pipeline.
if (flags.zoomOutbound) {
// If we're doing init zoomOutbound, make sure we're actually drawing
// something already.
if (rendererData_->gfxByStyle[GraphicElement::STYLE_FRAME].vertices.size() != 0) {
zoomOutbound();
flags.zoomOutbound = false;
{
QMutexLocker lock(&rendererArgsLock_);
rendererArgs_->flags.zoomOutbound = false;
}
}
2018-07-27 01:43:00 +08:00
}
2018-10-21 16:20:50 +08:00
QtImGui::newFrame();
2018-10-21 20:01:44 +08:00
QMutexLocker lock(&rendererArgsLock_);
if (!(rendererArgs_->hoveredDecal == DecalXY()))
{
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted(rendererArgs_->hintText.c_str());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
2018-10-21 16:20:50 +08:00
ImGui::Render();
2018-07-18 02:16:26 +08:00
}
2018-07-20 20:19:45 +08:00
void FPGAViewWidget::pokeRenderer(void) { renderRunner_->poke(); }
2018-07-18 02:16:26 +08:00
void FPGAViewWidget::renderLines(void)
{
if (ctx_ == nullptr)
return;
2018-07-27 00:26:26 +08:00
// Data from Context needed to render all decals.
2018-07-27 05:40:45 +08:00
std::vector<std::pair<DecalXY, BelId>> belDecals;
std::vector<std::pair<DecalXY, WireId>> wireDecals;
std::vector<std::pair<DecalXY, PipId>> pipDecals;
std::vector<std::pair<DecalXY, GroupId>> groupDecals;
2018-07-27 00:26:26 +08:00
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);
// For now, collapse any decal changes into change of all decals.
// TODO(q3k): fix this
if (ctx_->allUiReload) {
ctx_->allUiReload = false;
decalsChanged = true;
}
2018-07-27 00:26:26 +08:00
if (ctx_->frameUiReload) {
ctx_->frameUiReload = false;
decalsChanged = true;
}
2018-07-27 00:26:26 +08:00
if (ctx_->belUiReload.size() > 0) {
ctx_->belUiReload.clear();
decalsChanged = true;
}
2018-07-27 00:26:26 +08:00
if (ctx_->wireUiReload.size() > 0) {
ctx_->wireUiReload.clear();
decalsChanged = true;
}
2018-07-27 00:26:26 +08:00
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()) {
2018-07-27 05:40:45 +08:00
belDecals.push_back({ctx_->getBelDecal(bel), bel});
2018-07-27 00:26:26 +08:00
}
for (auto wire : ctx_->getWires()) {
2018-07-27 05:40:45 +08:00
wireDecals.push_back({ctx_->getWireDecal(wire), wire});
2018-07-27 00:26:26 +08:00
}
for (auto pip : ctx_->getPips()) {
2018-07-27 05:40:45 +08:00
pipDecals.push_back({ctx_->getPipDecal(pip), pip});
2018-07-27 00:26:26 +08:00
}
for (auto group : ctx_->getGroups()) {
2018-07-27 05:40:45 +08:00
groupDecals.push_back({ctx_->getGroupDecal(group), group});
2018-07-27 00:26:26 +08:00
}
}
2018-07-18 02:16:26 +08:00
}
2018-07-27 00:26:26 +08:00
// Arguments from the main UI thread on what we should render.
std::vector<DecalXY> selectedDecals;
2018-07-27 05:40:45 +08:00
DecalXY hoveredDecal;
2018-07-27 00:26:26 +08:00
std::vector<DecalXY> highlightedDecals[8];
bool highlightedOrSelectedChanged;
2018-07-27 08:21:50 +08:00
PassthroughFlags flags;
2018-07-27 00:26:26 +08:00
{
// Take the renderer arguments lock, copy over all we need.
QMutexLocker lock(&rendererArgsLock_);
2018-07-27 08:21:50 +08:00
2018-07-27 00:26:26 +08:00
selectedDecals = rendererArgs_->selectedDecals;
2018-07-27 05:40:45 +08:00
hoveredDecal = rendererArgs_->hoveredDecal;
2018-07-27 08:21:50 +08:00
2018-07-27 00:26:26 +08:00
for (int i = 0; i < 8; i++)
highlightedDecals[i] = rendererArgs_->highlightedDecals[i];
2018-07-27 08:21:50 +08:00
2018-07-27 05:40:45 +08:00
highlightedOrSelectedChanged = rendererArgs_->changed;
rendererArgs_->changed = false;
2018-07-27 08:21:50 +08:00
flags = rendererArgs_->flags;
2018-07-27 00:26:26 +08:00
}
2018-07-18 02:16:26 +08:00
2018-07-27 00:26:26 +08:00
// Render decals if necessary.
2018-07-18 02:16:26 +08:00
if (decalsChanged) {
auto data = std::unique_ptr<FPGAViewWidget::RendererData>(new FPGAViewWidget::RendererData);
2018-07-27 01:43:00 +08:00
// Reset bounding box.
2018-07-27 09:04:24 +08:00
data->bbGlobal.clear();
2018-07-27 01:43:00 +08:00
2018-07-18 02:16:26 +08:00
// Draw Bels.
for (auto const &decal : belDecals) {
2018-07-27 09:04:24 +08:00
renderArchDecal(data->gfxByStyle, data->bbGlobal, decal.first);
2018-07-18 02:16:26 +08:00
}
// Draw Wires.
for (auto const &decal : wireDecals) {
2018-07-27 09:04:24 +08:00
renderArchDecal(data->gfxByStyle, data->bbGlobal, decal.first);
2018-07-18 02:16:26 +08:00
}
// Draw Pips.
for (auto const &decal : pipDecals) {
2018-07-27 09:04:24 +08:00
renderArchDecal(data->gfxByStyle, data->bbGlobal, decal.first);
2018-07-18 02:16:26 +08:00
}
// Draw Groups.
for (auto const &decal : groupDecals) {
2018-07-27 09:04:24 +08:00
renderArchDecal(data->gfxByStyle, data->bbGlobal, decal.first);
2018-07-18 02:16:26 +08:00
}
2018-07-27 01:43:00 +08:00
// Bounding box should be calculated by now.
2018-07-27 09:04:24 +08:00
NPNR_ASSERT(data->bbGlobal.w() != 0);
NPNR_ASSERT(data->bbGlobal.h() != 0);
2018-07-27 01:43:00 +08:00
2018-08-20 06:39:27 +08:00
// Enlarge the bounding box slightly for the picking - when we insert
// elements into it, we enlarge their bounding boxes slightly, so
// we need to give ourselves some sagery margin here.
auto bb = data->bbGlobal;
bb.setX0(bb.x0() - 1);
bb.setY0(bb.y0() - 1);
bb.setX1(bb.x1() + 1);
bb.setY1(bb.y1() + 1);
2018-07-27 01:43:00 +08:00
// Populate picking quadtree.
2018-08-20 06:39:27 +08:00
data->qt = std::unique_ptr<PickQuadTree>(new PickQuadTree(bb));
2018-07-27 05:40:45 +08:00
for (auto const &decal : belDecals) {
populateQuadTree(data.get(), decal.first,
PickedElement::fromBel(decal.second, decal.first.x, decal.first.y));
2018-07-27 05:40:45 +08:00
}
for (auto const &decal : wireDecals) {
populateQuadTree(data.get(), decal.first,
PickedElement::fromWire(decal.second, decal.first.x, decal.first.y));
2018-07-27 05:40:45 +08:00
}
for (auto const &decal : pipDecals) {
populateQuadTree(data.get(), decal.first,
PickedElement::fromPip(decal.second, decal.first.x, decal.first.y));
2018-07-27 05:40:45 +08:00
}
for (auto const &decal : groupDecals) {
populateQuadTree(data.get(), decal.first,
PickedElement::fromGroup(decal.second, decal.first.x, decal.first.y));
2018-07-27 05:40:45 +08:00
}
2018-07-27 01:43:00 +08:00
2018-07-18 02:16:26 +08:00
// Swap over.
2018-07-27 00:26:26 +08:00
{
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;
2018-07-27 05:40:45 +08:00
data->gfxHovered = rendererData_->gfxHovered;
2018-07-27 00:26:26 +08:00
for (int i = 0; i < 8; i++)
data->gfxHighlighted[i] = rendererData_->gfxHighlighted[i];
}
2018-07-27 08:22:29 +08:00
2018-07-27 00:26:26 +08:00
rendererData_ = std::move(data);
}
2018-07-18 02:16:26 +08:00
}
2018-07-27 00:26:26 +08:00
if (highlightedOrSelectedChanged) {
QMutexLocker locker(&rendererDataLock_);
2018-07-27 05:40:45 +08:00
// Whether the currently being hovered decal is also selected.
bool hoveringSelected = false;
2018-07-27 00:26:26 +08:00
// Render selected.
2018-07-27 09:04:24 +08:00
rendererData_->bbSelected.clear();
2018-07-27 00:26:26 +08:00
rendererData_->gfxSelected.clear();
for (auto &decal : selectedDecals) {
2018-07-27 05:40:45 +08:00
if (decal == hoveredDecal)
hoveringSelected = true;
2018-07-27 09:04:24 +08:00
renderDecal(rendererData_->gfxSelected, rendererData_->bbSelected, decal);
2018-07-15 01:44:37 +08:00
}
2018-07-27 00:26:26 +08:00
2018-07-27 05:40:45 +08:00
// Render hovered.
rendererData_->gfxHovered.clear();
if (!hoveringSelected) {
2018-07-27 09:04:24 +08:00
renderDecal(rendererData_->gfxHovered, rendererData_->bbGlobal, hoveredDecal);
2018-07-27 05:40:45 +08:00
}
2018-07-27 00:26:26 +08:00
// Render highlighted.
2018-07-15 23:50:58 +08:00
for (int i = 0; i < 8; i++) {
2018-07-27 00:26:26 +08:00
rendererData_->gfxHighlighted[i].clear();
for (auto &decal : highlightedDecals[i]) {
2018-07-27 09:04:24 +08:00
renderDecal(rendererData_->gfxHighlighted[i], rendererData_->bbGlobal, decal);
2018-07-15 23:50:58 +08:00
}
}
}
2018-07-27 08:21:50 +08:00
{
QMutexLocker locker(&rendererDataLock_);
rendererData_->flags = flags;
}
2018-06-06 03:03:06 +08:00
}
void FPGAViewWidget::onSelectedArchItem(std::vector<DecalXY> decals, bool keep)
2018-07-15 01:44:37 +08:00
{
2018-07-27 00:26:26 +08:00
{
QMutexLocker locker(&rendererArgsLock_);
if (keep) {
std::copy(decals.begin(), decals.end(), std::back_inserter(rendererArgs_->selectedDecals));
} else {
rendererArgs_->selectedDecals = decals;
}
2018-07-27 05:40:45 +08:00
rendererArgs_->changed = true;
2018-07-27 00:26:26 +08:00
}
2018-07-18 02:16:26 +08:00
pokeRenderer();
2018-07-15 23:50:58 +08:00
}
void FPGAViewWidget::onHighlightGroupChanged(std::vector<DecalXY> decals, int group)
{
2018-07-27 00:26:26 +08:00
{
QMutexLocker locker(&rendererArgsLock_);
rendererArgs_->highlightedDecals[group] = decals;
2018-07-27 05:40:45 +08:00
rendererArgs_->changed = true;
2018-07-27 00:26:26 +08:00
}
2018-07-18 02:16:26 +08:00
pokeRenderer();
2018-06-06 03:03:06 +08:00
}
2018-08-22 23:38:42 +08:00
void FPGAViewWidget::onHoverItemChanged(DecalXY decal)
{
QMutexLocker locked(&rendererArgsLock_);
rendererArgs_->hoveredDecal = decal;
rendererArgs_->changed = true;
pokeRenderer();
}
void FPGAViewWidget::resizeGL(int width, int height) {}
2018-07-27 05:40:45 +08:00
boost::optional<FPGAViewWidget::PickedElement> FPGAViewWidget::pickElement(float worldx, float worldy)
{
// Get elements from renderer whose BBs correspond to the pick.
std::vector<PickedElement> elems;
{
QMutexLocker locker(&rendererDataLock_);
if (rendererData_->qt == nullptr) {
return {};
}
elems = rendererData_->qt->get(worldx, worldy);
}
if (elems.size() == 0) {
return {};
}
// Calculate distances to all elements picked.
using ElemDist = std::pair<const PickedElement *, float>;
std::vector<ElemDist> distances;
2018-07-27 08:22:29 +08:00
std::transform(elems.begin(), elems.end(), std::back_inserter(distances), [&](const PickedElement &e) -> ElemDist {
return std::make_pair(&e, e.distance(ctx_, worldx, worldy));
});
2018-07-27 05:40:45 +08:00
// Find closest non -1 element.
2018-07-27 08:22:29 +08:00
auto closest = std::min_element(distances.begin(), distances.end(), [&](const ElemDist &a, const ElemDist &b) {
if (a.second == -1)
return false;
if (b.second == -1)
return true;
2018-07-27 05:40:45 +08:00
return a.second < b.second;
});
// All out of reach?
if (closest->second < 0) {
return {};
}
return *(closest->first);
}
2018-07-26 23:20:58 +08:00
void FPGAViewWidget::mousePressEvent(QMouseEvent *event)
{
2018-10-21 20:46:31 +08:00
ImGuiIO &io = ImGui::GetIO();
if (io.WantCaptureMouse) return;
2018-07-26 23:20:58 +08:00
if (event->buttons() & Qt::RightButton || event->buttons() & Qt::MidButton) {
lastDragPos_ = event->pos();
2018-07-26 23:20:58 +08:00
}
if (event->buttons() & Qt::LeftButton) {
bool ctrl = QApplication::keyboardModifiers().testFlag(Qt::ControlModifier);
2018-07-27 05:40:45 +08:00
auto world = mouseToWorldCoordinates(event->x(), event->y());
auto closestOr = pickElement(world.x(), world.y());
2018-07-27 22:46:00 +08:00
if (!closestOr) {
// If we clicked on empty space and aren't holding down ctrl,
// clear the selection.
if (!ctrl) {
QMutexLocker locked(&rendererArgsLock_);
rendererArgs_->selectedDecals.clear();
rendererArgs_->changed = true;
pokeRenderer();
}
2018-07-27 05:40:45 +08:00
return;
2018-07-27 22:46:00 +08:00
}
2018-07-27 05:40:45 +08:00
auto closest = closestOr.value();
if (closest.type == ElementType::BEL) {
clickedBel(closest.bel, ctrl);
2018-07-27 05:40:45 +08:00
} else if (closest.type == ElementType::WIRE) {
clickedWire(closest.wire, ctrl);
2018-07-27 09:28:01 +08:00
} else if (closest.type == ElementType::PIP) {
clickedPip(closest.pip, ctrl);
2018-07-27 05:40:45 +08:00
}
}
}
void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event)
{
2018-10-21 20:46:31 +08:00
ImGuiIO &io = ImGui::GetIO();
if (io.WantCaptureMouse) return;
2018-07-27 05:40:45 +08:00
if (event->buttons() & Qt::RightButton || event->buttons() & Qt::MidButton) {
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());
update();
return;
}
auto world = mouseToWorldCoordinates(event->x(), event->y());
auto closestOr = pickElement(world.x(), world.y());
2018-07-27 09:17:04 +08:00
// No elements? No decal.
if (!closestOr) {
QMutexLocker locked(&rendererArgsLock_);
rendererArgs_->hoveredDecal = DecalXY();
rendererArgs_->changed = true;
2018-10-21 20:01:44 +08:00
rendererArgs_->hintText = "";
2018-07-27 09:17:04 +08:00
pokeRenderer();
2018-07-27 05:40:45 +08:00
return;
2018-07-27 09:17:04 +08:00
}
2018-07-27 05:40:45 +08:00
auto closest = closestOr.value();
{
QMutexLocker locked(&rendererArgsLock_);
rendererArgs_->hoveredDecal = closest.decal(ctx_);
rendererArgs_->changed = true;
2018-10-21 20:01:44 +08:00
if (closest.type == ElementType::BEL) {
rendererArgs_->hintText = std::string("BEL\n") + ctx_->getBelName(closest.bel).c_str(ctx_);
} else if (closest.type == ElementType::WIRE) {
rendererArgs_->hintText = std::string("WIRE\n") + ctx_->getWireName(closest.wire).c_str(ctx_);
} else if (closest.type == ElementType::PIP) {
rendererArgs_->hintText = std::string("PIP\n") + ctx_->getPipName(closest.pip).c_str(ctx_);
2018-10-21 20:46:31 +08:00
} else if (closest.type == ElementType::GROUP) {
rendererArgs_->hintText = std::string("GROUP\n") + ctx_->getGroupName(closest.group).c_str(ctx_);
2018-10-21 20:01:44 +08:00
} else rendererArgs_->hintText = "";
2018-07-27 05:40:45 +08:00
pokeRenderer();
2018-07-26 23:20:58 +08:00
}
2018-07-27 05:40:45 +08:00
update();
2018-07-26 23:20:58 +08:00
}
2018-06-06 03:03:06 +08:00
2018-07-13 03:22:53 +08:00
// Invert the projection matrix to calculate screen/mouse to world/grid
// coordinates.
QVector4D FPGAViewWidget::mouseToWorldCoordinates(int x, int y)
2018-07-26 23:20:58 +08:00
{
const qreal retinaScale = devicePixelRatio();
auto projection = getProjection();
QMatrix4x4 vp;
vp.viewport(0, 0, width() * retinaScale, height() * retinaScale);
2018-07-27 08:21:50 +08:00
QVector4D vec(x, y, 1, 1);
2018-07-26 23:20:58 +08:00
vec = vp.inverted() * vec;
2018-07-27 08:21:50 +08:00
vec = projection.inverted() * QVector4D(vec.x(), vec.y(), -1, 1);
// Hic sunt dracones.
// TODO(q3k): grab a book, remind yourselfl linear algebra and undo this
// operation properly.
QVector3D ray = vec.toVector3DAffine();
ray.normalize();
2018-07-27 08:22:29 +08:00
ray.setX((ray.x() / -ray.z()) * zoom_);
ray.setY((ray.y() / ray.z()) * zoom_);
2018-07-27 08:21:50 +08:00
ray.setZ(1.0);
2018-07-26 23:20:58 +08:00
2018-07-27 08:21:50 +08:00
vec = viewMove_.inverted() * QVector4D(ray.x(), ray.y(), ray.z(), 1.0);
vec.setZ(0);
2018-07-26 23:20:58 +08:00
2018-07-27 08:21:50 +08:00
return vec;
2018-07-26 23:20:58 +08:00
}
2018-07-27 05:40:45 +08:00
QVector4D FPGAViewWidget::mouseToWorldDimensions(float x, float y)
2018-07-13 03:22:53 +08:00
{
QMatrix4x4 p = getProjection();
2018-07-27 08:21:50 +08:00
p.translate(0.0f, 0.0f, -zoom_);
2018-07-13 03:22:53 +08:00
QVector2D unit = p.map(QVector4D(1, 1, 0, 1)).toVector2DAffine();
float sx = (((float)x) / (width() / 2));
float sy = (((float)y) / (height() / 2));
2018-07-13 03:22:53 +08:00
return QVector4D(sx / unit.x(), sy / unit.y(), 0, 1);
}
2018-06-06 03:03:06 +08:00
void FPGAViewWidget::wheelEvent(QWheelEvent *event)
{
2018-10-21 20:46:31 +08:00
ImGuiIO &io = ImGui::GetIO();
if (io.WantCaptureMouse) return;
2018-06-06 03:03:06 +08:00
QPoint degree = event->angleDelta() / 8;
2018-07-26 19:21:46 +08:00
if (!degree.isNull())
zoom(degree.y());
}
void FPGAViewWidget::zoom(int level)
{
2018-07-27 08:21:50 +08:00
if (zoom_ < zoomLvl1_) {
zoom_ -= level / 500.0;
2018-07-26 19:21:46 +08:00
} else if (zoom_ < zoomLvl2_) {
2018-07-27 08:21:50 +08:00
zoom_ -= level / 100.0;
2018-07-26 19:21:46 +08:00
} else {
2018-07-27 08:21:50 +08:00
zoom_ -= level / 10.0;
2018-06-06 03:03:06 +08:00
}
2018-07-27 08:21:50 +08:00
if (zoom_ < zoomNear_)
zoom_ = zoomNear_;
else if (zoom_ > zoomFar_)
zoom_ = zoomFar_;
2018-07-26 19:21:46 +08:00
update();
2018-06-06 03:03:06 +08:00
}
2018-07-27 22:46:00 +08:00
void FPGAViewWidget::clampZoom()
{
if (zoom_ < zoomNear_)
zoom_ = zoomNear_;
else if (zoom_ > zoomFar_)
zoom_ = zoomFar_;
}
2018-07-26 23:26:05 +08:00
void FPGAViewWidget::zoomIn() { zoom(10); }
2018-07-26 19:21:46 +08:00
2018-07-26 23:26:05 +08:00
void FPGAViewWidget::zoomOut() { zoom(-10); }
2018-07-26 19:21:46 +08:00
void FPGAViewWidget::zoomToBB(const PickQuadTree::BoundingBox &bb, float margin, bool clamp)
2018-07-27 08:21:50 +08:00
{
2018-08-20 02:54:41 +08:00
if (fabs(bb.w()) < 0.00005 && fabs(bb.h()) < 0.00005)
2018-07-27 09:04:24 +08:00
return;
2018-07-27 08:21:50 +08:00
viewMove_.setToIdentity();
2018-07-27 09:04:24 +08:00
viewMove_.translate(-(bb.x0() + bb.w() / 2), -(bb.y0() + bb.h() / 2));
2018-07-27 08:21:50 +08:00
// Our FOV is π/2, so distance for camera to see a plane of width H is H/2.
// We add 1 unit to cover half a unit of extra space around.
2018-07-27 22:46:00 +08:00
float distance_w = bb.w() / 2 + margin;
float distance_h = bb.h() / 2 + margin;
2018-07-27 08:21:50 +08:00
zoom_ = std::max(distance_w, distance_h);
if (clamp)
clampZoom();
2018-07-27 09:04:24 +08:00
}
void FPGAViewWidget::zoomSelected()
{
{
QMutexLocker lock(&rendererDataLock_);
2018-10-21 16:20:50 +08:00
zoomToBB(rendererData_->bbSelected, 0.5f, true);
2018-07-27 09:04:24 +08:00
}
2018-07-27 08:21:50 +08:00
update();
}
2018-07-26 19:21:46 +08:00
2018-07-27 09:04:24 +08:00
void FPGAViewWidget::zoomOutbound()
{
{
QMutexLocker lock(&rendererDataLock_);
zoomToBB(rendererData_->bbGlobal, 1.0f, false);
zoomFar_ = zoom_;
2018-07-27 09:04:24 +08:00
}
}
void FPGAViewWidget::leaveEvent(QEvent *event)
{
QMutexLocker locked(&rendererArgsLock_);
rendererArgs_->hoveredDecal = DecalXY();
rendererArgs_->changed = true;
pokeRenderer();
}
void FPGAViewWidget::update_vbos()
{
for (int style = GraphicElement::STYLE_FRAME; style
< GraphicElement::STYLE_HIGHLIGHTED0;
style++) {
lineShader_.update_vbos((enum GraphicElement::style_t)(style),
rendererData_->gfxByStyle[style]);
}
for (int i = 0; i < 8; i++) {
GraphicElement::style_t style = (GraphicElement::style_t)(
GraphicElement::STYLE_HIGHLIGHTED0 + i);
lineShader_.update_vbos(style, rendererData_->gfxHighlighted[i]);
}
lineShader_.update_vbos(GraphicElement::STYLE_SELECTED,
rendererData_->gfxSelected);
lineShader_.update_vbos(GraphicElement::STYLE_HOVER,
rendererData_->gfxHovered);
}
NEXTPNR_NAMESPACE_END