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