<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Batuhan Demirbilek</title>
    <description>The latest articles on DEV Community by Batuhan Demirbilek (@batuhan_demirbilek_a5c8dc).</description>
    <link>https://dev.to/batuhan_demirbilek_a5c8dc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3983208%2F4981e69c-7894-4840-aed2-b951cea2fb9d.png</url>
      <title>DEV Community: Batuhan Demirbilek</title>
      <link>https://dev.to/batuhan_demirbilek_a5c8dc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/batuhan_demirbilek_a5c8dc"/>
    <language>en</language>
    <item>
      <title>Putting your live windows on an infinite canvas: the "park &amp; swap" trick (C++ / WGC / D3D11)</title>
      <dc:creator>Batuhan Demirbilek</dc:creator>
      <pubDate>Sat, 13 Jun 2026 21:18:50 +0000</pubDate>
      <link>https://dev.to/batuhan_demirbilek_a5c8dc/putting-your-live-windows-on-an-infinite-canvas-the-park-swap-trick-c-wgc-d3d11-1l9n</link>
      <guid>https://dev.to/batuhan_demirbilek_a5c8dc/putting-your-live-windows-on-an-infinite-canvas-the-park-swap-trick-c-wgc-d3d11-1l9n</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Alt-tab and multiple monitors are how we've juggled windows for 30 years. I wanted something spatial: zoom out, see every window at once; zoom into one, work in it for real; zoom back out. Like a map for your desktop.&lt;/p&gt;

&lt;p&gt;On Linux there's niri and driftwm (they're compositors). On Windows you can't replace the compositor — so the question was: can you do this &lt;em&gt;on top of&lt;/em&gt; Windows, with the real windows, in a single exe?&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 1 (and why it fails)
&lt;/h2&gt;

&lt;p&gt;The naive idea: capture every window, hide the originals off-screen, and draw the captures on a fullscreen surface. When the user "dives" into one, show the real window again.&lt;/p&gt;

&lt;p&gt;This breaks immediately. The moment a window is fully occluded or moved off the visible desktop, DWM stops compositing it — Windows.Graphics.Capture then returns black or stale frames. Your beautiful canvas fills with black tiles.&lt;/p&gt;

&lt;h2&gt;
  
  
  The park &amp;amp; swap trick
&lt;/h2&gt;

&lt;p&gt;The fix is to never fully hide a window:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Each window is captured with Windows.Graphics.Capture (FreeThreaded frame pool, poll-based) into a persistent D3D11 texture.&lt;/li&gt;
&lt;li&gt;The window is "parked" in a 2px-tall visible strip at the bottom of the primary monitor. 2px is enough that DWM keeps compositing it, so the capture stays live — but it's invisible behind the canvas.&lt;/li&gt;
&lt;li&gt;The canvas is a borderless fullscreen D3D11 swapchain. Each window is a 1:1-pixel textured quad placed in world space; pan/zoom is just a camera transform. A D2D/DWrite overlay draws labels, a world-anchored dot grid, the minimap, docks.&lt;/li&gt;
&lt;li&gt;When you zoom past a threshold (or double-click), the &lt;em&gt;real&lt;/em&gt; HWND is moved onto its quad's screen rect and given focus. Now you're typing into the actual window — no input simulation, no proxying. Pull back (a thumb-button press) and it returns to the park strip.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So there are two states per window: parked (you see its live texture on the canvas) and swapped-in (you see and use the real window). The transition is a SetWindowPos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things that bit me
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;IsBorderRequired(false) and the borderless-capture path black out capture on some Windows builds — removed.&lt;/li&gt;
&lt;li&gt;CopySubresourceRegion with content-sized copies + a drain-to-newest loop also produced black tiles; a single TryGetNextFrame + full CopyResource is the proven path.&lt;/li&gt;
&lt;li&gt;Added a constant-buffer field the pixel shader reads, but only bound the cbuffer to the vertex stage — every tile came out alpha=0 (invisible). If a shader stage reads a cbuffer, bind it to that stage.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;A single 559 KB exe (statically linked, no .NET/redist), ~3300 lines in one translation unit. Idle-throttled so it doesn't cook the GPU. It has grown search-and-fly, sticky notes, a minimap, an app launcher, named workspaces.&lt;/p&gt;

&lt;p&gt;Demo + download: &lt;a href="https://github.com/13auth/spatial-canvas" rel="noopener noreferrer"&gt;https://github.com/13auth/spatial-canvas&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer questions about the capture/compositing pipeline.&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>windows</category>
      <category>graphics</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
