1 //
2 // Copyright 2014 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 
7 // SwapChainPanelNativeWindow.cpp: NativeWindow for managing ISwapChainPanel native window types.
8 
9 #include "libANGLE/renderer/d3d/d3d11/winrt/SwapChainPanelNativeWindow.h"
10 
11 #include <math.h>
12 #include <algorithm>
13 
14 using namespace ABI::Windows::Foundation;
15 using namespace ABI::Windows::Foundation::Collections;
16 using namespace ABI::Windows::UI::Core;
17 using namespace ABI::Windows::UI::Xaml;
18 using namespace Microsoft::WRL;
19 
20 namespace rx
21 {
~SwapChainPanelNativeWindow()22 SwapChainPanelNativeWindow::~SwapChainPanelNativeWindow()
23 {
24     unregisterForSizeChangeEvents();
25 }
26 
27 template <typename T>
28 struct AddFtmBase
29 {
30     typedef Implements<RuntimeClassFlags<ClassicCom>, T, FtmBase> Type;
31 };
32 
33 template <typename CODE>
RunOnUIThread(CODE && code,const ComPtr<ICoreDispatcher> & dispatcher)34 HRESULT RunOnUIThread(CODE &&code, const ComPtr<ICoreDispatcher> &dispatcher)
35 {
36     ComPtr<IAsyncAction> asyncAction;
37     HRESULT result = S_OK;
38 
39     boolean hasThreadAccess;
40     result = dispatcher->get_HasThreadAccess(&hasThreadAccess);
41     if (FAILED(result))
42     {
43         return result;
44     }
45 
46     if (hasThreadAccess)
47     {
48         return code();
49     }
50     else
51     {
52         Event waitEvent(
53             CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS));
54         if (!waitEvent.IsValid())
55         {
56             return E_FAIL;
57         }
58 
59         HRESULT codeResult = E_FAIL;
60         auto handler =
61             Callback<AddFtmBase<IDispatchedHandler>::Type>([&codeResult, &code, &waitEvent] {
62                 codeResult = code();
63                 SetEvent(waitEvent.Get());
64                 return S_OK;
65             });
66 
67         result = dispatcher->RunAsync(CoreDispatcherPriority_Normal, handler.Get(),
68                                       asyncAction.GetAddressOf());
69         if (FAILED(result))
70         {
71             return result;
72         }
73 
74         auto waitResult = WaitForSingleObjectEx(waitEvent.Get(), 10 * 1000, true);
75         if (waitResult != WAIT_OBJECT_0)
76         {
77             // Wait 10 seconds before giving up. At this point, the application is in an
78             // unrecoverable state (probably deadlocked). We therefore terminate the application
79             // entirely. This also prevents stack corruption if the async operation is eventually
80             // run.
81             ERR()
82                 << "Timeout waiting for async action on UI thread. The UI thread might be blocked.";
83             std::terminate();
84             return E_FAIL;
85         }
86 
87         return codeResult;
88     }
89 }
90 
initialize(EGLNativeWindowType window,IPropertySet * propertySet)91 bool SwapChainPanelNativeWindow::initialize(EGLNativeWindowType window, IPropertySet *propertySet)
92 {
93     ComPtr<IPropertySet> props = propertySet;
94     ComPtr<IInspectable> win   = window;
95     SIZE swapChainSize         = {};
96     HRESULT result             = S_OK;
97 
98     // IPropertySet is an optional parameter and can be null.
99     // If one is specified, cache as an IMap and read the properties
100     // used for initial host initialization.
101     if (propertySet)
102     {
103         result = props.As(&mPropertyMap);
104         if (FAILED(result))
105         {
106             return false;
107         }
108 
109         // The EGLRenderSurfaceSizeProperty is optional and may be missing. The IPropertySet
110         // was prevalidated to contain the EGLNativeWindowType before being passed to
111         // this host.
112         result = GetOptionalSizePropertyValue(mPropertyMap, EGLRenderSurfaceSizeProperty,
113                                               &swapChainSize, &mSwapChainSizeSpecified);
114         if (FAILED(result))
115         {
116             return false;
117         }
118 
119         // The EGLRenderResolutionScaleProperty is optional and may be missing. The IPropertySet
120         // was prevalidated to contain the EGLNativeWindowType before being passed to
121         // this host.
122         result = GetOptionalSinglePropertyValue(mPropertyMap, EGLRenderResolutionScaleProperty,
123                                                 &mSwapChainScale, &mSwapChainScaleSpecified);
124         if (FAILED(result))
125         {
126             return false;
127         }
128 
129         if (!mSwapChainScaleSpecified)
130         {
131             // Default value for the scale is 1.0f
132             mSwapChainScale = 1.0f;
133         }
134 
135         // A EGLRenderSurfaceSizeProperty and a EGLRenderResolutionScaleProperty can't both be
136         // specified
137         if (mSwapChainScaleSpecified && mSwapChainSizeSpecified)
138         {
139             ERR() << "It is invalid to specify both an EGLRenderSurfaceSizeProperty and a "
140                      "EGLRenderResolutionScaleProperty.";
141             return false;
142         }
143     }
144 
145     if (SUCCEEDED(result))
146     {
147         result = win.As(&mSwapChainPanel);
148     }
149 
150     ComPtr<IDependencyObject> swapChainPanelDependencyObject;
151     if (SUCCEEDED(result))
152     {
153         result = mSwapChainPanel.As(&swapChainPanelDependencyObject);
154     }
155 
156     if (SUCCEEDED(result))
157     {
158         result = swapChainPanelDependencyObject->get_Dispatcher(
159             mSwapChainPanelDispatcher.GetAddressOf());
160     }
161 
162     if (SUCCEEDED(result))
163     {
164         // If a swapchain size is specfied, then the automatic resize
165         // behaviors implemented by the host should be disabled.  The swapchain
166         // will be still be scaled when being rendered to fit the bounds
167         // of the host.
168         // Scaling of the swapchain output needs to be handled by the
169         // host for swapchain panels even though the scaling mode setting
170         // DXGI_SCALING_STRETCH is configured on the swapchain.
171         if (mSwapChainSizeSpecified)
172         {
173             mClientRect = {0, 0, swapChainSize.cx, swapChainSize.cy};
174         }
175         else
176         {
177             Size swapChainPanelSize;
178             result = GetSwapChainPanelSize(mSwapChainPanel, mSwapChainPanelDispatcher,
179                                            &swapChainPanelSize);
180 
181             if (SUCCEEDED(result))
182             {
183                 // Update the client rect to account for any swapchain scale factor
184                 mClientRect = clientRect(swapChainPanelSize);
185             }
186         }
187     }
188 
189     if (SUCCEEDED(result))
190     {
191         mNewClientRect     = mClientRect;
192         mClientRectChanged = false;
193         return registerForSizeChangeEvents();
194     }
195 
196     return false;
197 }
198 
registerForSizeChangeEvents()199 bool SwapChainPanelNativeWindow::registerForSizeChangeEvents()
200 {
201     ComPtr<ISizeChangedEventHandler> sizeChangedHandler;
202     ComPtr<IFrameworkElement> frameworkElement;
203     HRESULT result = Microsoft::WRL::MakeAndInitialize<SwapChainPanelSizeChangedHandler>(
204         sizeChangedHandler.ReleaseAndGetAddressOf(), this->shared_from_this());
205 
206     if (SUCCEEDED(result))
207     {
208         result = mSwapChainPanel.As(&frameworkElement);
209     }
210 
211     if (SUCCEEDED(result))
212     {
213         result = RunOnUIThread(
214             [this, frameworkElement, sizeChangedHandler] {
215                 return frameworkElement->add_SizeChanged(sizeChangedHandler.Get(),
216                                                          &mSizeChangedEventToken);
217             },
218             mSwapChainPanelDispatcher);
219     }
220 
221     if (SUCCEEDED(result))
222     {
223         return true;
224     }
225 
226     return false;
227 }
228 
unregisterForSizeChangeEvents()229 void SwapChainPanelNativeWindow::unregisterForSizeChangeEvents()
230 {
231     ComPtr<IFrameworkElement> frameworkElement;
232     if (mSwapChainPanel && SUCCEEDED(mSwapChainPanel.As(&frameworkElement)))
233     {
234         RunOnUIThread(
235             [this, frameworkElement] {
236                 return frameworkElement->remove_SizeChanged(mSizeChangedEventToken);
237             },
238             mSwapChainPanelDispatcher);
239     }
240 
241     mSizeChangedEventToken.value = 0;
242 }
243 
createSwapChain(ID3D11Device * device,IDXGIFactory2 * factory,DXGI_FORMAT format,unsigned int width,unsigned int height,bool containsAlpha,IDXGISwapChain1 ** swapChain)244 HRESULT SwapChainPanelNativeWindow::createSwapChain(ID3D11Device *device,
245                                                     IDXGIFactory2 *factory,
246                                                     DXGI_FORMAT format,
247                                                     unsigned int width,
248                                                     unsigned int height,
249                                                     bool containsAlpha,
250                                                     IDXGISwapChain1 **swapChain)
251 {
252     if (device == nullptr || factory == nullptr || swapChain == nullptr || width == 0 ||
253         height == 0)
254     {
255         return E_INVALIDARG;
256     }
257 
258     DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0};
259     swapChainDesc.Width                 = width;
260     swapChainDesc.Height                = height;
261     swapChainDesc.Format                = format;
262     swapChainDesc.Stereo                = FALSE;
263     swapChainDesc.SampleDesc.Count      = 1;
264     swapChainDesc.SampleDesc.Quality    = 0;
265     swapChainDesc.BufferUsage =
266         DXGI_USAGE_SHADER_INPUT | DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_BACK_BUFFER;
267     swapChainDesc.BufferCount = 2;
268     swapChainDesc.SwapEffect  = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
269     swapChainDesc.Scaling     = DXGI_SCALING_STRETCH;
270     swapChainDesc.AlphaMode =
271         containsAlpha ? DXGI_ALPHA_MODE_PREMULTIPLIED : DXGI_ALPHA_MODE_IGNORE;
272 
273     *swapChain = nullptr;
274 
275     ComPtr<IDXGISwapChain1> newSwapChain;
276     ComPtr<ISwapChainPanelNative> swapChainPanelNative;
277     Size currentPanelSize = {};
278 
279     HRESULT result = factory->CreateSwapChainForComposition(device, &swapChainDesc, nullptr,
280                                                             newSwapChain.ReleaseAndGetAddressOf());
281 
282     if (SUCCEEDED(result))
283     {
284         result = mSwapChainPanel.As(&swapChainPanelNative);
285     }
286 
287     if (SUCCEEDED(result))
288     {
289         result = RunOnUIThread(
290             [swapChainPanelNative, newSwapChain] {
291                 return swapChainPanelNative->SetSwapChain(newSwapChain.Get());
292             },
293             mSwapChainPanelDispatcher);
294     }
295 
296     if (SUCCEEDED(result))
297     {
298         // The swapchain panel host requires an instance of the swapchain set on the SwapChainPanel
299         // to perform the runtime-scale behavior.  This swapchain is cached here because there are
300         // no methods for retreiving the currently configured on from ISwapChainPanelNative.
301         mSwapChain = newSwapChain;
302         result     = newSwapChain.CopyTo(swapChain);
303     }
304 
305     // If the host is responsible for scaling the output of the swapchain, then
306     // scale it now before returning an instance to the caller.  This is done by
307     // first reading the current size of the swapchain panel, then scaling
308     if (SUCCEEDED(result))
309     {
310         if (mSwapChainSizeSpecified || mSwapChainScaleSpecified)
311         {
312             result = GetSwapChainPanelSize(mSwapChainPanel, mSwapChainPanelDispatcher,
313                                            &currentPanelSize);
314 
315             // Scale the swapchain to fit inside the contents of the panel.
316             if (SUCCEEDED(result))
317             {
318                 result = scaleSwapChain(currentPanelSize, mClientRect);
319             }
320         }
321     }
322 
323     return result;
324 }
325 
scaleSwapChain(const Size & windowSize,const RECT & clientRect)326 HRESULT SwapChainPanelNativeWindow::scaleSwapChain(const Size &windowSize, const RECT &clientRect)
327 {
328     Size renderScale = {windowSize.Width / (float)clientRect.right,
329                         windowSize.Height / (float)clientRect.bottom};
330     // Setup a scale matrix for the swap chain
331     DXGI_MATRIX_3X2_F scaleMatrix = {};
332     scaleMatrix._11               = renderScale.Width;
333     scaleMatrix._22               = renderScale.Height;
334 
335     ComPtr<IDXGISwapChain2> swapChain2;
336     HRESULT result = mSwapChain.As(&swapChain2);
337     if (SUCCEEDED(result))
338     {
339         result = swapChain2->SetMatrixTransform(&scaleMatrix);
340     }
341 
342     return result;
343 }
344 
GetSwapChainPanelSize(const ComPtr<ABI::Windows::UI::Xaml::Controls::ISwapChainPanel> & swapChainPanel,const ComPtr<ICoreDispatcher> & dispatcher,Size * windowSize)345 HRESULT GetSwapChainPanelSize(
346     const ComPtr<ABI::Windows::UI::Xaml::Controls::ISwapChainPanel> &swapChainPanel,
347     const ComPtr<ICoreDispatcher> &dispatcher,
348     Size *windowSize)
349 {
350     ComPtr<IUIElement> uiElement;
351     HRESULT result = swapChainPanel.As(&uiElement);
352     if (SUCCEEDED(result))
353     {
354         result = RunOnUIThread(
355             [uiElement, windowSize] { return uiElement->get_RenderSize(windowSize); }, dispatcher);
356     }
357 
358     return result;
359 }
360 }  // namespace rx
361