From c7ca6071ce97d216ebdf98b8e48e897c76383e1e Mon Sep 17 00:00:00 2001 From: Berthold Krevert Date: Mon, 11 Mar 2024 13:38:12 +0100 Subject: [PATCH] Enable MSAA via render buffers This is the canonical Qt Quick way of enabling MSAA and fixes "Framebuffer incomplete" errors when using the OpenGL RHI backend Now the user can enable MSAA by using QSurfaceFormat: QSurfaceFormat f; f.setSamples(4); QSurfaceFormat::setDefaultFormat(f); MSAA is switched off by setting the sample number to 1. If the platform doesn't support MSAA, the internal SMAA postprocessing code path can still be used (but this code path comes with a slightly performance penalty); postprocessingMode: RiveQtQuickItem.SMAA // in QML --- examples/SimpleViewer/RiveInspectorView.qml | 2 +- examples/SimpleViewer/main.cpp | 6 +- src/RiveQtQuickItem/riveqsgrendernode.cpp | 2 - src/RiveQtQuickItem/riveqsgrhirendernode.cpp | 92 ++++++++++++++++++-- src/RiveQtQuickItem/riveqsgrhirendernode.h | 5 +- 5 files changed, 94 insertions(+), 13 deletions(-) diff --git a/examples/SimpleViewer/RiveInspectorView.qml b/examples/SimpleViewer/RiveInspectorView.qml index 90c170e..af611d1 100644 --- a/examples/SimpleViewer/RiveInspectorView.qml +++ b/examples/SimpleViewer/RiveInspectorView.qml @@ -154,7 +154,7 @@ Item { currentStateMachineIndex: -1 renderQuality: RiveQtQuickItem.Medium - postprocessingMode: RiveQtQuickItem.SMAA + postprocessingMode: RiveQtQuickItem.None fillMode: RiveQtQuickItem.PreserveAspectFit onStateMachineStringInterfaceChanged: { diff --git a/examples/SimpleViewer/main.cpp b/examples/SimpleViewer/main.cpp index 2849851..a70759a 100644 --- a/examples/SimpleViewer/main.cpp +++ b/examples/SimpleViewer/main.cpp @@ -12,9 +12,13 @@ int main(int argc, char *argv[]) { + QSurfaceFormat f; + f.setSamples(4); + QSurfaceFormat::setDefaultFormat(f); + #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // Force OpenGL - // QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGLRhi); + QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); #endif #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) diff --git a/src/RiveQtQuickItem/riveqsgrendernode.cpp b/src/RiveQtQuickItem/riveqsgrendernode.cpp index 47dfa80..513cd11 100644 --- a/src/RiveQtQuickItem/riveqsgrendernode.cpp +++ b/src/RiveQtQuickItem/riveqsgrendernode.cpp @@ -6,8 +6,6 @@ #include "riveqsgrendernode.h" #include "riveqtquickitem.h" -const int RiveQSGBaseNode::MULTISAMPLE_COUNT = 4; - QRectF RiveQSGRenderNode::rect() const { return m_rect; diff --git a/src/RiveQtQuickItem/riveqsgrhirendernode.cpp b/src/RiveQtQuickItem/riveqsgrhirendernode.cpp index e003f9e..97d53a9 100644 --- a/src/RiveQtQuickItem/riveqsgrhirendernode.cpp +++ b/src/RiveQtQuickItem/riveqsgrhirendernode.cpp @@ -6,7 +6,12 @@ #include #include +#include +#include +#include + #include +#include #include #include "riveqsgrhirendernode.h" @@ -325,6 +330,23 @@ bool RiveQSGRHIRenderNode::isCurrentRenderBufferA() return m_currentRenderSurface == &m_renderSurfaceA; } +#ifdef OPENGL_DEBUG +void GLAPIENTRY + MessageCallback( GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam ) +{ + fprintf( stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", + ( type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "" ), + type, severity, message ); +} +#endif + + void RiveQSGRHIRenderNode::prepare() { if (!m_window) { @@ -338,17 +360,31 @@ void RiveQSGRHIRenderNode::prepare() Q_ASSERT(swapChain); Q_ASSERT(rhi); +#ifdef OPENGL_DEBUG + if (rhi->backend() == QRhi::OpenGLES2) { + const QRhiGles2NativeHandles* native = static_cast(rhi->nativeHandles()); + const auto context = native->context; + if (context) { + const auto gl = context->functions(); + const auto extra = context->extraFunctions(); + gl->glEnable ( GL_DEBUG_OUTPUT ); + extra->glDebugMessageCallback( MessageCallback, 0 ); + } + } +#endif + QRhiCommandBuffer *commandBuffer = swapChain->currentFrameCommandBuffer(); if (!m_stencilClippingBuffer) { - m_stencilClippingBuffer = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, QSize(m_rect.width(), m_rect.height()), 1); + m_stencilClippingBuffer = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, QSize(m_rect.width(), m_rect.height()), m_sampleCount); m_stencilClippingBuffer->create(); m_cleanupList.append(m_stencilClippingBuffer); } - bool textureCreated = m_renderSurfaceA.create(rhi, QSize(m_rect.width(), m_rect.height()), m_stencilClippingBuffer); - m_renderSurfaceB.create(rhi, QSize(m_rect.width(), m_rect.height()), m_stencilClippingBuffer); - m_renderSurfaceIntern.create(rhi, QSize(m_rect.width(), m_rect.height()), m_stencilClippingBuffer, {}); + m_sampleCount = swapChain->sampleCount(); + bool textureCreated = m_renderSurfaceA.create(rhi, m_sampleCount, QSize(m_rect.width(), m_rect.height()), m_stencilClippingBuffer); + m_renderSurfaceB.create(rhi, m_sampleCount, QSize(m_rect.width(), m_rect.height()), m_stencilClippingBuffer); + m_renderSurfaceIntern.create(rhi, m_sampleCount, QSize(m_rect.width(), m_rect.height()), m_stencilClippingBuffer, {}); // only set the renderSurface to A in case we created a new texture if (textureCreated) { @@ -539,7 +575,9 @@ void RiveQSGRHIRenderNode::prepare() artboardInstance->draw(m_renderer); if (!m_cleanUpTextureTarget) { - QRhiColorAttachment colorAttachment(m_renderSurfaceA.texture); + QRhiColorAttachment colorAttachment(m_renderSurfaceA.buffer); + colorAttachment.setResolveTexture(m_renderSurfaceA.texture); + QRhiTextureRenderTargetDescription desc(colorAttachment); m_cleanUpTextureTarget = rhi->newTextureRenderTarget(desc); QRhiRenderPassDescriptor *renderPassDescriptor = m_cleanUpTextureTarget->newCompatibleRenderPassDescriptor(); @@ -800,6 +838,12 @@ QRhiGraphicsPipeline *RiveQSGRHIRenderNode::createBlendPipeline(QRhi *rhi, QRhiR void RiveQSGRHIRenderNode::RenderSurface::cleanUp() { + if (buffer) { + buffer->destroy(); + buffer->deleteLater(); + buffer = nullptr; + } + if (texture) { texture->destroy(); texture->deleteLater(); @@ -827,17 +871,25 @@ void RiveQSGRHIRenderNode::RenderSurface::cleanUp() } } -bool RiveQSGRHIRenderNode::RenderSurface::create(QRhi *rhi, const QSize &surfaceSize, QRhiRenderBuffer *stencilClippingBuffer, +bool RiveQSGRHIRenderNode::RenderSurface::create(QRhi *rhi, int samples, const QSize &surfaceSize, QRhiRenderBuffer *stencilClippingBuffer, QRhiTextureRenderTarget::Flags flags) { bool textureCreated = false; + + if (!buffer) { + buffer = rhi->newRenderBuffer(QRhiRenderBuffer::Color, surfaceSize, samples); + buffer->create(); + } + if (!texture) { texture = rhi->newTexture(QRhiTexture::RGBA8, surfaceSize, 1, QRhiTexture::RenderTarget); texture->create(); textureCreated = true; } if (!target) { - QRhiColorAttachment colorAttachment(texture); + QRhiColorAttachment colorAttachment(buffer); + colorAttachment.setResolveTexture(texture); + QRhiTextureRenderTargetDescription textureTargetDesc(colorAttachment); textureTargetDesc.setDepthStencilBuffer(stencilClippingBuffer); target = rhi->newTextureRenderTarget(textureTargetDesc, flags); @@ -846,12 +898,36 @@ bool RiveQSGRHIRenderNode::RenderSurface::create(QRhi *rhi, const QSize &surface target->create(); } if (!blendTarget) { - QRhiColorAttachment colorAttachment(texture); + QRhiColorAttachment colorAttachment(buffer); + colorAttachment.setResolveTexture(texture); + QRhiTextureRenderTargetDescription textureTargetDesc(colorAttachment); blendTarget = rhi->newTextureRenderTarget(textureTargetDesc); blendDesc = blendTarget->newCompatibleRenderPassDescriptor(); blendTarget->setRenderPassDescriptor(blendDesc); blendTarget->create(); } + +#ifdef OPENGL_DEBUG + if (textureCreated && rhi->backend() == QRhi::OpenGLES2) { + const QRhiGles2NativeHandles* native = static_cast(rhi->nativeHandles()); + const auto context = native->context; + if (context) { + const auto format = context->format(); + qDebug() << "QSurfaceFormat is" << format; + qDebug() << "is supported: QRhi::MultisampleTexture" << rhi->isFeatureSupported(QRhi::MultisampleTexture); + qDebug() << "is supported: QRhi::MultisampleRenderBuffer" << rhi->isFeatureSupported(QRhi::MultisampleRenderBuffer); + const auto gl = context->functions(); + GLenum err; + while((err = glGetError()) != GL_NO_ERROR) + { + qDebug() << "Got an OpenGL error:" << err; + + } + } + + } +#endif + return textureCreated; } diff --git a/src/RiveQtQuickItem/riveqsgrhirendernode.h b/src/RiveQtQuickItem/riveqsgrhirendernode.h index dba561a..8e80e84 100644 --- a/src/RiveQtQuickItem/riveqsgrhirendernode.h +++ b/src/RiveQtQuickItem/riveqsgrhirendernode.h @@ -68,6 +68,7 @@ class RiveQSGRHIRenderNode : public RiveQSGRenderNode protected: struct RenderSurface { + QRhiRenderBuffer *buffer { nullptr }; QRhiTexture *texture { nullptr }; QRhiRenderPassDescriptor *desc { nullptr }; QRhiRenderPassDescriptor *blendDesc { nullptr }; @@ -81,7 +82,7 @@ class RiveQSGRHIRenderNode : public RiveQSGRenderNode bool valid() { return texture != nullptr; } // this creates all resources needed to have a texture to draw on - bool create(QRhi *rhi, const QSize &surfaceSize, QRhiRenderBuffer *stencilClippingBuffer, + bool create(QRhi *rhi, int samples, const QSize &surfaceSize, QRhiRenderBuffer *stencilClippingBuffer, QRhiTextureRenderTarget::Flags flags = QRhiTextureRenderTarget::PreserveColorContents); }; @@ -158,6 +159,8 @@ class RiveQSGRHIRenderNode : public RiveQSGRenderNode PostprocessingSMAA *m_postprocessing { nullptr }; + int m_sampleCount { 1 }; + private: QRhiGraphicsPipeline *createBlendPipeline(QRhi *rhi, QRhiRenderPassDescriptor *renderPass, QRhiShaderResourceBindings *bindings); QRhiGraphicsPipeline *createClipPipeline(QRhi *rhi, QRhiRenderPassDescriptor *renderPassDescriptor,