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