async threading for map progress, etc. no cancelling yet

This commit is contained in:
Jonathan 2023-06-19 15:38:16 -04:00
parent 539c722666
commit caa7418375
8 changed files with 290 additions and 49 deletions

View File

@ -79,6 +79,12 @@ void close()
}
static std::mutex print_mutex;
static print_callback_t active_print_callback;
void set_print_callback(print_callback_t cb)
{
active_print_callback = cb;
}
void print(flag logflag, const char *str)
{
@ -86,6 +92,12 @@ void print(flag logflag, const char *str)
return;
}
if (active_print_callback)
{
active_print_callback(logflag, str);
return;
}
fmt::text_style style;
if (enable_color_codes) {
@ -157,6 +169,13 @@ void assert_(bool success, const char *expr, const char *file, int line)
}
}
static percent_callback_t active_percent_callback;
void set_percent_callback(percent_callback_t cb)
{
active_percent_callback = cb;
}
void percent(uint64_t count, uint64_t max, bool displayElapsed)
{
bool expected = false;
@ -188,9 +207,17 @@ void percent(uint64_t count, uint64_t max, bool displayElapsed)
is_timing = false;
if (displayElapsed) {
if (max == indeterminate) {
print(flag::PERCENT, "[done] time elapsed: {:.3}\n", elapsed);
if (active_percent_callback) {
active_percent_callback(std::nullopt, elapsed);
} else {
print(flag::PERCENT, "[done] time elapsed: {:.3}\n", elapsed);
}
} else {
print(flag::PERCENT, "[100%] time elapsed: {:.3}\n", elapsed);
if (active_percent_callback) {
active_percent_callback(100u, elapsed);
} else {
print(flag::PERCENT, "[100%] time elapsed: {:.3}\n", elapsed);
}
}
}
last_count = -1;
@ -198,7 +225,11 @@ void percent(uint64_t count, uint64_t max, bool displayElapsed)
if (max != indeterminate) {
uint32_t pct = static_cast<uint32_t>((static_cast<float>(count) / max) * 100);
if (last_count != pct) {
print(flag::PERCENT, "[{:>3}%]\r", pct);
if (active_percent_callback) {
active_percent_callback(pct, std::nullopt);
} else {
print(flag::PERCENT, "[{:>3}%]\r", pct);
}
last_count = pct;
}
} else {
@ -206,8 +237,12 @@ void percent(uint64_t count, uint64_t max, bool displayElapsed)
if (t - last_indeterminate_time > std::chrono::milliseconds(100)) {
constexpr const char *spinners[] = {". ", " . ", " . ", " ."};
last_count = (last_count + 1) >= std::size(spinners) ? 0 : (last_count + 1);
print(flag::PERCENT, "[{}]\r", spinners[last_count]);
if (active_percent_callback) {
active_percent_callback(std::nullopt, std::nullopt);
} else {
last_count = (last_count + 1) >= std::size(spinners) ? 0 : (last_count + 1);
print(flag::PERCENT, "[{}]\r", spinners[last_count]);
}
last_indeterminate_time = t;
}
}

View File

@ -31,9 +31,12 @@
#include <list>
#include <cmath> // for log10
#include <stdexcept> // for std::runtime_error
#include <functional> // for std::function
#include <optional> // for std::optional
#include <fmt/core.h>
#include <common/bitflags.hh>
#include <common/fs.hh>
#include <common/cmdlib.hh>
// forward declaration
namespace settings
@ -89,6 +92,11 @@ inline void print(const char *formt, const Args &...args)
print(flag::DEFAULT, fmt::format(fmt::runtime(formt), std::forward<const Args &>(args)...).c_str());
}
// set print callback
using print_callback_t = std::function<void(flag logflag, const char *str)>;
void set_print_callback(print_callback_t cb);
void header(const char *name);
// TODO: C++20 source_location
@ -102,6 +110,11 @@ void header(const char *name);
void assert_(bool success, const char *expr, const char *file, int line);
// set percent callback
using percent_callback_t = std::function<void(std::optional<uint32_t> percent, std::optional<duration> elapsed)>;
void set_percent_callback(percent_callback_t cb);
// Display a percent timer. This also keeps track of how long the
// current task is taking to execute. Note that only one of these
// can be active at a time. Once `count` == `max`, the progress

View File

@ -253,6 +253,7 @@ enum class visapprox_t
enum class emissivequality_t
{
LOW,
MEDIUM,
HIGH
};

View File

@ -222,23 +222,27 @@ static void MakeBounceLightsThread(const settings::worldspawn_keys &cfg, const m
// Get face normal and midpoint...
qvec3d facenormal = faceplane.normal;
qvec3d facemidpoint = winding.center() + facenormal; // Lift 1 unit
vector<qvec3f> points;
if (light_options.emissivequality.value() == emissivequality_t::LOW) {
vector<qvec3f> points{facemidpoint};
if (light_options.emissivequality.value() == emissivequality_t::LOW ||
light_options.emissivequality.value() == emissivequality_t::MEDIUM) {
points = {facemidpoint};
for (auto &style : emitcolors) {
MakeBounceLight(
bsp, cfg, surf, style.second, style.first, points, winding, area, facenormal, facemidpoint);
if (light_options.emissivequality.value() == emissivequality_t::MEDIUM) {
for (auto &pt : winding) {
points.push_back(pt + faceplane.normal);
}
}
} else {
vector<qvec3f> points;
winding.dice(cfg.bouncelightsubdivision.value(),
[&points, &faceplane](winding_t &w) { points.push_back(w.center() + faceplane.normal); });
}
for (auto &style : emitcolors) {
MakeBounceLight(
bsp, cfg, surf, style.second, style.first, points, winding, area, facenormal, facemidpoint);
}
for (auto &style : emitcolors) {
MakeBounceLight(
bsp, cfg, surf, style.second, style.first, points, winding, area, facenormal, facemidpoint);
}
}

View File

@ -300,8 +300,8 @@ light_settings::light_settings()
this, "lightmap_scale", 0, &experimental_group, "force change lightmap scale; vanilla engines only allow 16"},
extra{
this, {"extra", "extra4"}, 1, &performance_group, "supersampling; 2x2 (extra) or 4x4 (extra4) respectively"},
emissivequality{this, "emissivequality", emissivequality_t::LOW, { { "LOW", emissivequality_t::LOW }, { "HIGH", emissivequality_t::HIGH } }, &performance_group,
"low = one point in the center of the face, high = spread points out for antialiasing"},
emissivequality{this, "emissivequality", emissivequality_t::LOW, { { "LOW", emissivequality_t::LOW }, { "MEDIUM", emissivequality_t::MEDIUM }, { "HIGH", emissivequality_t::HIGH } }, &performance_group,
"low = one point in the center of the face, med = center + all verts, high = spread points out for antialiasing"},
visapprox{this, "visapprox", visapprox_t::AUTO,
{{"auto", visapprox_t::AUTO}, {"none", visapprox_t::NONE}, {"vis", visapprox_t::VIS},
{"rays", visapprox_t::RAYS}},

View File

@ -123,10 +123,32 @@ static void MakeSurfaceLight(const mbsp_t *bsp, const settings::worldspawn_keys
// Dice winding...
l->points_before_culling = 0;
if (light_options.emissivequality.value() == emissivequality_t::LOW) {
if (light_options.emissivequality.value() == emissivequality_t::LOW ||
light_options.emissivequality.value() == emissivequality_t::MEDIUM) {
l->points = { l->pos };
l->points_before_culling++;
total_surflight_points++;
if (light_options.emissivequality.value() == emissivequality_t::MEDIUM) {
for (auto &pt : winding) {
l->points_before_culling++;
auto point = pt + l->surfnormal;
auto diff = qv::normalize(l->pos - pt);
point += diff;
// optimization - cull surface lights in the void
// also try to move them if they're slightly inside a wall
auto [fixed_point, success] = FixLightOnFace(bsp, point, false, 0.5f);
if (!success) {
continue;
}
l->points.push_back(fixed_point);
total_surflight_points++;
}
}
} else {
winding.dice(cfg.surflightsubdivision.value(), [&](winding_t &w) {
++l->points_before_culling;

View File

@ -40,17 +40,21 @@ See file, 'COPYING', for details.
#include <QTimer>
#include <QScrollArea>
#include <QSpinBox>
#include <QScrollBar>
#include <QFrame>
#include <QLabel>
#include <QTextEdit>
#include <QStatusBar>
#include <QStringList>
#include <QThread>
#include <QApplication>
#include <common/bspfile.hh>
#include <qbsp/qbsp.hh>
#include <vis/vis.hh>
#include <light/light.hh>
#include <common/bspinfo.hh>
#include <fmt/chrono.h>
#include "glview.h"
@ -95,6 +99,23 @@ static QStringList GetRecents()
return recents;
}
// ETLogWidget
ETLogWidget::ETLogWidget(QWidget *parent) :
QTabWidget(parent)
{
for (size_t i = 0; i < std::size(logTabNames); i++) {
m_textEdits[i] = new QTextEdit();
auto *formLayout = new QFormLayout();
auto *form = new QWidget();
formLayout->addRow(m_textEdits[i]);
form->setLayout(formLayout);
setTabText(i, logTabNames[i]);
addTab(form, logTabNames[i]);
formLayout->setContentsMargins(0, 0, 0, 0);
}
}
// MainWindow
MainWindow::MainWindow(QWidget *parent)
@ -223,16 +244,74 @@ void MainWindow::createPropertiesSidebar()
m_fileReloadTimer->connect(m_fileReloadTimer.get(), &QTimer::timeout, this, &MainWindow::fileReloadTimerExpired);
}
void MainWindow::logWidgetSetText(ETLogTab tab, const std::string &str)
{
m_outputLogWidget->setTabText((int32_t) tab, str.c_str());
}
void MainWindow::lightpreview_percent_callback(std::optional<uint32_t> percent, std::optional<duration> elapsed)
{
int32_t tabIndex = (int32_t) m_activeLogTab;
if (elapsed.has_value()) {
lightpreview_log_callback(logging::flag::PROGRESS, fmt::format("finished in: {:.3}\n", elapsed.value()).c_str());
QMetaObject::invokeMethod(this, std::bind(&MainWindow::logWidgetSetText, this, m_activeLogTab, ETLogWidget::logTabNames[tabIndex]));
} else {
if (percent.has_value()) {
QMetaObject::invokeMethod(this, std::bind(&MainWindow::logWidgetSetText, this, m_activeLogTab, fmt::format("{} [{:>3}%]", ETLogWidget::logTabNames[tabIndex], percent.value())));
} else {
QMetaObject::invokeMethod(this, std::bind(&MainWindow::logWidgetSetText, this, m_activeLogTab, fmt::format("{} (...)", ETLogWidget::logTabNames[tabIndex])));
}
}
}
void MainWindow::lightpreview_log_callback(logging::flag flags, const char *str)
{
if (bitflags(flags) & logging::flag::PERCENT)
return;
if (QApplication::instance()->thread() != QThread::currentThread())
{
QMetaObject::invokeMethod(this, std::bind([this, flags](const std::string &s) -> void
{
lightpreview_log_callback(flags, s.c_str());
}, std::string(str)));
return;
}
auto *textEdit = m_outputLogWidget->textEdit(m_activeLogTab);
const bool atBottom = textEdit->verticalScrollBar()->value() == textEdit->verticalScrollBar()->maximum();
QTextDocument* doc = textEdit->document();
QTextCursor cursor(doc);
cursor.movePosition(QTextCursor::End);
cursor.beginEditBlock();
cursor.insertBlock();
cursor.insertHtml(QString::asprintf("%s\n", str));
cursor.endEditBlock();
//scroll scrollarea to bottom if it was at bottom when we started
//(we don't want to force scrolling to bottom if user is looking at a
//higher position)
if (atBottom) {
QScrollBar* bar = textEdit->verticalScrollBar();
bar->setValue(bar->maximum());
}
}
void MainWindow::createOutputLog()
{
QDockWidget *dock = new QDockWidget(tr("Output Log"), this);
QDockWidget *dock = new QDockWidget(tr("Tool Logs"), this);
m_outputTextEdit = new QTextEdit();
m_outputLogWidget = new ETLogWidget();
// finish dock widget setup
dock->setWidget(m_outputTextEdit);
dock->setWidget(m_outputLogWidget);
addDockWidget(Qt::BottomDockWidgetArea, dock);
viewMenu->addAction(dock->toggleViewAction());
logging::set_print_callback(std::bind(&MainWindow::lightpreview_log_callback, this, std::placeholders::_1, std::placeholders::_2));
logging::set_percent_callback(std::bind(&MainWindow::lightpreview_percent_callback, this, std::placeholders::_1, std::placeholders::_2));
}
void MainWindow::createStatusBar()
@ -386,9 +465,13 @@ std::filesystem::path MakeFSPath(const QString &string)
return std::filesystem::path{string.toStdU16String()};
}
static bspdata_t QbspVisLight_Common(const std::filesystem::path &name, std::vector<std::string> extra_qbsp_args,
bspdata_t MainWindow::QbspVisLight_Common(const std::filesystem::path &name, std::vector<std::string> extra_qbsp_args,
std::vector<std::string> extra_vis_args, std::vector<std::string> extra_light_args, bool run_vis)
{
auto resetActiveTabText = [&]() {
QMetaObject::invokeMethod(this, std::bind(&MainWindow::logWidgetSetText, this, m_activeLogTab, ETLogWidget::logTabNames[(int32_t) m_activeLogTab]));
};
auto bsp_path = name;
bsp_path.replace_extension(".bsp");
@ -401,12 +484,16 @@ static bspdata_t QbspVisLight_Common(const std::filesystem::path &name, std::vec
args.push_back(name.string());
// run qbsp
m_activeLogTab = ETLogTab::TAB_BSP;
InitQBSP(args);
ProcessFile();
resetActiveTabText();
// run vis
if (run_vis) {
m_activeLogTab = ETLogTab::TAB_VIS;
std::vector<std::string> vis_args{
"", // the exe path, which we're ignoring in this case
};
@ -417,8 +504,11 @@ static bspdata_t QbspVisLight_Common(const std::filesystem::path &name, std::vec
vis_main(vis_args);
}
resetActiveTabText();
// run light
{
m_activeLogTab = ETLogTab::TAB_LIGHT;
std::vector<std::string> light_args{
"", // the exe path, which we're ignoring in this case
};
@ -430,6 +520,10 @@ static bspdata_t QbspVisLight_Common(const std::filesystem::path &name, std::vec
light_main(light_args);
}
resetActiveTabText();
m_activeLogTab = ETLogTab::TAB_LIGHTPREVIEW;
// serialize obj
{
bspdata_t bspdata;
@ -515,30 +609,12 @@ private:
GLView *glView;
};
void MainWindow::loadFileInternal(const QString &file, bool is_reload)
int MainWindow::compileMap(const QString &file, bool is_reload)
{
qDebug() << "loadFileInternal " << file;
// just in case
m_fileReloadTimer->stop();
// persist settings
QSettings s;
s.setValue("qbsp_options", qbsp_options->text());
s.setValue("vis_enabled", vis_checkbox->isChecked());
s.setValue("vis_options", vis_options->text());
s.setValue("light_options", light_options->text());
s.setValue("nearest", nearest->isChecked());
// update title bar
setWindowFilePath(file);
setWindowTitle(QFileInfo(file).fileName() + " - lightpreview");
fs::path fs_path = MakeFSPath(file);
m_bspdata = {};
settings::common_settings render_settings;
render_settings.reset();
try {
if (fs_path.extension().compare(".bsp") == 0) {
@ -576,13 +652,22 @@ void MainWindow::loadFileInternal(const QString &file, bool is_reload)
m_bspdata.loadversion->game->init_filesystem(file.toStdString(), settings);
}
} catch (const settings::parse_exception &p) {
m_outputTextEdit->append(QString::fromUtf8(p.what()) + QString::fromLatin1("\n"));
return;
auto *textEdit = m_outputLogWidget->textEdit(m_activeLogTab);
textEdit->append(QString::fromUtf8(p.what()) + QString::fromLatin1("\n"));
m_activeLogTab = ETLogTab::TAB_LIGHTPREVIEW;
return 1;
} catch (const settings::quit_after_help_exception &p) {
m_outputTextEdit->append(QString::fromUtf8(p.what()) + QString::fromLatin1("\n"));
return;
auto *textEdit = m_outputLogWidget->textEdit(m_activeLogTab);
textEdit->append(QString::fromUtf8(p.what()) + QString::fromLatin1("\n"));
m_activeLogTab = ETLogTab::TAB_LIGHTPREVIEW;
return 1;
}
return 0;
}
void MainWindow::compileThreadExited()
{
const auto &bsp = std::get<mbsp_t>(m_bspdata.bsp);
auto ents = EntData_Parse(bsp);
@ -590,9 +675,9 @@ void MainWindow::loadFileInternal(const QString &file, bool is_reload)
// build lightmap atlas
auto atlas = build_lightmap_atlas(bsp, m_bspdata.bspx.entries, false, bspx_decoupled_lm->isChecked());
glView->renderBSP(file, bsp, m_bspdata.bspx.entries, ents, atlas, render_settings, bspx_normals->isChecked());
glView->renderBSP(m_mapFile, bsp, m_bspdata.bspx.entries, ents, atlas, render_settings, bspx_normals->isChecked());
if (!is_reload && !glView->getKeepOrigin()) {
if (!m_fileWasReload && !glView->getKeepOrigin()) {
for (auto &ent : ents) {
if (ent.get("classname") == "info_player_start") {
qvec3d origin;
@ -624,6 +709,42 @@ void MainWindow::loadFileInternal(const QString &file, bool is_reload)
auto *style = new QLightStyleSlider(style_entry.first, glView);
lightstyles->addWidget(style);
}
delete m_compileThread;
m_compileThread = nullptr;
}
void MainWindow::loadFileInternal(const QString &file, bool is_reload)
{
// TODO
if (m_compileThread)
return;
qDebug() << "loadFileInternal " << file;
// just in case
m_fileReloadTimer->stop();
m_fileWasReload = is_reload;
// persist settings
QSettings s;
s.setValue("qbsp_options", qbsp_options->text());
s.setValue("vis_enabled", vis_checkbox->isChecked());
s.setValue("vis_options", vis_options->text());
s.setValue("light_options", light_options->text());
s.setValue("nearest", nearest->isChecked());
// update title bar
setWindowFilePath(file);
setWindowTitle(QFileInfo(file).fileName() + " - lightpreview");
for (auto &edit : m_outputLogWidget->textEdits()) {
edit->clear();
}
m_compileThread = QThread::create(std::bind(&MainWindow::compileMap, this, file, is_reload));
connect(m_compileThread, &QThread::finished, this, &MainWindow::compileThreadExited);
m_compileThread->start();
}
void MainWindow::displayCameraPositionInfo()

View File

@ -31,6 +31,40 @@ class QCheckBox;
class QStringList;
class QTextEdit;
enum class ETLogTab
{
TAB_LIGHTPREVIEW,
TAB_BSP,
TAB_VIS,
TAB_LIGHT,
TAB_TOTAL
};
class ETLogWidget : public QTabWidget
{
Q_OBJECT
public:
static constexpr const char *logTabNames[(size_t) ETLogTab::TAB_TOTAL] = {
"lightpreview",
"bsp",
"vis",
"light"
};
explicit ETLogWidget(QWidget *parent = nullptr);
~ETLogWidget() { }
QTextEdit *textEdit(ETLogTab i) { return m_textEdits[(size_t) i]; }
const QTextEdit *textEdit(ETLogTab i) const { return m_textEdits[(size_t) i]; }
auto &textEdits() { return m_textEdits; }
private:
std::array<QTextEdit *, std::size(logTabNames)> m_textEdits;
};
class MainWindow : public QMainWindow
{
Q_OBJECT
@ -38,9 +72,13 @@ class MainWindow : public QMainWindow
private:
QFileSystemWatcher *m_watcher = nullptr;
std::unique_ptr<QTimer> m_fileReloadTimer;
bool m_fileWasReload = false;
QString m_mapFile;
bspdata_t m_bspdata;
settings::common_settings render_settings;
qint64 m_fileSize = -1;
ETLogTab m_activeLogTab = ETLogTab::TAB_LIGHTPREVIEW;
QThread *m_compileThread = nullptr;
public:
explicit MainWindow(QWidget *parent = nullptr);
@ -49,12 +87,19 @@ public:
private:
void createPropertiesSidebar();
void createOutputLog();
void lightpreview_log_callback(logging::flag flags, const char *str);
void lightpreview_percent_callback(std::optional<uint32_t> percent, std::optional<duration> elapsed);
void logWidgetSetText(ETLogTab tab, const std::string &str);
void createStatusBar();
void updateRecentsSubmenu(const QStringList &recents);
void setupMenu();
void fileOpen();
void takeScreenshot();
void fileReloadTimerExpired();
int compileMap(const QString &file, bool is_reload);
void compileThreadExited();
bspdata_t QbspVisLight_Common(const fs::path &name, std::vector<std::string> extra_qbsp_args,
std::vector<std::string> extra_vis_args, std::vector<std::string> extra_light_args, bool run_vis);
protected:
void dragEnterEvent(QDragEnterEvent *event) override;
@ -83,5 +128,5 @@ private:
QMenu *viewMenu = nullptr;
QMenu *openRecentMenu = nullptr;
QTextEdit *m_outputTextEdit = nullptr;
ETLogWidget *m_outputLogWidget = nullptr;
};