Skip to content

Commit f70ab50

Browse files
committed
FBO MSAA WIP
1 parent e936683 commit f70ab50

File tree

8 files changed

+213
-18
lines changed

8 files changed

+213
-18
lines changed

src/configuration/configuration.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,7 @@ void Configuration::CanvasSettings::read(const QSettings &conf)
596596
connectionNormalColor = lookupColor(KEY_CONNECTION_NORMAL_COLOR, Colors::white.toHex());
597597
roomDarkColor = lookupColor(KEY_ROOM_DARK_COLOR, DEFAULT_DARK_COLOR);
598598
roomDarkLitColor = lookupColor(KEY_ROOM_DARK_LIT_COLOR, DEFAULT_NO_SUNDEATH_COLOR);
599-
antialiasingSamples = conf.value(KEY_NUMBER_OF_ANTI_ALIASING_SAMPLES, 0).toInt();
599+
advanced.antialiasingSamples.set(conf.value(KEY_NUMBER_OF_ANTI_ALIASING_SAMPLES, 0).toInt());
600600
trilinearFiltering = conf.value(KEY_USE_TRILINEAR_FILTERING, true).toBool();
601601
advanced.use3D.set(conf.value(KEY_3D_CANVAS, false).toBool());
602602
advanced.autoTilt.set(conf.value(KEY_3D_AUTO_TILT, true).toBool());
@@ -779,7 +779,7 @@ void Configuration::CanvasSettings::write(QSettings &conf) const
779779
conf.setValue(KEY_ROOM_DARK_COLOR, getQColorName(roomDarkColor));
780780
conf.setValue(KEY_ROOM_DARK_LIT_COLOR, getQColorName(roomDarkLitColor));
781781
conf.setValue(KEY_CONNECTION_NORMAL_COLOR, getQColorName(connectionNormalColor));
782-
conf.setValue(KEY_NUMBER_OF_ANTI_ALIASING_SAMPLES, antialiasingSamples);
782+
conf.setValue(KEY_NUMBER_OF_ANTI_ALIASING_SAMPLES, advanced.antialiasingSamples.get());
783783
conf.setValue(KEY_USE_TRILINEAR_FILTERING, trilinearFiltering);
784784
conf.setValue(KEY_3D_CANVAS, advanced.use3D.get());
785785
conf.setValue(KEY_3D_AUTO_TILT, advanced.autoTilt.get());

src/configuration/configuration.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,6 @@ class NODISCARD Configuration final
139139
NamedConfig<bool> showUnmappedExits{"SHOW_UNMAPPED_EXITS", false};
140140
bool drawUpperLayersTextured = false;
141141
bool drawDoorNames = false;
142-
int antialiasingSamples = 0;
143142
bool trilinearFiltering = false;
144143
bool softwareOpenGL = false;
145144
QString resourcesDirectory;
@@ -155,6 +154,7 @@ class NODISCARD Configuration final
155154

156155
struct NODISCARD Advanced final
157156
{
157+
NamedConfig<int> antialiasingSamples{"ANTIALIASING_SAMPLES", 0};
158158
NamedConfig<bool> use3D{"MMAPPER_3D", true};
159159
NamedConfig<bool> autoTilt{"MMAPPER_AUTO_TILT", true};
160160
NamedConfig<bool> printPerfStats{"MMAPPER_GL_PERFSTATS", IS_DEBUG_BUILD};

src/display/mapcanvas_gl.cpp

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,11 @@ void MapCanvas::initializeGL()
268268
setConfig().canvas.showUnmappedExits.registerChangeCallback(m_lifetime, [this]() {
269269
this->forceUpdateMeshes();
270270
});
271+
272+
setConfig().canvas.advanced.antialiasingSamples.registerChangeCallback(m_lifetime, [this]() {
273+
this->updateMultisampling();
274+
this->update();
275+
});
271276
}
272277

273278
/* Direct means it is always called from the emitter's thread */
@@ -472,6 +477,10 @@ void MapCanvas::resizeGL(int width, int height)
472477

473478
setViewportAndMvp(width, height);
474479

480+
// Update the multisampling FBO size when the canvas is resized
481+
const int wantMultisampling = getConfig().canvas.advanced.antialiasingSamples.get();
482+
getOpenGL().setMultisamplingFbo(wantMultisampling, size());
483+
475484
// Render
476485
update();
477486
}
@@ -621,6 +630,20 @@ void MapCanvas::actuallyPaintGL()
621630
setViewportAndMvp(width(), height());
622631

623632
auto &gl = getOpenGL();
633+
634+
// Bind the appropriate FBO: multisampling if valid, otherwise resolved if valid, fallback to default
635+
QOpenGLFramebufferObject *fboToBind = nullptr;
636+
if (gl.getMultisamplingFbo() && gl.getMultisamplingFbo()->isValid()) {
637+
fboToBind = gl.getMultisamplingFbo();
638+
} else if (gl.getResolvedFbo() && gl.getResolvedFbo()->isValid()) {
639+
fboToBind = gl.getResolvedFbo();
640+
}
641+
642+
if (fboToBind) {
643+
fboToBind->bind();
644+
}
645+
646+
// Clear the FBO
624647
gl.clear(Color{getConfig().canvas.backgroundColor});
625648

626649
if (m_data.isEmpty()) {
@@ -633,6 +656,14 @@ void MapCanvas::actuallyPaintGL()
633656
paintSelections();
634657
paintCharacters();
635658
paintDifferences();
659+
660+
// Unbind the FBO we rendered to
661+
if (fboToBind) {
662+
fboToBind->release();
663+
}
664+
665+
// Blit from the resolved FBO to the default framebuffer (handles multisample resolve if needed)
666+
gl.blitResolvedToDefault(size());
636667
}
637668

638669
NODISCARD bool MapCanvas::Diff::isUpToDate(const Map &saved, const Map &current) const
@@ -1007,14 +1038,15 @@ void MapCanvas::paintSelectionArea()
10071038

10081039
void MapCanvas::updateMultisampling()
10091040
{
1010-
const int wantMultisampling = getConfig().canvas.antialiasingSamples;
1041+
const int wantMultisampling = getConfig().canvas.advanced.antialiasingSamples.get();
10111042
std::optional<int> &activeStatus = m_graphicsOptionsStatus.multisampling;
10121043
if (activeStatus == wantMultisampling) {
10131044
return;
10141045
}
10151046

1016-
// REVISIT: check return value?
1017-
MAYBE_UNUSED const bool enabled = getOpenGL().tryEnableMultisampling(wantMultisampling);
1047+
// Update the multisampling FBO in the OpenGL class
1048+
getOpenGL().setMultisamplingFbo(wantMultisampling, size());
1049+
10181050
activeStatus = wantMultisampling;
10191051
}
10201052

src/main.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ static void setSurfaceFormat()
9393
// Probe for supported OpenGL versions
9494
QSurfaceFormat fmt = OpenGL::createDefaultSurfaceFormat();
9595

96-
const auto &config = getConfig().canvas;
97-
fmt.setSamples(config.antialiasingSamples);
96+
//const auto &config = getConfig().canvas;
97+
//fmt.setSamples(config.advanced.antialiasingSamples.get());
9898
QSurfaceFormat::setDefaultFormat(fmt);
9999
}
100100

src/opengl/OpenGL.cpp

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,9 +323,146 @@ void OpenGL::setProjectionMatrix(const glm::mat4 &m)
323323
getFunctions().setProjectionMatrix(m);
324324
}
325325

326+
void OpenGL::setMultisamplingFbo(int requestedSamples, const QSize &size)
327+
{
328+
// Calculate physical size based on logical size and device pixel ratio
329+
const float dpr = getDevicePixelRatio();
330+
const QSize physicalSize(size.width() * static_cast<int>(dpr),
331+
size.height() * static_cast<int>(dpr));
332+
333+
// Always manage the resolved FBO if size is not empty
334+
if (!physicalSize.isEmpty()) {
335+
QOpenGLFramebufferObjectFormat resolvedFormat;
336+
resolvedFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
337+
resolvedFormat.setSamples(0); // Not multisampled
338+
resolvedFormat.setTextureTarget(GL_TEXTURE_2D);
339+
resolvedFormat.setInternalTextureFormat(GL_RGBA8);
340+
341+
if (!m_resolvedFbo || m_resolvedFbo->size() != physicalSize) {
342+
m_resolvedFbo = std::make_unique<QOpenGLFramebufferObject>(physicalSize, resolvedFormat);
343+
if (!m_resolvedFbo->isValid()) {
344+
MMLOG_ERROR() << "Failed to create resolved FBO with physical size "
345+
<< physicalSize.width() << "x" << physicalSize.height()
346+
<< ". This may cause rendering issues.";
347+
m_resolvedFbo.reset(); // Indicate failure
348+
} else {
349+
MMLOG_INFO() << "Created resolved FBO with physical size "
350+
<< m_resolvedFbo->size().width() << "x"
351+
<< m_resolvedFbo->size().height();
352+
}
353+
}
354+
} else {
355+
m_resolvedFbo.reset();
356+
MMLOG_INFO() << "Resolved FBO destroyed (size empty)";
357+
}
358+
359+
// Manage the multisampling FBO only if samples > 0 and size is not empty
360+
if (requestedSamples > 0 && !physicalSize.isEmpty()) {
361+
auto &gl = getFunctions();
362+
GLint maxSamples = 0;
363+
gl.glGetIntegerv(GL_MAX_SAMPLES, &maxSamples);
364+
int actualSamples = std::min(requestedSamples, static_cast<int>(maxSamples));
365+
366+
if (actualSamples == 0 && requestedSamples > 0) {
367+
MMLOG_WARNING() << "Requested " << requestedSamples
368+
<< " samples, but max supported is 0. Disabling multisampling.";
369+
} else if (actualSamples < requestedSamples) {
370+
MMLOG_INFO() << "Requested " << requestedSamples << " samples, but using "
371+
<< actualSamples << " (max supported).";
372+
}
373+
374+
QOpenGLFramebufferObjectFormat msFormat;
375+
msFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
376+
msFormat.setSamples(actualSamples);
377+
msFormat.setTextureTarget(GL_TEXTURE_2D_MULTISAMPLE);
378+
msFormat.setInternalTextureFormat(GL_RGBA8);
379+
380+
if (!m_multisamplingFbo || m_multisamplingFbo->size() != physicalSize
381+
|| m_multisamplingFbo->format().samples() != actualSamples) {
382+
m_multisamplingFbo = std::make_unique<QOpenGLFramebufferObject>(physicalSize, msFormat);
383+
if (!m_multisamplingFbo->isValid()) {
384+
MMLOG_ERROR() << "Failed to create multisampling FBO with " << actualSamples
385+
<< " samples and physical size " << physicalSize.width() << "x"
386+
<< physicalSize.height() << ". Falling back to no multisampling.";
387+
m_multisamplingFbo.reset(); // Fallback
388+
} else {
389+
MMLOG_INFO() << "Created multisampling FBO with "
390+
<< m_multisamplingFbo->format().samples()
391+
<< " samples and physical size " << m_multisamplingFbo->size().width()
392+
<< "x" << m_multisamplingFbo->size().height();
393+
}
394+
}
395+
} else {
396+
m_multisamplingFbo.reset();
397+
MMLOG_INFO() << "Multisampling FBO destroyed (samples <= 0 or size empty)";
398+
}
399+
}
400+
401+
QOpenGLFramebufferObject *OpenGL::getRenderFbo() const
402+
{
403+
// Render to the multisampling FBO if it's valid, otherwise render to the resolved FBO
404+
if (m_multisamplingFbo && m_multisamplingFbo->isValid()) {
405+
return m_multisamplingFbo.get();
406+
}
407+
// Fallback to resolved FBO (which should always be valid if size is not empty)
408+
return m_resolvedFbo.get();
409+
}
410+
411+
void OpenGL::bindMultisamplingFbo()
412+
{
413+
if (m_multisamplingFbo) {
414+
m_multisamplingFbo->bind();
415+
}
416+
}
417+
418+
void OpenGL::releaseMultisamplingFbo()
419+
{
420+
if (m_multisamplingFbo) {
421+
m_multisamplingFbo->release();
422+
}
423+
}
424+
425+
void OpenGL::blitResolvedToDefault(const QSize & /*size*/)
426+
{
427+
if (!m_resolvedFbo || !m_resolvedFbo->isValid()) {
428+
MMLOG_WARNING() << "Resolved FBO not valid for blitting. Skipping blit sequence.";
429+
return;
430+
}
431+
432+
// If multisampling FBO is valid, blit from it to the resolved FBO first
433+
if (m_multisamplingFbo && m_multisamplingFbo->isValid()) {
434+
QOpenGLFramebufferObject::blitFramebuffer(m_resolvedFbo.get(),
435+
m_multisamplingFbo.get(),
436+
GL_COLOR_BUFFER_BIT,
437+
GL_LINEAR); // Use GL_LINEAR for filtering during resolve
438+
}
439+
// Always blit from the resolved FBO to the default framebuffer (displays on screen)
440+
QOpenGLFramebufferObject::blitFramebuffer(
441+
nullptr, // Default framebuffer
442+
m_resolvedFbo.get(),
443+
GL_COLOR_BUFFER_BIT,
444+
GL_NEAREST); // GL_NEAREST is usually fine for 1:1 blit to screen
445+
}
446+
326447
bool OpenGL::tryEnableMultisampling(const int requestedSamples)
327448
{
328-
return getFunctions().tryEnableMultisampling(requestedSamples);
449+
// This function now primarily manages the GL_MULTISAMPLE state for the default framebuffer.
450+
// The FBO handles the actual multisampling rendering.
451+
auto &gl = getFunctions();
452+
if (requestedSamples > 0) {
453+
gl.glEnable(GL_MULTISAMPLE);
454+
// The old smoothing hints might still be useful as a fallback or in conjunction,
455+
// but the primary multisampling is now via FBO. We can keep them for now.
456+
gl.glEnable(GL_LINE_SMOOTH);
457+
gl.glDisable(GL_POLYGON_SMOOTH);
458+
gl.glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
459+
return true;
460+
} else {
461+
gl.glDisable(GL_MULTISAMPLE);
462+
gl.glDisable(GL_LINE_SMOOTH);
463+
gl.glDisable(GL_POLYGON_SMOOTH);
464+
return false;
465+
}
329466
}
330467

331468
UniqueMesh OpenGL::createPointBatch(const std::vector<ColorVert> &batch)

src/opengl/OpenGL.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include <glm/glm.hpp>
1515

16+
#include <QOpenGLFramebufferObject>
1617
#include <QSurfaceFormat>
1718
#include <qopengl.h>
1819

@@ -27,6 +28,10 @@ class NODISCARD OpenGL final
2728
static std::string g_highest_reportable_version_string;
2829
std::shared_ptr<Legacy::Functions> m_opengl;
2930
bool m_rendererInitialized = false;
31+
// Replaced raw multisampling FBO with QOpenGLFramebufferObject
32+
std::unique_ptr<QOpenGLFramebufferObject> m_multisamplingFbo;
33+
// Added resolved FBO using QOpenGLFramebufferObject
34+
std::unique_ptr<QOpenGLFramebufferObject> m_resolvedFbo;
3035

3136
private:
3237
NODISCARD auto &getFunctions() { return deref(m_opengl); }
@@ -65,6 +70,21 @@ class NODISCARD OpenGL final
6570
public:
6671
NODISCARD bool tryEnableMultisampling(int samples);
6772

73+
public:
74+
// Modified setMultisamplingFbo to handle QOpenGLFramebufferObject
75+
void setMultisamplingFbo(int samples, const QSize &size);
76+
// Modified bindMultisamplingFbo to use QOpenGLFramebufferObject
77+
void bindMultisamplingFbo();
78+
// Modified releaseMultisamplingFbo to use QOpenGLFramebufferObject
79+
void releaseMultisamplingFbo();
80+
// Removed blitMultisamplingFboToDefault as blitting will be handled differently
81+
82+
// Added a new function to perform the blit sequence
83+
void blitResolvedToDefault(const QSize &size);
84+
85+
// Added function to get the FBO to render to
86+
NODISCARD QOpenGLFramebufferObject *getRenderFbo() const;
87+
6888
public:
6989
NODISCARD UniqueMesh createPointBatch(const std::vector<ColorVert> &verts);
7090

@@ -162,4 +182,14 @@ class NODISCARD OpenGL final
162182
public:
163183
void cleanup();
164184
void setTextureLookup(MMTextureId, SharedMMTexture);
185+
186+
public:
187+
NODISCARD QOpenGLFramebufferObject *getMultisamplingFbo() const
188+
{
189+
return m_multisamplingFbo.get();
190+
}
191+
NODISCARD QOpenGLFramebufferObject *getResolvedFbo() const { return m_resolvedFbo.get(); }
192+
193+
public:
194+
void initArray(const SharedMMTexture &array, const std::vector<SharedMMTexture> &input);
165195
};

src/preferences/graphicspage.cpp

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@ GraphicsPage::GraphicsPage(QWidget *parent)
4949
connect(ui->antialiasingSamplesComboBox,
5050
&QComboBox::currentTextChanged,
5151
this,
52-
&GraphicsPage::slot_antialiasingSamplesTextChanged);
52+
[this](const QString &text) {
53+
setConfig().canvas.advanced.antialiasingSamples.set(text.toInt());
54+
graphicsSettingsChanged();
55+
});
5356
connect(ui->trilinearFilteringCheckBox,
5457
&QCheckBox::stateChanged,
5558
this,
@@ -109,7 +112,7 @@ void GraphicsPage::slot_loadConfig()
109112
setIconColor(ui->darkLitPushButton, settings.roomDarkLitColor);
110113
setIconColor(ui->connectionNormalPushButton, settings.connectionNormalColor);
111114

112-
const QString antiAliasingSamples = QString::number(settings.antialiasingSamples);
115+
const QString antiAliasingSamples = QString::number(settings.advanced.antialiasingSamples.get());
113116
const int index = utils::clampNonNegative(
114117
ui->antialiasingSamplesComboBox->findText(antiAliasingSamples));
115118
ui->antialiasingSamplesComboBox->setCurrentIndex(index);
@@ -133,12 +136,6 @@ void GraphicsPage::changeColorClicked(XNamedColor &namedColor, QPushButton *cons
133136
}
134137
}
135138

136-
void GraphicsPage::slot_antialiasingSamplesTextChanged(const QString & /*unused*/)
137-
{
138-
setConfig().canvas.antialiasingSamples = ui->antialiasingSamplesComboBox->currentText().toInt();
139-
graphicsSettingsChanged();
140-
}
141-
142139
void GraphicsPage::slot_trilinearFilteringStateChanged(int /*unused*/)
143140
{
144141
setConfig().canvas.trilinearFiltering = ui->trilinearFilteringCheckBox->isChecked();

src/preferences/graphicspage.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ class NODISCARD_QOBJECT GraphicsPage final : public QWidget
4040

4141
public slots:
4242
void slot_loadConfig();
43-
void slot_antialiasingSamplesTextChanged(const QString &);
4443
void slot_trilinearFilteringStateChanged(int);
4544
void slot_drawNeedsUpdateStateChanged(int);
4645
void slot_drawNotMappedExitsStateChanged(int);

0 commit comments

Comments
 (0)