Skip to content

feat: SG-43127: SG-43594: 10-bit Vulkan presentation (Linux + Windows)#1319

Open
cedrik-fuoco-adsk wants to merge 17 commits into
AcademySoftwareFoundation:mainfrom
cedrik-fuoco-adsk:vulkna-10bit-windows
Open

feat: SG-43127: SG-43594: 10-bit Vulkan presentation (Linux + Windows)#1319
cedrik-fuoco-adsk wants to merge 17 commits into
AcademySoftwareFoundation:mainfrom
cedrik-fuoco-adsk:vulkna-10bit-windows

Conversation

@cedrik-fuoco-adsk

Copy link
Copy Markdown
Contributor

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 to QTVulkanVideoDevice, which blits it into a texture whose memory is shared with a Vulkan image through external-memory interop, and VulkanView presents 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 existing GLView path unchanged. On the Vulkan path m_glView is null, but the DiagnosticsView is still created: it is an independent QOpenGLWidget with its own GL context, so when m_glView is 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:

RvDocument is decoupled from GLView by routing the active view through a generic QWidget* m_viewWidget / viewWidget() accessor instead of hardcoding m_glView at every shared call site (focus, geometry, stacked-layout, popup/menu coordinate mapping). RvApplication now tolerates a null view()/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 the A2B10G10R10_UNORM_PACK32 format) and QTVulkanVideoDevice (offscreen 16F FBO + GL/Vulkan external-memory interop), wired into RvDocument behind #if defined(PLATFORM_LINUX) || defined(PLATFORM_WINDOWS). Most of the backend is platform-neutral and lives in shared .cpp files; only the GPU-interop handle plumbing differs per platform:

  • Linux uses opaque-FD external memory and semaphores (VK_KHR_external_memory_fd, VK_KHR_external_semaphore_fd, GL_EXT_memory_object_fd, GL_EXT_semaphore_fd).
  • Windows uses the Win32-HANDLE equivalents (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 Windows glImportMemoryWin32HandleEXT does 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: getSharedImageInfo returns 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 offer A2B10G10R10), it falls back to the OpenGL GLView path.

Vulkan (headers + loader) is fetched as a managed dependency (cmake/dependencies/vulkan.cmake) and linked on both Linux and Windows. CMake gates that were previously IF(RV_TARGET_LINUX) are widened to IF(RV_TARGET_LINUX OR RV_TARGET_WINDOWS), and Windows links Vulkan::Vulkan without pulling in X11. A small VulkanBuildProbe.cpp keeps a direct vkGetInstanceProcAddr reference 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 uses GLView.

Windows (AMD Radeon 740M, dual monitor): the Vulkan path builds and runs, selects a true A2B10G10R10_UNORM_PACK32 swapchain, and activates the GPU-interop bridge. One important platform caveat surfaced during testing:

  • Windows always composites windowed apps through the Desktop Window Manager (DWM), and DWM only composes at greater than 8 bpc when the target display is in HDR mode. On an SDR-only monitor, DWM truncates the 10-bit swapchain to 8 bpc before scanout, so banding remains even though the swapchain, the GL/Vulkan interop, and the AMD link depth are all correctly 10-bit.
  • On an HDR-capable monitor with HDR enabled, the 10-bit swapchain visibly reaches the panel and banding is reduced (confirmed: the surface then advertises HDR colorspaces, a clear signal DWM is in deep-color composition mode).
  • This is a known Windows platform limitation, not a bug in RV. Linux has no mandatory compositor gate, so the same Vulkan code, same GPU, and same monitor deliver 10-bit directly.

On the format/colorspace choice: createSwapchain() selects the first A2B10G10R10_UNORM_PACK32 format the surface offers and uses whichever colorspace is paired with it. On the tested hardware that first match is VK_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 / RvApplication view abstraction (m_viewWidget / viewWidget()), shared and platform-neutral.
  • New VulkanView (10-bit swapchain) and QTVulkanVideoDevice (16F FBO + GL/Vulkan interop), shared .cpp with per-platform handle plumbing.
  • New VulkanBuildProbe.cpp to force loader symbol resolution at link time on both platforms.
  • CMake: Vulkan dependency and link gates widened from Linux-only to Linux + Windows; Windows links Vulkan::Vulkan without X11.
  • Graceful CPU pack-and-upload fallback and Vulkan-to-GLView fallback paths.

Worth special attention during review:

  • Handle-ownership asymmetry between platforms: Linux dup()s the FD and the GL import takes ownership, while the Windows glImportMemoryWin32HandleEXT import does not take ownership, so each side must close exactly the handles it owns.
  • GLEW Win32 import symbols (glImportMemoryWin32HandleEXT, glImportSemaphoreWin32HandleEXT) are resolved through wglGetProcAddress on Windows; confirm they are present on the target driver.
  • No 10-bit Windows surface in CI yet, so the Windows GPU path was verified manually on a 10-bit-capable monitor.

Decouple RvDocument from GLView by routing the active view through a
generic QWidget* m_viewWidget and a viewWidget() accessor, instead of
hardcoding m_glView at every shared call site (focus, geometry,
stacked-layout, popup/menu coordinate mapping in Mu/Py UI commands).

RvApplication now tolerates a null view()/GL context and uses the
session's control video device for the primary display group, so an
alternative presentation backend can be plugged in.

No backend-specific code is introduced here; the GL path behaves exactly
as before. This is the shared base for the Metal (macOS) and Vulkan
(Linux) 10-bit presentation backends.

Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
Builds on the neutral view-abstraction base. Adds VulkanView (a Vulkan
swapchain over a 30-bit X11/Wayland visual) and QTVulkanVideoDevice
(offscreen 16F FBO + GL<->Vulkan zero-copy interop), wired into
RvDocument behind '#if defined(PLATFORM_LINUX)'.

Backend selection is driven by the display-depth preference: a 10-bit
request (RGB10 + A2) routes to Vulkan when VulkanView::supports10Bit
Presentation() is true, otherwise it falls back to the OpenGL GLView.
On the Vulkan path m_glView is null, so DiagnosticsView (a QOpenGLWidget)
is not created.

Vulkan (headers + loader) is fetched as a managed Linux dependency
(cmake/dependencies/vulkan.cmake) and linked unconditionally on Linux.
The shared view plumbing lives in the neutral base; this commit only
adds the Vulkan-specific pieces.

Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
- Replaced platform-specific size policy and content size handling with unified methods for setting active view sizes.
- Introduced new methods: setActiveViewContentSize, setActiveViewMinimumContentSize, and activeViewFirstPaintCompleted to enhance code clarity and maintainability.
- Updated resize logic to utilize the new methods, ensuring consistent behavior across platforms.

This refactor simplifies the management of view sizes and improves the overall structure of the RvDocument class.

Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
- Added QTVulkanVideoDevice, a new class that serves as a GL-to-Vulkan bridge, enabling 10-bit display on Linux by transferring frames from an OpenGL rendering pipeline to a Vulkan swapchain.
- Implemented lifecycle management for VulkanView, including lazy GL context setup and buffer synchronization.
- Enhanced video device API to support Vulkan, ensuring compatibility with existing GLVideoDevice functionality.
- Updated main application to ensure all QOpenGLContexts share resources, preventing issues with font texture uploads.

This addition significantly improves rendering capabilities on Linux platforms, leveraging Vulkan's advanced features.

Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
…ew and QTVulkanVideoDevice

- Added error handling for Vulkan instance creation and memory allocation failures, ensuring proper cleanup and resource management.
- Improved format validation logic to ensure only supported formats are used, with detailed warnings for unsupported configurations.
- Updated comments for clarity regarding format assumptions and fallback mechanisms.

These changes enhance the robustness of the Vulkan integration, improving error reporting and preventing potential crashes due to unsupported formats.

Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
- Updated CMake configuration to fetch Vulkan dependencies for both Linux and Windows, ensuring consistent availability across platforms.
- Improved error handling and format validation in VulkanView and QTVulkanVideoDevice, enhancing robustness and preventing crashes due to unsupported formats.
- Added support for 10-bit presentation on Windows, aligning with existing Linux functionality.
- Refactored Vulkan-related code to streamline platform-specific implementations and improve maintainability.

These changes significantly enhance Vulkan integration, providing better error reporting and expanding functionality for Windows users

Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
- Added a fallback mechanism in RvDocument to switch from VulkanView to GLView upon runtime failures, enhancing stability on Linux and Windows platforms.
- Introduced error handling in VulkanView to request a fallback when Vulkan initialization fails or when the swapchain becomes out of date.
- Updated RvDocument destructor to ensure proper cleanup of Vulkan resources during closure.
- Enhanced VulkanView with methods to manage presentation conditions and handle swapchain recreation.

These changes improve the robustness of the Vulkan integration, ensuring a seamless user experience by gracefully handling failures and maintaining functionality across platforms.

Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
…r insights during development and troubleshooting.

These changes streamline the debugging process and improve code readability, ensuring a more maintainable codebase.

Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
Comment thread src/lib/app/RvPackage/PackageManager.cpp
Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>

@bernie-laberge bernie-laberge left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

- Added QTVulkanVideoDevice class to facilitate rendering from OpenGL to Vulkan, enabling true 10-bit display on Linux.
- Implemented lifecycle management, including lazy GL context setup and buffer synchronization between GL and Vulkan.
- Enhanced VulkanView to support both A2B10G10R10 and A2R10G10B10 formats for improved compatibility.
- Updated logging for better insights into GPU operations and format handling.

These changes significantly enhance Vulkan integration, providing a robust solution for high-quality video rendering across platforms.

Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
…lkan swapchain (VulkanView) fed by a GL↔Vulkan shared image (QTVulkanVideoDevice). That present was fully synchronous and single-frame-in-flight. Every frame ended in a blocking vkWaitForFences under FIFO vsync, and every resize step tore down and re-exported the shared image and recreated the swapchain. This saturated the Qt event loop, so dragging a dock splitter over the media view was choppy and playback would stall during the drag then fast-forward on release.

This change removes that cost in three steps:

- Per-frame sync objects and the GL interop resources become per-in-flight-slot rings.
- The present pipelines to 2 frames in flight, with the fence wait moved from the end of the frame to the start (the throttle is now FIFO back-pressure plus that wait). The present-wait semaphore is tied to the swapchain image, an imagesInFlight fence map avoids semaphore reuse, and a small drain submit keeps the GL/VK semaphore pair balanced when a frame is dropped on OUT_OF_DATE.
- The shared image is allocated grow-only to a capacity, and only the used region is transferred, so a resize within bounds no longer re-exports it.

Format and channel handling, the OUT_OF_DATE-only swapchain recreate policy, and the CPU fallback are all preserved. The result: resize tracks the cursor and playback cadence stays locked to refresh, matching the OpenGL GLView path.

Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
@cedrik-fuoco-adsk

Copy link
Copy Markdown
Contributor Author

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.

Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
@cedrik-fuoco-adsk

Copy link
Copy Markdown
Contributor Author

Added @bernie-laberge fix for Nvidia and optimization for the CPU fallback.

- Introduced a CPU fallback mechanism in QTVulkanVideoDevice to handle scenarios where GPU interop is unavailable.
- Added methods to ensure and clean up CPU fallback targets, allowing for efficient readback of pixel data.
- Enhanced the present function to utilize the CPU fallback, ensuring compatibility across platforms when Vulkan interop fails.

These changes improve the robustness of the Vulkan integration, providing a reliable alternative for rendering when GPU resources are not accessible.

Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants