feat: SG-43127: SG-43594: 10-bit Vulkan presentation (Linux + Windows) - #1319
feat: SG-43127: SG-43594: 10-bit Vulkan presentation (Linux + Windows)#1319cedrik-fuoco-adsk wants to merge 6 commits into
Conversation
8e7968c to
eab360c
Compare
97db5f8 to
4fc864b
Compare
f6eddc0 to
0be0c50
Compare
|
Before the latest changes: The present was single-frame-in-flight. Every frame ended with a blocking wait on the GPU fence, so the CPU sat idle until the GPU finished before starting the next frame. Resize made it worse by rebuilding the shared image each step. The result was choppy resize and playback that stalled then fast-forwarded. After: Up to 2 frames run in flight. Per-frame resources (fence, semaphores, shared image, staging buffer) are now per-slot rings indexed by m_currentFrame, so the CPU can prepare the next frame while the GPU finishes the current one. The GL side reads m_view->currentFrame() and uses the same slot, so its imported objects stay paired with the matching Vulkan ones. The blocking wait moved from the end of the frame to the start of the slot it reuses, so it only blocks when the CPU is a full frame ahead. Pacing still comes from FIFO vsync plus that start-of-frame wait. |
78297c8 to
3384afc
Compare
|
Added @bernie-laberge fix for Nvidia and optimization for the CPU fallback. |
3384afc to
095c13b
Compare
| virtual void makeCurrent() const override; | ||
| virtual void syncBuffers() const override; | ||
| virtual void redraw() const override; | ||
| virtual void redrawImmediately() const override; | ||
| virtual void clearCaches() const override; | ||
|
|
||
| virtual Resolution resolution() const override; | ||
| virtual Offset offset() const override; | ||
| virtual Timing timing() const override; | ||
| virtual VideoFormat format() const override; | ||
|
|
||
| virtual size_t width() const override; | ||
| virtual size_t height() const override; | ||
|
|
||
| virtual void open(const StringVector& args) override; | ||
| virtual void close() override; | ||
| virtual bool isOpen() const override; | ||
|
|
||
| virtual float devicePixelRatio() const override; | ||
|
|
||
| virtual void setPhysicalDevice(VideoDevice* d) override; | ||
|
|
||
| // GLVideoDevice API | ||
| virtual TwkGLF::GLFBO* defaultFBO() override; | ||
| virtual const TwkGLF::GLFBO* defaultFBO() const override; | ||
| virtual std::string hardwareIdentification() const override; |
There was a problem hiding this comment.
virtual is redundant since these functions are already declared override
| #include <array> | ||
| #include <cstdint> | ||
| #include <string> | ||
| #include <memory> |
There was a problem hiding this comment.
I believe this header is not used in this file
| virtual bool event(QEvent* event) override; | ||
| virtual bool eventFilter(QObject* object, QEvent* event) override; |
There was a problem hiding this comment.
virtual is redundant since these functions are already declared override
| #include <cstdlib> | ||
| #include <cstring> | ||
| #include <iostream> | ||
| #include <vector> |
There was a problem hiding this comment.
I believe this header is unsed in this file
| #include <QOpenGLContext> | ||
| #include <QOffscreenSurface> | ||
|
|
||
| #include <algorithm> |
There was a problem hiding this comment.
I believe this header is unsed in this file
| // With a non-OpenGL presentation backend view() returns null — no | ||
| // GL context to make current; presentation handles it per-frame. | ||
| if (doc->view()) | ||
| doc->view()->makeCurrent(); |
There was a problem hiding this comment.
Nit: This statement should be inside braces
| // With a non-OpenGL presentation backend view() is null — no GL | ||
| // context to make current. | ||
| if (doc->view()) | ||
| doc->view()->makeCurrent(); |
There was a problem hiding this comment.
Nit: This statement should be inside braces
| rvDoc->view()->videoDevice()->makeCurrent(); | ||
| // With a non-OpenGL presentation backend view() is null — skip GL makeCurrent. | ||
| if (rvDoc->view()) | ||
| rvDoc->view()->videoDevice()->makeCurrent(); |
There was a problem hiding this comment.
Nit: This statement should be inside braces
… baseline `-debug gpu` only toggled ImageRenderer's GL reporting, and the flag was named after that one consumer. The Vulkan presentation work needs the same switch to gate its own diagnostics, so rename it to the backend-neutral debugGpu() and keep reportGL() as thin deprecated forwarders for existing callers. Under the flag, the GL viewport now reports a one-shot baseline that answers "why did I only get 8 bits per component" without a second run: the format GLView asked for, the format Qt negotiated, the context Qt actually created, the screen depth, the GL vendor/renderer/version, and -- on Linux -- which display server we are on. glDebugFormatSummary()/glDebugEnvOrUnset() live in GLView.cpp and are shared with GLWindow, which owns the post-negotiation half of that report. Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
The 10-bit presentation backend needs <vulkan/vulkan.h> and the loader, so add Vulkan to the managed dependency set rather than requiring a VULKAN_SDK environment variable or a setup-env.sh on the developer's machine. Vulkan here is Vulkan-Headers plus Vulkan-Loader, fetched from hash-pinned Khronos tarballs and modeled on glew.cmake. RV consumes only those two: every API call goes through vkGetInstanceProcAddr/vkGetDeviceProcAddr, so no validation layers or shader tools are pulled in. RV_DEPS_PREFER_INSTALLED still lets a system Vulkan win via the built-in FindVulkan module or the `vulkan` pkg-config module. FORCE_LIB pins the install dir to install/lib so the loader lands in a deterministic place across RHEL (lib64) and non-RHEL. Only Linux and Windows include it; macOS presents through Metal. Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
…idge ImageRenderer's pipeline -- renderMain, the shader cache, the paint effects -- is GL code, and rewriting it for Vulkan is not on the table. So keep rendering in GL and change only the final present: this device wraps a VulkanWindow as a TwkGLF::GLVideoDevice, so everything above it runs unchanged while the frame reaches the screen through a Vulkan swapchain that can actually carry 10 bits per component. RV renders into a GL texture that is exported to Vulkan as a shared image (external memory, plus external semaphores for the GL/Vulkan handoff), which VulkanWindow then blits into the swapchain. The device owns the GL side of that bridge: ensureGLContext() creates the context lazily and joins the Qt::AA_ShareOpenGLContexts group so FTGL font-atlas uploads land somewhere the atlas texture has storage, and the shared image is re-exported when the viewport is resized. The eventWidget is the QWindow's container QWidget, which QTTranslator needs for coordinate mapping (height/mapToGlobal) and mouse grab -- a QWindow alone cannot serve those. Not built yet; the CMake wiring lands with the integration commit. Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
VulkanWindow is the native presentation surface: it owns the instance, device, swapchain and per-frame sync, picks a 10-bit format when the surface offers one, and blits the shared GL image exported by QTVulkanVideoDevice into the swapchain. Presentation is pipelined rather than one-frame-in-flight, so a frame does not end in a blocking vkWaitForFences under FIFO vsync -- that is what kept dragging a dock splitter over the media view from starving the Qt event loop and stalling playback. Resizes reuse the exported image and recreate only the swapchain where they can. VulkanView is the host QWidget that embeds it via createWindowContainer() and owns the video device. This is deliberately the same shape as GLView/GLWindow: keeping the viewport on a native window of its own, rather than on a widget Qt composites into the top-level, keeps the main window off a render-to-texture composite path, and it leaves the two backends sharing one set of embedding and lifetime rules instead of two. VulkanBuildProbe.cpp holds one direct vkGetInstanceProcAddr reference so a Linux/Windows build proves headers and loader link availability up front instead of failing deep in the presentation code. Not built yet; the CMake wiring lands with the integration commit. Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
Builds the Vulkan sources and switches RvDocument over to creating a
VulkanView instead of a GLView on Linux and Windows, with a runtime fallback
to GLView (fallbackVulkanToGLView) when instance/device/swapchain creation
fails so a machine without a usable Vulkan driver still starts.
That fallback is why the rest of this is a spread of small changes rather than
one call site. RvDocument::view() is the GL-only accessor and is null on the
Vulkan path, so backend-neutral callers move to viewWidget() (the host QWidget)
or viewVideoDevice() (the active presentation device):
- MuUICommands/PyUICommands used view() for coordinate mapping, cursor and
focus. Wrapping a null QWidget* for Mu made the Session Manager's event
filter dereference null and crash.
- RvApplication called view()->makeCurrent() and view()->videoDevice()
unconditionally; there is no GL context to make current when presentation
is Vulkan, and the primary display group now comes from the session's
control device.
- DesktopVideoDevice is constructed with a null GL share device on this path;
it falls back to the default surface format and no explicit share context,
which Qt::AA_ShareOpenGLContexts already covers.
main.cpp keeps Qt::AA_ShareOpenGLContexts for a second reason now: with no
GLView to chain from, QTVulkanVideoDevice::ensureGLContext() joins that global
group instead.
The CMake change compiles the Vulkan sources only on Linux and Windows and
promotes WIN32_LEAN_AND_MEAN to target scope, since vulkan_win32.h includes
<windows.h> unconditionally and the legacy winsock.h that drags in collides
with the winsock2.h Qt pulls in. MuUICommands/PyUICommands guarded their GL
includes *inside* `#ifndef WIN32_LEAN_AND_MEAN`, so a target-scope define
would have skipped those includes entirely -- the `#endif` now closes the
define guard before the includes.
Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
RvSettings could fail to persist with AccessError (err: 1) on Windows for two independent reasons. QSettings writes atomically by default -- temp file plus rename -- and the rename fails if security software has RV.ini open at that instant. Writing directly to the file removes that failure point (setAtomicSyncRequired, Qt 5.13+). QSettings with IniFormat also does not create the parent directory on Windows, so the very first sync() on a clean machine fails outright. Create it before use. Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
06a3686 to
11dbba1
Compare
SG-43127 - 10-bit support with Vulkan (Linux and Windows)
Linked issues
none
Summarize your change.
Adds a 10-bit Vulkan presentation backend for both Linux and Windows, enabling 30-bit (RGB10 + A2) display output. This supersedes the Linux-only work in #1310 by extending the same backend to Windows in a single PR.
New rendering path (Vulkan 10-bit):
When the user requests a 10-bit display (RGB10 + A2) and the hardware/driver supports it, RV renders through a Vulkan presentation path instead of the usual OpenGL one. The renderer still draws into an offscreen 16-bit float framebuffer (
GL_RGBA16F) using OpenGL as before.The difference is the final step: instead of presenting that framebuffer through a
GLView, the GL color buffer is handed toQTVulkanVideoDevice, which blits it into a texture whose memory is shared with a Vulkan image through external-memory interop, andVulkanViewpresents it through a true 10-bit (VK_FORMAT_A2B10G10R10_UNORM_PACK32) Vulkan swapchain. The GL-to-Vulkan handoff is zero-copy (shared memory and semaphores, no cross-API blit or CPU copy). This is what gives us real 10-bit output without banding, which the OpenGL path could not do on either platform.Backend selection happens at view-creation time: a 10-bit preference routes to Vulkan when
VulkanView::supports10BitPresentation()returns true, and otherwise everything falls back to the existingGLViewpath unchanged. On the Vulkan pathm_glViewis null, but theDiagnosticsViewis still created: it is an independentQOpenGLWidgetwith its own GL context, so whenm_glViewis null it simply falls back to the global default surface format (OpenGL 2.1) instead of the main view's format.Platform-neutral view abstraction:
RvDocumentis decoupled fromGLViewby routing the active view through a genericQWidget* m_viewWidget/viewWidget()accessor instead of hardcodingm_glViewat every shared call site (focus, geometry, stacked-layout, popup/menu coordinate mapping).RvApplicationnow tolerates a nullview()/GL context and uses the session's control video device for the primary display group. The GL path is unchanged; this is the shared base for the Vulkan backend (and the same base can be shared with a future macOS Metal backend).Vulkan backend (Linux + Windows):
Adds
VulkanView(a Vulkan swapchain that selects theA2B10G10R10_UNORM_PACK32format) andQTVulkanVideoDevice(offscreen 16F FBO + GL/Vulkan external-memory interop), wired intoRvDocumentbehind#if defined(PLATFORM_LINUX) || defined(PLATFORM_WINDOWS). Most of the backend is platform-neutral and lives in shared.cppfiles; only the GPU-interop handle plumbing differs per platform:VK_KHR_external_memory_fd,VK_KHR_external_semaphore_fd,GL_EXT_memory_object_fd,GL_EXT_semaphore_fd).VK_KHR_external_memory_win32,VK_KHR_external_semaphore_win32,GL_EXT_memory_object_win32,GL_EXT_semaphore_win32).Handle ownership differs between the two: on Linux the FD is duplicated with
dup()and the GL import takes ownership, while on WindowsglImportMemoryWin32HandleEXTdoes not take ownership, so the GL and Vulkan sides each keep and close their own handle. Cleanup paths are written accordingly.When the driver lacks the interop extensions (or the stride cannot be represented as an integer pixel width), the path degrades gracefully:
getSharedImageInforeturns null and a CPU pack-and-upload fallback (VulkanView::presentPixelData) presents through the same Vulkan swapchain, so the output is still 10-bit even when the zero-copy GPU bridge is off. If Vulkan 10-bit presentation is unavailable entirely (or the real surface does not offerA2B10G10R10), it falls back to the OpenGLGLViewpath.Vulkan (headers + loader) is fetched as a managed dependency (
cmake/dependencies/vulkan.cmake) and linked on both Linux and Windows. CMake gates that were previouslyIF(RV_TARGET_LINUX)are widened toIF(RV_TARGET_LINUX OR RV_TARGET_WINDOWS), and Windows linksVulkan::Vulkanwithout pulling in X11. A smallVulkanBuildProbe.cppkeeps a directvkGetInstanceProcAddrreference so a missing loader fails at link time rather than at runtime.Describe the reason for the change.
OpenRV's OpenGL presentation path is limited to 8-bit output on both Linux and Windows, which causes visible banding on 10-bit-capable displays. Vulkan provides reliable access to 30-bit swapchain formats on modern drivers, allowing true 10-bit presentation. The view abstraction was extracted first so the new backend could be plugged in cleanly without disturbing the existing GL path.
Describe what you have tested and on which operating system.
Linux: 10-bit gradient ramps render smoothly through the Vulkan swapchain (
A2B10G10R10_UNORM_PACK32) with the GPU-interop path active. The 8-bit default launch is unchanged and usesGLView.Windows (AMD Radeon 740M, dual monitor): the Vulkan path builds and runs, selects a true
A2B10G10R10_UNORM_PACK32swapchain, and activates the GPU-interop bridge. One important platform caveat surfaced during testing:On the format/colorspace choice:
createSwapchain()selects the firstA2B10G10R10_UNORM_PACK32format the surface offers and uses whichever colorspace is paired with it. On the tested hardware that first match isVK_COLOR_SPACE_SRGB_NONLINEAR_KHR, which is the correct 10-bit target for our sRGB-emitting renderer (the HDR10/scRGB colorspaces would require a separate color-managed re-encode of the shader output and are out of scope here).Add a list of changes, and note any that might need special attention during the review.
RvDocument/RvApplicationview abstraction (m_viewWidget/viewWidget()), shared and platform-neutral.VulkanView(10-bit swapchain) andQTVulkanVideoDevice(16F FBO + GL/Vulkan interop), shared.cppwith per-platform handle plumbing.VulkanBuildProbe.cppto force loader symbol resolution at link time on both platforms.Vulkan::Vulkanwithout X11.GLViewfallback paths.Worth special attention during review:
dup()s the FD and the GL import takes ownership, while the WindowsglImportMemoryWin32HandleEXTimport does not take ownership, so each side must close exactly the handles it owns.glImportMemoryWin32HandleEXT,glImportSemaphoreWin32HandleEXT) are resolved throughwglGetProcAddresson Windows; confirm they are present on the target driver.