This commit is contained in:
Claire Andrews 2015-06-02 10:32:29 -07:00
Родитель 31c5094fa9
Коммит 98953e3ee0
9 изменённых файлов: 453 добавлений и 3 удалений

Просмотреть файл

@ -91,5 +91,52 @@ under the License.
</remarks>
</member>
<member name="M:Microsoft.Graphics.Canvas.CanvasDevice.IsDeviceLost(System.Int32)">
<summary>Returns whether this device has lost the ability to be operational.</summary>
<remarks>
<p>
This method expects an error code from an exception that your app has caught.
IsDeviceLost will return true if the device is indeed lost, <i>and</i> the error
code actually corresponds to device removal.
</p>
<p>
This is intended to be used like:
<code>
try { DrawStuff(); }
catch (Exception e) where canvasDevice.IsDeviceLost(e.ErrorCode)
{
canvasDevice.RaiseDeviceLost();
}
</code>
</p>
</remarks>
</member>
<member name="M:Microsoft.Graphics.Canvas.CanvasDevice.RaiseDeviceLost">
<summary>Raises an event on the device, indicating that it is no longer operational.</summary>
<remarks>
<p>
This method should be called when your app has caught a device lost exception.
See <see cref="M:Microsoft.Graphics.Canvas.CanvasDevice.IsDeviceLost(System.Int32)"/>.
</p>
<p>
Any event handlers subscribed to the <see cref="E:Microsoft.Graphics.Canvas.CanvasDevice.DeviceLost"/>
will be issued. This will occur, even if there was no actual Direct3D device loss. This
is just a convenience, to make application testing easier.
</p>
</remarks>
</member>
<member name="E:Microsoft.Graphics.Canvas.CanvasDevice.DeviceLost">
<summary>Subscribe to this event to be notified whenever the device ceases to be operational.</summary>
<remarks>
<p>
Any handlers subscribed to this event will be issued whenever <see cref="M:Microsoft.Graphics.Canvas.CanvasDevice.RaiseDeviceLost"/>
is called.
</p>
<p>
An implementation of this handler is expected to re-create the device, and any device-dependent resources.
</p>
</remarks>
</member>
</members>
</doc>

Просмотреть файл

@ -74,6 +74,35 @@ namespace Microsoft.Graphics.Canvas
[propget]
HRESULT MaximumBitmapSizeInPixels(
[out, retval] INT32* value);
//
// This event is raised whenever the native device resource is lost-
// for example, due to a user switch, lock screen, or unexpected
// graphics driver behavior. Apps are expected to re-create their
// CanvasDevice and device-dependent resources in response to this event.
//
[eventadd] HRESULT DeviceLost(
[in] Windows.Foundation.TypedEventHandler<CanvasDevice*, IInspectable*>* value,
[out, retval] EventRegistrationToken* token);
[eventremove] HRESULT DeviceLost([in] EventRegistrationToken token);
//
// Identifies whether this error code matches a device lost.
// If this device was not actually lost, this method always
// returns false.
//
HRESULT IsDeviceLost([in] int hresult, [out, retval] boolean* value);
//
// Call this method when a lost device error occurs- in particular,
// if an exception was caught by the app, whose error code yielded
// IsDeviceLost = true.
//
// This will cause invocation of the DeviceLost event. Occurs
// regardless of whether the native device as actually lost.
//
HRESULT RaiseDeviceLost();
};
[version(VERSION), activatable(VERSION), activatable(ICanvasDeviceFactory, VERSION), static(ICanvasDeviceStatics, VERSION)]

Просмотреть файл

@ -426,6 +426,69 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas
});
}
IFACEMETHODIMP CanvasDevice::add_DeviceLost(
DeviceLostHandlerType* value,
EventRegistrationToken* token)
{
return ExceptionBoundary(
[&]
{
GetResource(); // this ensures that Close() hasn't been called
CheckInPointer(value);
CheckInPointer(token);
ThrowIfFailed(m_deviceLostEventList.Add(value, token));
});
}
IFACEMETHODIMP CanvasDevice::remove_DeviceLost(
EventRegistrationToken token)
{
return ExceptionBoundary(
[&]
{
//
// This does not check if this object was closed, so to
// allow shutdown paths to freely remove events.
//
ThrowIfFailed(m_deviceLostEventList.Remove(token));
});
}
IFACEMETHODIMP CanvasDevice::IsDeviceLost(
int hresult,
boolean* value)
{
return ExceptionBoundary(
[&]
{
auto& dxgiDevice = m_dxgiDevice.EnsureNotClosed();
CheckInPointer(value);
if (DeviceLostException::IsDeviceLostHResult(hresult))
{
auto d3dDevice = As<ID3D11Device>(dxgiDevice);
*value = d3dDevice->GetDeviceRemovedReason() != S_OK;
}
else
{
*value = false;
}
});
}
IFACEMETHODIMP CanvasDevice::RaiseDeviceLost()
{
return ExceptionBoundary(
[&]
{
GetResource(); // this ensures that Close() hasn't been called
ThrowIfFailed(m_deviceLostEventList.InvokeAll(this, nullptr));
});
}
IFACEMETHODIMP CanvasDevice::Close()
{
HRESULT hr = ResourceWrapper::Close();

Просмотреть файл

@ -146,6 +146,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas
typedef CanvasDeviceManager manager_t;
};
typedef ITypedEventHandler<CanvasDevice*, IInspectable*> DeviceLostHandlerType;
//
// The CanvasDevice class itself.
@ -172,6 +173,8 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas
// device object is open or closed.
ComPtr<IDXGIOutput> m_primaryOutput;
EventSource<DeviceLostHandlerType, InvokeModeOptions<StopOnFirstError>> m_deviceLostEventList;
public:
CanvasDevice(
std::shared_ptr<CanvasDeviceManager> manager,
@ -188,6 +191,14 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas
IFACEMETHOD(get_MaximumBitmapSizeInPixels)(int32_t* value) override;
IFACEMETHOD(add_DeviceLost)(DeviceLostHandlerType* value, EventRegistrationToken* token) override;
IFACEMETHOD(remove_DeviceLost)(EventRegistrationToken token) override;
IFACEMETHOD(IsDeviceLost)(int hresult, boolean* value) override;
IFACEMETHOD(RaiseDeviceLost)() override;
//
// ICanvasResourceCreator
//

Просмотреть файл

@ -428,3 +428,214 @@ TEST_CLASS(DefaultDeviceResourceCreationAdapterTests)
Assert::AreEqual(dxgiDevice.Get(), actualDxgiDevice.Get());
}
};
static const HRESULT deviceRemovedHResults[] = {
DXGI_ERROR_DEVICE_HUNG,
DXGI_ERROR_DEVICE_REMOVED,
DXGI_ERROR_DEVICE_RESET,
DXGI_ERROR_DRIVER_INTERNAL_ERROR,
DXGI_ERROR_INVALID_CALL,
D2DERR_RECREATE_TARGET
};
TEST_CLASS(CanvasDeviceLostTests)
{
std::shared_ptr<TestDeviceResourceCreationAdapter> m_resourceCreationAdapter;
std::shared_ptr<CanvasDeviceManager> m_deviceManager;
public:
TEST_METHOD_INITIALIZE(Reset)
{
m_resourceCreationAdapter = std::make_shared<TestDeviceResourceCreationAdapter>();
m_deviceManager = std::make_shared<CanvasDeviceManager>(m_resourceCreationAdapter);
}
TEST_METHOD_EX(CanvasDeviceLostTests_Closed)
{
auto canvasDevice = m_deviceManager->Create(CanvasDebugLevel::None, CanvasHardwareAcceleration::On);
Assert::AreEqual(S_OK, canvasDevice->Close());
EventRegistrationToken token{};
MockEventHandler<DeviceLostHandlerType> dummyDeviceLostHandler(L"DeviceLost");
Assert::AreEqual(RO_E_CLOSED, canvasDevice->add_DeviceLost(dummyDeviceLostHandler.Get(), &token));
// remove_DeviceLost is intended to not check if the object is closed, and like all EventSource
// events it returns success if you try and remove an unregistered token.
Assert::AreEqual(S_OK, canvasDevice->remove_DeviceLost(token));
boolean b;
Assert::AreEqual(RO_E_CLOSED, canvasDevice->IsDeviceLost(0, &b));
Assert::AreEqual(RO_E_CLOSED, canvasDevice->RaiseDeviceLost());
}
TEST_METHOD_EX(CanvasDeviceLostTests_NullArgs)
{
auto canvasDevice = m_deviceManager->Create(CanvasDebugLevel::None, CanvasHardwareAcceleration::On);
EventRegistrationToken token{};
MockEventHandler<DeviceLostHandlerType> dummyDeviceLostHandler(L"DeviceLost");
Assert::AreEqual(E_INVALIDARG, canvasDevice->add_DeviceLost(nullptr, &token));
Assert::AreEqual(E_INVALIDARG, canvasDevice->add_DeviceLost(dummyDeviceLostHandler.Get(), nullptr));
Assert::AreEqual(E_INVALIDARG, canvasDevice->IsDeviceLost(0, nullptr));
}
class DeviceLostResourceCreationAdapter : public TestDeviceResourceCreationAdapter
{
virtual ComPtr<StubD3D11Device> CreateStubD3D11Device() override
{
auto stubD3DDevice = Make<StubD3D11Device>();
stubD3DDevice->GetDeviceRemovedReasonMethod.AllowAnyCall(
[] { return DXGI_ERROR_DEVICE_REMOVED; });
return stubD3DDevice;
}
};
class DeviceLostFixture
{
std::shared_ptr<DeviceLostResourceCreationAdapter> m_resourceCreationAdapter;
public:
std::shared_ptr<CanvasDeviceManager> DeviceManager;
DeviceLostFixture()
: m_resourceCreationAdapter(std::make_shared<DeviceLostResourceCreationAdapter>())
, DeviceManager(std::make_shared<CanvasDeviceManager>(m_resourceCreationAdapter))
{
}
};
TEST_METHOD_EX(CanvasDeviceLostTests_IsDeviceLost_DeviceRemovedHr_DeviceIsLost_ReturnsTrue)
{
DeviceLostFixture f;
auto canvasDevice = f.DeviceManager->Create(CanvasDebugLevel::None, CanvasHardwareAcceleration::On);
for (HRESULT hr : deviceRemovedHResults)
{
boolean isDeviceLost;
Assert::AreEqual(S_OK, canvasDevice->IsDeviceLost(hr, &isDeviceLost));
Assert::IsTrue(!!isDeviceLost);
}
}
TEST_METHOD_EX(CanvasDeviceLostTests_IsDeviceLost_SomeArbitraryHr_DeviceIsLost_ReturnsFalse)
{
DeviceLostFixture f;
auto canvasDevice = f.DeviceManager->Create(CanvasDebugLevel::None, CanvasHardwareAcceleration::On);
boolean isDeviceLost;
Assert::AreEqual(S_OK, canvasDevice->IsDeviceLost(E_INVALIDARG, &isDeviceLost));
Assert::IsFalse(!!isDeviceLost);
}
TEST_METHOD_EX(CanvasDeviceLostTests_IsDeviceLost_DeviceRemovedHr_DeviceNotActuallyLost_ReturnsFalse)
{
auto canvasDevice = m_deviceManager->Create(CanvasDebugLevel::None, CanvasHardwareAcceleration::On);
for (HRESULT hr : deviceRemovedHResults)
{
boolean isDeviceLost;
Assert::AreEqual(S_OK, canvasDevice->IsDeviceLost(hr, &isDeviceLost));
Assert::IsFalse(!!isDeviceLost);
}
}
TEST_METHOD_EX(CanvasDeviceLostTests_IsDeviceLost_SomeArbitraryHr_DeviceNotActuallyLost_ReturnsFalse)
{
auto canvasDevice = m_deviceManager->Create(CanvasDebugLevel::None, CanvasHardwareAcceleration::On);
boolean isDeviceLost;
Assert::AreEqual(S_OK, canvasDevice->IsDeviceLost(E_INVALIDARG, &isDeviceLost));
Assert::IsFalse(!!isDeviceLost);
}
TEST_METHOD_EX(CanvasDeviceLostTests_RaiseDeviceLost_RaisesSubscribedHandlers_DeviceActuallyLost)
{
DeviceLostFixture f;
auto canvasDevice = f.DeviceManager->Create(CanvasDebugLevel::None, CanvasHardwareAcceleration::On);
MockEventHandler<DeviceLostHandlerType> deviceLostHandler(L"DeviceLost");
deviceLostHandler.SetExpectedCalls(1);
EventRegistrationToken token{};
Assert::AreEqual(S_OK, canvasDevice->add_DeviceLost(deviceLostHandler.Get(), &token));
Assert::AreEqual(S_OK, canvasDevice->RaiseDeviceLost());
}
TEST_METHOD_EX(CanvasDeviceLostTests_RaiseDeviceLost_RaisesSubscribedHandlers_DeviceNotActuallyLost)
{
//
// The unit tests testing the DeviceLost event do not exhaustively test
// everything concerning adding/removing events, because DeviceLost is
// implemented directly on top of EventSource<...>, which
// already has coverage elsewhere.
//
auto canvasDevice = m_deviceManager->Create(CanvasDebugLevel::None, CanvasHardwareAcceleration::On);
MockEventHandler<DeviceLostHandlerType> deviceLostHandler(L"DeviceLost");
deviceLostHandler.SetExpectedCalls(1);
EventRegistrationToken token{};
Assert::AreEqual(S_OK, canvasDevice->add_DeviceLost(deviceLostHandler.Get(), &token));
Assert::AreEqual(S_OK, canvasDevice->RaiseDeviceLost());
}
TEST_METHOD_EX(CanvasDeviceLostTests_RemoveEventThen_RaiseDeviceLost_DoesNotInvokeHandler)
{
auto canvasDevice = m_deviceManager->Create(CanvasDebugLevel::None, CanvasHardwareAcceleration::On);
MockEventHandler<DeviceLostHandlerType> deviceLostHandler(L"DeviceLost");
deviceLostHandler.SetExpectedCalls(0);
EventRegistrationToken token{};
Assert::AreEqual(S_OK, canvasDevice->add_DeviceLost(deviceLostHandler.Get(), &token));
Assert::AreEqual(S_OK, canvasDevice->remove_DeviceLost(token));
Assert::AreEqual(S_OK, canvasDevice->RaiseDeviceLost());
}
TEST_METHOD_EX(CanvasDeviceLostTests_RaiseDeviceLost_HasCorrectSenderAndArgs)
{
DeviceLostFixture f;
auto canvasDevice = f.DeviceManager->Create(CanvasDebugLevel::None, CanvasHardwareAcceleration::On);
MockEventHandler<DeviceLostHandlerType> deviceLostHandler(L"DeviceLost");
deviceLostHandler.SetExpectedCalls(1,
[&](ICanvasDevice* sender, IInspectable* args)
{
Assert::AreEqual(static_cast<ICanvasDevice*>(canvasDevice.Get()), sender);
Assert::IsNull(args);
return S_OK;
});
EventRegistrationToken token{};
Assert::AreEqual(S_OK, canvasDevice->add_DeviceLost(deviceLostHandler.Get(), &token));
Assert::AreEqual(S_OK, canvasDevice->RaiseDeviceLost());
}
TEST_METHOD_EX(CanvasDeviceLostTests_RaiseDeviceLost_ExceptionFromHandlerIsPropagated)
{
DeviceLostFixture f;
auto canvasDevice = f.DeviceManager->Create(CanvasDebugLevel::None, CanvasHardwareAcceleration::On);
MockEventHandler<DeviceLostHandlerType> deviceLostHandler(L"DeviceLost");
deviceLostHandler.SetExpectedCalls(1,
[&](ICanvasDevice* sender, IInspectable* args)
{
return E_UNEXPECTED;
});
EventRegistrationToken token{};
Assert::AreEqual(S_OK, canvasDevice->add_DeviceLost(deviceLostHandler.Get(), &token));
Assert::AreEqual(E_UNEXPECTED, canvasDevice->RaiseDeviceLost());
}
};

Просмотреть файл

@ -91,6 +91,35 @@ namespace canvas
return E_NOTIMPL;
}
IFACEMETHODIMP add_DeviceLost(
DeviceLostHandlerType* value,
EventRegistrationToken* token)
{
Assert::Fail(L"Unexpected call to add_DeviceLost");
return E_NOTIMPL;
}
IFACEMETHODIMP remove_DeviceLost(
EventRegistrationToken token)
{
Assert::Fail(L"Unexpected call to remove_DeviceLost");
return E_NOTIMPL;
}
IFACEMETHODIMP IsDeviceLost(
int hresult,
boolean* value)
{
Assert::Fail(L"Unexpected call to IsDeviceLost");
return E_NOTIMPL;
}
IFACEMETHODIMP RaiseDeviceLost()
{
Assert::Fail(L"Unexpected call to RaiseDeviceLost");
return E_NOTIMPL;
}
//
// ICanvasResourceCreator
//

Просмотреть файл

@ -63,7 +63,7 @@ public:
// Stub device is used here, because, in execution of these tests, product code will
// actually call methods on the factory and expect them to succeed.
*device = Make<StubD3D11Device>();
*device = CreateStubD3D11Device();
return true;
}
}
@ -81,6 +81,11 @@ public:
m_allowHardware = enable;
}
virtual ComPtr<StubD3D11Device> CreateStubD3D11Device()
{
return Make<StubD3D11Device>();
}
// These are left public because it's test code and it's convenient to do so.
int m_numD2DFactoryCreationCalls;
CanvasDebugLevel m_debugLevel;

Просмотреть файл

@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use these files except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Microsoft.Graphics.Canvas;
namespace test.managed
{
[TestClass]
public class DeviceRemovedTests_WithoutControl
{
bool m_deviceLostRaised;
[TestMethod]
public void DeviceRaisesDeviceLostEvent()
{
CanvasDevice device = new CanvasDevice();
// Verifies that the event is raised, even if the native
// underlying device was not lost.
Assert.IsFalse(device.IsDeviceLost(0));
device.DeviceLost += device_DeviceLost;
device.RaiseDeviceLost();
Assert.IsTrue(m_deviceLostRaised);
device.DeviceLost -= device_DeviceLost;
// If the event was actually removed, shout not hit the
// assert at the top of device_DeviceLost.
device.RaiseDeviceLost();
}
void device_DeviceLost(CanvasDevice sender, object args)
{
Assert.IsFalse(m_deviceLostRaised);
Assert.IsNull(args);
m_deviceLostRaised = true;
}
}
}

Просмотреть файл

@ -9,10 +9,11 @@
<Import_RootNamespace>test.managed</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)DeviceLost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DpiTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)EffectTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GraphicsInterop.cs" />
<Compile Include="$(MSBuildThisFileDirectory)VectorInterop.cs" />
<Compile Include="$(MSBuildThisFileDirectory)PathReceiver.cs" />
<Compile Include="$(MSBuildThisFileDirectory)VectorInterop.cs" />
</ItemGroup>
</Project>