blob: a18482a527345accd47e189b9e52901d0d9b167a [file] [log] [blame]
//
// Copyright (c) 2014 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// SwapChainPanelNativeWindow.cpp: NativeWindow for managing ISwapChainPanel native window types.
#include "libANGLE/renderer/d3d/d3d11/winrt/SwapChainPanelNativeWindow.h"
#include <algorithm>
#include <math.h>
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Foundation::Collections;
using namespace ABI::Windows::UI::Core;
using namespace ABI::Windows::UI::Xaml;
using namespace Microsoft::WRL;
namespace rx
{
SwapChainPanelNativeWindow::~SwapChainPanelNativeWindow()
{
unregisterForSizeChangeEvents();
}
template <typename T>
struct AddFtmBase
{
typedef Implements<RuntimeClassFlags<ClassicCom>, T, FtmBase> Type;
};
template <typename CODE>
HRESULT RunOnUIThread(CODE &&code, const ComPtr<ICoreDispatcher> &dispatcher)
{
ComPtr<IAsyncAction> asyncAction;
HRESULT result = S_OK;
boolean hasThreadAccess;
result = dispatcher->get_HasThreadAccess(&hasThreadAccess);
if (FAILED(result))
{
return result;
}
if (hasThreadAccess)
{
return code();
}
else
{
Event waitEvent(
CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS));
if (!waitEvent.IsValid())
{
return E_FAIL;
}
HRESULT codeResult = E_FAIL;
auto handler =
Callback<AddFtmBase<IDispatchedHandler>::Type>([&codeResult, &code, &waitEvent]
{
codeResult = code();
SetEvent(waitEvent.Get());
return S_OK;
});
result = dispatcher->RunAsync(CoreDispatcherPriority_Normal, handler.Get(),
asyncAction.GetAddressOf());
if (FAILED(result))
{
return result;
}
auto waitResult = WaitForSingleObjectEx(waitEvent.Get(), 10 * 1000, true);
if (waitResult != WAIT_OBJECT_0)
{
// Wait 10 seconds before giving up. At this point, the application is in an
// unrecoverable state (probably deadlocked). We therefore terminate the application
// entirely. This also prevents stack corruption if the async operation is eventually
// run.
ERR()
<< "Timeout waiting for async action on UI thread. The UI thread might be blocked.";
std::terminate();
return E_FAIL;
}
return codeResult;
}
}
bool SwapChainPanelNativeWindow::initialize(EGLNativeWindowType window, IPropertySet *propertySet)
{
ComPtr<IPropertySet> props = propertySet;
ComPtr<IInspectable> win = static_cast<IInspectable *>(window);
SIZE swapChainSize = {};
HRESULT result = S_OK;
// IPropertySet is an optional parameter and can be null.
// If one is specified, cache as an IMap and read the properties
// used for initial host initialization.
if (propertySet)
{
result = props.As(&mPropertyMap);
if (FAILED(result))
{
return false;
}
// The EGLRenderSurfaceSizeProperty is optional and may be missing. The IPropertySet
// was prevalidated to contain the EGLNativeWindowType before being passed to
// this host.
result = GetOptionalSizePropertyValue(mPropertyMap, EGLRenderSurfaceSizeProperty, &swapChainSize, &mSwapChainSizeSpecified);
if (FAILED(result))
{
return false;
}
// The EGLRenderResolutionScaleProperty is optional and may be missing. The IPropertySet
// was prevalidated to contain the EGLNativeWindowType before being passed to
// this host.
result = GetOptionalSinglePropertyValue(mPropertyMap, EGLRenderResolutionScaleProperty, &mSwapChainScale, &mSwapChainScaleSpecified);
if (FAILED(result))
{
return false;
}
if (!mSwapChainScaleSpecified)
{
// Default value for the scale is 1.0f
mSwapChainScale = 1.0f;
}
// A EGLRenderSurfaceSizeProperty and a EGLRenderResolutionScaleProperty can't both be specified
if (mSwapChainScaleSpecified && mSwapChainSizeSpecified)
{
ERR() << "It is invalid to specify both an EGLRenderSurfaceSizeProperty and a "
"EGLRenderResolutionScaleProperty.";
return false;
}
}
if (SUCCEEDED(result))
{
result = win.As(&mSwapChainPanel);
}
ComPtr<IDependencyObject> swapChainPanelDependencyObject;
if (SUCCEEDED(result))
{
result = mSwapChainPanel.As(&swapChainPanelDependencyObject);
}
if (SUCCEEDED(result))
{
result = swapChainPanelDependencyObject->get_Dispatcher(
mSwapChainPanelDispatcher.GetAddressOf());
}
if (SUCCEEDED(result))
{
// If a swapchain size is specfied, then the automatic resize
// behaviors implemented by the host should be disabled. The swapchain
// will be still be scaled when being rendered to fit the bounds
// of the host.
// Scaling of the swapchain output needs to be handled by the
// host for swapchain panels even though the scaling mode setting
// DXGI_SCALING_STRETCH is configured on the swapchain.
if (mSwapChainSizeSpecified)
{
mClientRect = { 0, 0, swapChainSize.cx, swapChainSize.cy };
}
else
{
Size swapChainPanelSize;
result = GetSwapChainPanelSize(mSwapChainPanel, mSwapChainPanelDispatcher,
&swapChainPanelSize);
if (SUCCEEDED(result))
{
// Update the client rect to account for any swapchain scale factor
mClientRect = clientRect(swapChainPanelSize);
}
}
}
if (SUCCEEDED(result))
{
mNewClientRect = mClientRect;
mClientRectChanged = false;
return registerForSizeChangeEvents();
}
return false;
}
bool SwapChainPanelNativeWindow::registerForSizeChangeEvents()
{
ComPtr<ISizeChangedEventHandler> sizeChangedHandler;
ComPtr<IFrameworkElement> frameworkElement;
HRESULT result = Microsoft::WRL::MakeAndInitialize<SwapChainPanelSizeChangedHandler>(sizeChangedHandler.ReleaseAndGetAddressOf(), this->shared_from_this());
if (SUCCEEDED(result))
{
result = mSwapChainPanel.As(&frameworkElement);
}
if (SUCCEEDED(result))
{
result = RunOnUIThread(
[this, frameworkElement, sizeChangedHandler]
{
return frameworkElement->add_SizeChanged(sizeChangedHandler.Get(),
&mSizeChangedEventToken);
},
mSwapChainPanelDispatcher);
}
if (SUCCEEDED(result))
{
return true;
}
return false;
}
void SwapChainPanelNativeWindow::unregisterForSizeChangeEvents()
{
ComPtr<IFrameworkElement> frameworkElement;
if (mSwapChainPanel && SUCCEEDED(mSwapChainPanel.As(&frameworkElement)))
{
RunOnUIThread(
[this, frameworkElement]
{
return frameworkElement->remove_SizeChanged(mSizeChangedEventToken);
},
mSwapChainPanelDispatcher);
}
mSizeChangedEventToken.value = 0;
}
HRESULT SwapChainPanelNativeWindow::createSwapChain(ID3D11Device *device,
IDXGIFactory2 *factory,
DXGI_FORMAT format,
unsigned int width,
unsigned int height,
bool containsAlpha,
IDXGISwapChain1 **swapChain)
{
if (device == nullptr || factory == nullptr || swapChain == nullptr || width == 0 ||
height == 0)
{
return E_INVALIDARG;
}
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 };
swapChainDesc.Width = width;
swapChainDesc.Height = height;
swapChainDesc.Format = format;
swapChainDesc.Stereo = FALSE;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage =
DXGI_USAGE_SHADER_INPUT | DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_BACK_BUFFER;
swapChainDesc.BufferCount = 2;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
swapChainDesc.AlphaMode =
containsAlpha ? DXGI_ALPHA_MODE_PREMULTIPLIED : DXGI_ALPHA_MODE_IGNORE;
*swapChain = nullptr;
ComPtr<IDXGISwapChain1> newSwapChain;
ComPtr<ISwapChainPanelNative> swapChainPanelNative;
Size currentPanelSize = {};
HRESULT result = factory->CreateSwapChainForComposition(device, &swapChainDesc, nullptr, newSwapChain.ReleaseAndGetAddressOf());
if (SUCCEEDED(result))
{
result = mSwapChainPanel.As(&swapChainPanelNative);
}
if (SUCCEEDED(result))
{
result = RunOnUIThread(
[swapChainPanelNative, newSwapChain]
{
return swapChainPanelNative->SetSwapChain(newSwapChain.Get());
},
mSwapChainPanelDispatcher);
}
if (SUCCEEDED(result))
{
// The swapchain panel host requires an instance of the swapchain set on the SwapChainPanel
// to perform the runtime-scale behavior. This swapchain is cached here because there are
// no methods for retreiving the currently configured on from ISwapChainPanelNative.
mSwapChain = newSwapChain;
result = newSwapChain.CopyTo(swapChain);
}
// If the host is responsible for scaling the output of the swapchain, then
// scale it now before returning an instance to the caller. This is done by
// first reading the current size of the swapchain panel, then scaling
if (SUCCEEDED(result))
{
if (mSwapChainSizeSpecified || mSwapChainScaleSpecified)
{
result = GetSwapChainPanelSize(mSwapChainPanel, mSwapChainPanelDispatcher,
&currentPanelSize);
// Scale the swapchain to fit inside the contents of the panel.
if (SUCCEEDED(result))
{
result = scaleSwapChain(currentPanelSize, mClientRect);
}
}
}
return result;
}
HRESULT SwapChainPanelNativeWindow::scaleSwapChain(const Size &windowSize, const RECT &clientRect)
{
Size renderScale = {windowSize.Width / (float)clientRect.right,
windowSize.Height / (float)clientRect.bottom};
// Setup a scale matrix for the swap chain
DXGI_MATRIX_3X2_F scaleMatrix = {};
scaleMatrix._11 = renderScale.Width;
scaleMatrix._22 = renderScale.Height;
ComPtr<IDXGISwapChain2> swapChain2;
HRESULT result = mSwapChain.As(&swapChain2);
if (SUCCEEDED(result))
{
result = swapChain2->SetMatrixTransform(&scaleMatrix);
}
return result;
}
HRESULT GetSwapChainPanelSize(
const ComPtr<ABI::Windows::UI::Xaml::Controls::ISwapChainPanel> &swapChainPanel,
const ComPtr<ICoreDispatcher> &dispatcher,
Size *windowSize)
{
ComPtr<IUIElement> uiElement;
HRESULT result = swapChainPanel.As(&uiElement);
if (SUCCEEDED(result))
{
result = RunOnUIThread(
[uiElement, windowSize] { return uiElement->get_RenderSize(windowSize); }, dispatcher);
}
return result;
}
}