diff --git a/cmake/dependencies/crashpad.cmake b/cmake/dependencies/crashpad.cmake
index e4b3992a8..83d14e2ca 100644
--- a/cmake/dependencies/crashpad.cmake
+++ b/cmake/dependencies/crashpad.cmake
@@ -72,6 +72,16 @@ SET(_crashpad_handler
LIST(APPEND _configure_options "-DCMAKE_POSITION_INDEPENDENT_CODE=ON")
LIST(APPEND _configure_options "-DCRASHPAD_BUILD_TESTS=OFF")
+# Pin the MSVC toolset to match the parent build. Without this, cmake auto-selects the newest installed toolset (e.g. 14.44) while the main build uses a pinned
+# version (e.g. 14.40). The mismatch causes LNK2019 for __std_find_end_1 and similar CRT symbols added only in newer toolsets. Only applies to Visual Studio
+# generators; the crashpad build dir must be clean.
+IF(CMAKE_GENERATOR MATCHES "Visual Studio"
+ AND CMAKE_GENERATOR_TOOLSET
+)
+ LIST(APPEND _configure_options "-T")
+ LIST(APPEND _configure_options "${CMAKE_GENERATOR_TOOLSET}")
+ENDIF()
+
# Disable deprecation warnings on macOS — mini_chromium uses deprecated Security APIs
IF(RV_TARGET_DARWIN)
LIST(APPEND _configure_options "-DCMAKE_CXX_FLAGS=-Wno-error=deprecated-declarations -Wno-deprecated-declarations")
diff --git a/docs/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-four.md b/docs/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-four.md
index 72cdfebe5..7680c069d 100644
--- a/docs/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-four.md
+++ b/docs/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-four.md
@@ -236,7 +236,9 @@ import rv.qtutils
# Gets the current RV session windows as a PySide QMainWindow.
rvSessionWindow = rv.qtutils.sessionWindow()
-# Gets the current RV session GL view as a PySide QGLWidget.
+# Gets the current RV session view host as a PySide QWidget. Note: the GL
+# viewport is now a native child window embedded in this host (via
+# createWindowContainer), so this is a plain QWidget, not a QOpenGLWidget.
rvSessionGLView = rv.qtutils.sessionGLView()
# Gets the current RV session top tool bar as a PySide QToolBar.
diff --git a/src/bin/apps/rv/main.cpp b/src/bin/apps/rv/main.cpp
index 32b10b467..448310496 100644
--- a/src/bin/apps/rv/main.cpp
+++ b/src/bin/apps/rv/main.cpp
@@ -353,11 +353,6 @@ int utf8Main(int argc, char* argv[])
TwkFB::ThreadPool::initialize();
- // Qt 5.12.1 specific
- // Disable Qt Quick hardware rendering because QwebEngineView conflicts with
- // QGLWidget
- setEnvVar("QT_QUICK_BACKEND", "software");
-
#if defined(PLATFORM_LINUX)
// Work around for Wacom Tablet issue on linux
// Note: This is a Qt 5.12.4 regression
@@ -369,6 +364,14 @@ int utf8Main(int argc, char* argv[])
// (RV Preferences/Rendering/Multithread GPU Upload)
QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
+ // Share GL resources across every QOpenGLContext in the process. This is a
+ // documented QtWebEngine requirement, and it lets RV's auxiliary GL
+ // surfaces -- the second-output ScreenView and the multithreaded-upload
+ // worker device -- share textures/FBOs with the main viewport context
+ // without an explicit, ordering-sensitive setShareContext() call. Must be
+ // set before the QApplication is constructed.
+ QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
+
TwkUtil::MemPool::initialize();
string altPrefsPath;
diff --git a/src/bin/nsapps/RV/main.cpp b/src/bin/nsapps/RV/main.cpp
index 9ac24f5b9..c276951cc 100644
--- a/src/bin/nsapps/RV/main.cpp
+++ b/src/bin/nsapps/RV/main.cpp
@@ -284,9 +284,34 @@ int main(int argc, char* argv[])
setPlatformSpecificLocale();
- // Qt 5.12.1 specific
- // Disable Qt Quick hardware rendering because QwebEngineView conflicts with
- // QGLWidget
+ //
+ // Keep Qt Quick on the software backend. Do not remove this on macOS.
+ //
+ // It was originally added because QWebEngineView conflicted with the
+ // QGLWidget viewport, and src/bin/apps/rv/main.cpp has since dropped it for
+ // Windows and Linux -- but on macOS it is now load bearing for a different
+ // reason. The viewport is a native QOpenGLWindow embedded via
+ // QWidget::createWindowContainer, which makes it a QObject child of the top
+ // level window's QWidgetWindow. With the hardware Qt Quick backend, adding a
+ // QWebEngineView to the widget tree makes Qt destroy and recreate that
+ // native subtree; the viewport window is deleted with it rather than
+ // reparented, and QWindowContainer then dereferences the window it just
+ // lost. RV segfaults inside Qt, before it can react.
+ //
+ // Reproducible in four lines from any docked panel -- with the software
+ // backend this survives, with the hardware backend it crashes:
+ //
+ // w = rv.qtutils.sessionWindow()
+ // d = QtWidgets.QDockWidget('t', w)
+ // v = QtWebEngineWidgets.QWebEngineView(d)
+ // d.setWidget(v); w.addDockWidget(QtCore.Qt.RightDockWidgetArea, d)
+ // v.setHtml('hi')
+ //
+ // The cost of keeping it is that web panels composite in software on macOS,
+ // so macOS gets the cheap-viewport-repaint half of SG-43585 but not the
+ // hardware web panel half. Removing it needs the viewport to stop being
+ // owned by the top level window's native subtree, not just a comment change.
+ //
setenv("QT_QUICK_BACKEND", "software", 0 /* changeFlag : Do not change the existing value */);
// Prevent usage of native sibling widgets on Mac. This attribute can be
diff --git a/src/lib/app/RvCommon/CMakeLists.txt b/src/lib/app/RvCommon/CMakeLists.txt
index 64e324549..2c525727f 100644
--- a/src/lib/app/RvCommon/CMakeLists.txt
+++ b/src/lib/app/RvCommon/CMakeLists.txt
@@ -48,6 +48,7 @@ SET(_sources
RvApplication.cpp
DiagnosticsView.cpp
GLView.cpp
+ GLWindow.cpp
TwkQTAction.cpp
MediaDirModel.cpp
RvNetworkDialog.cpp
diff --git a/src/lib/app/RvCommon/DesktopVideoDevice.cpp b/src/lib/app/RvCommon/DesktopVideoDevice.cpp
index 2e2740c5c..3b159e710 100644
--- a/src/lib/app/RvCommon/DesktopVideoDevice.cpp
+++ b/src/lib/app/RvCommon/DesktopVideoDevice.cpp
@@ -157,10 +157,10 @@ namespace Rv
{
TWK_GLDEBUG;
- QSurfaceFormat fmt = shareDevice()->widget()->format();
+ QSurfaceFormat fmt = shareDevice()->glSurfaceFormat();
fmt.setSwapInterval(m_vsync ? 1 : 0);
- ScreenView* vw = new ScreenView(fmt, 0, shareDevice()->widget(), Qt::Window);
+ ScreenView* vw = new ScreenView(fmt, 0, shareDevice()->glShareContext(), Qt::Window);
setViewWidget(vw);
QTGLVideoDevice* vd = new QTGLVideoDevice(0, "local view", vw);
@@ -747,11 +747,11 @@ namespace Rv
void DesktopVideoDevice::sortVideoFormatsByWidth() { sort(m_videoFormats.begin(), m_videoFormats.end(), widthSort); }
- DesktopVideoDevice::ScreenView::ScreenView(const QSurfaceFormat& fmt, QWidget* parent, QOpenGLWidget* glViewShare,
+ DesktopVideoDevice::ScreenView::ScreenView(const QSurfaceFormat& fmt, QWidget* parent, QOpenGLContext* glShareContext,
Qt::WindowFlags flags)
: QOpenGLWidget(parent, flags)
{
- m_glViewShare = glViewShare;
+ m_glShareContext = glShareContext;
setFormat(fmt);
// Important: set PartialUpdate, because otherwise
@@ -764,9 +764,9 @@ namespace Rv
{
QOpenGLWidget::initializeGL();
- if (m_glViewShare && context() && context()->isValid())
+ if (m_glShareContext && context() && context()->isValid())
{
- context()->setShareContext(m_glViewShare->context());
+ context()->setShareContext(m_glShareContext);
}
}
diff --git a/src/lib/app/RvCommon/GLView.cpp b/src/lib/app/RvCommon/GLView.cpp
index 617f29581..dc9aec0d4 100644
--- a/src/lib/app/RvCommon/GLView.cpp
+++ b/src/lib/app/RvCommon/GLView.cpp
@@ -13,189 +13,106 @@
#endif
#include
+#include
#include
-#include
-#include
#include
#include
+#include
+#include
+#include
#include
-#include
-#include
-#include
-#include
-
-#include
+#include
namespace Rv
{
- using namespace boost;
using namespace std;
- using namespace TwkApp;
- using namespace IPCore;
-
- namespace
- {
-
- class SyncBufferThreadData
- {
- public:
- explicit SyncBufferThreadData(const VideoDevice* device)
- : m_device(device)
- , m_done(false)
- , m_doSync(false)
- , m_running(false)
- , m_mutex()
- , m_cond()
- {
- }
-
- void run()
- {
- while (!m_done)
- {
- boost::mutex::scoped_lock lock(m_mutex);
- m_running = true;
- m_doSync = false;
-
- m_cond.wait(lock);
-
- if (m_device && m_doSync && !m_done)
- m_device->syncBuffers();
-
- m_doSync = false;
- }
- }
-
- void notify(bool finish = false)
- {
- {
- boost::mutex::scoped_lock lock(m_mutex);
- if (finish)
- m_done = true;
- else
- m_doSync = true;
- if (!m_running)
- return;
- }
-
- m_cond.notify_one();
- }
-
- const VideoDevice* device() const { return m_device; }
-
- void setDevice(const VideoDevice* d) { m_device = d; }
-
- private:
- SyncBufferThreadData(SyncBufferThreadData&) {}
-
- void operator=(SyncBufferThreadData&) {}
-
- private:
- bool m_done;
- bool m_doSync;
- bool m_running;
- boost::mutex m_mutex;
- boost::condition_variable m_cond;
- const VideoDevice* m_device;
- };
-
- class ThreadTrampoline
- {
- public:
- ThreadTrampoline(GLView* view)
- : m_view(view)
- {
- }
-
- void operator()()
- {
- SyncBufferThreadData* closure = reinterpret_cast(m_view->syncClosure());
- closure->run();
- }
-
- private:
- GLView* m_view;
- };
-
- } // namespace
GLView::GLView(QWidget* parent, QOpenGLContext* sharedContext, RvDocument* doc, bool stereo, bool vsync, bool doubleBuffer, int red,
int green, int blue, int alpha, bool noResize)
- : QOpenGLWidget(parent)
- , m_sharedContext(sharedContext)
+ : QWidget(parent)
, m_doc(doc)
- , m_red(red)
- , m_green(green)
- , m_blue(blue)
- , m_alpha(alpha)
- , m_lastKey(0)
- , m_lastKeyType(QEvent::None)
- , m_userActive(true)
- , m_renderCount(0)
- , m_firstPaintCompleted(false)
+ , m_container(0)
+ , m_videoDevice(0)
, m_csize(1024, 576)
, m_msize(128, 128)
- , m_postFirstNonEmptyRender(noResize)
- , m_stopProcessingEvents(false)
- , m_syncThreadData(0)
+ , m_sharedContext(sharedContext)
{
- setFormat(rvGLFormat(stereo, vsync, doubleBuffer, red, green, blue, alpha));
+ //
+ // Native GL viewport window (renders + presents on its own surface).
+ //
+ m_glWindow = new GLWindow(sharedContext, doc, stereo, vsync, doubleBuffer, red, green, blue, alpha, noResize);
+
+ //
+ // Embed the native window in the widget tree. The container is a
+ // normal QWidget; there is no QOpenGLWidget in the window, so the
+ // top-level window is not forced onto the OpenGL RHI backend.
+ //
+ m_container = QWidget::createWindowContainer(m_glWindow, this);
+ m_container->setFocusPolicy(Qt::StrongFocus);
+
+ //
+ // Create the native platform surface up-front. Unlike a QOpenGLWidget
+ // (which renders offscreen to an FBO), a QOpenGLWindow has no GL
+ // context until its window surface exists; RV performs GL setup
+ // (makeCurrent + capability queries) during startup before the window
+ // is shown, so the surface must exist by then.
+ //
+ m_glWindow->create();
+ QVBoxLayout* layout = new QVBoxLayout(this);
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->setSpacing(0);
+ layout->addWidget(m_container);
+
+ //
+ // The device drives the GL surface (the window) for rendering, and
+ // uses the container QWidget for event / coordinate translation
+ // (height-based y-flip, mapToGlobal, mouse grab).
+ //
ostringstream str;
str << UI_APPLICATION_NAME " Main Window" << "/" << m_doc;
- m_videoDevice = new QTGLVideoDevice(0, str.str(), this);
+ m_videoDevice = new QTGLVideoDevice(0, str.str(), m_glWindow, m_container);
+ m_glWindow->setVideoDevice(m_videoDevice);
setObjectName((m_doc->session()) ? m_doc->session()->name().c_str() : "no session");
+ setFocusProxy(m_container);
+ }
- m_activityTimer.start();
- setMouseTracking(true);
- setAcceptDrops(true);
- setFocusPolicy(Qt::StrongFocus);
+ GLView::~GLView() { delete m_videoDevice; }
- m_eventProcessingTimer.setSingleShot(true);
- connect(&m_eventProcessingTimer, SIGNAL(timeout()), this, SLOT(eventProcessingTimeout()));
- }
+ QOpenGLContext* GLView::context() const { return m_glWindow->context(); }
- GLView::~GLView()
+ QSurfaceFormat GLView::format() const { return m_glWindow->format(); }
+
+ void GLView::makeCurrent()
{
- // delete m_frameBuffer;
- delete m_videoDevice;
+ // Route through the device, which guards on context validity. Unlike a
+ // QOpenGLWidget (which can render to its FBO before being shown), a
+ // QOpenGLWindow has no GL context until it is created/exposed, so a
+ // direct makeCurrent() here can deref a null context during startup.
+ if (m_videoDevice)
+ m_videoDevice->makeCurrent();
+ }
- if (m_syncThreadData)
- {
- SyncBufferThreadData* closure = reinterpret_cast(m_syncThreadData);
- closure->notify(true);
- m_swapThread.join();
+ bool GLView::isValid() const { return m_glWindow != nullptr; }
- delete closure;
- }
- }
+ void GLView::absolutePosition(int& x, int& y) const { m_glWindow->absolutePosition(x, y); }
+
+ void GLView::stopProcessingEvents() { m_glWindow->stopProcessingEvents(); }
- void GLView::stopProcessingEvents() { m_stopProcessingEvents = true; }
+ bool GLView::firstPaintCompleted() const { return m_glWindow->firstPaintCompleted(); }
QSize GLView::sizeHint() const { return m_csize; }
QSize GLView::minimumSizeHint() const { return m_msize; }
- void GLView::absolutePosition(int& x, int& y) const
- {
- x = 0;
- y = 0;
-
- QPoint p(0, 0);
- QPoint gp = mapToGlobal(p);
+ QImage GLView::readPixels(int x, int y, int w, int h) { return m_glWindow->readPixels(x, y, w, h); }
- x = gp.x();
- y = gp.y();
- }
+ float GLView::devicePixelRatio() const { return videoDevice() ? videoDevice()->devicePixelRatio() : 1.0f; }
QSurfaceFormat GLView::rvGLFormat(bool stereo, bool vsync, bool doubleBuffer, int red, int green, int blue, int alpha)
{
- const Rv::Options& opts = Rv::Options::sharedOptions();
-
// NOTE_QT6: QGLFormat into QSurfaceFormat
- // NOTE_QT6: setStencil, setDepth does not exist anymore. Trying to use
- // setDepthBufferSize and setStencilBufferSize.
QSurfaceFormat fmt;
fmt.setDepthBufferSize(24);
fmt.setSwapBehavior(doubleBuffer ? QSurfaceFormat::DoubleBuffer : QSurfaceFormat::SingleBuffer);
@@ -208,9 +125,6 @@ namespace Rv
fmt.setMajorVersion(2);
fmt.setMinorVersion(1);
- // fmt.setProfile(QSurfaceFormat::CoreProfile);
- // fmt.setProfile(QSurfaceFormat::CompatibilityProfile);
-
//
// The default value for these buffer sizes is -1, but it is
// illegal to set to that value so test for positive red, not
@@ -232,603 +146,4 @@ namespace Rv
return fmt;
}
- void GLView::initializeGL()
- {
- //
- // At this point the format is known. Can't do this in the constructor
- //
-
- // QUESTION_QT6: Should we use isValid from QOpenGLWidget or directly
- // using from QOpenGLContext? NOTE_QT6: Returns true if the widget and
- // OpenGL resources, like the context, have been successfully
- // initialized.
- // Note that the return value is always false until the widget
- // is shown.
- // NOTE_QT6: QOpenGLContext: Returns if this context is valid, i.e. has
- // been successfully created.
- if (context()->isValid())
- {
- initializeGLExtensions();
- initializeOpenGLFunctions();
-
- if (m_sharedContext)
- {
- context()->setShareContext(m_sharedContext);
- }
-
- if (m_doc)
- {
- m_doc->initializeSession();
- }
-
- // NOTE_QT6: QGLFormat is deprecated. Using QSurfaceFormat now.
- QSurfaceFormat f = context()->format();
-
-#ifndef PLATFORM_DARWIN
- //
- // Doesn't work on OS X
- //
- if (f.redBufferSize() != m_red && m_red != 0)
- {
- // QMessageBox box(this);
- // box.setWindowTitle(tr("Ouput Display Format"));
-
- ostringstream str;
-
- str << "WARNING: asked for"
- << " " << m_red << " " << m_green << " " << m_blue << " " << m_alpha << " RGBA color but got"
- << " " << f.redBufferSize() << " " << f.greenBufferSize() << " " << f.blueBufferSize() << " "
- << (f.alphaBufferSize() <= 0 ? 0 : f.alphaBufferSize()) << " RGBA instead";
-
- cout << str.str() << endl;
-
- // box.setText(str.str().c_str());
- // box.setDetailedText("You can change the default display color
- // depth and target "
- // "from the preferences under
- // Rendering->Display Output Format.\n"
- // "Choosing \"OpenGL Default Format\" will
- // tell RV to ask for the " "default
- // prefered format for this display.");
- // box.setWindowModality(Qt::WindowModal);
- // QPushButton* b1 = box.addButton(tr("Continue"),
- // QMessageBox::AcceptRole); box.setIcon(QMessageBox::Critical);
- // box.exec();
- }
-#endif
- if (f.stencilBufferSize() == 0)
- {
- cout << "WARNING: no stencil buffer available" << endl;
- }
- }
- else
- {
- cout << "WARNING: invalid GL context" << endl;
- }
- }
-
- void GLView::resizeGL(int w, int h)
- {
- if (m_doc)
- m_doc->viewSizeChanged(w, h);
-#ifdef PLATFORM_WINDOWS
- SetWindowRgn(reinterpret_cast(this->winId()), 0, false);
-#endif
- }
-
- bool GLView::validateReadPixels(int x, int y, int w, int h)
- {
- int r = x + w;
- int t = y + h;
-
- // are the extents of the read region out of bounds?
- if (x < 0 || y < 0 || r > width() * devicePixelRatio() || t > height() * devicePixelRatio())
- return false;
-
- return true;
- }
-
- QImage GLView::readPixels(int x, int y, int w, int h)
- {
- // If out of bounds, return an empty image.
- if (validateReadPixels(x, y, w, h) == false)
- {
- QImage image(0, 0, QImage::Format_RGBA8888);
- return image;
- }
-
- makeCurrent();
-
- QImage image(w, h, QImage::Format_RGBA8888);
- glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.bits());
-
- return image;
- }
-
- void GLView::debugSaveFramebuffer()
- {
- // Create a QImage with the same size as the FBO
- QImage image(width(), height(), QImage::Format_RGBA8888);
- glReadPixels(0, 0, width(), height(), GL_RGBA, GL_UNSIGNED_BYTE, image.bits());
-
- // image.save("/home/>//fbo.png");
- }
-
- void GLView::paintGL()
- {
- TWK_GLDEBUG;
-
- IPCore::Session* session = m_doc->session();
- bool debug = IPCore::debugProfile && session;
-
- if (!m_postFirstNonEmptyRender && session && session->postFirstNonEmptyRender())
- {
- m_postFirstNonEmptyRender = true;
-
- if (!session->isFullScreen())
- {
- m_doc->resizeToFit(false, false);
- m_doc->center();
- TWK_GLDEBUG;
- }
- }
-
- if (debug)
- {
- Session::ProfilingRecord& trecord = session->beginProfilingSample();
- trecord.renderStart = session->profilingElapsedTime();
- }
-
- // should be no longer necessary, moved it to resize()
-#if 0
- //
- // This is necessary to stop the Windows Desktop Window Manager
- // (new in Vista/win7) fromc caching portions of rv's glview
- // and holding them in the display even when rv redraws. the
- // effect being that parts of previous displays will be "left
- // behind" and not updated even when rv plays. especially when
- // going to/from fullscreen.
- //
-#ifdef PLATFORM_WINDOWS
- SetWindowRgn (this->winId(), 0, false);
-#endif
-#endif
-
- if (m_doc && session && m_videoDevice)
- {
- // m_frameBuffer->makeCurrent();
- TWK_GLDEBUG;
- m_videoDevice->makeCurrent();
- TWK_GLDEBUG;
-
- if (m_userActive && m_activityTimer.elapsed() > 1.0)
- {
- if (m_doc->mainPopup() && !m_doc->mainPopup()->isVisible() && hasFocus())
- {
- TwkApp::ActivityChangeEvent aevent("user-inactive", m_videoDevice);
- m_videoDevice->sendEvent(aevent);
- TWK_GLDEBUG;
- m_userActive = false;
- }
- }
-
- //
- // Make sure the video device knows where it is on screen.
- //
- int x = 0, y = 0;
- absolutePosition(x, y);
- m_videoDevice->setAbsolutePosition(x, y);
-
- TWK_GLDEBUG;
- session->render();
- TWK_GLDEBUG;
-
- m_firstPaintCompleted = true;
-
- // Starting with Qt 5.12.1, the resulting texture is later
- // composited over white which creates undesirable artifacts. As a
- // work around, we set the resulting alpha channel to 1 to make sure
- // that the resulting texture is fully opaque.
-
- // NOTE_QT: That code seems to fix an issue on MacOS only. (Not on
- // Linux, not test on Windows)
- // The issue I see on MacOS that the background is brown
- // istead of black with the default background.
- //
- // Even on Qt6/QOpenGLWidget, we need to call
- // glBindFramebuffer(); it otherwise complains and fails
- // on glClear() on macOS
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, QOpenGLContext::currentContext()->defaultFramebufferObject());
- TWK_GLDEBUG;
-
- glPushAttrib(GL_COLOR_BUFFER_BIT);
- TWK_GLDEBUG;
- glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
- TWK_GLDEBUG;
- glClearColor(0.f, 0.f, 0.f, 1.0f);
- TWK_GLDEBUG;
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- TWK_GLDEBUG;
- glPopAttrib();
- TWK_GLDEBUG;
- }
- else
- {
- glClearColor(0.f, 0.f, 0.f, 1.0f);
- TWK_GLDEBUG;
- glClear(GL_COLOR_BUFFER_BIT);
- TWK_GLDEBUG;
- }
-
- if (m_stopProcessingEvents)
- return;
-
- //
- // We're done with drawing and about to (possibly) wait for
- // vsync before the buffer swaps, but if the driver is layzy,
- // it may not performs some requested gl actions until after
- // the wait on vsync, in which case we get tearing.
- //
- // A glFlush here should make these gl actions happen during
- // the wait.
- //
- // This could be a problem if further drawing is done to this
- // buffer by Qt, for example if it's doing it's graphics area
- // widget drawing. Seems fine for now.
- //
- // Update: We want to actually wait until the gfx operations are
- // complete. glFlush just flushes the command buffer to
- // hardware, glFinish blocks until the hardware is finished
- // processing the commands.
- //
-
- // DONT
- // glFlush();
- // glFinish();
-
- //
- // Do the swap, the vsync code is in the swapBuffers() code in
- // Qt. It should block here waiting for the refresh. The debug
- // code here is accumulating profiling information in the session
- // ProfilingRecord struct. This can be dumped on exit to figure out
- // what's going on.
- //
-
- // Note for Qt6 . QOpenGLWidget implementation:
- //
- // With the new QOpenGLWidget (Qt6 branch), all of the rendering of
- // each widget is done in its own FBOs (aka: off-screen buffers), as
- // opposed to the old Qt5/QGLWidget branch where the rendering was
- // done in each GGLWidget's backbuffer (aka: GL_BACK, an on-screen
- // framebuffer surface).
- //
- // With QOpenGLWidget, it is now the WainWindow's responsibility
- // (more technically, the MainWindow's rendering backend, which
- // happens to be Qt's OpenGL rendering backend when QOpenGLWidget are
- // present in the children tree) to gather and composite all of the
- // off-screen buffers (regardless of which image type they are, eg:
- // cpu-memory images, gpu/opengl images, etc) and finally to call
- // swapBuffers to show the final contents of the mainWindow.
- //
- // As a result, I'm not sure there's a point -- at all --
- // in calling swapBuffers on the GLView anywhere amnymore. From old
- // comments in the previous version of this tile, it appears that
- // calling swapBuffers was done to force a quicker visual update, or
- // to minimize visual tearing of some sort. This would have worked
- // with the old QGLWidget (becayuse each QGLWidget had its own
- // context, and its rendering target was directly the GL_BACK
- // framebuffer, but, again, with QOpenGLWidget, the rendering target
- // is no longer GL_BACK, it is an FBO that is meant to be used at
- // the end of the application's drawing / visual update pipeline.
-
- if (debug)
- {
- Session::ProfilingRecord& trecord = session->currentProfilingSample();
- trecord.renderEnd = session->profilingElapsedTime();
- trecord.swapStart = trecord.renderEnd;
-
- if (session->outputVideoDevice() != videoDevice())
- {
- session->outputVideoDevice()->syncBuffers();
- }
- else
- {
- m_videoDevice->widget()->context()->swapBuffers(m_videoDevice->widget()->context()->surface());
- }
-
- trecord.swapEnd = session->profilingElapsedTime();
- session->endProfilingSample();
- }
- else
- {
- if (session->outputVideoDevice() != videoDevice())
- {
- session->outputVideoDevice()->syncBuffers();
- }
- }
-
- session->addSyncSample();
-
- session->postRender();
-
- m_eventProcessingTimer.start();
-
- TWK_GLDEBUG;
- }
-
- void GLView::eventProcessingTimeout() { m_doc->session()->userGenericEvent("per-render-event-processing", ""); }
-
- bool GLView::event(QEvent* event)
- {
- // qDebug() << "Event type: " << event->type();
-
- bool keyevent = false;
- Rv::Session* session = m_doc->session();
-
- if (m_stopProcessingEvents)
- {
- event->accept();
- return true;
- }
-
- //
- // We want to exclude the "click-through" clicks on
- // click-to-focus systems (like osX). Turns out the event
- // sequence in these cases is exactly the same as a
- // tab-to-focus, then click sequence. So the only way we have
- // to identify the "click-through" is by how quickly the click
- // (button push) follows the windowActivate event.
- //
-
- if (event->type() == QEvent::WindowActivate)
- m_activationTimer.start();
-
-#if 0
- //
- // This doesn't work. Nothing I've tried will make a stylus
- // press raise and activate the window when we are handling
- // native tablet events. It works fine when we are treating
- // stylus events as mouse events.
- //
- if (event->type() == QEvent::TabletPress && !isActiveWindow())
- {
- cerr << "activating window" << endl;
- QEvent activateEvent(QEvent::WindowActivate);
- //m_frameBuffer->translator().sendQTEvent(new QEvent(QEvent::WindowActivate));
- session->setEventVideoDevice(videoDevice());
- m_videoDevice->translator().sendQTEvent(new QEvent(QEvent::WindowActivate));
- event->accept();
- /*
- activateWindow();
- raise();
- event->accept();
- */
- return true;
- }
-#endif
-
- float activationTime = 0.0;
- if (m_activationTimer.isRunning())
- {
- if (event->type() == QEvent::MouseButtonPress)
- {
- //
- // Pass this time through with the event, so that
- // event handling code can decide witehr or not to ignore
- // this 'click-through' event.
- //
- activationTime = m_activationTimer.elapsed();
- m_activationTimer.stop();
- }
- if (event->type() == QEvent::MouseMove)
- m_activationTimer.stop();
- }
-
- if (event->type() != QEvent::Paint)
- {
- m_activityTimer.stop();
- m_activityTimer.start();
-
- if (!m_userActive)
- {
- TwkApp::ActivityChangeEvent aevent("user-active", m_videoDevice);
-
- //
- // m_userActive set first will prevent recursive nightmare. In
- // Qt 4.4 an event may recursively produce more
- // events. For example, changing the cursor in 4.4 will
- // cause a Paint event immediately (not after the
- // previous event returns).
- //
-
- m_userActive = true;
- m_videoDevice->sendEvent(aevent);
- }
- }
-
- if (QKeyEvent* kevent = dynamic_cast(event))
- {
- keyevent = true;
-
- //
- // Get around really annoying event bugs on Qt/Mac
- //
-
- if (m_lastKey == kevent->key()
- && (m_lastKeyType == QEvent::ShortcutOverride && (kevent->type() == QEvent::KeyPress) || (m_lastKeyType == kevent->type())))
- {
- //
- // Qt 4.3.3 (4.5 too) will give both override and press events
- // even if key is not in menu. Filter that here.
- //
- // Remember the new key/type, otherwise we filter out
- // any number of ShortcutOverride/Press pairs, and
- // auto-repeat doesn't work.
- //
- m_lastKey = kevent->key();
- m_lastKeyType = kevent->type();
-
- event->accept();
- return true;
- }
-
- m_lastKeyType = kevent->type();
- m_lastKey = kevent->key();
- }
-
- switch (event->type())
- {
- case QEvent::FocusIn:
- m_videoDevice->translator().resetModifiers();
- case QEvent::Enter:
- setFocus(Qt::MouseFocusReason);
- break;
- default:
- break;
- }
- if (event->type() == QEvent::Resize)
- {
- QResizeEvent* e = static_cast(event);
-
- // QT5 BUG -- results in invalid drawable
- if (!isVisible())
- return true;
-
- if (e->oldSize().width() != -1 && e->oldSize().height() != -1)
- {
- ostringstream contents;
- contents << e->oldSize().width() << " " << e->oldSize().height() << "|" << e->size().width() << " " << e->size().height();
-
- if (m_doc && session)
- {
- session->userGenericEvent("view-resized", contents.str());
- }
- }
- return QOpenGLWidget::event(event);
- }
-
- if (session && session->outputVideoDevice()
- && session->outputVideoDevice()->displayMode() == TwkApp::VideoDevice::MirrorDisplayMode)
- {
- if (const TwkApp::VideoDevice* cdv = session->controlVideoDevice())
- {
- const TwkApp::VideoDevice* odv = session->outputVideoDevice();
-
- if (odv && cdv != odv && cdv == videoDevice())
- {
- const float w = width();
- const float h = height();
- const float ow = odv->width();
- const float oh = odv->height();
-
- const float aspect = w / h;
- const float oaspect = ow / oh;
-
- m_videoDevice->translator().setRelativeDomain(ow, oh);
-
- if (aspect >= oaspect)
- {
- const float yscale = oh / h;
- const float yoffset = 0.0;
- const float xscale = yscale;
- const float xoffset = -(w * yscale - ow) / 2.0;
- m_videoDevice->translator().setScaleAndOffset(xoffset, yoffset, xscale, yscale);
- }
- else
- {
- const float xscale = ow / w;
- const float xoffset = 0.0;
- const float yscale = xscale;
- const float yoffset = -(xscale * h - oh) / 2.0;
- m_videoDevice->translator().setScaleAndOffset(xoffset, yoffset, xscale, yscale);
- }
- }
- else
- {
- m_videoDevice->translator().setScaleAndOffset(0, 0, 1.0, 1.0);
- m_videoDevice->translator().setRelativeDomain(width(), height());
- }
- }
- else
- {
- m_videoDevice->translator().setScaleAndOffset(0, 0, 1.0, 1.0);
- m_videoDevice->translator().setRelativeDomain(width(), height());
- }
- }
- else
- {
- m_videoDevice->translator().setScaleAndOffset(0, 0, 1.0, 1.0);
- m_videoDevice->translator().setRelativeDomain(width(), height());
- }
-
- if (session)
- session->setEventVideoDevice(videoDevice());
-
- if (m_videoDevice->translator().sendQTEvent(event, activationTime))
- {
- event->accept();
- return true;
- }
- else
- {
- bool result = QOpenGLWidget::event(event);
-
- return result;
- }
- }
-
- bool GLView::eventFilter(QObject* object, QEvent* event)
- {
- if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease || event->type() == QEvent::Shortcut
- || event->type() == QEvent::ShortcutOverride)
- {
-
- //
- // Get around really annoying event bugs on Qt/Mac
- // (copied from GLView::event... same bug applies -lo)
- //
- // Qt 4.3.3 (4.5 too) will give both override and press events even
- // if key is not in menu. Filter that here.
- //
- // Remember the new key/type, otherwise we filter out
- // any number of ShortcutOverride/Press pairs, and
- // auto-repeat doesn't work.
- //
-
- if (QKeyEvent* kevent = dynamic_cast(event))
- {
- if (m_lastKey == kevent->key()
- && (m_lastKeyType == QEvent::ShortcutOverride && (kevent->type() == QEvent::KeyPress)
- || (m_lastKeyType == kevent->type())))
- {
- m_lastKey = kevent->key();
- m_lastKeyType = kevent->type();
-
- event->accept();
- return true;
- }
-
- m_lastKeyType = kevent->type();
- m_lastKey = kevent->key();
- }
-
- // if (m_frameBuffer->translator().sendQTEvent(event))
- Session* session = m_doc->session();
- session->setEventVideoDevice(videoDevice());
- if (m_videoDevice->translator().sendQTEvent(event))
- {
-
- event->accept();
- return true;
- }
-
- event->accept();
- return true;
- }
-
- return false;
- }
-
- float GLView::devicePixelRatio() const { return videoDevice() ? videoDevice()->devicePixelRatio() : 1.0f; }
-
} // namespace Rv
diff --git a/src/lib/app/RvCommon/GLWindow.cpp b/src/lib/app/RvCommon/GLWindow.cpp
new file mode 100644
index 000000000..6e536d42f
--- /dev/null
+++ b/src/lib/app/RvCommon/GLWindow.cpp
@@ -0,0 +1,372 @@
+//******************************************************************************
+// Copyright (c) 2007 Tweak Inc.
+// All rights reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//******************************************************************************
+
+#ifdef PLATFORM_WINDOWS
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace Rv
+{
+ using namespace std;
+ using namespace TwkApp;
+ using namespace IPCore;
+
+ GLWindow::GLWindow(QOpenGLContext* sharedContext, RvDocument* doc, bool stereo, bool vsync, bool doubleBuffer, int red, int green,
+ int blue, int alpha, bool noResize)
+ : QOpenGLWindow(QOpenGLWindow::NoPartialUpdate)
+ , m_doc(doc)
+ , m_red(red)
+ , m_green(green)
+ , m_blue(blue)
+ , m_alpha(alpha)
+ , m_lastKey(0)
+ , m_lastKeyType(QEvent::None)
+ , m_userActive(true)
+ , m_firstPaintCompleted(false)
+ , m_postFirstNonEmptyRender(noResize)
+ , m_stopProcessingEvents(false)
+ , m_sharedContext(sharedContext)
+ {
+ setFormat(GLView::rvGLFormat(stereo, vsync, doubleBuffer, red, green, blue, alpha));
+
+ m_videoDevice = nullptr; // set later by the hosting GLView
+
+ m_activityTimer.start();
+
+ m_eventProcessingTimer.setSingleShot(true);
+ connect(&m_eventProcessingTimer, SIGNAL(timeout()), this, SLOT(eventProcessingTimeout()));
+ }
+
+ GLWindow::~GLWindow() {}
+
+ void GLWindow::stopProcessingEvents() { m_stopProcessingEvents = true; }
+
+ void GLWindow::eventProcessingTimeout() { m_doc->session()->userGenericEvent("per-render-event-processing", ""); }
+
+ float GLWindow::devicePixelRatioF() const { return static_cast(devicePixelRatio()); }
+
+ void GLWindow::absolutePosition(int& x, int& y) const
+ {
+ QPoint gp = mapToGlobal(QPoint(0, 0));
+ x = gp.x();
+ y = gp.y();
+ }
+
+ void GLWindow::initializeGL()
+ {
+ if (context()->isValid())
+ {
+ initializeGLExtensions();
+ initializeOpenGLFunctions();
+
+ if (m_sharedContext)
+ {
+ context()->setShareContext(m_sharedContext);
+ }
+
+ //
+ // NOTE: session initialization is deliberately NOT driven from
+ // here. Loading packages creates web panels, and adding a
+ // QWebEngineView makes Qt tear down the main window's native
+ // subtree -- destroying this very window while this method is
+ // still on the stack, so every later member access is a
+ // use-after-free. RvApplication::newSessionFromFiles() calls
+ // RvDocument::initializeSession() after show() instead, with no
+ // GL callback in the call chain.
+ //
+
+ QSurfaceFormat f = context()->format();
+
+#ifndef PLATFORM_DARWIN
+ if (f.redBufferSize() != m_red && m_red != 0)
+ {
+ ostringstream str;
+ str << "WARNING: asked for"
+ << " " << m_red << " " << m_green << " " << m_blue << " " << m_alpha << " RGBA color but got"
+ << " " << f.redBufferSize() << " " << f.greenBufferSize() << " " << f.blueBufferSize() << " "
+ << (f.alphaBufferSize() <= 0 ? 0 : f.alphaBufferSize()) << " RGBA instead";
+ cout << str.str() << endl;
+ }
+#endif
+ if (f.stencilBufferSize() == 0)
+ {
+ cout << "WARNING: no stencil buffer available" << endl;
+ }
+ }
+ else
+ {
+ cout << "WARNING: invalid GL context" << endl;
+ }
+ }
+
+ void GLWindow::resizeGL(int w, int h)
+ {
+ if (m_doc)
+ m_doc->viewSizeChanged(w, h);
+ }
+
+ QImage GLWindow::readPixels(int x, int y, int w, int h)
+ {
+ const int pw = width() * devicePixelRatio();
+ const int ph = height() * devicePixelRatio();
+
+ // If out of bounds, return an empty image.
+ if (x < 0 || y < 0 || (x + w) > pw || (y + h) > ph)
+ return QImage(0, 0, QImage::Format_RGBA8888);
+
+ if (m_videoDevice)
+ m_videoDevice->makeCurrent();
+ else
+ makeCurrent();
+
+ QImage image(w, h, QImage::Format_RGBA8888);
+ glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.bits());
+
+ return image;
+ }
+
+ void GLWindow::paintGL()
+ {
+ TWK_GLDEBUG;
+
+ IPCore::Session* session = m_doc->session();
+
+ if (!m_postFirstNonEmptyRender && session && session->postFirstNonEmptyRender())
+ {
+ m_postFirstNonEmptyRender = true;
+
+ if (!session->isFullScreen())
+ {
+ m_doc->resizeToFit(false, false);
+ m_doc->center();
+ TWK_GLDEBUG;
+ }
+ }
+
+ if (m_doc && session && m_videoDevice)
+ {
+ m_videoDevice->makeCurrent();
+ TWK_GLDEBUG;
+
+ if (m_userActive && m_activityTimer.elapsed() > 1.0)
+ {
+ if (m_doc->mainPopup() && !m_doc->mainPopup()->isVisible())
+ {
+ TwkApp::ActivityChangeEvent aevent("user-inactive", m_videoDevice);
+ m_videoDevice->sendEvent(aevent);
+ TWK_GLDEBUG;
+ m_userActive = false;
+ }
+ }
+
+ //
+ // Make sure the video device knows where it is on screen.
+ //
+ int x = 0, y = 0;
+ absolutePosition(x, y);
+ m_videoDevice->setAbsolutePosition(x, y);
+
+ TWK_GLDEBUG;
+ session->render();
+ TWK_GLDEBUG;
+
+ m_firstPaintCompleted = true;
+
+ // Force the resulting alpha channel to 1 so the surface is fully
+ // opaque (matches the former GLView behavior).
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, QOpenGLContext::currentContext()->defaultFramebufferObject());
+ TWK_GLDEBUG;
+ glPushAttrib(GL_COLOR_BUFFER_BIT);
+ glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
+ glClearColor(0.f, 0.f, 0.f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ glPopAttrib();
+ TWK_GLDEBUG;
+ }
+ else
+ {
+ glClearColor(0.f, 0.f, 0.f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ TWK_GLDEBUG;
+ }
+
+ if (m_stopProcessingEvents)
+ return;
+
+ // If a separate output device is presenting, sync it. The control
+ // (window) surface presents itself: QOpenGLWindow swaps automatically
+ // after paintGL returns.
+ if (session->outputVideoDevice() != m_videoDevice)
+ {
+ session->outputVideoDevice()->syncBuffers();
+ }
+
+ session->addSyncSample();
+ session->postRender();
+
+ m_eventProcessingTimer.start();
+
+ TWK_GLDEBUG;
+ }
+
+ bool GLWindow::event(QEvent* event)
+ {
+ // The device (and its translator) is wired by the hosting GLView just
+ // after construction; ignore any events that arrive before then.
+ if (!m_videoDevice)
+ return QOpenGLWindow::event(event);
+
+ bool keyevent = false;
+ Rv::Session* session = m_doc->session();
+
+ if (m_stopProcessingEvents)
+ {
+ event->accept();
+ return true;
+ }
+
+ if (event->type() == QEvent::WindowActivate)
+ m_activationTimer.start();
+
+ float activationTime = 0.0;
+ if (m_activationTimer.isRunning())
+ {
+ if (event->type() == QEvent::MouseButtonPress)
+ {
+ activationTime = m_activationTimer.elapsed();
+ m_activationTimer.stop();
+ }
+ if (event->type() == QEvent::MouseMove)
+ m_activationTimer.stop();
+ }
+
+ if (event->type() != QEvent::Paint && event->type() != QEvent::UpdateRequest)
+ {
+ m_activityTimer.stop();
+ m_activityTimer.start();
+
+ if (!m_userActive)
+ {
+ TwkApp::ActivityChangeEvent aevent("user-active", m_videoDevice);
+ m_userActive = true;
+ m_videoDevice->sendEvent(aevent);
+ }
+ }
+
+ if (QKeyEvent* kevent = dynamic_cast(event))
+ {
+ keyevent = true;
+
+ if (m_lastKey == kevent->key()
+ && (m_lastKeyType == QEvent::ShortcutOverride && (kevent->type() == QEvent::KeyPress) || (m_lastKeyType == kevent->type())))
+ {
+ m_lastKey = kevent->key();
+ m_lastKeyType = kevent->type();
+ event->accept();
+ return true;
+ }
+
+ m_lastKeyType = kevent->type();
+ m_lastKey = kevent->key();
+ }
+
+ switch (event->type())
+ {
+ case QEvent::FocusIn:
+ m_videoDevice->translator().resetModifiers();
+ case QEvent::Enter:
+ requestActivate();
+ break;
+ default:
+ break;
+ }
+
+ if (session && session->outputVideoDevice()
+ && session->outputVideoDevice()->displayMode() == TwkApp::VideoDevice::MirrorDisplayMode)
+ {
+ if (const TwkApp::VideoDevice* cdv = session->controlVideoDevice())
+ {
+ const TwkApp::VideoDevice* odv = session->outputVideoDevice();
+
+ if (odv && cdv != odv && cdv == m_videoDevice)
+ {
+ const float w = width();
+ const float h = height();
+ const float ow = odv->width();
+ const float oh = odv->height();
+
+ const float aspect = w / h;
+ const float oaspect = ow / oh;
+
+ m_videoDevice->translator().setRelativeDomain(ow, oh);
+
+ if (aspect >= oaspect)
+ {
+ const float yscale = oh / h;
+ const float xscale = yscale;
+ const float xoffset = -(w * yscale - ow) / 2.0;
+ m_videoDevice->translator().setScaleAndOffset(xoffset, 0.0, xscale, yscale);
+ }
+ else
+ {
+ const float xscale = ow / w;
+ const float yscale = xscale;
+ const float yoffset = -(xscale * h - oh) / 2.0;
+ m_videoDevice->translator().setScaleAndOffset(0.0, yoffset, xscale, yscale);
+ }
+ }
+ else
+ {
+ m_videoDevice->translator().setScaleAndOffset(0, 0, 1.0, 1.0);
+ m_videoDevice->translator().setRelativeDomain(width(), height());
+ }
+ }
+ else
+ {
+ m_videoDevice->translator().setScaleAndOffset(0, 0, 1.0, 1.0);
+ m_videoDevice->translator().setRelativeDomain(width(), height());
+ }
+ }
+ else
+ {
+ m_videoDevice->translator().setScaleAndOffset(0, 0, 1.0, 1.0);
+ m_videoDevice->translator().setRelativeDomain(width(), height());
+ }
+
+ if (session)
+ session->setEventVideoDevice(m_videoDevice);
+
+ if (m_videoDevice->translator().sendQTEvent(event, activationTime))
+ {
+ event->accept();
+ return true;
+ }
+
+ return QOpenGLWindow::event(event);
+ }
+
+} // namespace Rv
diff --git a/src/lib/app/RvCommon/QTGLVideoDevice.cpp b/src/lib/app/RvCommon/QTGLVideoDevice.cpp
index 50fd6e5e4..ba4fd3eed 100644
--- a/src/lib/app/RvCommon/QTGLVideoDevice.cpp
+++ b/src/lib/app/RvCommon/QTGLVideoDevice.cpp
@@ -47,6 +47,18 @@ namespace Rv
{
}
+ QTGLVideoDevice::QTGLVideoDevice(VideoModule* m, const string& name, QOpenGLWindow* window, QWidget* eventWidget)
+ : GLVideoDevice(m, name, ImageOutput | ProvidesSync | SubWindow)
+ , m_view(0)
+ , m_window(window)
+ , m_translator(new QTTranslator(this, eventWidget))
+ , m_x(0)
+ , m_y(0)
+ , m_refresh(-1.0)
+ {
+ assert(window);
+ }
+
QTGLVideoDevice::QTGLVideoDevice(const string& name, QOpenGLWidget* view)
: GLVideoDevice(NULL, name, NoCapabilities)
, m_view(view)
@@ -68,16 +80,36 @@ namespace Rv
GLVideoDevice* QTGLVideoDevice::newSharedContextWorkerDevice() const
{
- // NOTE_QT: QOpenGLWidget does not take a share parameter anymore. Try
- // to share with setShareContext.
- QOpenGLWidget* openGLWidget = new QOpenGLWidget(m_view->parentWidget());
- openGLWidget->context()->setShareContext(m_view->context());
+ // A freshly constructed QOpenGLWidget has no context() yet -- it is
+ // created lazily on first show/makeCurrent -- so the previous
+ // openGLWidget->context()->setShareContext(...) here dereferenced a null
+ // context and crashed (reachable via ImageRenderer::createGLContexts()
+ // when "Multithread GPU Upload" is enabled). Explicit sharing is also
+ // unnecessary: Qt::AA_ShareOpenGLContexts (set in main.cpp) makes all GL
+ // contexts in the process share resources, so this worker context shares
+ // with the control viewport automatically.
+ QOpenGLWidget* openGLWidget = new QOpenGLWidget(m_view ? m_view->parentWidget() : nullptr);
return new QTGLVideoDevice(name() + "-workerContextDevice", openGLWidget);
}
void QTGLVideoDevice::makeCurrent() const
{
- if (m_view->context() && m_view->context()->isValid())
+ if (m_window)
+ {
+ // QOpenGLWindow creates its GL context lazily on the first
+ // makeCurrent(), provided the platform window (surface) exists.
+ if (m_window->handle())
+ {
+ m_window->makeCurrent();
+ TWK_GLDEBUG;
+
+ GLint surfaceFBO = m_window->defaultFramebufferObject();
+ if (surfaceFBO != 0)
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, surfaceFBO);
+ TWK_GLDEBUG;
+ }
+ }
+ else if (m_view && m_view->context() && m_view->context()->isValid())
{
m_view->makeCurrent();
TWK_GLDEBUG;
@@ -94,6 +126,8 @@ namespace Rv
GLuint QTGLVideoDevice::fboID() const
{
+ if (m_window)
+ return m_window->defaultFramebufferObject();
if (m_view)
return m_view->defaultFramebufferObject();
@@ -129,14 +163,27 @@ namespace Rv
{
if (!isWorkerDevice())
{
- QSize s = m_view->size();
- m_view->update();
+ if (m_window)
+ m_window->update();
+ else if (m_view)
+ m_view->update();
}
}
void QTGLVideoDevice::redrawImmediately() const
{
- if (!isWorkerDevice())
+ if (isWorkerDevice())
+ return;
+
+ if (m_window)
+ {
+ if (m_window->isVisible())
+ m_window->update();
+ else
+ redraw();
+ return;
+ }
+
{
if (m_view->isVisible())
{
@@ -166,12 +213,16 @@ namespace Rv
VideoDevice::Resolution QTGLVideoDevice::resolution() const
{
- return Resolution(m_view->width() * devicePixelRatio(), m_view->height() * devicePixelRatio(), 1.0f, 1.0f);
+ const int w = m_window ? m_window->width() : m_view->width();
+ const int h = m_window ? m_window->height() : m_view->height();
+ return Resolution(w * devicePixelRatio(), h * devicePixelRatio(), 1.0f, 1.0f);
}
VideoDevice::Resolution QTGLVideoDevice::internalResolution() const
{
- return Resolution(m_view->width(), m_view->height(), 1.0f, 1.0f);
+ const int w = m_window ? m_window->width() : m_view->width();
+ const int h = m_window ? m_window->height() : m_view->height();
+ return Resolution(w, h, 1.0f, 1.0f);
}
VideoDevice::Offset QTGLVideoDevice::offset() const { return Offset(m_x, m_y); }
@@ -180,45 +231,52 @@ namespace Rv
VideoDevice::VideoFormat QTGLVideoDevice::format() const
{
- return VideoFormat(m_view->width() * devicePixelRatio(), m_view->height() * devicePixelRatio(), 1.0, 1.0,
- (m_refresh != -1.0) ? m_refresh : 0.0, hardwareIdentification());
+ const int w = m_window ? m_window->width() : m_view->width();
+ const int h = m_window ? m_window->height() : m_view->height();
+ return VideoFormat(w * devicePixelRatio(), h * devicePixelRatio(), 1.0, 1.0, (m_refresh != -1.0) ? m_refresh : 0.0,
+ hardwareIdentification());
}
void QTGLVideoDevice::open(const StringVector& args)
{
- if (!isWorkerDevice())
+ if (isWorkerDevice())
+ return;
+ if (m_window)
+ m_window->show();
+ else
m_view->show();
}
void QTGLVideoDevice::close()
{
- if (!isWorkerDevice())
+ if (isWorkerDevice())
+ return;
+ if (m_window)
+ m_window->hide();
+ else
m_view->hide();
}
bool QTGLVideoDevice::isOpen() const
{
if (isWorkerDevice())
- {
return false;
- }
- else
- {
- return m_view->isVisible();
- }
+ return m_window ? m_window->isVisible() : m_view->isVisible();
}
- size_t QTGLVideoDevice::width() const { return m_view->width() * devicePixelRatio(); }
+ size_t QTGLVideoDevice::width() const { return (m_window ? m_window->width() : m_view->width()) * devicePixelRatio(); }
- size_t QTGLVideoDevice::height() const { return m_view->height() * devicePixelRatio(); }
+ size_t QTGLVideoDevice::height() const { return (m_window ? m_window->height() : m_view->height()) * devicePixelRatio(); }
void QTGLVideoDevice::syncBuffers() const
{
- if (!isWorkerDevice())
- {
- makeCurrent();
+ if (isWorkerDevice())
+ return;
+ makeCurrent();
+ if (m_window)
+ m_window->context()->swapBuffers(m_window);
+ else
m_view->context()->swapBuffers(m_view->context()->surface());
- }
}
void QTGLVideoDevice::setAbsolutePosition(int x, int y)
diff --git a/src/lib/app/RvCommon/RvApplication.cpp b/src/lib/app/RvCommon/RvApplication.cpp
index 4920fae65..9e569c7db 100644
--- a/src/lib/app/RvCommon/RvApplication.cpp
+++ b/src/lib/app/RvCommon/RvApplication.cpp
@@ -734,6 +734,16 @@ namespace Rv
doc->raise();
#endif
+ //
+ // Create the session now that the viewport window exists and its GL
+ // context has been created by show(). This must happen here rather
+ // than from GLWindow::initializeGL(): loading packages creates web
+ // panels, and adding a QWebEngineView makes Qt destroy the main
+ // window's native subtree -- including the viewport window -- which
+ // is fatal if a GLWindow method is still on the call stack.
+ //
+ doc->initializeSession();
+
// doc->ensurePolished();
Rv::RvSession* s = doc->session();
diff --git a/src/lib/app/RvCommon/RvCommon/DesktopVideoDevice.h b/src/lib/app/RvCommon/RvCommon/DesktopVideoDevice.h
index 394804930..dc78b2f2c 100644
--- a/src/lib/app/RvCommon/RvCommon/DesktopVideoDevice.h
+++ b/src/lib/app/RvCommon/RvCommon/DesktopVideoDevice.h
@@ -18,6 +18,7 @@
#include
#include
#include
+#include
#include
namespace Rv
@@ -57,13 +58,19 @@ namespace Rv
class ScreenView : public QOpenGLWidget
{
public:
- ScreenView(const QSurfaceFormat& fmt, QWidget* parent, QOpenGLWidget* glViewShare, Qt::WindowFlags flags);
+ //
+ // glShareContext is the control view's GL context to share with
+ // (so blits/FBOs are usable across the two surfaces). It comes
+ // from QTGLVideoDevice::glShareContext() and is backing-agnostic:
+ // the control view may be a QOpenGLWidget or a QOpenGLWindow.
+ //
+ ScreenView(const QSurfaceFormat& fmt, QWidget* parent, QOpenGLContext* glShareContext, Qt::WindowFlags flags);
void initializeGL() override;
void paintGL() override;
private:
- QOpenGLWidget* m_glViewShare = nullptr;
+ QOpenGLContext* m_glShareContext = nullptr;
};
public:
diff --git a/src/lib/app/RvCommon/RvCommon/GLView.h b/src/lib/app/RvCommon/RvCommon/GLView.h
index 4cda0171e..40f1a0dbb 100644
--- a/src/lib/app/RvCommon/RvCommon/GLView.h
+++ b/src/lib/app/RvCommon/RvCommon/GLView.h
@@ -8,55 +8,74 @@
#ifndef __rv_qt__GLView__h__
#define __rv_qt__GLView__h__
#include
-#include
-#include
+#include
#include
-#include
-#include
-#include
-#include
-#include
+#include
+#include
+
+class QOpenGLContext;
namespace Rv
{
class RvDocument;
class QTGLVideoDevice;
-
- class GLView
- : public QOpenGLWidget
- , protected QOpenGLFunctions
+ class GLWindow;
+
+ //
+ // GLView
+ //
+ // Host QWidget that embeds the native GL viewport (GLWindow) via
+ // QWidget::createWindowContainer(). GLView keeps the public API the rest
+ // of RV expects -- view()->context()/format()/makeCurrent()/videoDevice()
+ // etc. -- by delegating to the GLWindow.
+ //
+ // Crucially, GLView is no longer a QOpenGLWidget. That keeps the top-level
+ // QMainWindow off the OpenGL RHI backend (so docked QWebEngineView panels
+ // render on the platform default backend), and lets the viewport present
+ // on its own native surface instead of via the full-window widget
+ // composite.
+ //
+
+ class GLView : public QWidget
{
Q_OBJECT
public:
- typedef TwkUtil::Timer Timer;
-
GLView(QWidget* parent, QOpenGLContext* sharedContext, RvDocument* doc, bool stereo = false, bool vsync = true,
bool doubleBuffer = true, int red = 0, int green = 0, int blue = 0, int alpha = 0, bool noResize = true);
- ~GLView();
+ ~GLView() override;
static QSurfaceFormat rvGLFormat(bool stereo = false, bool vsync = true, bool doubleBuffer = true, int red = 8, int green = 8,
int blue = 8, int alpha = 8);
- void absolutePosition(int& x, int& y) const;
+ GLWindow* glWindow() const { return m_glWindow; }
QTGLVideoDevice* videoDevice() const { return m_videoDevice; }
- void stopProcessingEvents();
+ //
+ // Delegated QOpenGLWidget-compatible API still used across RV.
+ //
+ QOpenGLContext* context() const;
+ QSurfaceFormat format() const;
+ void makeCurrent();
+
+ // Compatibility shim for the former QOpenGLWidget::isValid(): the
+ // native GL window is created up-front, so the viewport is considered
+ // valid once the window exists.
+ bool isValid() const;
- virtual bool event(QEvent*);
- virtual bool eventFilter(QObject* object, QEvent* event);
+ void absolutePosition(int& x, int& y) const;
+
+ void stopProcessingEvents();
- bool firstPaintCompleted() const { return m_firstPaintCompleted; };
+ bool firstPaintCompleted() const;
void setContentSize(int w, int h) { m_csize = QSize(w, h); }
void setMinimumContentSize(int w, int h) { m_msize = QSize(w, h); }
- virtual QSize sizeHint() const;
- virtual QSize minimumSizeHint() const;
-
- void* syncClosure() const { return m_syncThreadData; }
+ QSize sizeHint() const override;
+ QSize minimumSizeHint() const override;
QImage readPixels(int x, int y, int w, int h);
@@ -64,38 +83,13 @@ namespace Rv
// For reference: https://doc.qt.io/qt-6/highdpi.html
float devicePixelRatio() const;
- public slots:
- void eventProcessingTimeout();
-
- protected:
- void initializeGL();
- void resizeGL(int w, int h);
- void paintGL();
- bool validateReadPixels(int x, int y, int w, int h);
- void debugSaveFramebuffer();
-
private:
RvDocument* m_doc;
- boost::thread m_swapThread;
+ GLWindow* m_glWindow;
+ QWidget* m_container;
QTGLVideoDevice* m_videoDevice;
- unsigned int m_lastKey;
- QEvent::Type m_lastKeyType;
- Timer m_activityTimer;
- Timer m_renderTimer;
- QTimer m_eventProcessingTimer;
- bool m_userActive;
- size_t m_renderCount;
- Timer m_activationTimer;
- bool m_firstPaintCompleted;
QSize m_csize;
QSize m_msize;
- int m_red;
- int m_green;
- int m_blue;
- int m_alpha;
- bool m_postFirstNonEmptyRender;
- bool m_stopProcessingEvents;
- void* m_syncThreadData;
QOpenGLContext* m_sharedContext;
};
diff --git a/src/lib/app/RvCommon/RvCommon/GLWindow.h b/src/lib/app/RvCommon/RvCommon/GLWindow.h
new file mode 100644
index 000000000..ef24551cf
--- /dev/null
+++ b/src/lib/app/RvCommon/RvCommon/GLWindow.h
@@ -0,0 +1,102 @@
+//******************************************************************************
+// Copyright (c) 2007 Tweak Inc.
+// All rights reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//******************************************************************************
+#ifndef __rv_qt__GLWindow__h__
+#define __rv_qt__GLWindow__h__
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace Rv
+{
+ class RvDocument;
+ class QTGLVideoDevice;
+
+ //
+ // GLWindow
+ //
+ // The RV viewport rendered as a *native* GL surface (QOpenGLWindow)
+ // instead of a composited QOpenGLWidget. Because it is a real window and
+ // not a QOpenGLWidget in the main window's widget tree, the top-level
+ // QMainWindow is no longer forced to the OpenGL RHI backend -- so docked
+ // QWebEngineView panels render on the platform default backend -- and the
+ // viewport presents on its own surface instead of via the ~90 ms
+ // full-window widget composite.
+ //
+ // It is embedded in the widget hierarchy by GLView via
+ // QWidget::createWindowContainer(). Rendering and event routing are ported
+ // from the former QOpenGLWidget-based GLView.
+ //
+
+ class GLWindow
+ : public QOpenGLWindow
+ , protected QOpenGLFunctions
+ {
+ Q_OBJECT
+
+ public:
+ typedef TwkUtil::Timer Timer;
+
+ GLWindow(QOpenGLContext* sharedContext, RvDocument* doc, bool stereo = false, bool vsync = true, bool doubleBuffer = true,
+ int red = 0, int green = 0, int blue = 0, int alpha = 0, bool noResize = true);
+ ~GLWindow() override;
+
+ QTGLVideoDevice* videoDevice() const { return m_videoDevice; }
+
+ // The device is created and owned by the hosting GLView (it needs the
+ // container QWidget for event/coordinate translation), then handed
+ // here. GLWindow does not take ownership.
+ void setVideoDevice(QTGLVideoDevice* d) { m_videoDevice = d; }
+
+ void stopProcessingEvents();
+
+ bool event(QEvent*) override;
+
+ bool firstPaintCompleted() const { return m_firstPaintCompleted; }
+
+ // Absolute (global) top-left position of the surface in pixels.
+ void absolutePosition(int& x, int& y) const;
+
+ float devicePixelRatioF() const;
+
+ QImage readPixels(int x, int y, int w, int h);
+
+ public slots:
+ void eventProcessingTimeout();
+
+ protected:
+ void initializeGL() override;
+ void resizeGL(int w, int h) override;
+ void paintGL() override;
+
+ private:
+ RvDocument* m_doc;
+ QTGLVideoDevice* m_videoDevice;
+ unsigned int m_lastKey;
+ QEvent::Type m_lastKeyType;
+ Timer m_activityTimer;
+ QTimer m_eventProcessingTimer;
+ bool m_userActive;
+ Timer m_activationTimer;
+ bool m_firstPaintCompleted;
+ int m_red;
+ int m_green;
+ int m_blue;
+ int m_alpha;
+ bool m_postFirstNonEmptyRender;
+ bool m_stopProcessingEvents;
+ QOpenGLContext* m_sharedContext;
+ };
+
+} // namespace Rv
+
+#endif // __rv_qt__GLWindow__h__
diff --git a/src/lib/app/RvCommon/RvCommon/QTGLVideoDevice.h b/src/lib/app/RvCommon/RvCommon/QTGLVideoDevice.h
index 6bc414926..91cab3c10 100644
--- a/src/lib/app/RvCommon/RvCommon/QTGLVideoDevice.h
+++ b/src/lib/app/RvCommon/RvCommon/QTGLVideoDevice.h
@@ -10,6 +10,9 @@
#include
#include
#include
+#include
+#include
+#include
#include
#include
#include
@@ -29,6 +32,13 @@ namespace Rv
{
public:
QTGLVideoDevice(TwkApp::VideoModule*, const std::string& name, QOpenGLWidget* view);
+ //
+ // Native-window backed variant. The GL surface is a QOpenGLWindow
+ // (embedded in the widget tree via createWindowContainer); eventWidget
+ // is the container QWidget used by the QTTranslator for coordinate
+ // mapping (height/mapToGlobal) and mouse grab.
+ //
+ QTGLVideoDevice(TwkApp::VideoModule*, const std::string& name, QOpenGLWindow* window, QWidget* eventWidget);
QTGLVideoDevice(TwkApp::VideoModule*, const std::string& name);
virtual ~QTGLVideoDevice();
@@ -36,6 +46,19 @@ namespace Rv
QOpenGLWidget* widget() const { return m_view; }
+ QOpenGLWindow* window() const { return m_window; }
+
+ //
+ // Backing-agnostic accessors for the shareable GL context and its
+ // surface format. The control view may be backed by either a
+ // QOpenGLWidget (m_view) or, in the native-window port, a
+ // QOpenGLWindow (m_window). Second-output devices (DesktopVideoDevice)
+ // share the control context and need these without caring which.
+ //
+ QOpenGLContext* glShareContext() const { return m_window ? m_window->context() : (m_view ? m_view->context() : nullptr); }
+
+ QSurfaceFormat glSurfaceFormat() const { return m_window ? m_window->format() : (m_view ? m_view->format() : QSurfaceFormat()); }
+
virtual void makeCurrent() const;
const QTTranslator& translator() const { return *m_translator; }
@@ -85,6 +108,7 @@ namespace Rv
float m_refresh;
float m_devicePixelRatio{1.0f};
QOpenGLWidget* m_view;
+ QOpenGLWindow* m_window{nullptr};
QTTranslator* m_translator;
};
diff --git a/src/lib/app/RvCommon/RvCommon/RvDocument.h b/src/lib/app/RvCommon/RvCommon/RvDocument.h
index 91634cc62..2744d8106 100644
--- a/src/lib/app/RvCommon/RvCommon/RvDocument.h
+++ b/src/lib/app/RvCommon/RvCommon/RvDocument.h
@@ -148,6 +148,12 @@ namespace Rv
void mergeMenu(const TwkApp::Menu*, bool shortcuts = true);
void convert(QMenu*, const TwkApp::Menu*, bool shortcuts);
+ // Position the UI blocking overlay over the main window's client area.
+ // The overlay is a frameless top-level window (not a child widget) so
+ // it can cover the native GL viewport window, which renders above
+ // sibling raster widgets and so cannot be dimmed by a child overlay.
+ void positionBlockingOverlay();
+
void closeEvent(QCloseEvent*) override;
void changeEvent(QEvent*) override;
bool event(QEvent*) override;
diff --git a/src/lib/app/RvCommon/RvDocument.cpp b/src/lib/app/RvCommon/RvDocument.cpp
index 773864e6d..a80cfb375 100644
--- a/src/lib/app/RvCommon/RvDocument.cpp
+++ b/src/lib/app/RvCommon/RvDocument.cpp
@@ -282,16 +282,29 @@ namespace Rv
}
//
- // Create UI blocking overlay - transparent widget that captures all input
+ // Create UI blocking overlay - a transparent window that captures all
+ // input and dims the UI.
//
- m_blockingOverlay = new QWidget(this);
+ // It is a frameless top-level window (owned by this document) rather
+ // than a child widget. The viewport is a native QOpenGLWindow, which
+ // renders above any sibling raster child widget regardless of
+ // raise()/stacking order, so a child overlay could never dim or block
+ // the viewport. A top-level window sits above the main window and its
+ // native child, so it covers the viewport too.
+ //
+ m_blockingOverlay = new QWidget(this, Qt::FramelessWindowHint | Qt::Tool);
m_blockingOverlay->setObjectName("UIBlockingOverlay");
- // Semi-transparent dark overlay for visual feedback
- m_blockingOverlay->setStyleSheet("QWidget#UIBlockingOverlay { background-color: rgba(0, 0, 0, 100); }");
+ // Semi-transparent dark overlay for visual feedback. A top-level window
+ // has no per-pixel alpha unless WA_TranslucentBackground is set, and an
+ // rgba stylesheet fill does not reliably paint on a plain translucent
+ // top-level QWidget. Instead fill solid black and make the whole window
+ // see-through with window opacity, which is deterministic on all
+ // platforms and composites correctly above the native GL viewport.
+ m_blockingOverlay->setStyleSheet("QWidget#UIBlockingOverlay { background-color: rgb(0, 0, 0); }");
+ m_blockingOverlay->setWindowOpacity(0.4);
- // Ensure it stays on top and captures input
- m_blockingOverlay->raise();
+ // Capture input (mouse + keyboard) while shown
m_blockingOverlay->setAttribute(Qt::WA_TransparentForMouseEvents, false);
m_blockingOverlay->setFocusPolicy(Qt::StrongFocus);
@@ -299,10 +312,35 @@ namespace Rv
m_blockingOverlay->hide();
}
+ void RvDocument::positionBlockingOverlay()
+ {
+ if (!m_blockingOverlay)
+ return;
+
+ // Cover the main window's client area in global coordinates (the
+ // overlay is a top-level window, so it needs screen coordinates).
+ m_blockingOverlay->setGeometry(QRect(mapToGlobal(QPoint(0, 0)), size()));
+ m_blockingOverlay->raise();
+ }
+
void RvDocument::initializeSession()
{
+ //
+ // Called by RvApplication::newSessionFromFiles() once the document has
+ // been shown, so the viewport window exists and its GL context has
+ // been created. Constructing an RvSession queries
+ // GL_SHADING_LANGUAGE_VERSION and aborts without a current context, so
+ // make the viewport context current first.
+ //
+ if (!m_glView)
+ {
+ return;
+ }
+
if (!m_session)
{
+ m_glView->makeCurrent();
+
m_session = new RvSession;
// m_session->setFrameBuffer(fb);
@@ -1977,18 +2015,19 @@ namespace Rv
{
if (m_session)
m_session->askForRedraw();
+
+ // Keep the top-level overlay aligned with the window when visible.
+ if (m_blockingOverlay && m_blockingOverlay->isVisible())
+ positionBlockingOverlay();
}
void RvDocument::resizeEvent(QResizeEvent* event)
{
QMainWindow::resizeEvent(event);
- // Keep overlay covering entire window when visible
+ // Keep overlay covering entire client area when visible
if (m_blockingOverlay && m_blockingOverlay->isVisible())
- {
- m_blockingOverlay->setGeometry(0, 0, width(), height());
- m_blockingOverlay->raise();
- }
+ positionBlockingOverlay();
}
void RvDocument::setUIBlocked(bool blocked)
@@ -2000,12 +2039,13 @@ namespace Rv
if (blocked)
{
- // Cover entire window
- m_blockingOverlay->setGeometry(0, 0, width(), height());
- m_blockingOverlay->raise(); // Bring to front, above everything
+ // Cover entire client area (top-level window, global coords)
+ positionBlockingOverlay();
m_blockingOverlay->show();
+ m_blockingOverlay->raise(); // Bring to front, above everything
// Grab focus to also block keyboard input
+ m_blockingOverlay->activateWindow();
m_blockingOverlay->setFocus(Qt::OtherFocusReason);
}
else
diff --git a/src/lib/app/mu_rvui/mode_manager.mu b/src/lib/app/mu_rvui/mode_manager.mu
index 001e736c1..cc8528662 100644
--- a/src/lib/app/mu_rvui/mode_manager.mu
+++ b/src/lib/app/mu_rvui/mode_manager.mu
@@ -755,8 +755,40 @@ class: ModeManagerMode : MinorMode
let vnode = viewNode(),
vtype = nodeType(vnode),
- name = if vtype.substr(0,2) == "RV" then vtype.substr(2,0) + "_edit_mode" else "",
- entry = findModeEntry(name);
+ name = if vtype.substr(0,2) == "RV" then vtype.substr(2,0) + "_edit_mode" else "";
+
+ //
+ // Avoid redundant deactivate->reactivate churn when the view-edit-mode
+ // is not actually changing. Toggling an edit mode is expensive (it
+ // rebuilds menus/event tables), and clearSession re-sets the same
+ // default view twice with force=true -- so without this the same edit
+ // mode is toggled off then on up to four times per clear for no net
+ // change. On the 'before' event, event.contents() holds the node we
+ // are switching TO while viewNode() is still the OLD view; if both map
+ // to the same edit mode, skip the deactivation. The matching 'after'
+ // event then finds the mode already active and no-ops in activateEntry.
+ //
+ if (!on)
+ {
+ try
+ {
+ let toName = event.contents();
+ if (toName != "")
+ {
+ let toType = nodeType(toName),
+ toEditMode = if toType.substr(0,2) == "RV"
+ then toType.substr(2,0) + "_edit_mode"
+ else "";
+ if (toEditMode == name)
+ {
+ return;
+ }
+ }
+ }
+ catch (exception exc) { let ignore = exc; }
+ }
+
+ let entry = findModeEntry(name);
if (entry neq nil)
{
diff --git a/src/lib/app/py_rvui/rv/qtutils.py b/src/lib/app/py_rvui/rv/qtutils.py
index f1fcfc97d..ec72da90c 100644
--- a/src/lib/app/py_rvui/rv/qtutils.py
+++ b/src/lib/app/py_rvui/rv/qtutils.py
@@ -36,12 +36,16 @@ def sessionWindow():
def sessionGLView():
"""
- Returns the QOpenGLWidget for the current RV session GL view.
+ Returns the QWidget hosting the current RV session GL view.
+
+ Note: as of the native-GL-window change (Direction C) the session view is a
+ plain QWidget host (it embeds the GL surface via createWindowContainer), no
+ longer a QOpenGLWidget.
"""
rvPyLongPtr = rv.commands.sessionGLView()
if rvPyLongPtr is not None:
- return wrapInstance(rvPyLongPtr, QOpenGLWidget)
+ return wrapInstance(rvPyLongPtr, QWidget)
else:
return None
diff --git a/src/lib/ip/IPCore/Session.cpp b/src/lib/ip/IPCore/Session.cpp
index 6419e3845..666bf2e21 100644
--- a/src/lib/ip/IPCore/Session.cpp
+++ b/src/lib/ip/IPCore/Session.cpp
@@ -2261,20 +2261,18 @@ namespace IPCore
void Session::askForRedraw(bool force)
{
if (m_beingDeleted)
+ {
return;
+ }
m_wantsRedraw = true;
- if (!isPlaying())
- {
- if (!m_stopTimer.isRunning())
- {
- m_stopTimer.stop();
- m_stopTimer.start();
- App()->startPlay(this);
- send(startPlayMessage());
- }
- }
+ //
+ // Non-playback redraws only need a single frame update via d->redraw()
+ // below. Starting the 2s m_stopTimer/startPlay settle window kept
+ // isUpdating() true and starved Qt6 QWebChannel delivery during spam
+ // clicks, without providing further redraw work once wantsRedraw clears.
+ //
if (!isRendering() || force)
{
@@ -3223,7 +3221,24 @@ namespace IPCore
// This is the "heartbeat" entry point in the session
//
- if (force || isUpdating() || m_rangeDirty)
+ if (m_stopTimer.isRunning() && !isPlaying())
+ {
+ if (!m_wantsRedraw && !isEvalRunning() && !isBuffering() && !currentStateIsError())
+ {
+ stopCompletely();
+ }
+ else if (m_stopTimer.elapsed() > 2.0 && !isEvalRunning() && !isBuffering() && !currentStateIsError())
+ {
+ stopCompletely();
+ }
+ }
+
+ //
+ // During non-playback settle, only redraw when something actually
+ // requested it. The old isUpdating() guard repainted at heartbeat
+ // rate for ~2s even with wantsRedraw=false, starving Qt6 QWebChannel.
+ //
+ if (force || isPlaying() || m_wantsRedraw || m_rangeDirty)
{
if (multipleVideoDevices() && outputVideoDevice()->willBlockOnTransfer()
&& (outputVideoDevice()->capabilities() & TwkApp::VideoDevice::ASyncReadBack))