diff --git a/CHANGES.md b/CHANGES.md index a562e16..a49f7d0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,10 @@ ## Unreleased +- Use Siwin's platform ABI fixes and reusable X11 visual/XCB helpers. + Pin upstream Siwin with merged [#58](https://github.com/levovix0/siwin/pull/58). +- Accept Siwin's `uint64` Vulkan surfaces without losing their upper bits; + retain pointer inputs for other window backends. - Mark recursive fragment and diagnostic render trees acyclic; rely on Nim inference for other render data. Document the downward ownership contract. - Add cross-thread ARC/ORC render-data coverage and fragment-cycle rejection diff --git a/figdraw.nimble b/figdraw.nimble index b9f3144..97ef7ce 100644 --- a/figdraw.nimble +++ b/figdraw.nimble @@ -41,7 +41,8 @@ feature "surfer": requires "xkb#b4d50f4cccad1cd9e39d2f5a5e1fef2710edcc31" # TODO: Put this in surfer's manifest. feature "siwin": - requires "siwin >= 1.1.2" + # Merged platform ABI fixes and X11 helpers: Siwin #56, #57, and #58. + requires "gh:levovix0/siwin#78bb49c7fd7e5e14590f567e1ccbb07eb5ab6739" feature "vulkan": requires "https://github.com/planetis-m/vulkan#b223dc9" feature "metal": diff --git a/src/figdraw/vulkan/vulkan_context.nim b/src/figdraw/vulkan/vulkan_context.nim index b99a05a..f27b60c 100644 --- a/src/figdraw/vulkan/vulkan_context.nim +++ b/src/figdraw/vulkan/vulkan_context.nim @@ -421,10 +421,10 @@ proc createPresentSurface(ctx: VulkanContext) = let fnPtr = vkGetInstanceProcAddrNative(ctx.instance, "vkCreateXcbSurfaceKHR") if fnPtr.isNil: raise newException(ValueError, "vkCreateXcbSurfaceKHR unavailable") - let xcbConn = XGetXCBConnection(ctx.presentXlibDisplay) + let xcbConn = x11XcbConnection(ctx.presentXlibDisplay) if xcbConn.isNil: raise newException( - ValueError, "XGetXCBConnection returned nil for Vulkan XCB surface" + ValueError, "X11-XCB connection unavailable for Vulkan XCB surface" ) let vkCreateXcbSurfaceKHRNative = cast[VkCreateXcbSurfaceKHRNativeProc](fnPtr) var createInfo = VkXcbSurfaceCreateInfoKHRNative( @@ -1814,12 +1814,11 @@ proc ensureGpuRuntime(ctx: VulkanContext) = vendorId = ctx.driverInfo.vendorId, swapchainProfile = $ctx.activeSwapchainProfile - var queueCreateInfos = - @[ - newVkDeviceQueueCreateInfo( - queueFamilyIndex = ctx.queueFamily, queuePriorities = [1.0'f32] - ) - ] + var queueCreateInfos = @[ + newVkDeviceQueueCreateInfo( + queueFamilyIndex = ctx.queueFamily, queuePriorities = [1.0'f32] + ) + ] if ctx.presentQueueFamily != ctx.queueFamily: queueCreateInfos.add( newVkDeviceQueueCreateInfo( @@ -4209,12 +4208,12 @@ proc instanceHandle*(ctx: VulkanContext): pointer = proc setExternalSurface*( ctx: VulkanContext, - surface: pointer, + surface: pointer | uint64, target: PresentTargetKind, ownedByContext = false, ) = - if surface.isNil: - raise newException(ValueError, "External Vulkan surface pointer is nil") + if cast[uint64](surface) == 0: + raise newException(ValueError, "External Vulkan surface handle is zero") ctx.clearPresentTarget() ctx.presentTargetKind = target ctx.instanceSurfaceHint = ctx.presentTargetKind diff --git a/src/figdraw/vulkan/vulkan_utils.nim b/src/figdraw/vulkan/vulkan_utils.nim index 18498f5..8ba449d 100644 --- a/src/figdraw/vulkan/vulkan_utils.nim +++ b/src/figdraw/vulkan/vulkan_utils.nim @@ -52,6 +52,12 @@ type transferSrcEnabled*: bool when defined(linux) or defined(bsd): + when defined(features.figdraw.siwin): + import siwin/platforms/x11/x11api as siX11Api + + proc x11XcbConnection*(display: pointer): pointer = + siX11Api.x11XcbConnection(cast[siX11Api.PDisplay](display)) + type VkXlibSurfaceCreateInfoKHRNative* {.bycopy.} = object sType*: VkStructureType pNext*: pointer @@ -80,9 +86,10 @@ when defined(linux) or defined(bsd): pSurface: ptr VkSurfaceKHR, ): VkResult {.cdecl.} - proc XGetXCBConnection*( - display: pointer - ): pointer {.cdecl, dynlib: "libX11-xcb.so.1", importc.} + when not defined(features.figdraw.siwin): + proc x11XcbConnection*( + display: pointer + ): pointer {.cdecl, dynlib: "libX11-xcb.so.1", importc: "XGetXCBConnection".} proc findGraphicsQueueFamily*(vk: VulkanDispatch, device: VkPhysicalDevice): int = let families = vk.getQueueFamilyProperties(device) diff --git a/src/figdraw/windowing/siwinshim.nim b/src/figdraw/windowing/siwinshim.nim index fbdb681..6934e36 100644 --- a/src/figdraw/windowing/siwinshim.nim +++ b/src/figdraw/windowing/siwinshim.nim @@ -26,10 +26,9 @@ when defined(linux) or defined(bsd): import std/importutils privateAccess siWaylandWindow.WindowWayland when UseOpenGlFallback: - import x11/x as x11Types except Window - import x11/[xlib, xutil] import siwin/platforms/x11/glx as siX11Glx import siwin/platforms/x11/windowOpengl as siX11OpenGlWindow + import siwin/platforms/x11/x11api as siX11Api import siwin/platforms/wayland/egl as siWaylandEgl from siwin/platforms/wayland/protocol import Wl_surface, commit import siwin/platforms/wayland/windowOpengl as siWaylandOpenGlWindow @@ -467,8 +466,8 @@ when UseVulkanBackend and UseOpenGlFallback and (defined(linux) or defined(bsd)) initialized: bool case kind: SiwinOpenGlFallbackKind of sogfX11: - x11Display: PDisplay - x11Drawable: x11Types.Drawable + x11Display: siX11Api.PDisplay + x11Drawable: siX11Api.Drawable x11Context: siX11Glx.GlxContext of sogfWayland: waylandNativeDisplay: pointer @@ -578,8 +577,8 @@ when UseVulkanBackend and UseOpenGlFallback and (defined(linux) or defined(bsd)) let fallback = SiwinOpenGlFallbackState(window: window, kind: sogfX11) x11Window = siX11Window.WindowX11(window) - display = cast[PDisplay](x11Window.nativeDisplayHandle()) - drawable = x11Types.Drawable(x11Window.nativeWindowHandle()) + display = cast[siX11Api.PDisplay](x11Window.nativeDisplayHandle()) + drawable = siX11Api.Drawable(x11Window.nativeWindowHandle()) renderer.backendState.openGlFallback = fallback fallback.x11Display = display fallback.x11Drawable = drawable @@ -627,22 +626,8 @@ when UseVulkanBackend and UseOpenGlFallback and (defined(linux) or defined(bsd)) of sogfX11: let initializeOpenGl = not fallback.initialized if initializeOpenGl: - var attributes: XWindowAttributes - if fallback.x11Display.XGetWindowAttributes( - fallback.x11Drawable, attributes.addr - ) == 0: - raise newException(ValueError, "Failed to query X11 window visual") - var - visualTemplate = XVisualInfo(visualid: XVisualIDFromVisual(attributes.visual)) - visualCount: cint - let visualInfos = fallback.x11Display.XGetVisualInfo( - VisualIDMask.clong, visualTemplate.addr, visualCount.addr - ) - if visualInfos.isNil or visualCount <= 0: - raise newException(ValueError, "Failed to resolve X11 visual for OpenGL") - defer: - discard XFree(visualInfos) - fallback.x11Context = fallback.x11Display.newGlxContext(visualInfos) + var visualInfo = fallback.x11Display.getWindowVisualInfo(fallback.x11Drawable) + fallback.x11Context = fallback.x11Display.newGlxContext(visualInfo.addr) fallback.initialized = true fallback.x11Display.makeCurrent(fallback.x11Drawable, fallback.x11Context) if siX11Glx.cGlxCurrentContext().isNil: @@ -846,7 +831,7 @@ proc setupBackend*(renderer: FigRenderer, window: Window) = vkCtx.setPresentMetalLayer(renderer.backendState.vulkanMetalLayer.layer) hasPresentTarget = true elif defined(linux) or defined(bsd): - var surface: pointer = nil + let surface = cast[uint64](window.vulkanSurface()) if window of siX11Window.WindowX11SoftwareRendering: siX11Window.WindowX11SoftwareRendering(window).setSoftwarePresentEnabled( false @@ -854,8 +839,7 @@ proc setupBackend*(renderer: FigRenderer, window: Window) = if window of siWaylandWindow.WindowWaylandSoftwareRendering: siWaylandWindow.WindowWaylandSoftwareRendering(window).softwarePresentEnabled = false - surface = window.vulkanSurface() - if not surface.isNil: + if surface != 0: if window of siWaylandWindow.WindowWayland: vkCtx.setExternalSurface( surface, presentTargetWayland, ownedByContext = true @@ -867,18 +851,18 @@ proc setupBackend*(renderer: FigRenderer, window: Window) = ) hasPresentTarget = true elif defined(windows): - let surface = window.vulkanSurface() - if not surface.isNil: + let surface = cast[uint64](window.vulkanSurface()) + if surface != 0: vkCtx.setExternalSurface(surface, presentTargetWin32, ownedByContext = true) hasPresentTarget = true when defined(linux) or defined(bsd): - if surface.isNil and window of siX11Window.WindowX11: + if surface == 0 and window of siX11Window.WindowX11: let x11Window = siX11Window.WindowX11(window) vkCtx.setPresentXlibTarget( x11Window.nativeDisplayHandle(), x11Window.nativeWindowHandle() ) hasPresentTarget = true - elif surface.isNil and window of siWaylandWindow.WindowWayland: + elif surface == 0 and window of siWaylandWindow.WindowWayland: let waylandWindow = siWaylandWindow.WindowWayland(window) vkCtx.setPresentWaylandTarget( waylandWindow.nativeWaylandDisplayHandle(), diff --git a/tests/tsiwin_fallback_window.nim b/tests/tsiwin_fallback_window.nim index 9f87a76..fe9ac47 100644 --- a/tests/tsiwin_fallback_window.nim +++ b/tests/tsiwin_fallback_window.nim @@ -4,6 +4,10 @@ import siwin/platforms import figdraw/figrender import figdraw/windowing/siwinshim +when defined(linux) or defined(bsd): + import siwin/platforms/x11/x11api as xlib + from figdraw/vulkan/vulkan_utils import nil + suite "Siwin OpenGL fallback window selection": test "forced OpenGL always creates an OpenGL window": check usesOpenGlWindowForVulkanFallback(Platform.x11, true) @@ -27,3 +31,34 @@ suite "Siwin OpenGL fallback window selection": ) else: skip() + + test "a missing optional XCB bridge returns nil": + when (defined(linux) or defined(bsd)) and defined(features.figdraw.siwin): + let original = xlib.XGetXCBConnection + defer: + xlib.XGetXCBConnection = original + xlib.XGetXCBConnection = nil + check vulkan_utils.x11XcbConnection(nil) == nil + else: + skip() + + test "Siwin visual queries and XCB bridge accept an existing Xlib display": + when defined(linux) or defined(bsd): + if not xlib.x11Available(): + skip() + else: + let display = xlib.XOpenDisplay(nil) + if display == nil: + skip() + else: + defer: + discard xlib.XCloseDisplay(display) + let visual = xlib.getWindowVisualInfo(display, xlib.XRootWindow(display, 0)) + check visual.visual != nil + check visual.depth > 0 + if xlib.x11XcbAvailable(): + check vulkan_utils.x11XcbConnection(cast[pointer](display)) != nil + else: + check vulkan_utils.x11XcbConnection(cast[pointer](display)) == nil + else: + skip() diff --git a/tests/tvulkan_frame_resources.nim b/tests/tvulkan_frame_resources.nim index 299df8b..c06a921 100644 --- a/tests/tvulkan_frame_resources.nim +++ b/tests/tvulkan_frame_resources.nim @@ -60,6 +60,22 @@ when UseVulkanBackend: copyMem(result.addr, mapped, 4) ctx.vk.unmapMemory(ctx.device, buffer.allocation) + suite "Vulkan external surfaces": + test "accepts integer and pointer handles without losing their upper bits": + let ctx = newContext(atlasSize = 64, maxQuads = 8) + defer: + ctx.clearPresentTarget() + when sizeof(VkSurfaceKHR) == 8: + let handle = 0x123456789ABCDEF0'u64 + ctx.setExternalSurface(handle, presentTargetXlib) + check cast[uint64](ctx.surface) == handle + ctx.setExternalSurface(cast[pointer](handle), presentTargetWayland) + check cast[uint64](ctx.surface) == handle + expect ValueError: + ctx.setExternalSurface(0'u64, presentTargetXlib) + expect ValueError: + ctx.setExternalSurface(nil, presentTargetXlib) + suite "Vulkan atlas packing": test "Vulkan failures remain exceptions in every build mode": expect VulkanError: