1 /**
2 * A GUIGraphics is the interface between a plugin client and a IWindow.
3 *
4 * Copyright: Copyright Auburn Sounds 2015 and later.
5 * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6 * Authors:   Guillaume Piolat
7 */
8 module dplug.gui.graphics;
9 
10 import core.stdc.stdio;
11 
12 import std.math;
13 import std.algorithm.comparison;
14 import std.algorithm.sorting;
15 import std.algorithm.mutation;
16 
17 import inteli.emmintrin;
18 
19 import dplug.core.math;
20 import dplug.core.thread;
21 
22 import dplug.client.client;
23 import dplug.client.graphics;
24 import dplug.client.daw;
25 
26 import dplug.window.window;
27 
28 import dplug.graphics.mipmap;
29 
30 import dplug.gui.boxlist;
31 import dplug.gui.context;
32 import dplug.gui.element;
33 import dplug.gui.compositor;
34 import dplug.gui.legacypbr;
35 import dplug.gui.sizeconstraints;
36 import dplug.gui.profiler;
37 
38 /// In the whole package:
39 /// The diffuse maps contains:
40 ///   RGBA = red/green/blue/emissiveness
41 /// The depth maps contains depth (0 being lowest, 65535 highest)
42 /// The material map contains:
43 ///   RGBA = roughness / metalness / specular / unused
44 
45 alias RMSP = RGBA; // reminder
46 
47 // Uncomment to enter the marvellous world of dirty rectangles.
48 //debug = resizing;
49 
50 // A GUIGraphics is the interface between a plugin client and a IWindow.
51 // It is also an UIElement and the root element of the plugin UI hierarchy.
52 // You have to derive it to have a GUI.
53 // It dispatches window events to the GUI hierarchy.
54 class GUIGraphics : UIElement, IGraphics
55 {
56 nothrow:
57 @nogc:
58 
59     // Max size of tiles when doing the expensive PBR compositing step.
60     // Difficult trade-off in general: not launching threads (one tile) might be better on low-powered devices and 
61     // in case of a large number of opened UI.
62     // But: having too large tiles also makes a visible delay when operating a single UI, even with only two threads.
63     enum PBR_TILE_MAX_WIDTH = 64;
64     enum PBR_TILE_MAX_HEIGHT = 64;
65 
66     this(SizeConstraints sizeConstraints, UIFlags flags)
67     {
68         _sizeConstraints = sizeConstraints;
69 
70         _uiContext = mallocNew!UIContext(this);
71         super(_uiContext, flags);
72 
73         _windowListener = mallocNew!WindowListener(this);
74 
75         _window = null;
76 
77         // Find what size the UI should at first opening.
78         _sizeConstraints.suggestDefaultSize(&_currentUserWidth, &_currentUserHeight);
79         _currentLogicalWidth = _currentUserWidth;
80         _currentLogicalHeight = _currentUserHeight;
81 
82         int numThreads = 0; // auto
83 
84         // Was lowered to 2 in October 2018 to save CPU usage.
85         // Now in Jan 2023, increased to 3 to have a bit smoother PBR.
86         // FUTURE: could make that 4 eventually, see Issue #752. This has minimal memory and CPU 
87         // costs, but is worse on slower plugins.
88         int maxThreads = 3;
89         _threadPool = mallocNew!ThreadPool(numThreads, maxThreads);
90 
91         // Build the compositor
92         {
93             CompositorCreationContext compositorContext;
94             compositorContext.threadPool = _threadPool;
95             _compositor = buildCompositor(&compositorContext);
96         }
97 
98         _rectsToUpdateDisjointedRaw = makeVec!box2i;
99         _rectsToUpdateDisjointedPBR = makeVec!box2i;
100         _rectsTemp = makeVec!box2i;
101 
102         _updateRectScratch[0] = makeVec!box2i;
103         _updateRectScratch[1] = makeVec!box2i;
104 
105         _rectsToComposite = makeVec!box2i;
106         _rectsToCompositeDisjointed = makeVec!box2i;
107         _rectsToCompositeDisjointedTiled = makeVec!box2i;
108 
109         _rectsToDisplay = makeVec!box2i;
110         _rectsToDisplayDisjointed = makeVec!box2i;
111 
112         _rectsToResize = makeVec!box2i;
113         _rectsToResizeDisjointed = makeVec!box2i;
114 
115         _elemsToDrawRaw = makeVec!UIElement;
116         _elemsToDrawPBR = makeVec!UIElement;
117         _sortScratchBuf = makeVec!UIElement;
118 
119         _diffuseMap = mallocNew!(Mipmap!RGBA)();
120         _materialMap = mallocNew!(Mipmap!RGBA)();
121         _depthMap = mallocNew!(Mipmap!L16)();
122 
123         _compositedBuffer = mallocNew!(OwnedImage!RGBA)();
124         _renderedBuffer = mallocNew!(OwnedImage!RGBA)();
125     }
126 
127     // Don't like the default rendering? Override this function and make another compositor.
128     ICompositor buildCompositor(CompositorCreationContext* context)
129     {
130         return mallocNew!PBRCompositor(context);
131     }
132 
133     final ICompositor compositor()
134     {
135         return _compositor;
136     }
137 
138     ~this()
139     {
140         closeUI();
141         _uiContext.destroyFree();
142 
143         _threadPool.destroyFree();
144 
145         _compositor.destroyFree();
146         _diffuseMap.destroyFree();
147         _materialMap.destroyFree();
148         _depthMap.destroyFree();
149 
150         _windowListener.destroyFree();
151 
152         destroyFree(_compositedBuffer);
153         destroyFree(_renderedBuffer);
154 
155         alignedFree(_resizedBuffer, 16);
156     }
157 
158     // <IGraphics implementation>
159 
160     override void* openUI(void* parentInfo,
161                           void* controlInfo,
162                           IClient client,
163                           GraphicsBackend backend)
164     {
165         _client = client;
166 
167         WindowBackend wbackend = void;
168         final switch(backend)
169         {
170             case GraphicsBackend.autodetect: wbackend = WindowBackend.autodetect; break;
171             case GraphicsBackend.win32: wbackend = WindowBackend.win32; break;
172             case GraphicsBackend.cocoa: wbackend = WindowBackend.cocoa; break;
173             case GraphicsBackend.carbon: wbackend = WindowBackend.carbon; break;
174             case GraphicsBackend.x11: wbackend = WindowBackend.x11; break;
175         }
176 
177         position = box2i(0, 0, _currentUserWidth, _currentUserHeight);
178 
179         // Sets the whole UI dirty.
180         // This needs to be done _before_ window creation, else there could be a race
181         // displaying partial updates to the UI.
182         setDirtyWhole(UILayer.allLayers);
183 
184         // We create this window each time.
185         _window = createWindow(WindowUsage.plugin, parentInfo, controlInfo, _windowListener, wbackend, _currentLogicalWidth, _currentLogicalHeight);
186 
187         version(Dplug_ProfileUI) profiler.category("ui").instant("Open UI");
188 
189         return _window.systemHandle();
190     }
191 
192     override void closeUI()
193     {
194         // Destroy window.
195         if (_window !is null)
196         {
197             version(Dplug_ProfileUI)
198             {
199                 profiler.category("ui").instant("Close UI");
200             }
201 
202             _window.destroyFree();
203             _window = null;
204         }
205         _client = null;
206     }
207 
208     override void getGUISize(int* widthLogicalPixels, int* heightLogicalPixels)
209     {
210         *widthLogicalPixels = _currentLogicalWidth;
211         *heightLogicalPixels = _currentLogicalHeight;
212     }
213 
214     override bool isResizeable()
215     {
216         return isUIResizable();
217     }
218 
219     override void getMaxSmallerValidSize(int* inoutWidth, int* inoutHeight)
220     {
221         _sizeConstraints.getMaxSmallerValidSize(inoutWidth, inoutHeight);
222     }
223 
224     override void getNearestValidSize(int* inoutWidth, int* inoutHeight)
225     {
226         _sizeConstraints.getNearestValidSize(inoutWidth, inoutHeight);
227     }
228 
229     override bool nativeWindowResize(int newWidthLogicalPixels, int newHeightLogicalPixels)
230     {
231         // If it's already the same logical size, nothing to do.
232         if ( (newWidthLogicalPixels == _currentLogicalWidth)
233              &&  (newHeightLogicalPixels == _currentLogicalHeight) )
234             return true;
235 
236         // Issue #669.
237         // Can't resize a non-existing window, return failure.
238         // Hosts where this is needed: VST3PluginTestHost
239         // It calls onSize way too soon.
240         if (_window is null)
241             return false;
242 
243         // Here we request the native window to resize.
244         // The actual resize will be received by the window listener, later.
245         return _window.requestResize(newWidthLogicalPixels, newHeightLogicalPixels, false);
246     }
247 
248     // </IGraphics implementation>
249 
250     // This class is only here to avoid name conflicts between
251     // UIElement and IWindowListener methods :|
252     // Explicit outer to avoid emplace crashing
253     static class WindowListener : IWindowListener
254     {
255     nothrow:
256     @nogc:
257         GUIGraphics outer;
258 
259         this(GUIGraphics outer)
260         {
261             this.outer = outer;
262         }
263 
264         override bool onMouseClick(int x, int y, MouseButton mb, bool isDoubleClick, MouseState mstate)
265         {
266             x -= outer._userArea.min.x;
267             y -= outer._userArea.min.y;
268             bool hitSomething = outer.mouseClick(x, y, mb, isDoubleClick, mstate);
269             if (!hitSomething)
270             {
271                 // Nothing was clicked, nothing is focused anymore
272                 outer._uiContext.setFocused(null);
273             }
274             return hitSomething;
275         }
276 
277         override bool onMouseRelease(int x, int y, MouseButton mb, MouseState mstate)
278         {
279             x -= outer._userArea.min.x;
280             y -= outer._userArea.min.y;
281             outer.mouseRelease(x, y, mb, mstate);
282             return true;
283         }
284 
285         override bool onMouseWheel(int x, int y, int wheelDeltaX, int wheelDeltaY, MouseState mstate)
286         {
287             x -= outer._userArea.min.x;
288             y -= outer._userArea.min.y;
289 
290             // Sends the event to the currently dragged element, if any exists.
291             UIElement dragged = outer._uiContext.dragged;
292             if (dragged !is null)
293             {
294                 box2i pos = dragged._position;
295                 if (dragged.onMouseWheel(x - pos.min.x, y - pos.min.y, wheelDeltaX, wheelDeltaY, mstate))
296                     return true;
297             }
298 
299             return outer.mouseWheel(x, y, wheelDeltaX, wheelDeltaY, mstate);
300         }
301 
302         override void onMouseMove(int x, int y, int dx, int dy, MouseState mstate)
303         {
304             x -= outer._userArea.min.x;
305             y -= outer._userArea.min.y;
306             bool hitSomething = outer.mouseMove(x, y, dx, dy, mstate, false);
307             version(futureMouseDrag)
308             {}
309             else
310             {
311                 if (!hitSomething)
312                 {
313                     // Nothing was mouse-over'ed, nothing is `isMouseOver()` anymore
314                     outer._uiContext.setMouseOver(null);
315                 }
316             }
317         }
318 
319         override void recomputeDirtyAreas()
320         {
321             return outer.recomputeDirtyAreas();
322         }
323 
324         override bool onKeyDown(Key key)
325         {
326             // Sends the event to the last clicked element first
327             if (outer._uiContext.focused !is null)
328                 if (outer._uiContext.focused.onKeyDown(key))
329                     return true;
330 
331             // else to all Elements
332             return outer.keyDown(key);
333         }
334 
335         override bool onKeyUp(Key key)
336         {
337             // Sends the event to the last clicked element first
338             if (outer._uiContext.focused !is null)
339                 if (outer._uiContext.focused.onKeyUp(key))
340                     return true;
341             // else to all Elements
342             return outer.keyUp(key);
343         }
344 
345         /// Returns areas affected by updates.
346         override box2i getDirtyRectangle() nothrow @nogc
347         {
348             box2i r = outer._rectsToResize[].boundingBox();
349 
350             // If _userArea changed recently, mark the whole area as in need of redisplay.
351             if (outer._reportBlackBordersAndResizedAreaAsDirty)
352                 r = r.expand( box2i(0, 0, outer._currentLogicalWidth, outer._currentLogicalHeight) );
353 
354             debug(resizing)
355             {
356                 if (!r.empty)
357                 {
358                     debugLogf("getDirtyRectangle returned rectangle(%d, %d, %d, %d)\n", r.min.x, r.min.y, r.width, r.height);
359                 }
360             }
361 
362             return r;
363         }
364 
365         override ImageRef!RGBA onResized(int width, int height)
366         {
367             return outer.doResize(width, height);
368         }
369 
370         override void onDraw(WindowPixelFormat pf) nothrow @nogc
371         {
372             return outer.doDraw(pf);
373         }
374 
375         override void onMouseCaptureCancelled()
376         {
377             // Stop an eventual drag operation
378             outer._uiContext.stopDragging();
379         }
380 
381         override void onMouseExitedWindow()
382         {
383             // Stop an eventual isMouseOver
384             version(futureMouseDrag)
385             {
386                 if (outer._uiContext.dragged is null)
387                     outer._uiContext.setMouseOver(null);
388             }
389             else
390             {
391                 outer._uiContext.setMouseOver(null);
392             }
393         }
394 
395         override void onAnimate(double dt, double time)
396         {
397             version(Dplug_ProfileUI) outer.profiler.category("ui").begin("animate");
398             outer.animate(dt, time);
399             version(Dplug_ProfileUI) outer.profiler.end();
400         }
401 
402         override MouseCursor getMouseCursor()
403         {
404             return outer._uiContext.getCurrentMouseCursor();
405         }
406     }
407 
408     /// Tune this to tune the trade-off between light quality and speed.
409     /// The default value was tuned by hand on very shiny light sources.
410     /// Too high and processing becomes more expensive.
411     /// Too little and the ligth decay doesn't feel natural.
412     /// IMPORTANT: This should be called only inside your main reflow() or at UI creation time.
413     void setUpdateMargin(int margin = 20) nothrow @nogc
414     {
415         _updateMargin = margin; // theoretically it should dirty every PBR rectangle... hence restricting to reflow().
416     }
417 
418 package:
419 
420     // <resizing support>
421 
422     final float getUIScale()
423     {
424         // There is currently no support for this in Dplug, so it is always 1.0f for now.
425         // The OS _might_ upscale the UI without our knowledge though.
426         return 1.0f;
427     }
428 
429     final float getUserScale()
430     {
431         // There is currently no _userArea resize in Dplug, so it is always 1.0f for now.
432         return 1.0f;
433     }
434 
435     final vec2i getDefaultUISizeInPixels()
436     {
437         int w = 0, h = 0;
438         _sizeConstraints.suggestDefaultSize(&w, &h);
439         return vec2i(w, h);
440     }
441 
442     final vec2i getUISizeInPixelsUser()
443     {
444         return vec2i(_currentUserWidth, _currentUserHeight);
445     }
446 
447     final vec2i getUISizeInPixelsLogical()
448     {
449         return vec2i(_currentLogicalWidth, _currentLogicalHeight);
450     }
451 
452     final vec2i getUISizeInPixelsPhysical()
453     {
454         return getUISizeInPixelsLogical(); // no support yet
455     }
456 
457     final bool requestUIResize(int widthLogicalPixels,
458                                int heightLogicalPixels)
459     {
460         // If it's already the same logical size, nothing to do.
461         if ( (widthLogicalPixels == _currentLogicalWidth)
462             &&  (heightLogicalPixels == _currentLogicalHeight) )
463             return true;
464 
465         bool parentWasResized = _client.requestResize(widthLogicalPixels, heightLogicalPixels);
466 
467         // We are be able to early-exit here in VST3.
468         // This is because once a VST3 host has resized the parent window, it calls a callback
469         // and that leads to `nativeWindowResize` to be called.
470         if (parentWasResized && (_client.getPluginFormat() == PluginFormat.vst3))
471             return true;
472 
473         // Cubase + VST2 + Windows need special treatment to resize parent and grandparent windows manually (Issue #595).
474         bool needResizeParentWindow = false;
475         version(Windows)
476         {
477             if (_client.getPluginFormat() == PluginFormat.vst2 && _client.getDAW() == DAW.Cubase)
478                 needResizeParentWindow = true;
479         }
480 
481         // In VST2, very few hosts also resize the plugin window. We get to do it manually.
482 
483         // Here we request the native window to resize.
484         // The actual resize will be received by the window listener, later.
485         return _window.requestResize(widthLogicalPixels, heightLogicalPixels, needResizeParentWindow);
486     }
487 
488     final void getUINearestValidSize(int* widthLogicalPixels, int* heightLogicalPixels)
489     {
490         // Convert this size to a user width and height
491         int userWidth = cast(int)(0.5f + *widthLogicalPixels * getUserScale());
492         int userHeight = cast(int)(0.5f + *heightLogicalPixels * getUserScale());
493 
494         _sizeConstraints.getNearestValidSize(&userWidth, &userHeight);
495 
496         // Convert back to logical pixels
497         // Note that because of rounding, there might be small problems yet unsolved.
498         *widthLogicalPixels = cast(int)(0.5f + userWidth / getUserScale());
499         *heightLogicalPixels = cast(int)(0.5f + userHeight / getUserScale());
500     }
501 
502     final bool isUIResizable()
503     {
504         // TODO: allow logic resize if internally user area is resampled
505         return _sizeConstraints.isResizable();
506     }
507     // </resizing support>
508 
509 protected:
510 
511     // The link to act on the client through the interface.
512     // Eventually it may supersedes direct usage of the client, or its IHostCommand in UIElements.
513     // Only valid in a openUI/closeUI pair.
514     IClient _client;
515 
516     ICompositor _compositor;
517 
518     UIContext _uiContext;
519 
520     WindowListener _windowListener;
521 
522     // An interface to the underlying window
523     IWindow _window;
524 
525     // Task pool for multi-threaded image work
526     package ThreadPool _threadPool;
527 
528     // Size constraints of this UI.
529     // Currently can only be a fixed size.
530     SizeConstraints _sizeConstraints;
531 
532     // The _external_ size in pixels of the plugin interface.
533     // This is the size seen by the host/window.
534     int _currentLogicalWidth = 0;
535     int _currentLogicalHeight = 0;
536 
537     // The _internal_ size in pixels of our UI.
538     // This is not the same as the size seen by the window ("logical").
539     int _currentUserWidth = 0;
540     int _currentUserHeight = 0;
541 
542     /// the area in logical area where the user area is drawn.
543     box2i _userArea;
544 
545     // Force userArea refresh on first resize.
546     bool _firstResize = true;
547 
548     // if true, it means the whole resize buffer and accompanying black
549     // borders should be redrawn at the next onDraw
550     bool _redrawBlackBordersAndResizedArea;
551 
552     // if true, it means the whole resize buffer and accompanying black
553     // borders should be reported as dirty at the next recomputeDirtyAreas, and until
554     // it is drawn.
555     bool _reportBlackBordersAndResizedAreaAsDirty;
556 
557     // Diffuse color values for the whole UI.
558     Mipmap!RGBA _diffuseMap;
559 
560     // Depth values for the whole UI.
561     Mipmap!L16 _depthMap;
562 
563     // Depth values for the whole UI.
564     Mipmap!RGBA _materialMap;
565 
566     /// The list of areas to be redrawn at the Raw and PBR levels (composited).
567     /// These are accumulated over possibly several calls of `recomputeDirtyRects`
568     /// and cleared by a call to `onDraw`.
569     /// Other lists of areas are purely derived from `_rectsToUpdateDisjointedRaw`
570     /// and `_rectsToUpdateDisjointedPBR`.
571     Vec!box2i _rectsToUpdateDisjointedRaw;
572     ///ditto
573     Vec!box2i _rectsToUpdateDisjointedPBR;
574 
575     // Used to maintain the _rectsToUpdateXXX invariant of no overlap
576     Vec!box2i _rectsTemp;
577 
578     // Same, but temporary variable for mipmap generation
579     Vec!box2i[2] _updateRectScratch;
580 
581     // The areas that must be effectively re-composited.
582     Vec!box2i _rectsToComposite;
583     Vec!box2i _rectsToCompositeDisjointed; // same list, but reorganized to avoid overlap
584     Vec!box2i _rectsToCompositeDisjointedTiled; // same list, but separated in smaller tiles
585 
586     // The areas that must be effectively redisplayed, which also mean the Raw layer is redrawn.
587     Vec!box2i _rectsToDisplay;
588     Vec!box2i _rectsToDisplayDisjointed; // same list, but reorganized to avoid overlap
589 
590     // The areas that must be effectively redisplayed, in logical space (like _userArea).
591     Vec!box2i _rectsToResize;
592     Vec!box2i _rectsToResizeDisjointed;
593 
594     /// The list of UIElement to potentially call `onDrawPBR` on.
595     Vec!UIElement _elemsToDrawRaw;
596 
597     /// The list of UIElement to potentially call `onDrawPBR` on.
598     Vec!UIElement _elemsToDrawPBR;
599 
600     /// The scratch buffer used to sort the two above list.
601     Vec!UIElement _sortScratchBuf;
602 
603     /// Amount of pixels dirty rectangles are extended with.
604     int _updateMargin = 20;
605 
606     /// The composited buffer, before the Raw layer is applied.
607     OwnedImage!RGBA _compositedBuffer = null;
608 
609     /// The rendered framebuffer.
610     /// This is copied from `_compositedBuffer`, then Raw layer is drawn on top.
611     /// Components are reordered there.
612     /// It must be possible to use a Canvas on it.
613     OwnedImage!RGBA _renderedBuffer = null;
614 
615     /// The final framebuffer.
616     /// It is the only buffer to have a size in logical pixels.
617     /// Internally the UI has an "user" size.
618     /// FUTURE: resize from user size to logical size using a resizer,
619     /// to allow better looking DPI without the OS blurry resizing.
620     /// Or to allow higher internal pixel count.
621     ubyte* _resizedBuffer = null;
622 
623     void recomputeDrawLists()
624     {
625         // recompute draw lists
626         _elemsToDrawRaw.clearContents();
627         _elemsToDrawPBR.clearContents();
628         getDrawLists(_elemsToDrawRaw, _elemsToDrawPBR);
629 
630         // Sort by ascending z-order (high z-order gets drawn last)
631         // This sort must be stable to avoid messing with tree natural order.
632         int compareZOrder(in UIElement a, in UIElement b) nothrow @nogc
633         {
634             return a.zOrder() - b.zOrder();
635         }
636         timSort!UIElement(_elemsToDrawRaw[], _sortScratchBuf, &compareZOrder);
637         timSort!UIElement(_elemsToDrawPBR[], _sortScratchBuf, &compareZOrder);
638     }
639 
640     // Useful to convert 16-byte aligned buffer into an ImageRef!RGBA
641     // This was probably still needed because of Issue #693. This was secretly a 
642     // workaround. FUTURE: replace by regular toRef
643     final ImageRef!RGBA toImageRef(ubyte* alignedBuffer, int width, int height)
644     {
645         ImageRef!RGBA ir = void;
646         ir.w = width;
647         ir.h = height;
648         ir.pitch = byteStride(width);
649         ir.pixels = cast(RGBA*)alignedBuffer;
650         return ir;
651     }
652 
653     IProfiler profiler()
654     {
655         return _uiContext.profiler();
656     }
657 
658     void doDraw(WindowPixelFormat pf) nothrow @nogc
659     {
660         version(Dplug_ProfileUI) profiler.category("ui").begin("doDraw");
661 
662         debug(resizing) debugLogf(">doDraw\n");
663 
664         debug(resizing)
665         {
666             foreach(r; _rectsToUpdateDisjointedPBR[])
667             {
668                 debugLogf("  * this will redraw PBR rectangle(%d, %d, %d, %d)\n", r.min.x, r.min.y, r.width, r.height);
669             }
670             foreach(r; _rectsToUpdateDisjointedRaw[])
671             {
672                 debugLogf("  * this will redraw RAW rectangle(%d, %d, %d, %d)\n", r.min.x, r.min.y, r.width, r.height);
673             }
674         }
675 
676         // A. Recompute draw lists
677         // These are the `UIElement`s that _may_ have their onDrawXXX callbacks called.
678 
679         version(Dplug_ProfileUI) profiler.begin("Recompute Draw Lists");
680         recomputeDrawLists();
681         version(Dplug_ProfileUI) profiler.end();
682 
683         // Composite GUI
684         // Most of the cost of rendering is here
685         // B. 1st PASS OF REDRAW
686         // Some UIElements are redrawn at the PBR level
687         version(Dplug_ProfileUI) profiler.begin("Draw Elements PBR");
688         redrawElementsPBR();
689         version(Dplug_ProfileUI) profiler.end();
690 
691         // C. MIPMAPPING
692         version(Dplug_ProfileUI) profiler.begin("Regenerate Mipmaps");
693         regenerateMipmaps();
694         version(Dplug_ProfileUI) profiler.end();
695 
696         // D. COMPOSITING
697         auto compositedRef = _compositedBuffer.toRef();
698 
699         version(Dplug_ProfileUI) profiler.begin("Composite GUI");
700         compositeGUI(compositedRef); // Launch the possibly-expensive Compositor step, which implements PBR rendering
701         version(Dplug_ProfileUI) profiler.end();
702 
703         // E. COPY FROM "COMPOSITED" TO "RENDERED" BUFFER
704         // Copy _compositedBuffer onto _renderedBuffer for every rect that will be changed on display
705         auto renderedRef = _renderedBuffer.toRef();
706         version(Dplug_ProfileUI) profiler.begin("Copy to renderbuffer");
707         foreach(rect; _rectsToDisplayDisjointed[])
708         {
709             auto croppedComposite = compositedRef.cropImageRef(rect);
710             auto croppedRendered = renderedRef.cropImageRef(rect);
711             croppedComposite.blitTo(croppedRendered); // failure to optimize this: 1
712         }
713         version(Dplug_ProfileUI) profiler.end();
714         
715         // F. 2nd PASS OF REDRAW
716         version(Dplug_ProfileUI) profiler.begin("Draw Elements Raw");
717         redrawElementsRaw();
718         version(Dplug_ProfileUI) profiler.end();
719 
720         // G. Reorder components to the right pixel format
721         version(Dplug_ProfileUI) profiler.begin("Component Reorder");
722         reorderComponents(pf);
723         version(Dplug_ProfileUI) profiler.end();
724 
725         // H. Copy updated content to the final buffer. (hint: not actually resizing)
726         version(Dplug_ProfileUI) profiler.begin("Copy content");
727         resizeContent(pf);
728         version(Dplug_ProfileUI) profiler.end();
729 
730         // Only then is the list of rectangles to update cleared,
731         // before calling `doDraw` such work accumulates
732         _rectsToUpdateDisjointedPBR.clearContents();
733         _rectsToUpdateDisjointedRaw.clearContents();
734 
735         version(Dplug_ProfileUI) profiler.end();
736         debug(resizing) debugLogf("<doDraw\n");
737     }
738 
739     void recomputeDirtyAreas() nothrow @nogc
740     {
741         // First we pull dirty rectangles from the UI, for the PBR and Raw layers
742         // Note that there is indeed a race here (the same UIElement could have pushed rectangles in both
743         // at around the same time), but that isn't a problem.
744         context().dirtyListRaw.pullAllRectangles(_rectsToUpdateDisjointedRaw);
745         context().dirtyListPBR.pullAllRectangles(_rectsToUpdateDisjointedPBR);
746 
747         recomputePurelyDerivedRectangles();
748     }
749 
750     void recomputePurelyDerivedRectangles()
751     {
752         // If a resize has been made recently, we need to clip rectangles
753         // in the pending lists to the new size.
754         // All other rectangles are purely derived from those.
755         // PERF: this check is necessary because of #597.
756         //       Solveing this is a long-term quest in itself.
757         box2i validUserArea = rectangle(0, 0, _currentUserWidth, _currentUserHeight);
758         foreach (ref r; _rectsToUpdateDisjointedRaw[])
759         {
760             r = r.intersection(validUserArea);
761         }
762         foreach (ref r; _rectsToUpdateDisjointedPBR[])
763         {
764             r = r.intersection(validUserArea);
765         }
766 
767         // The problem here is that if the window isn't shown there may be duplicates in
768         // _rectsToUpdateDisjointedRaw and _rectsToUpdateDisjointedPBR
769         // (`recomputeDirtyAreas`called multiple times without clearing those arrays),
770         //  so we have to maintain unicity again.
771         // Also duplicate can accumulate in case of two successive onResize (to test: Studio One with continuous resizing plugin)
772         //
773         // PERF: when the window is shown, we could overwrite content of _rectsToUpdateDisjointedRaw/_rectsToUpdateDisjointedPBR?
774         //       instead of doing that.
775         {
776             // Make _rectsToUpdateDisjointedRaw disjointed
777             _rectsTemp.clearContents();
778             removeOverlappingAreas(_rectsToUpdateDisjointedRaw, _rectsTemp);
779             _rectsToUpdateDisjointedRaw.clearContents();
780             _rectsToUpdateDisjointedRaw.pushBack(_rectsTemp);
781             assert(haveNoOverlap(_rectsToUpdateDisjointedRaw[]));
782 
783             // Make _rectsToUpdateDisjointedPBR disjointed
784             _rectsTemp.clearContents();
785             removeOverlappingAreas(_rectsToUpdateDisjointedPBR, _rectsTemp);
786             _rectsToUpdateDisjointedPBR.clearContents();
787             _rectsToUpdateDisjointedPBR.pushBack(_rectsTemp);
788             assert(haveNoOverlap(_rectsToUpdateDisjointedPBR[]));
789         }
790 
791         // Compute _rectsToRender and _rectsToDisplay, purely derived from the above.
792         // Note that they are possibly overlapping collections
793         // _rectsToComposite <- margin(_rectsToUpdateDisjointedPBR)
794         // _rectsToDisplay <- union(_rectsToComposite, _rectsToUpdateDisjointedRaw)
795         {
796             _rectsToComposite.clearContents();
797             foreach(rect; _rectsToUpdateDisjointedPBR)
798             {
799                 assert(rect.isSorted);
800                 assert(!rect.empty);
801                 _rectsToComposite.pushBack( convertPBRLayerRectToRawLayerRect(rect, _currentUserWidth, _currentUserHeight) );
802             }
803 
804             // Compute the non-overlapping version
805             _rectsToCompositeDisjointed.clearContents();
806             removeOverlappingAreas(_rectsToComposite, _rectsToCompositeDisjointed);
807 
808             _rectsToDisplay.clearContents();
809             _rectsToDisplay.pushBack(_rectsToComposite);
810             foreach(rect; _rectsToUpdateDisjointedRaw)
811             {
812                 assert(rect.isSorted);
813                 assert(!rect.empty);
814                 _rectsToDisplay.pushBack( rect );
815             }
816 
817             // Compute the non-overlapping version
818             _rectsToDisplayDisjointed.clearContents();
819             removeOverlappingAreas(_rectsToDisplay, _rectsToDisplayDisjointed);
820         }
821 
822         // Compute _rectsToResize and _rectsToDisplayDisjointed to write resized content to (in the logical pixel area).
823         // These rectangle are constrained to update only _userArea.
824         {
825             _rectsToResize.clearContents();
826             foreach(rect; _rectsToDisplay[])
827             {
828                 box2i r = convertUserRectToLogicalRect(rect).intersection(_userArea);
829                 _rectsToResize.pushBack(r);
830             }
831 
832             if (_reportBlackBordersAndResizedAreaAsDirty)
833             {
834                 // Redraw whole resized zone and black borders on next draw, as this will
835                 // be reported to the OS as being repainted.
836                 _redrawBlackBordersAndResizedArea = true;
837             }
838             _rectsToResizeDisjointed.clearContents();
839             removeOverlappingAreas(_rectsToResize, _rectsToResizeDisjointed);
840 
841             // All those rectangles should be strictly in _userArea
842             foreach(r; _rectsToResizeDisjointed)
843                 assert(_userArea.contains(r));
844         }
845     }
846 
847     final box2i convertPBRLayerRectToRawLayerRect(box2i rect, int width, int height) nothrow @nogc
848     {
849         int xmin = rect.min.x - _updateMargin;
850         int ymin = rect.min.y - _updateMargin;
851         int xmax = rect.max.x + _updateMargin;
852         int ymax = rect.max.y + _updateMargin;
853 
854         if (xmin < 0) xmin = 0;
855         if (ymin < 0) ymin = 0;
856         if (xmax > width) xmax = width;
857         if (ymax > height) ymax = height;
858 
859         // This could also happen if an UIElement is moved quickly
860         if (xmax < 0) xmax = 0;
861         if (ymax < 0) ymax = 0;
862         if (xmin > width) xmin = width;
863         if (ymin > height) ymin = height;
864 
865         box2i result = box2i(xmin, ymin, xmax, ymax);
866         assert(result.isSorted);
867         return result;
868     }
869 
870     ImageRef!RGBA doResize(int widthLogicalPixels,
871                            int heightLogicalPixels) nothrow @nogc
872     {
873         version(Dplug_ProfileUI) profiler.category("ui").begin("doResize");
874         debug(resizing) debugLogf(">doResize(%d, %d)\n", widthLogicalPixels, heightLogicalPixels);
875 
876         /// We do receive a new size in logical pixels.
877         /// This is coming from getting the window client area. The reason
878         /// for this resize doesn't matter, we must find a mapping that fits
879         /// between this given logical size and user size.
880 
881         // 1.a Based upon the _sizeConstraints, select a user size in pixels.
882         //     Keep in mind if the _userArea has just moved (just moving the contents elsewhere)
883         //     or if its size has changed (user size), which require a redraw.
884 
885         // Has the logical available size changed?
886         bool logicalSizeChanged = false;
887         if (_currentLogicalWidth != widthLogicalPixels)
888         {
889             _currentLogicalWidth = widthLogicalPixels;
890             logicalSizeChanged = true;
891         }
892         if (_currentLogicalHeight != heightLogicalPixels)
893         {
894             _currentLogicalHeight = heightLogicalPixels;
895             logicalSizeChanged = true;
896         }
897 
898         int newUserWidth = widthLogicalPixels;
899         int newUserHeight = heightLogicalPixels;
900         _sizeConstraints.getMaxSmallerValidSize(&newUserWidth, &newUserHeight);
901 
902         bool userSizeChanged = false;
903         if (_currentUserWidth != newUserWidth)
904         {
905             _currentUserWidth = newUserWidth;
906             userSizeChanged = true;
907         }
908         if (_currentUserHeight != newUserHeight)
909         {
910             _currentUserHeight = newUserHeight;
911             userSizeChanged = true;
912         }
913 
914         // On first onResize, assume both sizes changed
915         if (_firstResize)
916         {
917             logicalSizeChanged = true;
918             userSizeChanged = true;
919             _firstResize = false;
920         }
921 
922         if (userSizeChanged) { assert(logicalSizeChanged); }
923 
924         // 1.b Update user area rect. We find a suitable space in logical area
925         //     to draw the whole UI.
926         if (logicalSizeChanged)
927         {
928             int x, y, w, h;
929             if (_currentLogicalWidth >= _currentUserWidth)
930             {
931                 x = (_currentLogicalWidth - _currentUserWidth) / 2;
932                 w = _currentUserWidth;
933             }
934             else
935             {
936                 x = 0;
937                 w = _currentLogicalWidth;
938             }
939             if (_currentLogicalHeight >= _currentUserHeight)
940             {
941                 y = (_currentLogicalHeight - _currentUserHeight) / 2;
942                 h = _currentUserHeight;
943             }
944             else
945             {
946                 y = 0;
947                 h = _currentLogicalHeight;
948             }
949 
950             _userArea = box2i.rectangle(x, y, w, h);
951 
952             debug(resizing)
953             {
954                 debugLogf("new _userArea is rectangle(%d, %d, %d, %d)\n", x, y, w, h);
955             }
956 
957             _reportBlackBordersAndResizedAreaAsDirty = true;
958 
959             // Note: out of range rectangles will still be in the dirtyListRaw/dirtyListPBR
960             // and also _rectsToUpdateDisjointedPBR/_rectsToUpdateDisjointedRaw
961             // This is the dreaded Issue #597
962             // Unicity and boundness is maintained inside recomputePurelyDerivedRectangles().
963 
964             // The user size has changed. Force an immediate full redraw, so that no ancient data is used.
965             // Not that this is on top of previous resizes or pulled rectangles in 
966             // _rectsToUpdateDisjointedPBR / _rectsToUpdateDisjointedRaw.
967             if (userSizeChanged)
968             {
969                 debug(resizing) debugLogf("  * provoke full redraw\n");
970                 _rectsToUpdateDisjointedPBR.pushBack( rectangle(0, 0, _userArea.width, _userArea.height) );
971             }
972 
973             // This avoids an onDraw with wrong rectangles
974             recomputePurelyDerivedRectangles();
975         }
976 
977         // 2. Invalidate UI region if user size change.
978         //    Note: _resizedBuffer invalidation is managed with flags instead of this.
979         position = box2i(0, 0, _currentUserWidth, _currentUserHeight);
980 
981         // 3. Resize compositor buffers.
982         _compositor.resizeBuffers(_currentUserWidth, _currentUserHeight, PBR_TILE_MAX_WIDTH, PBR_TILE_MAX_HEIGHT);
983 
984         _diffuseMap.size(5, _currentUserWidth, _currentUserHeight);
985         _depthMap.size(4, _currentUserWidth, _currentUserHeight);
986 
987         // The first level of the depth map has a border of 1 pixels and 2 pxiels on the right, to simplify some PBR passes
988         int border_1 = 1;
989         int rowAlign_1 = 1;
990         int xMultiplicity_1 = 1;
991         int trailingSamples_2 = 2;
992         _depthMap.levels[0].size(_currentUserWidth, _currentUserHeight, border_1, rowAlign_1, xMultiplicity_1, trailingSamples_2);
993 
994         _materialMap.size(0, _currentUserWidth, _currentUserHeight);
995 
996         // Extends buffers with user size
997 
998         int border_0 = 0;
999         int rowAlign_16 = 16;
1000         int trailingSamples_0 = 0;
1001         int trailingSamples_3 = 3;
1002         _compositedBuffer.size(_currentUserWidth, _currentUserHeight, border_0, rowAlign_16, xMultiplicity_1, trailingSamples_0);
1003         _renderedBuffer.size(_currentUserWidth, _currentUserHeight, border_0, rowAlign_16, xMultiplicity_1, trailingSamples_3);
1004 
1005         // Extends final buffer with logical size
1006         size_t sizeNeeded = byteStride(_currentLogicalWidth) * _currentLogicalHeight;
1007         _resizedBuffer = cast(ubyte*) alignedRealloc(_resizedBuffer, sizeNeeded, 16);
1008 
1009         debug(resizing) debugLogf("<doResize(%d, %d)\n", widthLogicalPixels, heightLogicalPixels);
1010 
1011         version(Dplug_ProfileUI) profiler.end();
1012 
1013         return toImageRef(_resizedBuffer, _currentLogicalWidth, _currentLogicalHeight);
1014     }
1015 
1016     /// Draw the Raw layer of `UIElement` widgets
1017     void redrawElementsRaw() nothrow @nogc
1018     {
1019         enum bool parallelDraw = true;
1020 
1021         ImageRef!RGBA renderedRef = _renderedBuffer.toRef();
1022 
1023         // No need to launch threads only to have them realize there isn't anything to do
1024         if (_rectsToDisplayDisjointed.length == 0)
1025             return;
1026 
1027         static if (parallelDraw)
1028         {
1029             int drawn = 0;
1030             int N = cast(int)_elemsToDrawRaw.length;
1031 
1032             while(drawn < N)
1033             {
1034                 // See: redrawElementsPBR below for a remark on performance there.
1035 
1036                 int canBeDrawn = 1; // at least one can be drawn without collision
1037 
1038                 // Does this first widget in the FIFO wants to be draw alone?
1039                 if (! _elemsToDrawRaw[drawn].isDrawAloneRaw())
1040                 {
1041                     // Search max number of parallelizable draws until the end of the list or a collision is found
1042                     bool foundIntersection = false;
1043                     for ( ; (drawn + canBeDrawn < N); ++canBeDrawn)
1044                     {
1045                         // Should we include this element to the assembled set of widgets to draw?
1046                         UIElement candidateWidget = _elemsToDrawRaw[drawn + canBeDrawn];
1047 
1048                         if (candidateWidget.isDrawAloneRaw())
1049                             break; // wants to be drawn alone
1050 
1051                         box2i candidatePos = candidateWidget.position;
1052 
1053                         for (int j = 0; j < canBeDrawn; ++j) // PERF: aaaand this is nicely quadratic
1054                         {
1055                             if (_elemsToDrawRaw[drawn + j].position.intersects(candidatePos))
1056                             {
1057                                 foundIntersection = true;
1058                                 break;
1059                             }
1060                         }
1061                         if (foundIntersection)
1062                             break;
1063                     }
1064                 }
1065 
1066                 assert(canBeDrawn >= 1);
1067 
1068                 // Draw a number of UIElement in parallel
1069                 void drawOneItem(int i, int threadIndex) nothrow @nogc
1070                 {
1071                     version(Dplug_ProfileUI) 
1072                     {
1073                         char[maxUIElementIDLength + 16] idstr;
1074                         snprintf(idstr.ptr, 128, 
1075                                  "draw Raw element %s".ptr, _elemsToDrawRaw[drawn + i].getId().ptr);
1076                         profiler.category("draw").begin(idstr);
1077                     }
1078 
1079                     _elemsToDrawRaw[drawn + i].renderRaw(renderedRef, _rectsToDisplayDisjointed[]);
1080 
1081                     version(Dplug_ProfileUI) profiler.end();
1082                 }
1083                 _threadPool.parallelFor(canBeDrawn, &drawOneItem);
1084 
1085                 drawn += canBeDrawn;
1086                 assert(drawn <= N);
1087             }
1088             assert(drawn == N);
1089         }
1090         else
1091         {
1092             foreach(elem; _elemsToDrawRaw)
1093                 elem.renderRaw(renderedRef, _rectsToDisplayDisjointed[]);
1094         }
1095     }
1096 
1097     /// Draw the PBR layer of `UIElement` widgets
1098     void redrawElementsPBR() nothrow @nogc
1099     {
1100         enum bool parallelDraw = true;
1101 
1102         assert(_diffuseMap.levels[0] !is null);
1103         assert(_depthMap.levels[0] !is null);
1104         assert(_materialMap.levels[0] !is null);
1105         auto diffuseRef = _diffuseMap.levels[0].toRef();
1106         auto depthRef = _depthMap.levels[0].toRef();
1107         auto materialRef = _materialMap.levels[0].toRef();
1108 
1109         // No need to launch threads only to have them realize there isn't anything to do
1110         if (_rectsToUpdateDisjointedPBR.length == 0)
1111             return;
1112 
1113         static if (parallelDraw)
1114         {
1115             int drawn = 0;
1116             int N = cast(int)_elemsToDrawPBR.length;
1117 
1118             while(drawn < N)
1119             {
1120                 // <Scheduling remark>
1121                 // PERF: scheduling here is not entirely optimal: consecutive overalapping widgets 
1122                 // would block further parallel draw if the next widget doesn't overlap the other two.
1123                 //
1124                 //  ________          _____
1125                 //  |      |          |   |
1126                 //  |  B   |______    | C |      <---- Will not draw A and C in parallel if
1127                 //  |______|     |    |___|            Z(A) < Z(B) < Z(C)
1128                 //      |    A   |
1129                 //      |________|
1130                 //
1131                 // PERF: to go further, could use the disjointed rects to draw even more in parallel. 
1132                 // Real updated graphics is intersection(position, union(_rectsToUpdateDisjointedPBR)),
1133                 // not simply the widget position.
1134                 // </Scheduling remark>
1135 
1136                 int canBeDrawn = 1; // at least one can be drawn without collision
1137 
1138                 // Does this first widget in the FIFO wants to be draw alone?
1139                 if (! _elemsToDrawPBR[drawn].isDrawAlonePBR())
1140                 {
1141                     // Search max number of parallelizable draws until the end of the list or a collision is found
1142                     bool foundIntersection = false;
1143                     for ( ; (drawn + canBeDrawn < N); ++canBeDrawn)
1144                     {
1145                         // Should we include this element to the assembled set of widgets to draw?
1146                         UIElement candidateWidget = _elemsToDrawPBR[drawn + canBeDrawn];
1147 
1148                         if (candidateWidget.isDrawAlonePBR())
1149                             break; // wants to be drawn alone
1150 
1151                         box2i candidatePos = _elemsToDrawPBR[drawn + canBeDrawn].position;
1152 
1153                         for (int j = 0; j < canBeDrawn; ++j) // check with each former selected widget, PERF quadratic
1154                         {
1155                             if (_elemsToDrawPBR[drawn + j].position.intersects(candidatePos))
1156                             {
1157                                 foundIntersection = true;
1158                                 break;
1159                             }
1160                         }
1161                         if (foundIntersection)
1162                             break;
1163                     }
1164                 }
1165 
1166                 assert(canBeDrawn >= 1);
1167 
1168                 // Draw a number of UIElement in parallel
1169                 void drawOneItem(int i, int threadIndex) nothrow @nogc
1170                 {
1171                     version(Dplug_ProfileUI) 
1172                     {
1173                         char[maxUIElementIDLength + 16] idstr;
1174                         snprintf(idstr.ptr, 128, 
1175                                  "draw PBR element %s", _elemsToDrawPBR[drawn + i].getId().ptr);
1176                         profiler.category("draw").begin(idstr);
1177                     }
1178 
1179                     _elemsToDrawPBR[drawn + i].renderPBR(diffuseRef, depthRef, materialRef, _rectsToUpdateDisjointedPBR[]);
1180 
1181                     version(Dplug_ProfileUI) profiler.end();
1182                 }
1183                 _threadPool.parallelFor(canBeDrawn, &drawOneItem);
1184 
1185                 drawn += canBeDrawn;
1186                 assert(drawn <= N);
1187             }
1188             assert(drawn == N);
1189         }
1190         else
1191         {
1192             // Render required areas in diffuse and depth maps, base level
1193             foreach(elem; _elemsToDraw)
1194                 elem.renderPBR(diffuseRef, depthRef, materialRef, _rectsToUpdateDisjointedPBR[]);
1195         }
1196     }
1197 
1198     /// Do the PBR compositing step. This is the most expensive step in the UI.
1199     void compositeGUI(ImageRef!RGBA wfb) nothrow @nogc
1200     {
1201         _rectsToCompositeDisjointedTiled.clearContents();
1202         tileAreas(_rectsToCompositeDisjointed[],  PBR_TILE_MAX_WIDTH, PBR_TILE_MAX_HEIGHT, _rectsToCompositeDisjointedTiled);
1203 
1204         _compositor.compositeTile(wfb,
1205                                   _rectsToCompositeDisjointedTiled[],
1206                                   _diffuseMap,
1207                                   _materialMap,
1208                                   _depthMap,
1209                                   profiler());
1210     }
1211 
1212     /// Compose lighting effects from depth and diffuse into a result.
1213     /// takes output image and non-overlapping areas as input
1214     /// Useful multithreading code.
1215     void regenerateMipmaps() nothrow @nogc
1216     {
1217         int numAreas = cast(int)_rectsToUpdateDisjointedPBR.length;
1218 
1219         // No mipmap to update, no need to launch threads
1220         if (numAreas == 0)
1221             return;
1222 
1223         // Fill update rect buffer with the content of _rectsToUpdateDisjointedPBR
1224         for (int i = 0; i < 2; ++i)
1225         {
1226             _updateRectScratch[i].clearContents();
1227             _updateRectScratch[i].pushBack(_rectsToUpdateDisjointedPBR[]);
1228         }
1229 
1230         // Mipmapping used to be threaded, however because it's completely memory-bound
1231         // (about 2mb read/sec) and fast enough, it's not worth launching threads for.
1232 
1233         version(Dplug_ProfileUI) profiler.category("mipmap").begin("diffuse mipmap");
1234 
1235         // Generate diffuse mipmap, useful for dealing with emissive
1236         {
1237             // diffuse
1238             Mipmap!RGBA mipmap = _diffuseMap;
1239             int levelMax = min(mipmap.numLevels(), 5);
1240             foreach(level; 1 .. mipmap.numLevels())
1241             {
1242                 Mipmap!RGBA.Quality quality;
1243                 if (level == 1)
1244                     quality = Mipmap!RGBA.Quality.boxAlphaCovIntoPremul;
1245                 else
1246                     quality = Mipmap!RGBA.Quality.cubic;
1247                 foreach(ref area; _updateRectScratch[0])
1248                 {
1249                     area = mipmap.generateNextLevel(quality, area, level);
1250                 }
1251             }
1252         }
1253 
1254         version(Dplug_ProfileUI) profiler.end;
1255 
1256         version(Dplug_ProfileUI) profiler.begin("depth mipmap");
1257 
1258         // Generate depth mipmap, useful for dealing with ambient occlusion
1259         {
1260             int W = _currentUserWidth;
1261             int H = _currentUserHeight;
1262 
1263             // Depth is special since it has a border!
1264             // Regenerate the border area that needs to be regenerated
1265             OwnedImage!L16 level0 = _depthMap.levels[0];
1266             foreach(box2i area; _updateRectScratch[1])
1267                 level0.replicateBordersTouching(area);
1268 
1269             // DEPTH MIPMAPPING
1270             Mipmap!L16 mipmap = _depthMap;
1271             foreach(level; 1 .. mipmap.numLevels())
1272             {
1273                 auto quality = level >= 3 ? Mipmap!L16.Quality.cubic : Mipmap!L16.Quality.box;
1274                 foreach(ref area; _updateRectScratch[1])
1275                 {
1276                     area = mipmap.generateNextLevel(quality, area, level);
1277                 }
1278             }
1279         }
1280 
1281         version(Dplug_ProfileUI) profiler.end;
1282     }
1283 
1284     void reorderComponents(WindowPixelFormat pf)
1285     {
1286         auto renderedRef = _renderedBuffer.toRef();
1287 
1288         final switch(pf)
1289         {
1290             case WindowPixelFormat.RGBA8:
1291                 foreach(rect; _rectsToDisplayDisjointed[])
1292                 {
1293                     shuffleComponentsRGBA8ToRGBA8AndForceAlphaTo255(renderedRef.cropImageRef(rect));
1294                 }
1295                 break;
1296 
1297             case WindowPixelFormat.BGRA8:
1298                 foreach(rect; _rectsToDisplayDisjointed[])
1299                 {
1300                     shuffleComponentsRGBA8ToBGRA8AndForceAlphaTo255(renderedRef.cropImageRef(rect));
1301                 }
1302                 break;
1303 
1304             case WindowPixelFormat.ARGB8:
1305                 foreach(rect; _rectsToDisplayDisjointed[])
1306                 {
1307                     shuffleComponentsRGBA8ToARGB8AndForceAlphaTo255(renderedRef.cropImageRef(rect));
1308                 }
1309                 break;
1310         }
1311     }
1312 
1313     // From a user area rectangle, return a logical are rectangle with the same size.
1314     final box2i convertUserRectToLogicalRect(box2i b)
1315     {
1316         return b.translate(_userArea.min);
1317     }
1318 
1319     final box2i convertLogicalRectToUserRect(box2i b)
1320     {
1321         return b.translate(-_userArea.min);
1322     }
1323 
1324     void resizeContent(WindowPixelFormat pf)
1325     {
1326         // TODO: eventually resize?
1327         // For now what we do for logical area is crop and offset.
1328         // In the future, could be beneficial to resample if needed.
1329 
1330         auto renderedRef = _renderedBuffer.toRef();
1331         auto resizedRef = toImageRef(_resizedBuffer, _currentLogicalWidth, _currentLogicalHeight);
1332 
1333         box2i[] rectsToCopy = _rectsToResizeDisjointed[];
1334 
1335         // If invalidated, the whole buffer needs to be redrawn
1336         // (because of borders, or changing offsets of the user area).
1337         if (_redrawBlackBordersAndResizedArea)
1338         {
1339             debug(resizing) debugLogf("  * redrawing black borders, and copy item\n");
1340             RGBA black;
1341             final switch(pf)
1342             {
1343                 case WindowPixelFormat.RGBA8:
1344                 case WindowPixelFormat.BGRA8: black = RGBA(0, 0, 0, 255); break;
1345                 case WindowPixelFormat.ARGB8: black = RGBA(255, 0, 0, 0); break;
1346             }
1347             resizedRef.fillAll(black); // PERF: Only do this in the location of the black border.
1348 
1349             // No need to report that everything is dirty anymore.
1350             _reportBlackBordersAndResizedAreaAsDirty = false;
1351 
1352             // and no need to draw everything in onDraw anymore.
1353             _redrawBlackBordersAndResizedArea = false;
1354 
1355             rectsToCopy = (&_userArea)[0..1];
1356         }
1357 
1358         foreach(rect; rectsToCopy[])
1359         {
1360             int dx = _userArea.min.x;
1361             int dy = _userArea.min.y;
1362 
1363             for (int j = rect.min.y; j < rect.max.y; ++j)
1364             {
1365                 RGBA* src  = renderedRef.scanline(j - dy).ptr;
1366                 RGBA* dest = resizedRef.scanline(j).ptr;
1367                 dest[rect.min.x..rect.max.x] = src[(rect.min.x - dx)..(rect.max.x - dx)];
1368             }
1369         }
1370     }
1371 }
1372 
1373 
1374 // given a width, how long in bytes should scanlines be for the final output buffer.
1375 // Note: it seems win32 needs this exact stride for returned buffer. It mimics BMP.
1376 //       On the other hands, is seems other platforms don't have the same constraints with row pitch.
1377 int byteStride(int width) pure nothrow @nogc
1378 {
1379     // See https://github.com/AuburnSounds/Dplug/issues/563, there
1380     // is currently a coupling with dplug:window and this can't be changed.
1381     enum scanLineAlignment = 4;
1382     int widthInBytes = width * 4;
1383     return (widthInBytes + (scanLineAlignment - 1)) & ~(scanLineAlignment-1);
1384 }
1385 
1386 void shuffleComponentsRGBA8ToARGB8AndForceAlphaTo255(ImageRef!RGBA image) pure nothrow @nogc
1387 {
1388     immutable int w = image.w;
1389     immutable int h = image.h;
1390     for (int j = 0; j < h; ++j)
1391     {
1392         ubyte* scan = cast(ubyte*)image.scanline(j).ptr;
1393 
1394         int i = 0;
1395         for( ; i + 3 < w; i += 4)
1396         {
1397             __m128i inputBytes = _mm_loadu_si128(cast(__m128i*)(&scan[4*i]));
1398             inputBytes = _mm_or_si128(inputBytes, _mm_set1_epi32(0xff000000));
1399 
1400             version(LDC)
1401             {
1402                 import ldc.intrinsics;
1403                 import ldc.simd;
1404                 __m128i outputBytes = cast(__m128i) shufflevector!(byte16, 3, 0,  1,  2,
1405                                                                            7, 4,  5,  6,
1406                                                                            11, 8,  9,  10,
1407                                                                            15, 12, 13, 14)(cast(byte16)inputBytes, cast(byte16)inputBytes);
1408                 _mm_storeu_si128(cast(__m128i*)(&scan[4*i]), outputBytes);
1409             }
1410             else
1411             {
1412                 // convert to ushort
1413                 __m128i zero = _mm_setzero_si128();
1414                 __m128i e0_7 = _mm_unpacklo_epi8(inputBytes, zero);
1415                 __m128i e8_15 = _mm_unpackhi_epi8(inputBytes, zero);
1416 
1417                 enum int swapRB = _MM_SHUFFLE(2, 1, 0, 3);
1418                 e0_7 = _mm_shufflelo_epi16!swapRB(_mm_shufflehi_epi16!swapRB(e0_7));
1419                 e8_15 = _mm_shufflelo_epi16!swapRB(_mm_shufflehi_epi16!swapRB(e8_15));
1420                 __m128i outputBytes = _mm_packus_epi16(e0_7, e8_15);
1421                 _mm_storeu_si128(cast(__m128i*)(&scan[4*i]), outputBytes);
1422             }
1423         }
1424 
1425         for(; i < w; i ++)
1426         {
1427             ubyte r = scan[4*i];
1428             ubyte g = scan[4*i+1];
1429             ubyte b = scan[4*i+2];
1430             scan[4*i] = 255;
1431             scan[4*i+1] = r;
1432             scan[4*i+2] = g;
1433             scan[4*i+3] = b;
1434         }
1435     }
1436 }
1437 
1438 void shuffleComponentsRGBA8ToBGRA8AndForceAlphaTo255(ImageRef!RGBA image) pure nothrow @nogc
1439 {
1440     immutable int w = image.w;
1441     immutable int h = image.h;
1442     for (int j = 0; j < h; ++j)
1443     {
1444         ubyte* scan = cast(ubyte*)image.scanline(j).ptr;
1445 
1446         int i = 0;
1447         for( ; i + 3 < w; i += 4)
1448         {
1449             __m128i inputBytes = _mm_loadu_si128(cast(__m128i*)(&scan[4*i]));
1450             inputBytes = _mm_or_si128(inputBytes, _mm_set1_epi32(0xff000000));
1451 
1452             version(LDC)
1453             {
1454                 import ldc.intrinsics;
1455                 import ldc.simd;
1456                 __m128i outputBytes = cast(__m128i) shufflevector!(byte16, 2,  1,  0,  3,
1457                                                                            6,  5,  4,  7,
1458                                                                           10,  9,  8, 11,
1459                                                                           14, 13, 12, 15)(cast(byte16)inputBytes, cast(byte16)inputBytes);
1460                 _mm_storeu_si128(cast(__m128i*)(&scan[4*i]), outputBytes);
1461             }
1462             else
1463             {
1464                 // convert to ushort
1465                 __m128i zero = _mm_setzero_si128();
1466                 __m128i e0_7 = _mm_unpacklo_epi8(inputBytes, zero);
1467                 __m128i e8_15 = _mm_unpackhi_epi8(inputBytes, zero);
1468 
1469                 // swap red and green
1470                 enum int swapRB = _MM_SHUFFLE(3, 0, 1, 2);
1471                 e0_7 = _mm_shufflelo_epi16!swapRB(_mm_shufflehi_epi16!swapRB(e0_7));
1472                 e8_15 = _mm_shufflelo_epi16!swapRB(_mm_shufflehi_epi16!swapRB(e8_15));
1473                 __m128i outputBytes = _mm_packus_epi16(e0_7, e8_15);
1474                 _mm_storeu_si128(cast(__m128i*)(&scan[4*i]), outputBytes);
1475             }
1476         }
1477 
1478         for(; i < w; i ++)
1479         {
1480             ubyte r = scan[4*i];
1481             ubyte g = scan[4*i+1];
1482             ubyte b = scan[4*i+2];
1483             scan[4*i] = b;
1484             scan[4*i+1] = g;
1485             scan[4*i+2] = r;
1486             scan[4*i+3] = 255;
1487         }
1488     }
1489 }
1490 
1491 void shuffleComponentsRGBA8ToRGBA8AndForceAlphaTo255(ImageRef!RGBA image) pure nothrow @nogc
1492 {
1493     immutable int w = image.w;
1494     immutable int h = image.h;
1495     for (int j = 0; j < h; ++j)
1496     {
1497         ubyte* scan = cast(ubyte*)image.scanline(j).ptr;
1498 
1499         int i = 0;
1500         for( ; i + 3 < w; i += 4)
1501         {
1502             __m128i inputBytes = _mm_loadu_si128(cast(__m128i*)(&scan[4*i]));
1503             inputBytes = _mm_or_si128(inputBytes, _mm_set1_epi32(0xff000000));
1504             // No reordering to do
1505             _mm_storeu_si128(cast(__m128i*)(&scan[4*i]), inputBytes);
1506         }
1507 
1508         for(; i < w; i ++)
1509         {
1510             scan[4*i+3] = 255;
1511         }
1512     }
1513 }