Skip to content

Commit 2825e57

Browse files
authored
Merge pull request #10240 from The-OpenROAD-Project-staging/web-debug-graphics
web: add debug graphics support via gui::HeadlessViewer
2 parents 2386c3b + 1bfdd11 commit 2825e57

32 files changed

Lines changed: 2162 additions & 172 deletions

src/Main.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,10 @@ static int tclAppInit(int& argc,
450450
}
451451
}
452452
#ifdef ENABLE_READLINE
453-
if (!gui::Gui::enabled() && !exit_after_cmd_file) {
453+
// Initialize readline unless the Qt GUI is active (it has its own
454+
// script widget). The web viewer's headless mode still needs the
455+
// terminal prompt.
456+
if (!gui::Gui::hasUI() && !exit_after_cmd_file) {
454457
return tclOrdReadlineInit(interp);
455458
}
456459
#endif

src/OpenRoad.cc

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ OpenRoad::~OpenRoad()
134134
delete example_;
135135
delete extractor_;
136136
delete detailed_router_;
137+
// Stop the web server first — its I/O threads access the database
138+
// for tile rendering, so it must be torn down before the DB.
139+
delete web_server_;
137140
delete replace_;
138141
delete pdnsim_;
139142
delete finale_;
@@ -147,7 +150,6 @@ OpenRoad::~OpenRoad()
147150
delete stt_builder_;
148151
delete dft_;
149152
delete estimate_parasitics_;
150-
delete web_server_;
151153
delete logger_;
152154
delete verilog_reader_;
153155
delete service_registry_;
@@ -257,7 +259,8 @@ void OpenRoad::init(Tcl_Interp* tcl_interp,
257259
icewall_ = new pad::ICeWall(db_, logger_);
258260
dft_ = new dft::Dft(db_, sta_, logger_);
259261
example_ = new exa::Example(db_, logger_);
260-
web_server_ = new web::WebServer(db_, sta_, logger_, tcl_interp);
262+
web_server_
263+
= new web::WebServer(db_, sta_, logger_, tcl_interp, getThreadCount());
261264

262265
// Init components.
263266
Ord_Init(tcl_interp);

src/gui/include/gui/gui.h

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,30 @@ class Chart
719719
Chart() = default;
720720
};
721721

722+
// Optional backend plugged in when the Qt GUI is not running (e.g. the web
723+
// viewer). Lets Gui::enabled/redraw/pause work without Qt so that debug
724+
// graphics (gpl, cts, drt, mpl, ...) light up in headless contexts.
725+
// Installed via Gui::setHeadlessViewer. The Qt GUI, when present, always
726+
// takes precedence: the viewer is only consulted when main_window is null.
727+
class HeadlessViewer
728+
{
729+
public:
730+
virtual ~HeadlessViewer() = default;
731+
732+
// Called by Renderer::redraw() / Gui::redraw(). Typically broadcasts
733+
// a refresh notification to connected clients.
734+
virtual void redraw() = 0;
735+
736+
// Called by Gui::pause(). Should block the calling thread until some
737+
// external signal (e.g. a client click) releases it, or until timeout_ms
738+
// expires. timeout_ms == 0 means wait indefinitely.
739+
virtual void pause(int timeout_ms) = 0;
740+
741+
// True while pause() is blocking. Consumers (like the web tile renderer)
742+
// can use this to gate unsafe cross-thread reads of renderer state.
743+
virtual bool isPaused() const = 0;
744+
};
745+
722746
// This is the API for the rest of the program to interact with the
723747
// GUI. This class is accessed by the GUI implementation to interact
724748
// with the rest of the system. This class itself doesn't hold the
@@ -1005,9 +1029,32 @@ class Gui
10051029
// returns the Gui singleton
10061030
static Gui* get();
10071031

1008-
// Will return true if the GUI is active, false otherwise
1032+
// Will return true if any GUI backend is active (Qt or headless).
1033+
// Tool renderers should use this to decide whether debug graphics
1034+
// are available.
10091035
static bool enabled();
10101036

1037+
// Will return true only when the Qt main window is running.
1038+
// Tcl commands that need Qt widgets (selection, zoom, labels, …)
1039+
// should gate on this rather than enabled().
1040+
static bool hasUI();
1041+
1042+
// Install / inspect a HeadlessViewer (used when the Qt GUI is not
1043+
// running). See the HeadlessViewer class comment for semantics.
1044+
void setHeadlessViewer(HeadlessViewer* viewer);
1045+
HeadlessViewer* getHeadlessViewer() const { return headless_viewer_; }
1046+
1047+
// Factory for gui::Chart instances when the Qt GUI is not running.
1048+
// The web viewer installs a factory that returns WebChart*. When the
1049+
// Qt GUI is running, the main window's ChartsWidget is used instead
1050+
// and this factory is ignored.
1051+
using ChartFactory
1052+
= std::function<Chart*(const std::string& name,
1053+
const std::string& x_label,
1054+
const std::vector<std::string>& y_labels)>;
1055+
void setChartFactory(ChartFactory factory);
1056+
const ChartFactory& getChartFactory() const { return chart_factory_; }
1057+
10111058
// initialize the GUI
10121059
void init(odb::dbDatabase* db, sta::dbSta* sta, utl::Logger* logger);
10131060

@@ -1044,6 +1091,10 @@ class Gui
10441091
static constexpr int kDefaultGifDelay = 250;
10451092

10461093
std::string main_window_title_ = "OpenROAD";
1094+
1095+
// Used when Qt GUI is not active. Installed by the web viewer.
1096+
HeadlessViewer* headless_viewer_ = nullptr;
1097+
ChartFactory chart_factory_;
10471098
};
10481099

10491100
// The main entry point

src/gui/src/gui.cpp

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -232,13 +232,19 @@ Gui::Gui() : continue_after_close_(false), logger_(nullptr), db_(nullptr)
232232
}
233233

234234
bool Gui::enabled()
235+
{
236+
Gui* self = Gui::get();
237+
return main_window != nullptr || self->headless_viewer_ != nullptr;
238+
}
239+
240+
bool Gui::hasUI()
235241
{
236242
return main_window != nullptr;
237243
}
238244

239245
void Gui::registerRenderer(Renderer* renderer)
240246
{
241-
if (Gui::enabled()) {
247+
if (main_window != nullptr) {
242248
main_window->getControls()->registerRenderer(renderer);
243249
}
244250

@@ -252,7 +258,7 @@ void Gui::unregisterRenderer(Renderer* renderer)
252258
return;
253259
}
254260

255-
if (Gui::enabled()) {
261+
if (main_window != nullptr) {
256262
main_window->getControls()->unregisterRenderer(renderer);
257263
}
258264

@@ -262,20 +268,42 @@ void Gui::unregisterRenderer(Renderer* renderer)
262268

263269
void Gui::redraw()
264270
{
265-
if (!Gui::enabled()) {
271+
if (main_window != nullptr) {
272+
main_window->redraw();
266273
return;
267274
}
268-
main_window->redraw();
275+
if (headless_viewer_ != nullptr) {
276+
headless_viewer_->redraw();
277+
}
269278
}
270279

271280
void Gui::status(const std::string& message)
272281
{
273-
main_window->status(message);
282+
if (main_window != nullptr) {
283+
main_window->status(message);
284+
}
285+
// No status surface in headless mode; silently drop.
274286
}
275287

276288
void Gui::pause(int timeout)
277289
{
278-
main_window->pause(timeout);
290+
if (main_window != nullptr) {
291+
main_window->pause(timeout);
292+
return;
293+
}
294+
if (headless_viewer_ != nullptr) {
295+
headless_viewer_->pause(timeout);
296+
}
297+
}
298+
299+
void Gui::setHeadlessViewer(HeadlessViewer* viewer)
300+
{
301+
headless_viewer_ = viewer;
302+
}
303+
304+
void Gui::setChartFactory(ChartFactory factory)
305+
{
306+
chart_factory_ = std::move(factory);
279307
}
280308

281309
Selected Gui::makeSelected(const std::any& object)
@@ -963,7 +991,7 @@ void Gui::registerHeatMap(HeatMapDataSource* heatmap)
963991
[this](HeatMapDataSource* source) { unregisterHeatMap(source); });
964992
registerRenderer(renderer.get());
965993
heat_map_renderers_[heatmap] = std::move(renderer);
966-
if (Gui::enabled()) {
994+
if (main_window != nullptr) {
967995
main_window->registerHeatMap(heatmap);
968996
}
969997
}
@@ -982,7 +1010,7 @@ void Gui::unregisterHeatMap(HeatMapDataSource* heatmap)
9821010
unregisterRenderer(renderer_itr->second.get());
9831011
heat_map_renderers_.erase(renderer_itr);
9841012
}
985-
if (Gui::enabled()) {
1013+
if (main_window != nullptr) {
9861014
main_window->unregisterHeatMap(heatmap);
9871015
}
9881016
heat_maps_.erase(heatmap);
@@ -1408,7 +1436,13 @@ Chart* Gui::addChart(const std::string& name,
14081436
const std::string& x_label,
14091437
const std::vector<std::string>& y_labels)
14101438
{
1411-
return main_window->getChartsWidget()->addChart(name, x_label, y_labels);
1439+
if (main_window != nullptr) {
1440+
return main_window->getChartsWidget()->addChart(name, x_label, y_labels);
1441+
}
1442+
if (chart_factory_) {
1443+
return chart_factory_(name, x_label, y_labels);
1444+
}
1445+
return nullptr;
14121446
}
14131447

14141448
void Gui::removeRouteGuides(odb::dbNet* net)

src/gui/src/gui.i

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ using utl::GUI;
1313
bool check_gui(const char* command)
1414
{
1515
auto logger = ord::OpenRoad::openRoad()->getLogger();
16-
if (!gui::Gui::enabled()) {
16+
if (!gui::Gui::hasUI()) {
1717
logger->info(GUI, 1, "Command {} is not usable in non-GUI mode", command);
1818
return false;
1919
}
@@ -505,7 +505,9 @@ const std::string input_dialog(const char* title, const char* question)
505505
// language to avoid conflicts in C++.
506506
void gui_pause(int timeout = 0)
507507
{
508-
if (!check_gui("pause")) {
508+
if (!gui::Gui::enabled()) {
509+
auto logger = ord::OpenRoad::openRoad()->getLogger();
510+
logger->info(GUI, 96, "Command pause is not usable in non-GUI mode");
509511
return;
510512
}
511513
auto gui = gui::Gui::get();

src/gui/src/stub.cpp

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <set>
1212
#include <string>
1313
#include <typeinfo>
14+
#include <utility>
1415
#include <vector>
1516

1617
#include "bufferTreeDescriptor.h"
@@ -46,20 +47,34 @@ Gui* gui::Gui::get()
4647
}
4748

4849
bool gui::Gui::enabled()
50+
{
51+
return Gui::get()->getHeadlessViewer() != nullptr;
52+
}
53+
54+
bool gui::Gui::hasUI()
4955
{
5056
return false;
5157
}
5258

53-
void gui::Gui::registerRenderer(gui::Renderer*)
59+
void gui::Gui::registerRenderer(gui::Renderer* renderer)
5460
{
61+
renderers_.insert(renderer);
62+
redraw();
5563
}
5664

5765
void HeatMapDataSource::registerHeatMap()
5866
{
67+
// gpl / other modules call this to expose their heatmap to the GUI.
68+
// In headless mode the web viewer enumerates heatmaps via
69+
// gui::getRegisteredHeatMapSources() (factory-backed sources) so this
70+
// one-off pathway does nothing here for now. Left intentionally as
71+
// a no-op until heatmap plumbing for ad-hoc sources lands.
5972
}
6073

61-
void gui::Gui::unregisterRenderer(gui::Renderer*)
74+
void gui::Gui::unregisterRenderer(gui::Renderer* renderer)
6275
{
76+
renderers_.erase(renderer);
77+
redraw();
6378
}
6479

6580
void gui::Gui::zoomTo(const odb::Rect& rect_dbu)
@@ -68,10 +83,26 @@ void gui::Gui::zoomTo(const odb::Rect& rect_dbu)
6883

6984
void gui::Gui::redraw()
7085
{
86+
if (headless_viewer_ != nullptr) {
87+
headless_viewer_->redraw();
88+
}
7189
}
7290

7391
void gui::Gui::pause(int timeout)
7492
{
93+
if (headless_viewer_ != nullptr) {
94+
headless_viewer_->pause(timeout);
95+
}
96+
}
97+
98+
void gui::Gui::setHeadlessViewer(HeadlessViewer* viewer)
99+
{
100+
headless_viewer_ = viewer;
101+
}
102+
103+
void gui::Gui::setChartFactory(ChartFactory factory)
104+
{
105+
chart_factory_ = std::move(factory);
75106
}
76107

77108
void Gui::status(const std::string& /* message */)
@@ -84,9 +115,13 @@ void Gui::triggerAction(const std::string& /* action */)
84115

85116
void Renderer::redraw()
86117
{
118+
Gui::get()->redraw();
87119
}
88120

89-
Renderer::~Renderer() = default;
121+
Renderer::~Renderer()
122+
{
123+
Gui::get()->unregisterRenderer(this);
124+
}
90125

91126
void DiscreteLegend::addLegendKey(const Painter::Color& color,
92127
const std::string& text)
@@ -97,17 +132,27 @@ void DiscreteLegend::draw(Painter& painter) const
97132
{
98133
}
99134

100-
bool Renderer::checkDisplayControl(const std::string& /* name */)
135+
bool Renderer::checkDisplayControl(const std::string& name)
101136
{
137+
auto it = controls_.find(name);
138+
if (it != controls_.end()) {
139+
return it->second.visibility;
140+
}
102141
return false;
103142
}
104143

105144
void Renderer::addDisplayControl(
106-
const std::string& /* name */,
107-
bool /* initial_visible */,
108-
const DisplayControlCallback& /* setup */,
109-
const std::vector<std::string>& /* mutual_exclusivity */)
145+
const std::string& name,
146+
bool initial_visible,
147+
const DisplayControlCallback& setup,
148+
const std::vector<std::string>& mutual_exclusivity)
110149
{
150+
DisplayControl control;
151+
control.visibility = initial_visible;
152+
control.interactive_setup = setup;
153+
control.mutual_exclusivity.insert(mutual_exclusivity.begin(),
154+
mutual_exclusivity.end());
155+
controls_[name] = std::move(control);
111156
}
112157

113158
Renderer::Settings Renderer::getSettings()
@@ -241,6 +286,9 @@ Chart* Gui::addChart(const std::string& name,
241286
const std::string& x_label,
242287
const std::vector<std::string>& y_labels)
243288
{
289+
if (chart_factory_) {
290+
return chart_factory_(name, x_label, y_labels);
291+
}
244292
return nullptr;
245293
}
246294

src/web/BUILD

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ cc_library(
8686
"src/timing_report.cpp",
8787
"src/timing_report.h",
8888
"src/web.cpp",
89+
"src/web_chart.cpp",
90+
"src/web_chart.h",
91+
"src/web_painter.cpp",
92+
"src/web_painter.h",
93+
"src/web_serve.cpp",
94+
"src/web_viewer_hook.cpp",
95+
"src/web_viewer_hook.h",
8996
":report_assets",
9097
],
9198
hdrs = [
@@ -109,6 +116,7 @@ cc_library(
109116
"@boost.asio",
110117
"@boost.beast",
111118
"@boost.geometry",
119+
"@spdlog",
112120
"@tcl_lang//:tcl",
113121
],
114122
)

0 commit comments

Comments
 (0)