1 module gfx.window.xcb;
2 
3 version(linux):
4 
5 import gfx.core.log : LogTag;
6 import gfx.graal : Instance;
7 import gfx.window;
8 import gfx.window.xkeyboard;
9 import xcb.xcb;
10 import xcb.xkb;
11 
12 version (VkXcb)
13 {
14 }
15 else
16 {
17     static assert(false, "gfx.window must be compiled with VkXcb version enabled");
18 }
19 
20 enum gfxXcbLogMask = 0x0800_0000;
21 package immutable gfxXcbLog = LogTag("GFX-XCB");
22 
23 /// List of X atoms that are fetched automatically
24 enum Atom
25 {
26     UTF8_STRING,
27 
28     WM_PROTOCOLS,
29     WM_DELETE_WINDOW,
30     WM_TRANSIENT_FOR,
31     WM_CHANGE_STATE,
32     WM_STATE,
33     _NET_WM_STATE,
34     _NET_WM_STATE_MODAL,
35     _NET_WM_STATE_STICKY,
36     _NET_WM_STATE_MAXIMIZED_VERT,
37     _NET_WM_STATE_MAXIMIZED_HORZ,
38     _NET_WM_STATE_SHADED,
39     _NET_WM_STATE_SKIP_TASKBAR,
40     _NET_WM_STATE_SKIP_PAGER,
41     _NET_WM_STATE_HIDDEN,
42     _NET_WM_STATE_FULLSCREEN,
43     _NET_WM_STATE_ABOVE,
44     _NET_WM_STATE_BELOW,
45     _NET_WM_STATE_DEMANDS_ATTENTION,
46     _NET_WM_STATE_FOCUSED,
47     _NET_WM_NAME,
48 }
49 
50 /// get the response_type field masked for
51 @property ubyte xcbEventType(EvT)(EvT* e)
52 {
53     return (e.response_type & ~0x80);
54 }
55 
56 class XcbDisplay : Display
57 {
58     import gfx.core.rc : atomicRcCode, Rc;
59     import gfx.graal : Backend;
60     import X11.Xlib : XDisplay = Display;
61 
62     mixin(atomicRcCode);
63 
64     private XDisplay *_dpy;
65     private xcb_connection_t* _conn;
66     private xcb_atom_t[Atom] _atoms;
67     private int _mainScreenNum;
68     private uint _xkbFirstEv;
69     private XcbKeyboard _xkb;
70     private xcb_screen_t*[] _screens;
71 
72     private Rc!Instance _instance;
73 
74     private Window[] _windows;
75     private XcbWindow[] _xcbWindows;
76 
77     this(DisplayCreateInfo createInfo)
78     {
79         import std.exception : enforce;
80         import X11.Xlib : XCloseDisplay, XDefaultScreen, XOpenDisplay;
81         import X11.Xlib_xcb : XGetXCBConnection, XSetEventQueueOwner, XCBOwnsEventQueue;
82 
83         gfxXcbLog.trace("opening X display");
84         _dpy = enforce(XOpenDisplay(null));
85         scope(failure) {
86             XCloseDisplay(_dpy);
87         }
88         _conn = enforce(cast(xcb_connection_t*)XGetXCBConnection(_dpy));
89         XSetEventQueueOwner(_dpy, XCBOwnsEventQueue);
90         _mainScreenNum = XDefaultScreen(_dpy);
91 
92         initializeAtoms();
93         initializeScreens();
94         initializeInstance(createInfo);
95 
96         _xkb = new XcbKeyboard(_conn, _xkbFirstEv);
97     }
98 
99     override void dispose()
100     {
101         import X11.Xlib : XCloseDisplay;
102 
103         if (_windows.length) {
104             auto ws = _windows.dup;
105             foreach (w; ws) w.close();
106         }
107         assert(!_windows.length);
108 
109         _xkb.dispose();
110 
111         _instance.unload();
112         gfxXcbLog.trace("closing X display");
113         XCloseDisplay(_dpy);
114     }
115 
116     private void initializeAtoms()
117     {
118         import core.stdc.stdlib : free;
119         import std.conv : to;
120         import std.string : toStringz;
121         import std.traits : EnumMembers;
122 
123         xcb_intern_atom_cookie_t[] cookies;
124         cookies.reserve(EnumMembers!Atom.length);
125 
126         foreach (immutable atom; EnumMembers!Atom) // static foreach
127         {
128             auto name = atom.to!string;
129             cookies ~= xcb_intern_atom(_conn, 1,
130                     cast(ushort)name.length, toStringz(name));
131         }
132 
133         foreach (i, immutable atom; EnumMembers!Atom) // static foreach
134         {
135             immutable name = atom.to!string;
136             xcb_generic_error_t* err;
137             auto reply = xcb_intern_atom_reply(_conn, cookies[i], &err);
138             if (err)
139             {
140                 throw new Exception("failed initializing atom " ~ name ~ ": ",
141                         (*err).to!string);
142             }
143             if (reply.atom == XCB_ATOM_NONE)
144             {
145                 throw new Exception("could not retrieve atom " ~ name);
146             }
147             _atoms[atom] = reply.atom;
148             free(reply);
149         }
150     }
151 
152     private void initializeScreens()
153     {
154         xcb_screen_iterator_t iter;
155         for (iter = xcb_setup_roots_iterator(xcb_get_setup(_conn)); iter.rem;
156                 xcb_screen_next(&iter))
157         {
158             _screens ~= iter.data;
159         }
160     }
161 
162     private void initializeInstance(DisplayCreateInfo createInfo)
163     {
164         assert(!_instance);
165 
166         foreach (b; createInfo.backendCreateOrder) {
167             final switch (b) {
168             case Backend.vulkan:
169                 try {
170                     import gfx.vulkan : createVulkanInstance, debugReportInstanceExtensions,
171                             lunarGValidationLayers, VulkanCreateInfo, vulkanInit;
172                     import gfx.vulkan.wsi : xcbSurfaceInstanceExtensions;
173 
174                     gfxXcbLog.trace("Attempting to instantiate Vulkan");
175                     vulkanInit();
176                     VulkanCreateInfo vci;
177                     vci.mandatoryExtensions = xcbSurfaceInstanceExtensions;
178                     vci.optionalExtensions = createInfo.debugCallbackEnabled ?
179                             debugReportInstanceExtensions : [];
180                     vci.optionalLayers = createInfo.validationEnabled ?
181                             lunarGValidationLayers : [];
182                     _instance = createVulkanInstance(vci);
183                 }
184                 catch (Exception ex) {
185                     gfxXcbLog.warningf("Vulkan is not available. %s", ex.msg);
186                 }
187                 break;
188             case Backend.gl3:
189                 version (GfxGl3)
190                 {
191                     try {
192                         import gfx.core.rc : makeRc;
193                         import gfx.gl3 : GlInstance;
194                         import gfx.gl3.context : GlAttribs;
195                         import gfx.window.xcb.context : XcbGlContext;
196                         gfxXcbLog.trace("Attempting to instantiate OpenGL");
197                         auto w = new XcbWindow(this, null, "", true);
198                         w.show(10, 10);
199                         scope(exit) w.close();
200                         auto ctx = makeRc!XcbGlContext(_dpy, _mainScreenNum, GlAttribs.init, w._win);
201                         gfxXcbLog.trace("Creating an OpenGL instance");
202                         _instance = new GlInstance(ctx);
203                     }
204                     catch (Exception ex) {
205                         gfxXcbLog.warningf("OpenGL is not available. %s", ex.msg);
206                     }
207                     break;
208                 }
209                 else
210                 {
211                     assert(false, "OpenGL3 support is not enabled");
212                 }
213             }
214             if (_instance) break;
215         }
216 
217         if (!_instance) {
218             throw new Exception("Could not instantiate a backend");
219         }
220     }
221 
222     override @property Instance instance() {
223         return _instance;
224     }
225 
226     override @property Window[] windows()
227     {
228         return _windows;
229     }
230 
231     override Window createWindow(in string title)
232     {
233         return new XcbWindow(this, _instance, title, false);
234     }
235 
236     override void pollAndDispatch()
237     {
238         while (true) {
239             auto e = xcb_poll_for_event(_conn);
240             if (!e) break;
241             handleEvent(e);
242         }
243     }
244 
245     private XcbWindow xcbWindow(xcb_window_t win) {
246         foreach(w; _xcbWindows) {
247             if (w._win == win) return w;
248         }
249         return null;
250     }
251 
252     void registerWindow(XcbWindow window) {
253         _windows ~= window;
254         _xcbWindows ~= window;
255     }
256 
257     void unregisterWindow(XcbWindow window) {
258         import std.algorithm : remove;
259         _windows = _windows.remove!(w => w is window);
260         _xcbWindows = _xcbWindows.remove!(w => w is window);
261     }
262 
263     private @property int mainScreenNum()
264     {
265         return _mainScreenNum;
266     }
267 
268     private @property xcb_screen_t* mainScreen()
269     {
270         return _screens[_mainScreenNum];
271     }
272 
273     private void handleEvent(xcb_generic_event_t* e)
274     {
275         immutable xcbType = xcbEventType(e);
276 
277         switch (xcbType)
278         {
279         case XCB_KEY_PRESS:
280             auto ev = cast(xcb_key_press_event_t*)e;
281             auto xcbWin = xcbWindow(ev.event);
282             _xkb.processKeyDown(ev.detail, xcbWin ? xcbWin._onKeyOnHandler : null);
283             break;
284         case XCB_KEY_RELEASE:
285             auto ev = cast(xcb_key_press_event_t*)e;
286             auto xcbWin = xcbWindow(ev.event);
287             _xkb.processKeyUp(ev.detail, xcbWin ? xcbWin._onKeyOffHandler : null);
288             break;
289         case XCB_BUTTON_PRESS:
290             auto ev = cast(xcb_button_press_event_t*)e;
291             auto xcbWin = xcbWindow(ev.event);
292             if (xcbWin && xcbWin._onHandler)
293                 xcbWin._onHandler(MouseEvent(ev.event_x, ev.event_y, _xkb.mods));
294             break;
295         case XCB_BUTTON_RELEASE:
296             auto ev = cast(xcb_button_press_event_t*)e;
297             auto xcbWin = xcbWindow(ev.event);
298             if (xcbWin && xcbWin._offHandler)
299                 xcbWin._offHandler(MouseEvent(ev.event_x, ev.event_y, _xkb.mods));
300             break;
301         case XCB_MOTION_NOTIFY:
302             auto ev = cast(xcb_motion_notify_event_t*)e;
303             auto xcbWin = xcbWindow(ev.event);
304             if (xcbWin && xcbWin._moveHandler)
305                 xcbWin._moveHandler(MouseEvent(ev.event_x, ev.event_y, _xkb.mods));
306             break;
307         case XCB_CONFIGURE_NOTIFY:
308             auto ev = cast(xcb_configure_notify_event_t*)e;
309             auto xcbWin = xcbWindow(ev.event);
310             if (xcbWin) xcbWin.handleConfigureNotify(ev);
311             break;
312         case XCB_PROPERTY_NOTIFY:
313             // auto ev = cast(xcb_configure_notify_event_t*)e;
314             // auto xcbWin = xcbWindow(ev.window);
315             break;
316         case XCB_CLIENT_MESSAGE:
317             auto ev = cast(xcb_client_message_event_t*)e;
318             if (ev.data.data32[0] == atom(Atom.WM_DELETE_WINDOW)) {
319                 auto win = xcbWindow(ev.window);
320                 if (win._onCloseHandler) {
321                     win._closeFlag = win._onCloseHandler();
322                 }
323                 else {
324                     win._closeFlag = true;
325                 }
326             }
327             break;
328         default:
329             if (xcbType == _xkbFirstEv)
330             {
331                 auto genKbd = cast(XkbGenericEvent*)e;
332                 if (genKbd.common.deviceID == _xkb.device)
333                 {
334                     switch (genKbd.common.xkbType)
335                     {
336                     case XCB_XKB_STATE_NOTIFY:
337                         auto es = &genKbd.state;
338                         _xkb.updateState(
339                             es.baseMods, es.latchedMods, es.lockedMods,
340                             es.baseGroup, es.latchedGroup, es.lockedGroup
341                         );
342                         break;
343                     default:
344                         break;
345                     }
346                 }
347             }
348             break;
349         }
350     }
351 
352     private xcb_atom_t atom(Atom atom) const
353     {
354         auto at = (atom in _atoms);
355         if (at)
356             return *at;
357         return XCB_ATOM_NONE;
358     }
359 }
360 
361 private union XkbGenericEvent
362     {
363 
364         struct CommonFields
365         {
366             ubyte response_type;
367             ubyte xkbType;
368             ushort sequence;
369             xcb_timestamp_t time;
370             ubyte deviceID;
371         }
372 
373         CommonFields common;
374         xcb_xkb_new_keyboard_notify_event_t newKbd;
375         xcb_xkb_map_notify_event_t map;
376         xcb_xkb_state_notify_event_t state;
377     }
378 
379 
380 private class XcbKeyboard : XKeyboard
381 {
382     private uint _device;
383 
384     this(xcb_connection_t *connection, out uint xkbFirstEv)
385     {
386         import core.stdc.stdlib : free;
387         import std.exception : enforce;
388         import xkbcommon.x11;
389         import xkbcommon.xkbcommon;
390 
391         xcb_prefetch_extension_data(connection, &xcb_xkb_id);
392 
393         auto reply = xcb_get_extension_data(connection, &xcb_xkb_id);
394         if (!reply || !reply.present) {
395             throw new Exception("XKB extension not supported by X server");
396         }
397         xkbFirstEv = reply.first_event;
398 
399         auto cookie = xcb_xkb_use_extension(connection,
400                 XKB_X11_MIN_MAJOR_XKB_VERSION,
401                 XKB_X11_MIN_MINOR_XKB_VERSION);
402         auto xkbReply = xcb_xkb_use_extension_reply(connection, cookie, null);
403         if (!xkbReply) {
404             throw new Exception("could not get xkb extension");
405         }
406         else if(!xkbReply.supported) {
407             free(xkbReply);
408             throw new Exception("xkb required version not supported");
409         }
410         free(xkbReply);
411 
412         ushort mapParts =
413             XCB_XKB_MAP_PART_KEY_TYPES |
414             XCB_XKB_MAP_PART_KEY_SYMS |
415             XCB_XKB_MAP_PART_MODIFIER_MAP |
416             XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
417             XCB_XKB_MAP_PART_KEY_ACTIONS |
418             XCB_XKB_MAP_PART_KEY_BEHAVIORS |
419             XCB_XKB_MAP_PART_VIRTUAL_MODS |
420             XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP;
421 
422         ushort events =
423             XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
424             XCB_XKB_EVENT_TYPE_MAP_NOTIFY |
425             XCB_XKB_EVENT_TYPE_STATE_NOTIFY;
426 
427         auto cookie2 = xcb_xkb_select_events_checked(
428                 connection, XCB_XKB_ID_USE_CORE_KBD,
429                 events, 0, events, mapParts, mapParts, null);
430         auto err = xcb_request_check(connection, cookie2);
431         if (err) {
432             throw new Exception("failed to select notify events from xcb xkb");
433         }
434 
435         auto ctx = enforce(
436             xkb_context_new(XKB_CONTEXT_NO_FLAGS), "Could not alloc XKB context"
437         );
438         scope(failure) xkb_context_unref(ctx);
439 
440         _device = xkb_x11_get_core_keyboard_device_id(connection);
441         enforce (_device != -1, "Could not get X11 keyboard device id");
442 
443         auto keymap = enforce(
444             xkb_x11_keymap_new_from_device(ctx, connection, _device,
445                     XKB_KEYMAP_COMPILE_NO_FLAGS),
446             "Could not get X11 Keymap");
447         scope(failure) xkb_keymap_unref(keymap);
448 
449         auto state = enforce(
450             xkb_x11_state_new_from_device(keymap, connection, _device),
451             "Could not alloc X11 XKB state"
452         );
453 
454         super(ctx, keymap, state);
455     }
456 
457     @property uint device()
458     {
459         return _device;
460     }
461 }
462 
463 class XcbWindow : Window
464 {
465     import gfx.graal.presentation : Surface;
466 
467     private XcbDisplay _dpy;
468     private Instance _instance;
469     private xcb_window_t _win;
470     private Surface _surface;
471     private string _title;
472     private uint _width;
473     private uint _height;
474     private ResizeHandler _resizeHandler;
475     private MouseHandler _moveHandler;
476     private MouseHandler _onHandler;
477     private MouseHandler _offHandler;
478     private KeyHandler _onKeyOnHandler;
479     private KeyHandler _onKeyOffHandler;
480     private CloseHandler _onCloseHandler;
481     private bool _closeFlag;
482     private bool _dummy;
483 
484     this(XcbDisplay dpy, Instance instance, in string title, bool dummy)
485     {
486         assert(dpy && (dummy || instance));
487         _dpy = dpy;
488         _instance = instance;
489         _title = title;
490         _dummy = dummy;
491     }
492 
493     override @property string title()
494     {
495         return _title;
496     }
497 
498     override void setTitle(in string title)
499     {
500         import std.string : toStringz;
501 
502         if (_win) {
503             xcb_change_property(_dpy._conn, cast(ubyte) XCB_PROP_MODE_REPLACE, _win,
504                     cast(xcb_atom_t) XCB_ATOM_WM_NAME, cast(xcb_atom_t) XCB_ATOM_STRING,
505                     8, cast(uint) title.length, toStringz(title));
506             xcb_change_property(_dpy._conn, cast(ubyte) XCB_PROP_MODE_REPLACE, _win,
507                     cast(xcb_atom_t) XCB_ATOM_WM_ICON_NAME, cast(xcb_atom_t) XCB_ATOM_STRING,
508                     8, cast(uint) title.length, toStringz(title));
509         }
510         _title = title;
511     }
512 
513     override @property void onResize(ResizeHandler handler) {
514         _resizeHandler = handler;
515     }
516     override @property void onMouseMove(MouseHandler handler) {
517         _moveHandler = handler;
518     }
519     override @property void onMouseOn(MouseHandler handler) {
520         _onHandler = handler;
521     }
522     override @property void onMouseOff(MouseHandler handler) {
523         _offHandler = handler;
524     }
525     override @property void onKeyOn(KeyHandler handler) {
526         _onKeyOnHandler = handler;
527     }
528     override @property void onKeyOff(KeyHandler handler) {
529         _onKeyOffHandler = handler;
530     }
531     override @property void onClose(CloseHandler handler) {
532         _onCloseHandler = handler;
533     }
534 
535     override @property Surface surface() {
536         return _surface;
537     }
538 
539     override @property bool closeFlag() const {
540         return _closeFlag;
541     }
542 
543     override @property void closeFlag(in bool flag) {
544         _closeFlag = flag;
545     }
546 
547     override void show(uint width, uint height)
548     {
549         const screen = _dpy.mainScreen;
550 
551         const cmap = xcb_generate_id(_dpy._conn);
552         _win = xcb_generate_id(_dpy._conn);
553 
554         auto visual = drawArgbVisual(screen);
555         const depth = drawVisualDepth(screen, visual.visual_id);
556 
557         xcb_create_colormap(_dpy._conn, XCB_COLORMAP_ALLOC_NONE, cmap, screen.root, visual.visual_id);
558 
559         immutable mask = XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP;
560         const uint[] values = [XCB_BACK_PIXMAP_NONE, 0, cmap, 0];
561 
562         auto cookie = xcb_create_window_checked(_dpy._conn, depth,
563                 _win, screen.root, 50, 50, cast(ushort) width, cast(ushort) height, 0,
564                 XCB_WINDOW_CLASS_INPUT_OUTPUT, visual.visual_id, mask, &values[0]);
565 
566         auto err = xcb_request_check(_dpy._conn, cookie);
567         if (err) {
568             import std.format : format;
569             throw new Exception(format("GFX-XCB: could not create window: %s", err.error_code));
570         }
571 
572         _width = width;
573         _height = height;
574 
575         if (_dummy) {
576             xcb_flush(_dpy._conn);
577             return;
578         }
579 
580         // register regular events
581         {
582             const uint[] attrs = [
583                 XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS
584                 | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_ENTER_WINDOW
585                 | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_POINTER_MOTION
586                 | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_EXPOSURE
587                 | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE, 0
588             ];
589             xcb_change_window_attributes(_dpy._conn, _win,
590                     XCB_CW_EVENT_MASK, &attrs[0]);
591         }
592         // register window close event
593         {
594             const xcb_atom_t[] props = [atom(Atom.WM_DELETE_WINDOW), 0];
595             xcb_change_property(_dpy._conn, XCB_PROP_MODE_REPLACE, _win,
596                     atom(Atom.WM_PROTOCOLS), XCB_ATOM_ATOM, 32, 1, &props[0]);
597         }
598         // setting title
599         setTitle(_title);
600 
601         _dpy.registerWindow(this);
602 
603         xcb_map_window(_dpy._conn, _win);
604         xcb_flush(_dpy._conn);
605 
606         import gfx.graal : Backend;
607         final switch (_instance.backend) {
608         case Backend.vulkan:
609             import gfx.vulkan.wsi : createVulkanXcbSurface;
610             _surface = createVulkanXcbSurface(_instance, _dpy._conn, _win);
611             break;
612         case Backend.gl3:
613             version (GfxGl3)
614             {
615                 import gfx.gl3 : GlInstance;
616                 import gfx.gl3.swapchain : GlSurface;
617 
618                 _surface = new GlSurface(_win);
619                 auto glInst = cast(GlInstance)_instance;
620                 auto ctx = glInst.ctx;
621                 ctx.makeCurrent(_win);
622                 break;
623             }
624             else
625             {
626                 assert(false, "OpenGL3 support is disabled");
627             }
628         }
629     }
630 
631     override void close()
632     {
633         if (_dummy) {
634             if (_win) {
635                 xcb_destroy_window(_dpy._conn, _win);
636                 _win = 0;
637             }
638         }
639         else {
640             if (_win) {
641                 xcb_unmap_window(_dpy._conn, _win);
642                 xcb_destroy_window(_dpy._conn, _win);
643                 xcb_flush(_dpy._conn);
644                 _win = 0;
645             }
646             _dpy.unregisterWindow(this);
647         }
648     }
649 
650     private xcb_atom_t atom(Atom atom) const
651     {
652         return _dpy.atom(atom);
653     }
654 
655     private void handleConfigureNotify(xcb_configure_notify_event_t* e)
656     {
657         if (!_resizeHandler) return;
658         if (e.width != _width || e.height != _height) {
659             _width = e.width;
660             _height = e.height;
661             _resizeHandler(_width, _height);
662         }
663     }
664 }
665 
666 xcb_visualtype_t *drawArgbVisual(const xcb_screen_t *s)
667 {
668     xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(s);
669 
670     if(depth_iter.data) {
671         for(; depth_iter.rem; xcb_depth_next (&depth_iter)) {
672             if(depth_iter.data.depth == 32) {
673                 for(xcb_visualtype_iterator_t visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
674                     visual_iter.rem; xcb_visualtype_next (&visual_iter))
675                 {
676                     if (visual_iter.data && visual_iter.data.class_ == XCB_VISUAL_CLASS_TRUE_COLOR)
677                         return visual_iter.data;
678                 }
679             }
680         }
681     }
682 
683     throw new Exception("could not find a draw visual");
684 }
685 
686 ubyte drawVisualDepth(const xcb_screen_t *s, xcb_visualid_t vis)
687 {
688     xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(s);
689 
690     if(depth_iter.data) {
691         for(; depth_iter.rem; xcb_depth_next (&depth_iter)) {
692             for(xcb_visualtype_iterator_t visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
693                 visual_iter.rem; xcb_visualtype_next (&visual_iter))
694             {
695                 if(vis == visual_iter.data.visual_id)
696                     return depth_iter.data.depth;
697             }
698         }
699     }
700     throw new Exception("could not find a visuals depth");
701 }