From 438de9ce925b5e205e4a0fead96b8b6a8579ce57 Mon Sep 17 00:00:00 2001 From: dfattal Date: Sun, 31 May 2026 12:24:37 -0700 Subject: [PATCH] Per-pixel surround click-through mask (#131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A 2D surround element with a non-rectangular shape (a comic bubble with a triangular tail) could only register a single bounding RECT for click-through (displayxr_set_overlay_surround_rect), so the transparent corners beside the tail caught clicks instead of routing to the desktop. Add displayxr_set_overlay_surround_mask(mask, mask_w, mask_h, dst_x,y,w,h): an owned per-pixel alpha mask of the element's exact shape, RLE-unioned into the SetWindowRgn region by displayxr_set_overlay_hit_mask using the same encoder as the tiger silhouette, mapped over its own dst rect. The empty area beside the shape stays click-through. The surround is flat post-weave 2D (no disparity / per-view union), so callers rasterize the mask directly on the CPU — no GPU silhouette pass. The single-rect API stays for compat; both are unioned in. Windows-only (displayxr_win32.c); macOS resolves via the C# EntryPointNotFound fallback like the existing surround-rect entry point. Co-Authored-By: Claude Opus 4.8 (1M context) --- Runtime/DisplayXRNative.cs | 19 ++++ .../Plugins/Windows/x64/displayxr_unity.dll | Bin 149504 -> 150528 bytes native~/displayxr_hooks.h | 17 +++ native~/displayxr_win32.c | 100 ++++++++++++++++++ 4 files changed, 136 insertions(+) diff --git a/Runtime/DisplayXRNative.cs b/Runtime/DisplayXRNative.cs index e8bec78..cd05f5a 100644 --- a/Runtime/DisplayXRNative.cs +++ b/Runtime/DisplayXRNative.cs @@ -672,6 +672,25 @@ public static extern void displayxr_set_overlay_hit_mask( public static extern void displayxr_set_overlay_surround_rect( int x, int y, int w, int h); + /// + /// (#131) Per-pixel variant of displayxr_set_overlay_surround_rect: + /// register the EXACT shape of a 2D surround element (e.g. a comic + /// bubble with a triangular tail) as an alpha mask (mask_w*mask_h + /// bytes, non-zero = opaque/catch) mapped over the dst rect (overlay + /// client px, top-left). RLE-unioned into the SetWindowRgn region each + /// frame, so the element catches clicks while the empty area beside it + /// (e.g. the corners next to the tail) keeps routing to the desktop — + /// which a single bounding rect can't express. The surround is flat + /// post-weave 2D, so the caller rasterizes the mask directly (no + /// disparity / per-view math). The plugin copies the bytes. Pass + /// mask = IntPtr.Zero or any dim <= 0 to clear. Transparent overlay + /// (hooked) path only. + /// + [DllImport(LibName, CallingConvention = CallingConvention.Cdecl)] + public static extern void displayxr_set_overlay_surround_mask( + IntPtr mask, int mask_w, int mask_h, + int dst_x, int dst_y, int dst_w, int dst_h); + /// /// Read cursor position (overlay-client coords, top-left origin) and /// mouse button state. Designed for transparent overlay mode where diff --git a/Runtime/Plugins/Windows/x64/displayxr_unity.dll b/Runtime/Plugins/Windows/x64/displayxr_unity.dll index 1bd5598fa4c6b1eaec86ca9fc37330d579607281..e6db2b774244718630b7a277e44eb93815c2eaa2 100755 GIT binary patch delta 25189 zcmeHvdt6ji_xIi#1`!x%P(}d}1|1Xy6$K3y40Omr!LZbf?^gJoQynR@C)MTdrHL&*9V9_FxJ;$LTVr+P&djKEPYg4RXfh`!@+Fh^= zO4%e>Jz=F_OT)-e|Em#f4fGk;IE!+J`bF`p?!S2N-q9{7&9I@O{hN$6v@(|8ZD!%P z;&DiR6}q*sSqoh59wQ=qZ?c2U5L&iwK%c)1#%j71X<5}~pgF8H40^iX7oE7neGH$2 zt8R)ng&132^`@cFU$|#g1qYoM;`Ji;^^jTOmj&(_#{S~91?~@w_lSuL+HZ4BGyC;mOo{Y3a^@oiz%o^ZRLaF44BjtUndVr*4hbl0|Gu)S({pTGc- z(Z6cUy+VkFZ1?!gPNFv3tpHxhc5epE&UXI@$jNpG4Co{}WV^c$unL~-9tQZ?;hqKM z7KeKcpu*wa11NO3&mnDq!)+eePIPd%yASLnt`2gK0(>>dy=b6KY#8Kz8&E#TeF!ji zP}RkOpYpI_S)3(*?WoAHC1q~)$6$Ky&2sO_+ALi5s%P#i=ArNP;>@vV_dX+IV;|e) zX_@??<2Npxn?%ugw#ONgV_}s2yR=RsdZV!=>UbQ>-Wg+@? ztt!Y1_7|&qR}~GBn6s;VAd`AvsHoB)2WrzzQ?)Qq?i$fvquZlW|kml~oMO_ltgrL`6hjQFeUQ;~W zQQ-)8xaI_699(nEnOhm0)bm*c`dwj|ndPo5NUWMOqn8jP+Eo2#mdj7nwW{j-^b|k8 ze*~|3XWrZ(@l0b?hjJ4almBt2EO}e(yj=C;k|nJ~pI_aNztmH_d7|pYm-+>Wlt0|Z z*4o7DKU7^`yDCV8eC=MjVXJuHtE!xh&j~T%u>1J??~D20S1tWdbKY*ocMR1pvnAm{ z$lSWgMA9l-3Eb5)`aAdgo07%H2djSFG}=$>I_A#Y+$erI>hAZ^VBtFIe)gky@$gai z2OkZv#2#gA`)Fj!Rt{5dl}1mCqwc^haiZ5zclRx}ZXX?C>`YsSayVQ0!6RUuqcmbT zxYzP!J*p%jBqmp>btuO?t|L{8w$yO3^K19Ot+it1ZgZcYXJLq?hgf*tyQ#<7kQKv#zS@=vXeEd#ft!o2y(bU*|S|+eIv1SC#hdcYfl# zHC5q1w&G&Ot5scpn&vOWzpK`q=o}!vSyff*`7S_QtEhV8d<+*EFS&O&l#2(Kxrbbs zA_A7Vw_ZpQb&C7kg^^T{#h^DE_R>#Csj0ayJH(Wh#iaE_cf-5y|a?B z%e#27aeKJh|1Uj7i`*+4Uld16++D8}h74&-{+VO5dRDA(zkDT~cXU60xwG5<>LlTI zqV#yTylN47mG1YiMvJ7%s>4?g`wO

J{%0KT+^h^%=qM<6_F(>STZZsb6GnDb2mz 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 NZAjf9XATm}{||-W?QQ@7 delta 24021 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 ABme*a diff --git a/native~/displayxr_hooks.h b/native~/displayxr_hooks.h index d09699d..c88bece 100644 --- a/native~/displayxr_hooks.h +++ b/native~/displayxr_hooks.h @@ -269,6 +269,23 @@ DISPLAYXR_EXPORT void displayxr_set_overlay_hit_mask(const uint8_t *mask, DISPLAYXR_EXPORT void displayxr_set_overlay_surround_rect(int x, int y, int w, int h); +/// (#131) Per-pixel variant of displayxr_set_overlay_surround_rect: register the +/// EXACT shape of a 2D surround element (e.g. a comic bubble with a triangular +/// tail) as an alpha mask (mask_w*mask_h bytes, non-zero = opaque/catch), mapped +/// over the dst rect (overlay client px, top-left). It is RLE'd and UNION-ed into +/// the SetWindowRgn region built by displayxr_set_overlay_hit_mask each frame, so +/// the element catches clicks while the empty area beside/around it (including the +/// corners next to a triangular tail) keeps routing past to the desktop — which a +/// single bounding rect cannot express. The surround is flat post-weave 2D, so +/// the caller rasterizes the mask directly (no disparity / per-view math). The +/// plugin copies the bytes. Pass mask=NULL or any dim <=0 to clear. Coexists with +/// the rect API (both are unioned in); callers using the mask should clear the +/// rect. Takes effect on the next hit-mask update. +DISPLAYXR_EXPORT void displayxr_set_overlay_surround_mask(const uint8_t *mask, + int mask_w, int mask_h, + int dst_x, int dst_y, + int dst_w, int dst_h); + /// (issue #57) Returns 1 if the OS foreground window belongs to our process, /// 0 otherwise. Use to gate input handlers (WASD etc.) that should be /// inactive when the user has clicked through the overlay to another app. diff --git a/native~/displayxr_win32.c b/native~/displayxr_win32.c index d81c63a..a7724d6 100644 --- a/native~/displayxr_win32.c +++ b/native~/displayxr_win32.c @@ -120,9 +120,28 @@ static int s_hit_mask_active = 0; // catch clicks even though it sits outside the 3D silhouette. UNION-ed into the // SetWindowRgn region built by displayxr_set_overlay_hit_mask each frame. Invalid // by default (no bubble) so empty surround keeps routing clicks to the desktop. +// +// The rect is a coarse bounding box; for a non-rectangular surround element (a +// comic bubble with a triangular tail) it would catch clicks in the empty corners +// beside the shape. The surround MASK below supersedes it: a per-pixel alpha of +// the actual shape, RLE'd into the region exactly like the tiger silhouette — but +// flat 2D (no disparity / no per-view union), so the caller can rasterize it on +// the CPU. When a mask is set the caller should clear the rect (both are unioned +// in if both are valid). The rect path stays for older callers / compat. static int s_surround_rect_valid = 0; static RECT s_surround_rect = {0, 0, 0, 0}; +// (#131) Per-pixel surround shape mask (non-zero = opaque/catch). Owned copy, +// mapped over s_surround_mask_dst (overlay client px, top-left) when the region is +// rebuilt. NULL/invalid by default. Unlike the tiger hit-mask (which maps into the +// canvas sub-rect and is owned by the silhouette each frame), this maps wherever +// the caller places its 2D element in the full surround and is unioned in. +static uint8_t *s_surround_mask = NULL; +static int s_surround_mask_w = 0; +static int s_surround_mask_h = 0; +static RECT s_surround_mask_dst = {0, 0, 0, 0}; +static int s_surround_mask_valid = 0; + // ============================================================================ // Shell mode detection // ============================================================================ @@ -1352,6 +1371,46 @@ displayxr_set_overlay_hit_mask(const uint8_t *mask, int mask_w, int mask_h, rects[n++] = s_surround_rect; } + // (#131) Union the per-pixel surround mask (e.g. the exact rounded-bubble + + // triangular-tail shape), RLE'd into rects over its dst rect with the same + // outward edge rounding as the tiger silhouette above. This is what makes the + // empty area BESIDE a non-rectangular tail route clicks through — a single + // bounding rect can't express that. Flat 2D: the caller hands us the shape + // directly (no view/disparity math, since the surround is post-weave). + if (s_surround_mask_valid && s_surround_mask != NULL) { + LONG sdx = s_surround_mask_dst.left; + LONG sdy = s_surround_mask_dst.top; + LONG sdw = s_surround_mask_dst.right - s_surround_mask_dst.left; + LONG sdh = s_surround_mask_dst.bottom - s_surround_mask_dst.top; + int smw = s_surround_mask_w, smh = s_surround_mask_h; + for (int my = 0; my < smh; my++) { + const uint8_t *row = s_surround_mask + (size_t)my * (size_t)smw; + int top = (int)(sdy + (LONGLONG)my * sdh / smh); + int bottom = (int)(sdy + ((LONGLONG)(my + 1) * sdh + smh - 1) / smh); + int mx = 0; + while (mx < smw) { + while (mx < smw && row[mx] == 0) mx++; + if (mx >= smw) break; + int x0 = mx; + while (mx < smw && row[mx] != 0) mx++; + int x1 = mx; + if (n >= cap) { + int new_cap = cap * 2; + RECT *nr = (RECT *)realloc(rects, + (size_t)new_cap * sizeof(RECT)); + if (nr == NULL) { free(rects); return; } + rects = nr; + cap = new_cap; + } + rects[n].left = (LONG)(sdx + (LONGLONG)x0 * sdw / smw); + rects[n].top = (LONG)top; + rects[n].right = (LONG)(sdx + ((LONGLONG)x1 * sdw + smw - 1) / smw); + rects[n].bottom = (LONG)bottom; + n++; + } + } + } + HRGN rgn = NULL; if (n == 0) { // Empty silhouette: 0x0 rect = nothing-catches region. @@ -1430,6 +1489,47 @@ displayxr_set_overlay_surround_rect(int x, int y, int w, int h) displayxr_log("[DisplayXR] surround_rect: (%d,%d) %dx%d\n", x, y, w, h); } +void +displayxr_set_overlay_surround_mask(const uint8_t *mask, int mask_w, int mask_h, + int dst_x, int dst_y, int dst_w, int dst_h) +{ + // (#131) Store a per-pixel surround shape mask (non-zero = opaque/catch), + // mapped over [dst_x,dst_y,dst_w,dst_h] in overlay client px when the window + // region is rebuilt by displayxr_set_overlay_hit_mask. Owned copy so the + // caller's buffer need not outlive the call. NULL/empty clears it. + if (mask == NULL || mask_w <= 0 || mask_h <= 0 || dst_w <= 0 || dst_h <= 0) { + if (s_surround_mask_valid) + displayxr_log("[DisplayXR] surround_mask: cleared\n"); + free(s_surround_mask); + s_surround_mask = NULL; + s_surround_mask_w = 0; + s_surround_mask_h = 0; + s_surround_mask_valid = 0; + return; + } + + size_t bytes = (size_t)mask_w * (size_t)mask_h; + uint8_t *copy = (uint8_t *)malloc(bytes); + if (copy == NULL) { + displayxr_log("[DisplayXR] surround_mask: alloc failed (%dx%d)\n", + mask_w, mask_h); + return; + } + memcpy(copy, mask, bytes); + + free(s_surround_mask); + s_surround_mask = copy; + s_surround_mask_w = mask_w; + s_surround_mask_h = mask_h; + s_surround_mask_dst.left = dst_x; + s_surround_mask_dst.top = dst_y; + s_surround_mask_dst.right = dst_x + dst_w; + s_surround_mask_dst.bottom = dst_y + dst_h; + s_surround_mask_valid = 1; + displayxr_log("[DisplayXR] surround_mask: %dx%d -> dst (%d,%d) %dx%d\n", + mask_w, mask_h, dst_x, dst_y, dst_w, dst_h); +} + void displayxr_get_overlay_size(int *width, int *height) {