From 926ff236d9b13e4779f72f6ce65263c82167bc5b Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Fri, 25 Sep 2026 08:17:05 -0600 Subject: [PATCH 1/9] Support Siwin optional X11 types in OpenGL fallback --- CHANGES.md | 2 + src/figdraw/windowing/siwinshim.nim | 22 ++------- src/figdraw/windowing/siwinx11compat.nim | 58 ++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 17 deletions(-) create mode 100644 src/figdraw/windowing/siwinx11compat.nim diff --git a/CHANGES.md b/CHANGES.md index a562e16..b250977 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,8 @@ ## Unreleased +- Accept Siwin's optional-library X11 types in the OpenGL fallback while + retaining compatibility with earlier Siwin releases. - 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/src/figdraw/windowing/siwinshim.nim b/src/figdraw/windowing/siwinshim.nim index fbdb681..7cc4d06 100644 --- a/src/figdraw/windowing/siwinshim.nim +++ b/src/figdraw/windowing/siwinshim.nim @@ -27,9 +27,9 @@ when defined(linux) or defined(bsd): 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 + from ./siwinx11compat import SiwinGlxDisplay, getX11VisualInfo, freeX11VisualInfo import siwin/platforms/wayland/egl as siWaylandEgl from siwin/platforms/wayland/protocol import Wl_surface, commit import siwin/platforms/wayland/windowOpengl as siWaylandOpenGlWindow @@ -467,7 +467,7 @@ when UseVulkanBackend and UseOpenGlFallback and (defined(linux) or defined(bsd)) initialized: bool case kind: SiwinOpenGlFallbackKind of sogfX11: - x11Display: PDisplay + x11Display: SiwinGlxDisplay x11Drawable: x11Types.Drawable x11Context: siX11Glx.GlxContext of sogfWayland: @@ -578,7 +578,7 @@ 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()) + display = cast[SiwinGlxDisplay](x11Window.nativeDisplayHandle()) drawable = x11Types.Drawable(x11Window.nativeWindowHandle()) renderer.backendState.openGlFallback = fallback fallback.x11Display = display @@ -627,21 +627,9 @@ 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") + let visualInfos = fallback.x11Display.getX11VisualInfo(fallback.x11Drawable) defer: - discard XFree(visualInfos) + freeX11VisualInfo(visualInfos) fallback.x11Context = fallback.x11Display.newGlxContext(visualInfos) fallback.initialized = true fallback.x11Display.makeCurrent(fallback.x11Drawable, fallback.x11Context) diff --git a/src/figdraw/windowing/siwinx11compat.nim b/src/figdraw/windowing/siwinx11compat.nim new file mode 100644 index 0000000..4f8d57c --- /dev/null +++ b/src/figdraw/windowing/siwinx11compat.nim @@ -0,0 +1,58 @@ +## Use the X11 types paired with Siwin's GLX API on each supported version. +import x11/x as x11Types except Window +import x11/[xlib, xutil] +import siwin/platforms/x11/glx as siX11Glx + +when compiles( + siX11Glx.newGlxContext(cast[xlib.PDisplay](nil), cast[xutil.PXVisualInfo](nil)) +): + type SiwinGlxDisplay* = xlib.PDisplay + type SiwinGlxVisualInfo = xutil.PXVisualInfo + + proc getX11VisualInfo*( + display: SiwinGlxDisplay, drawable: x11Types.Drawable + ): SiwinGlxVisualInfo = + var attributes: xlib.XWindowAttributes + if xlib.XGetWindowAttributes(display, drawable, attributes.addr) == 0: + raise newException(ValueError, "Failed to query X11 window visual") + + var + visual = xutil.XVisualInfo(visualid: xlib.XVisualIDFromVisual(attributes.visual)) + count: cint + result = + xutil.XGetVisualInfo(display, xutil.VisualIDMask.clong, visual.addr, count.addr) + if result == nil or count <= 0: + if result != nil: + discard xlib.XFree(result) + raise newException(ValueError, "Failed to resolve X11 visual for OpenGL") + + proc freeX11VisualInfo*(visual: SiwinGlxVisualInfo) = + discard xlib.XFree(visual) + +else: + import siwin/platforms/x11/x11api as siX11Api + + type SiwinGlxDisplay* = siX11Api.PDisplay + type SiwinGlxVisualInfo = siX11Api.PXVisualInfo + + proc getX11VisualInfo*( + display: SiwinGlxDisplay, drawable: x11Types.Drawable + ): SiwinGlxVisualInfo = + var attributes: siX11Api.XWindowAttributes + if siX11Api.XGetWindowAttributes(display, drawable, attributes.addr) == 0: + raise newException(ValueError, "Failed to query X11 window visual") + + var + visual = + siX11Api.XVisualInfo(visualid: siX11Api.XVisualIDFromVisual(attributes.visual)) + count: cint + result = siX11Api.XGetVisualInfo( + display, siX11Api.VisualIDMask.clong, visual.addr, count.addr + ) + if result == nil or count <= 0: + if result != nil: + discard siX11Api.XFree(result) + raise newException(ValueError, "Failed to resolve X11 visual for OpenGL") + + proc freeX11VisualInfo*(visual: SiwinGlxVisualInfo) = + discard siX11Api.XFree(visual) From c7708d5b97778f5c68816c0de33bd8a498665bc7 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Fri, 25 Sep 2026 08:55:51 -0600 Subject: [PATCH 2/9] Simplify Siwin X11 compatibility lookup --- src/figdraw/windowing/siwinx11compat.nim | 78 +++++++++--------------- 1 file changed, 29 insertions(+), 49 deletions(-) diff --git a/src/figdraw/windowing/siwinx11compat.nim b/src/figdraw/windowing/siwinx11compat.nim index 4f8d57c..e2df300 100644 --- a/src/figdraw/windowing/siwinx11compat.nim +++ b/src/figdraw/windowing/siwinx11compat.nim @@ -6,53 +6,33 @@ import siwin/platforms/x11/glx as siX11Glx when compiles( siX11Glx.newGlxContext(cast[xlib.PDisplay](nil), cast[xutil.PXVisualInfo](nil)) ): - type SiwinGlxDisplay* = xlib.PDisplay - type SiwinGlxVisualInfo = xutil.PXVisualInfo - - proc getX11VisualInfo*( - display: SiwinGlxDisplay, drawable: x11Types.Drawable - ): SiwinGlxVisualInfo = - var attributes: xlib.XWindowAttributes - if xlib.XGetWindowAttributes(display, drawable, attributes.addr) == 0: - raise newException(ValueError, "Failed to query X11 window visual") - - var - visual = xutil.XVisualInfo(visualid: xlib.XVisualIDFromVisual(attributes.visual)) - count: cint - result = - xutil.XGetVisualInfo(display, xutil.VisualIDMask.clong, visual.addr, count.addr) - if result == nil or count <= 0: - if result != nil: - discard xlib.XFree(result) - raise newException(ValueError, "Failed to resolve X11 visual for OpenGL") - - proc freeX11VisualInfo*(visual: SiwinGlxVisualInfo) = - discard xlib.XFree(visual) - + import x11/xlib as x11Api + import x11/xutil as x11VisualApi else: - import siwin/platforms/x11/x11api as siX11Api - - type SiwinGlxDisplay* = siX11Api.PDisplay - type SiwinGlxVisualInfo = siX11Api.PXVisualInfo - - proc getX11VisualInfo*( - display: SiwinGlxDisplay, drawable: x11Types.Drawable - ): SiwinGlxVisualInfo = - var attributes: siX11Api.XWindowAttributes - if siX11Api.XGetWindowAttributes(display, drawable, attributes.addr) == 0: - raise newException(ValueError, "Failed to query X11 window visual") - - var - visual = - siX11Api.XVisualInfo(visualid: siX11Api.XVisualIDFromVisual(attributes.visual)) - count: cint - result = siX11Api.XGetVisualInfo( - display, siX11Api.VisualIDMask.clong, visual.addr, count.addr - ) - if result == nil or count <= 0: - if result != nil: - discard siX11Api.XFree(result) - raise newException(ValueError, "Failed to resolve X11 visual for OpenGL") - - proc freeX11VisualInfo*(visual: SiwinGlxVisualInfo) = - discard siX11Api.XFree(visual) + import siwin/platforms/x11/x11api as x11Api + import siwin/platforms/x11/x11api as x11VisualApi + +type SiwinGlxDisplay* = x11Api.PDisplay +type SiwinGlxVisualInfo = x11VisualApi.PXVisualInfo + +proc getX11VisualInfo*( + display: SiwinGlxDisplay, drawable: x11Types.Drawable +): SiwinGlxVisualInfo = + var attributes: x11Api.XWindowAttributes + if x11Api.XGetWindowAttributes(display, drawable, attributes.addr) == 0: + raise newException(ValueError, "Failed to query X11 window visual") + + var + visual = + x11VisualApi.XVisualInfo(visualid: x11Api.XVisualIDFromVisual(attributes.visual)) + count: cint + result = x11VisualApi.XGetVisualInfo( + display, x11VisualApi.VisualIDMask.clong, visual.addr, count.addr + ) + if result == nil or count <= 0: + if result != nil: + discard x11Api.XFree(result) + raise newException(ValueError, "Failed to resolve X11 visual for OpenGL") + +proc freeX11VisualInfo*(visual: SiwinGlxVisualInfo) = + discard x11Api.XFree(visual) From 4e449f66a5af7dc903e305932cfc0cb41b8fdba7 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Fri, 25 Sep 2026 09:07:29 -0600 Subject: [PATCH 3/9] Use optional Siwin X11-XCB bridge --- CHANGES.md | 1 + src/figdraw/vulkan/vulkan_context.nim | 4 ++-- src/figdraw/vulkan/vulkan_utils.nim | 13 ++++++++++--- src/figdraw/windowing/siwinx11compat.nim | 9 +++++++++ tests/tsiwin_fallback_window.nim | 23 +++++++++++++++++++++++ 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b250977..216eb82 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ - Accept Siwin's optional-library X11 types in the OpenGL fallback while retaining compatibility with earlier Siwin releases. +- Use Siwin's optional X11-XCB bridge for Vulkan XCB surfaces when available. - 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/src/figdraw/vulkan/vulkan_context.nim b/src/figdraw/vulkan/vulkan_context.nim index b99a05a..0cb6bae 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( diff --git a/src/figdraw/vulkan/vulkan_utils.nim b/src/figdraw/vulkan/vulkan_utils.nim index 18498f5..f4ec5f6 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 ../windowing/siwinx11compat as siwinX11Compat + + proc x11XcbConnection*(display: pointer): pointer = + siwinX11Compat.x11XcbConnection(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/siwinx11compat.nim b/src/figdraw/windowing/siwinx11compat.nim index e2df300..9d2492a 100644 --- a/src/figdraw/windowing/siwinx11compat.nim +++ b/src/figdraw/windowing/siwinx11compat.nim @@ -8,10 +8,19 @@ when compiles( ): import x11/xlib as x11Api import x11/xutil as x11VisualApi + + proc x11XcbConnection*( + display: pointer + ): pointer {.cdecl, dynlib: "libX11-xcb.so.1", importc: "XGetXCBConnection".} + else: import siwin/platforms/x11/x11api as x11Api import siwin/platforms/x11/x11api as x11VisualApi + proc x11XcbConnection*(display: pointer): pointer = + if x11Api.x11XcbAvailable(): + x11Api.XGetXCBConnection(cast[x11Api.PDisplay](display)) + type SiwinGlxDisplay* = x11Api.PDisplay type SiwinGlxVisualInfo = x11VisualApi.PXVisualInfo diff --git a/tests/tsiwin_fallback_window.nim b/tests/tsiwin_fallback_window.nim index 9f87a76..74ceaab 100644 --- a/tests/tsiwin_fallback_window.nim +++ b/tests/tsiwin_fallback_window.nim @@ -4,6 +4,11 @@ import siwin/platforms import figdraw/figrender import figdraw/windowing/siwinshim +when defined(linux) or defined(bsd): + import std/dynlib + import x11/xlib as xlib + from figdraw/vulkan/vulkan_utils import x11XcbConnection + suite "Siwin OpenGL fallback window selection": test "forced OpenGL always creates an OpenGL window": check usesOpenGlWindowForVulkanFallback(Platform.x11, true) @@ -27,3 +32,21 @@ suite "Siwin OpenGL fallback window selection": ) else: skip() + + test "XCB bridge resolves an existing Xlib display": + when defined(linux) or defined(bsd): + let bridge = loadLib("libX11-xcb.so.1") + if bridge == nil: + skip() + else: + defer: + unloadLib(bridge) + let display = xlib.XOpenDisplay(nil) + if display == nil: + skip() + else: + defer: + discard xlib.XCloseDisplay(display) + check x11XcbConnection(cast[pointer](display)) != nil + else: + skip() From b93d2957f3e1942abbe6905c9dae1fe4e95aa4f3 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Thu, 1 Oct 2026 16:24:16 -0600 Subject: [PATCH 4/9] Return XCB connection from Siwin loader --- src/figdraw/windowing/siwinx11compat.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/figdraw/windowing/siwinx11compat.nim b/src/figdraw/windowing/siwinx11compat.nim index 9d2492a..c0489fc 100644 --- a/src/figdraw/windowing/siwinx11compat.nim +++ b/src/figdraw/windowing/siwinx11compat.nim @@ -19,7 +19,7 @@ else: proc x11XcbConnection*(display: pointer): pointer = if x11Api.x11XcbAvailable(): - x11Api.XGetXCBConnection(cast[x11Api.PDisplay](display)) + result = x11Api.XGetXCBConnection(cast[x11Api.PDisplay](display)) type SiwinGlxDisplay* = x11Api.PDisplay type SiwinGlxVisualInfo = x11VisualApi.PXVisualInfo From e56387578c0b887bc07bc9c455a97cafa5ff7c7e Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Tue, 6 Oct 2026 12:32:17 -0600 Subject: [PATCH 5/9] Accept Siwin uint64 Vulkan surface handles --- src/figdraw/vulkan/vulkan_context.nim | 17 ++++++++--------- src/figdraw/windowing/siwinshim.nim | 13 ++++++------- tests/tvulkan_frame_resources.nim | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/figdraw/vulkan/vulkan_context.nim b/src/figdraw/vulkan/vulkan_context.nim index 0cb6bae..f27b60c 100644 --- a/src/figdraw/vulkan/vulkan_context.nim +++ b/src/figdraw/vulkan/vulkan_context.nim @@ -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/windowing/siwinshim.nim b/src/figdraw/windowing/siwinshim.nim index 7cc4d06..8dc13cf 100644 --- a/src/figdraw/windowing/siwinshim.nim +++ b/src/figdraw/windowing/siwinshim.nim @@ -834,7 +834,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 @@ -842,8 +842,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 @@ -855,18 +854,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/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: From 919d1cab04504c24edb7825e8cbad457a82b1298 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Tue, 6 Oct 2026 13:51:37 -0600 Subject: [PATCH 6/9] Pin merged Siwin and use its optional X11 loader --- CHANGES.md | 7 +++-- figdraw.nimble | 3 +- src/figdraw/windowing/siwinx11compat.nim | 37 +++++++----------------- tests/tsiwin_fallback_window.nim | 30 ++++++++++++++----- 4 files changed, 38 insertions(+), 39 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 216eb82..782ec2f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,9 +2,10 @@ ## Unreleased -- Accept Siwin's optional-library X11 types in the OpenGL fallback while - retaining compatibility with earlier Siwin releases. -- Use Siwin's optional X11-XCB bridge for Vulkan XCB surfaces when available. +- Pin the Siwin feature to the merged platform ABI and optional-library fixes. + Use its X11 loader for OpenGL fallback and the optional X11-XCB bridge. +- 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..6f9ce05 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 optional platform libraries and C ABI fixes (Siwin #56 and #57). + requires "gh:levovix0/siwin#dd39b781df68ecee4ab34b55bab1f9e771fcb23f" feature "vulkan": requires "https://github.com/planetis-m/vulkan#b223dc9" feature "metal": diff --git a/src/figdraw/windowing/siwinx11compat.nim b/src/figdraw/windowing/siwinx11compat.nim index c0489fc..e9fc352 100644 --- a/src/figdraw/windowing/siwinx11compat.nim +++ b/src/figdraw/windowing/siwinx11compat.nim @@ -1,28 +1,13 @@ -## Use the X11 types paired with Siwin's GLX API on each supported version. +## Use Siwin's optional X11 entry points and the types paired with its GLX API. import x11/x as x11Types except Window -import x11/[xlib, xutil] -import siwin/platforms/x11/glx as siX11Glx - -when compiles( - siX11Glx.newGlxContext(cast[xlib.PDisplay](nil), cast[xutil.PXVisualInfo](nil)) -): - import x11/xlib as x11Api - import x11/xutil as x11VisualApi - - proc x11XcbConnection*( - display: pointer - ): pointer {.cdecl, dynlib: "libX11-xcb.so.1", importc: "XGetXCBConnection".} - -else: - import siwin/platforms/x11/x11api as x11Api - import siwin/platforms/x11/x11api as x11VisualApi - - proc x11XcbConnection*(display: pointer): pointer = - if x11Api.x11XcbAvailable(): - result = x11Api.XGetXCBConnection(cast[x11Api.PDisplay](display)) +import siwin/platforms/x11/x11api as x11Api type SiwinGlxDisplay* = x11Api.PDisplay -type SiwinGlxVisualInfo = x11VisualApi.PXVisualInfo +type SiwinGlxVisualInfo = x11Api.PXVisualInfo + +proc x11XcbConnection*(display: pointer): pointer = + if x11Api.x11XcbAvailable(): + result = x11Api.XGetXCBConnection(cast[x11Api.PDisplay](display)) proc getX11VisualInfo*( display: SiwinGlxDisplay, drawable: x11Types.Drawable @@ -32,12 +17,10 @@ proc getX11VisualInfo*( raise newException(ValueError, "Failed to query X11 window visual") var - visual = - x11VisualApi.XVisualInfo(visualid: x11Api.XVisualIDFromVisual(attributes.visual)) + visual = x11Api.XVisualInfo(visualid: x11Api.XVisualIDFromVisual(attributes.visual)) count: cint - result = x11VisualApi.XGetVisualInfo( - display, x11VisualApi.VisualIDMask.clong, visual.addr, count.addr - ) + result = + x11Api.XGetVisualInfo(display, x11Api.VisualIDMask.clong, visual.addr, count.addr) if result == nil or count <= 0: if result != nil: discard x11Api.XFree(result) diff --git a/tests/tsiwin_fallback_window.nim b/tests/tsiwin_fallback_window.nim index 74ceaab..2a510f5 100644 --- a/tests/tsiwin_fallback_window.nim +++ b/tests/tsiwin_fallback_window.nim @@ -5,8 +5,8 @@ import figdraw/figrender import figdraw/windowing/siwinshim when defined(linux) or defined(bsd): - import std/dynlib - import x11/xlib as xlib + import siwin/platforms/x11/x11api as xlib + from figdraw/windowing/siwinx11compat import nil from figdraw/vulkan/vulkan_utils import x11XcbConnection suite "Siwin OpenGL fallback window selection": @@ -33,20 +33,34 @@ suite "Siwin OpenGL fallback window selection": else: skip() - test "XCB bridge resolves an existing Xlib display": + 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 x11XcbConnection(nil) == nil + else: + skip() + + test "Siwin visual queries and XCB bridge accept an existing Xlib display": when defined(linux) or defined(bsd): - let bridge = loadLib("libX11-xcb.so.1") - if bridge == nil: + if not xlib.x11Available(): skip() else: - defer: - unloadLib(bridge) let display = xlib.XOpenDisplay(nil) if display == nil: skip() else: defer: discard xlib.XCloseDisplay(display) - check x11XcbConnection(cast[pointer](display)) != nil + let visual = + siwinx11compat.getX11VisualInfo(display, xlib.XRootWindow(display, 0)) + require visual != nil + siwinx11compat.freeX11VisualInfo(visual) + if xlib.x11XcbAvailable(): + check x11XcbConnection(cast[pointer](display)) != nil + else: + check x11XcbConnection(cast[pointer](display)) == nil else: skip() From 01d17998cfce81e8ba804690bafd6aa93d4482bc Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Tue, 6 Oct 2026 15:04:27 -0600 Subject: [PATCH 7/9] Use only Siwin x11api for X11 fallback types --- src/figdraw/windowing/siwinshim.nim | 8 ++++---- src/figdraw/windowing/siwinx11compat.nim | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/figdraw/windowing/siwinshim.nim b/src/figdraw/windowing/siwinshim.nim index 8dc13cf..e864cca 100644 --- a/src/figdraw/windowing/siwinshim.nim +++ b/src/figdraw/windowing/siwinshim.nim @@ -26,10 +26,10 @@ when defined(linux) or defined(bsd): import std/importutils privateAccess siWaylandWindow.WindowWayland when UseOpenGlFallback: - import x11/x as x11Types except Window import siwin/platforms/x11/glx as siX11Glx import siwin/platforms/x11/windowOpengl as siX11OpenGlWindow - from ./siwinx11compat import SiwinGlxDisplay, getX11VisualInfo, freeX11VisualInfo + from ./siwinx11compat import + SiwinGlxDisplay, SiwinGlxDrawable, getX11VisualInfo, freeX11VisualInfo import siwin/platforms/wayland/egl as siWaylandEgl from siwin/platforms/wayland/protocol import Wl_surface, commit import siwin/platforms/wayland/windowOpengl as siWaylandOpenGlWindow @@ -468,7 +468,7 @@ when UseVulkanBackend and UseOpenGlFallback and (defined(linux) or defined(bsd)) case kind: SiwinOpenGlFallbackKind of sogfX11: x11Display: SiwinGlxDisplay - x11Drawable: x11Types.Drawable + x11Drawable: SiwinGlxDrawable x11Context: siX11Glx.GlxContext of sogfWayland: waylandNativeDisplay: pointer @@ -579,7 +579,7 @@ when UseVulkanBackend and UseOpenGlFallback and (defined(linux) or defined(bsd)) fallback = SiwinOpenGlFallbackState(window: window, kind: sogfX11) x11Window = siX11Window.WindowX11(window) display = cast[SiwinGlxDisplay](x11Window.nativeDisplayHandle()) - drawable = x11Types.Drawable(x11Window.nativeWindowHandle()) + drawable = SiwinGlxDrawable(x11Window.nativeWindowHandle()) renderer.backendState.openGlFallback = fallback fallback.x11Display = display fallback.x11Drawable = drawable diff --git a/src/figdraw/windowing/siwinx11compat.nim b/src/figdraw/windowing/siwinx11compat.nim index e9fc352..49a15d3 100644 --- a/src/figdraw/windowing/siwinx11compat.nim +++ b/src/figdraw/windowing/siwinx11compat.nim @@ -1,16 +1,18 @@ ## Use Siwin's optional X11 entry points and the types paired with its GLX API. -import x11/x as x11Types except Window import siwin/platforms/x11/x11api as x11Api -type SiwinGlxDisplay* = x11Api.PDisplay -type SiwinGlxVisualInfo = x11Api.PXVisualInfo +type + SiwinGlxDisplay* = x11Api.PDisplay + # x11api does not re-export Drawable; derive its XID type from the API. + SiwinGlxDrawable* = typeof(x11Api.XRootWindow(nil, 0)) + SiwinGlxVisualInfo = x11Api.PXVisualInfo proc x11XcbConnection*(display: pointer): pointer = if x11Api.x11XcbAvailable(): result = x11Api.XGetXCBConnection(cast[x11Api.PDisplay](display)) proc getX11VisualInfo*( - display: SiwinGlxDisplay, drawable: x11Types.Drawable + display: SiwinGlxDisplay, drawable: SiwinGlxDrawable ): SiwinGlxVisualInfo = var attributes: x11Api.XWindowAttributes if x11Api.XGetWindowAttributes(display, drawable, attributes.addr) == 0: From b4d77e53371a4e6b2dbbb6cbc93a1456d4734aaf Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Tue, 6 Oct 2026 17:45:31 -0600 Subject: [PATCH 8/9] Use Siwin X11 helpers and remove the compatibility module --- CHANGES.md | 5 ++-- figdraw.nimble | 4 +-- src/figdraw/vulkan/vulkan_utils.nim | 4 +-- src/figdraw/windowing/siwinshim.nim | 17 ++++++------- src/figdraw/windowing/siwinx11compat.nim | 32 ------------------------ tests/tsiwin_fallback_window.nim | 16 ++++++------ 6 files changed, 21 insertions(+), 57 deletions(-) delete mode 100644 src/figdraw/windowing/siwinx11compat.nim diff --git a/CHANGES.md b/CHANGES.md index 782ec2f..9546038 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,8 +2,9 @@ ## Unreleased -- Pin the Siwin feature to the merged platform ABI and optional-library fixes. - Use its X11 loader for OpenGL fallback and the optional X11-XCB bridge. +- Use Siwin's platform ABI fixes and reusable X11 visual/XCB helpers. + Track [Siwin #58](https://github.com/levovix0/siwin/pull/58) while its helpers + are under review. - 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 diff --git a/figdraw.nimble b/figdraw.nimble index 6f9ce05..0d06a49 100644 --- a/figdraw.nimble +++ b/figdraw.nimble @@ -41,8 +41,8 @@ feature "surfer": requires "xkb#b4d50f4cccad1cd9e39d2f5a5e1fef2710edcc31" # TODO: Put this in surfer's manifest. feature "siwin": - # Merged optional platform libraries and C ABI fixes (Siwin #56 and #57). - requires "gh:levovix0/siwin#dd39b781df68ecee4ab34b55bab1f9e771fcb23f" + # X11 helpers on top of the merged platform ABI fixes: Siwin #58. + requires "gh:elcritch/siwin#fix/export-x11-drawable" feature "vulkan": requires "https://github.com/planetis-m/vulkan#b223dc9" feature "metal": diff --git a/src/figdraw/vulkan/vulkan_utils.nim b/src/figdraw/vulkan/vulkan_utils.nim index f4ec5f6..8ba449d 100644 --- a/src/figdraw/vulkan/vulkan_utils.nim +++ b/src/figdraw/vulkan/vulkan_utils.nim @@ -53,10 +53,10 @@ type when defined(linux) or defined(bsd): when defined(features.figdraw.siwin): - import ../windowing/siwinx11compat as siwinX11Compat + import siwin/platforms/x11/x11api as siX11Api proc x11XcbConnection*(display: pointer): pointer = - siwinX11Compat.x11XcbConnection(display) + siX11Api.x11XcbConnection(cast[siX11Api.PDisplay](display)) type VkXlibSurfaceCreateInfoKHRNative* {.bycopy.} = object sType*: VkStructureType diff --git a/src/figdraw/windowing/siwinshim.nim b/src/figdraw/windowing/siwinshim.nim index e864cca..6934e36 100644 --- a/src/figdraw/windowing/siwinshim.nim +++ b/src/figdraw/windowing/siwinshim.nim @@ -28,8 +28,7 @@ when defined(linux) or defined(bsd): when UseOpenGlFallback: import siwin/platforms/x11/glx as siX11Glx import siwin/platforms/x11/windowOpengl as siX11OpenGlWindow - from ./siwinx11compat import - SiwinGlxDisplay, SiwinGlxDrawable, getX11VisualInfo, freeX11VisualInfo + 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: SiwinGlxDisplay - x11Drawable: SiwinGlxDrawable + 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[SiwinGlxDisplay](x11Window.nativeDisplayHandle()) - drawable = SiwinGlxDrawable(x11Window.nativeWindowHandle()) + display = cast[siX11Api.PDisplay](x11Window.nativeDisplayHandle()) + drawable = siX11Api.Drawable(x11Window.nativeWindowHandle()) renderer.backendState.openGlFallback = fallback fallback.x11Display = display fallback.x11Drawable = drawable @@ -627,10 +626,8 @@ when UseVulkanBackend and UseOpenGlFallback and (defined(linux) or defined(bsd)) of sogfX11: let initializeOpenGl = not fallback.initialized if initializeOpenGl: - let visualInfos = fallback.x11Display.getX11VisualInfo(fallback.x11Drawable) - defer: - freeX11VisualInfo(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: diff --git a/src/figdraw/windowing/siwinx11compat.nim b/src/figdraw/windowing/siwinx11compat.nim deleted file mode 100644 index 49a15d3..0000000 --- a/src/figdraw/windowing/siwinx11compat.nim +++ /dev/null @@ -1,32 +0,0 @@ -## Use Siwin's optional X11 entry points and the types paired with its GLX API. -import siwin/platforms/x11/x11api as x11Api - -type - SiwinGlxDisplay* = x11Api.PDisplay - # x11api does not re-export Drawable; derive its XID type from the API. - SiwinGlxDrawable* = typeof(x11Api.XRootWindow(nil, 0)) - SiwinGlxVisualInfo = x11Api.PXVisualInfo - -proc x11XcbConnection*(display: pointer): pointer = - if x11Api.x11XcbAvailable(): - result = x11Api.XGetXCBConnection(cast[x11Api.PDisplay](display)) - -proc getX11VisualInfo*( - display: SiwinGlxDisplay, drawable: SiwinGlxDrawable -): SiwinGlxVisualInfo = - var attributes: x11Api.XWindowAttributes - if x11Api.XGetWindowAttributes(display, drawable, attributes.addr) == 0: - raise newException(ValueError, "Failed to query X11 window visual") - - var - visual = x11Api.XVisualInfo(visualid: x11Api.XVisualIDFromVisual(attributes.visual)) - count: cint - result = - x11Api.XGetVisualInfo(display, x11Api.VisualIDMask.clong, visual.addr, count.addr) - if result == nil or count <= 0: - if result != nil: - discard x11Api.XFree(result) - raise newException(ValueError, "Failed to resolve X11 visual for OpenGL") - -proc freeX11VisualInfo*(visual: SiwinGlxVisualInfo) = - discard x11Api.XFree(visual) diff --git a/tests/tsiwin_fallback_window.nim b/tests/tsiwin_fallback_window.nim index 2a510f5..fe9ac47 100644 --- a/tests/tsiwin_fallback_window.nim +++ b/tests/tsiwin_fallback_window.nim @@ -6,8 +6,7 @@ import figdraw/windowing/siwinshim when defined(linux) or defined(bsd): import siwin/platforms/x11/x11api as xlib - from figdraw/windowing/siwinx11compat import nil - from figdraw/vulkan/vulkan_utils import x11XcbConnection + from figdraw/vulkan/vulkan_utils import nil suite "Siwin OpenGL fallback window selection": test "forced OpenGL always creates an OpenGL window": @@ -39,7 +38,7 @@ suite "Siwin OpenGL fallback window selection": defer: xlib.XGetXCBConnection = original xlib.XGetXCBConnection = nil - check x11XcbConnection(nil) == nil + check vulkan_utils.x11XcbConnection(nil) == nil else: skip() @@ -54,13 +53,12 @@ suite "Siwin OpenGL fallback window selection": else: defer: discard xlib.XCloseDisplay(display) - let visual = - siwinx11compat.getX11VisualInfo(display, xlib.XRootWindow(display, 0)) - require visual != nil - siwinx11compat.freeX11VisualInfo(visual) + let visual = xlib.getWindowVisualInfo(display, xlib.XRootWindow(display, 0)) + check visual.visual != nil + check visual.depth > 0 if xlib.x11XcbAvailable(): - check x11XcbConnection(cast[pointer](display)) != nil + check vulkan_utils.x11XcbConnection(cast[pointer](display)) != nil else: - check x11XcbConnection(cast[pointer](display)) == nil + check vulkan_utils.x11XcbConnection(cast[pointer](display)) == nil else: skip() From 2662543dac98c453e6caf5fd66a89793b1410c20 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Wed, 7 Oct 2026 11:37:06 -0600 Subject: [PATCH 9/9] Use merged upstream Siwin X11 helpers --- CHANGES.md | 3 +-- figdraw.nimble | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9546038..a49f7d0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,8 +3,7 @@ ## Unreleased - Use Siwin's platform ABI fixes and reusable X11 visual/XCB helpers. - Track [Siwin #58](https://github.com/levovix0/siwin/pull/58) while its helpers - are under review. + 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 diff --git a/figdraw.nimble b/figdraw.nimble index 0d06a49..97ef7ce 100644 --- a/figdraw.nimble +++ b/figdraw.nimble @@ -41,8 +41,8 @@ feature "surfer": requires "xkb#b4d50f4cccad1cd9e39d2f5a5e1fef2710edcc31" # TODO: Put this in surfer's manifest. feature "siwin": - # X11 helpers on top of the merged platform ABI fixes: Siwin #58. - requires "gh:elcritch/siwin#fix/export-x11-drawable" + # 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":