gecko/gfx/layers/d3d9/DeviceManagerD3D9.cpp
Jeff Muizelaar c97adad679 Bug 626678. Try to handle docking station device resets better. r=bas,a=b
Docking stations seem to cause DEVICELOST errors that never become
DEVICERESET so we don't ever succeed in reseting the device. We now
try to detect these situations and recreate the device.
2011-02-23 09:18:00 -08:00

697 lines
19 KiB
C++

/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Corporation code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bas Schouten <bschouten@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "DeviceManagerD3D9.h"
#include "LayerManagerD3D9Shaders.h"
#include "ThebesLayerD3D9.h"
#include "nsIServiceManager.h"
#include "nsIConsoleService.h"
#include "nsPrintfCString.h"
#include "nsIPrefService.h"
#include "Nv3DVUtils.h"
#include "plstr.h"
namespace mozilla {
namespace layers {
const LPCWSTR kClassName = L"D3D9WindowClass";
#define USE_D3D9EX
typedef IDirect3D9* (WINAPI*Direct3DCreate9Func)(
UINT SDKVersion
);
typedef HRESULT (WINAPI*Direct3DCreate9ExFunc)(
UINT SDKVersion,
IDirect3D9Ex **ppD3D
);
struct vertex {
float x, y;
};
SwapChainD3D9::SwapChainD3D9(DeviceManagerD3D9 *aDeviceManager)
: mDeviceManager(aDeviceManager)
, mWnd(0)
{
mDeviceManager->mSwapChains.AppendElement(this);
}
SwapChainD3D9::~SwapChainD3D9()
{
mDeviceManager->mSwapChains.RemoveElement(this);
}
bool
SwapChainD3D9::Init(HWND hWnd)
{
RECT r;
::GetClientRect(hWnd, &r);
mWnd = hWnd;
D3DPRESENT_PARAMETERS pp;
memset(&pp, 0, sizeof(D3DPRESENT_PARAMETERS));
pp.BackBufferFormat = D3DFMT_A8R8G8B8;
pp.SwapEffect = D3DSWAPEFFECT_COPY;
pp.Windowed = TRUE;
pp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
pp.hDeviceWindow = mWnd;
if (r.left == r.right || r.top == r.bottom) {
pp.BackBufferHeight = 1;
pp.BackBufferWidth = 1;
}
HRESULT hr = mDeviceManager->device()->
CreateAdditionalSwapChain(&pp,
getter_AddRefs(mSwapChain));
if (FAILED(hr)) {
NS_WARNING("Failed to create swap chain for window.");
return false;
}
return true;
}
bool
SwapChainD3D9::PrepareForRendering()
{
RECT r;
if (!::GetClientRect(mWnd, &r)) {
return false;
}
if (!mDeviceManager->VerifyReadyForRendering()) {
return false;
}
if (!mSwapChain) {
Init(mWnd);
}
if (mSwapChain) {
nsRefPtr<IDirect3DSurface9> backBuffer;
mSwapChain->GetBackBuffer(0,
D3DBACKBUFFER_TYPE_MONO,
getter_AddRefs(backBuffer));
D3DSURFACE_DESC desc;
backBuffer->GetDesc(&desc);
if (desc.Width == r.right - r.left && desc.Height == r.bottom - r.top) {
mDeviceManager->device()->SetRenderTarget(0, backBuffer);
return true;
}
mSwapChain = nsnull;
Init(mWnd);
if (!mSwapChain) {
return false;
}
mSwapChain->GetBackBuffer(0,
D3DBACKBUFFER_TYPE_MONO,
getter_AddRefs(backBuffer));
mDeviceManager->device()->SetRenderTarget(0, backBuffer);
return true;
}
return false;
}
void
SwapChainD3D9::Present(const nsIntRect &aRect)
{
RECT r;
r.left = aRect.x;
r.top = aRect.y;
r.right = aRect.XMost();
r.bottom = aRect.YMost();
mSwapChain->Present(&r, &r, 0, 0, 0);
}
void
SwapChainD3D9::Reset()
{
mSwapChain = nsnull;
}
#define HAS_CAP(a, b) (((a) & (b)) == (b))
#define LACKS_CAP(a, b) !(((a) & (b)) == (b))
DeviceManagerD3D9::DeviceManagerD3D9()
: mDeviceResetCount(0)
, mHasDynamicTextures(false)
, mDeviceWasRemoved(false)
{
}
DeviceManagerD3D9::~DeviceManagerD3D9()
{
LayerManagerD3D9::OnDeviceManagerDestroy(this);
}
NS_IMPL_ADDREF(DeviceManagerD3D9)
NS_IMPL_RELEASE(DeviceManagerD3D9)
bool
DeviceManagerD3D9::Init()
{
WNDCLASSW wc;
HRESULT hr;
if (!GetClassInfoW(GetModuleHandle(NULL), kClassName, &wc)) {
ZeroMemory(&wc, sizeof(WNDCLASSW));
wc.hInstance = GetModuleHandle(NULL);
wc.lpfnWndProc = ::DefWindowProc;
wc.lpszClassName = kClassName;
if (!RegisterClassW(&wc)) {
NS_WARNING("Failed to register window class for DeviceManager.");
return false;
}
}
mFocusWnd = ::CreateWindowW(kClassName, L"D3D9Window", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL,
NULL, GetModuleHandle(NULL), NULL);
if (!mFocusWnd) {
NS_WARNING("Failed to create DeviceManagerD3D9 Window.");
return false;
}
/* Create an Nv3DVUtils instance */
if (!mNv3DVUtils) {
mNv3DVUtils = new Nv3DVUtils();
if (!mNv3DVUtils) {
NS_WARNING("Could not create a new instance of Nv3DVUtils.\n");
}
}
/* Initialize the Nv3DVUtils object */
if (mNv3DVUtils) {
mNv3DVUtils->Initialize();
}
HMODULE d3d9 = LoadLibraryW(L"d3d9.dll");
Direct3DCreate9Func d3d9Create = (Direct3DCreate9Func)
GetProcAddress(d3d9, "Direct3DCreate9");
Direct3DCreate9ExFunc d3d9CreateEx = (Direct3DCreate9ExFunc)
GetProcAddress(d3d9, "Direct3DCreate9Ex");
#ifdef USE_D3D9EX
if (d3d9CreateEx) {
hr = d3d9CreateEx(D3D_SDK_VERSION, getter_AddRefs(mD3D9Ex));
if (SUCCEEDED(hr)) {
mD3D9 = mD3D9Ex;
}
}
#endif
if (!mD3D9) {
if (!d3d9Create) {
return false;
}
mD3D9 = dont_AddRef(d3d9Create(D3D_SDK_VERSION));
if (!mD3D9) {
return false;
}
}
D3DADAPTER_IDENTIFIER9 ident;
hr = mD3D9->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &ident);
if (FAILED(hr)) {
return false;
}
if (!PL_strncasecmp(ident.Driver, "nvumdshim.dll", PL_strlen(ident.Driver))) {
// XXX - This is a device using NVidia Optimus. We have no idea how to do
// interop here so let's fail and use BasicLayers. See bug 597320.
return false;
}
D3DPRESENT_PARAMETERS pp;
memset(&pp, 0, sizeof(D3DPRESENT_PARAMETERS));
pp.BackBufferWidth = 1;
pp.BackBufferHeight = 1;
pp.BackBufferFormat = D3DFMT_A8R8G8B8;
pp.SwapEffect = D3DSWAPEFFECT_DISCARD;
pp.Windowed = TRUE;
pp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
pp.hDeviceWindow = mFocusWnd;
if (mD3D9Ex) {
hr = mD3D9Ex->CreateDeviceEx(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
mFocusWnd,
D3DCREATE_FPU_PRESERVE |
D3DCREATE_MULTITHREADED |
D3DCREATE_MIXED_VERTEXPROCESSING,
&pp,
NULL,
getter_AddRefs(mDeviceEx));
if (SUCCEEDED(hr)) {
mDevice = mDeviceEx;
}
D3DCAPS9 caps;
if (mDeviceEx && mDeviceEx->GetDeviceCaps(&caps)) {
if (LACKS_CAP(caps.Caps2, D3DCAPS2_DYNAMICTEXTURES)) {
// XXX - Should we actually hit this we'll need a CanvasLayer that
// supports static D3DPOOL_DEFAULT textures.
NS_WARNING("D3D9Ex device not used because of lack of support for \
dynamic textures. This is unexpected.");
mDevice = nsnull;
mDeviceEx = nsnull;
}
}
}
if (!mDevice) {
hr = mD3D9->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
mFocusWnd,
D3DCREATE_FPU_PRESERVE |
D3DCREATE_MULTITHREADED |
D3DCREATE_MIXED_VERTEXPROCESSING,
&pp,
getter_AddRefs(mDevice));
if (FAILED(hr)) {
NS_WARNING("Failed to create Device for DeviceManagerD3D9.");
return false;
}
}
if (!VerifyCaps()) {
return false;
}
/* Grab the associated HMONITOR so that we can find out
* if it changed later */
D3DDEVICE_CREATION_PARAMETERS parameters;
if (FAILED(mDevice->GetCreationParameters(&parameters)))
return false;
mDeviceMonitor = mD3D9->GetAdapterMonitor(parameters.AdapterOrdinal);
/*
* Do some post device creation setup
*/
if (mNv3DVUtils) {
IUnknown* devUnknown = NULL;
if (mDevice) {
mDevice->QueryInterface(IID_IUnknown, (void **)&devUnknown);
}
mNv3DVUtils->SetDeviceInfo(devUnknown);
}
hr = mDevice->CreateVertexShader((DWORD*)LayerQuadVS,
getter_AddRefs(mLayerVS));
if (FAILED(hr)) {
return false;
}
hr = mDevice->CreatePixelShader((DWORD*)RGBShaderPS,
getter_AddRefs(mRGBPS));
if (FAILED(hr)) {
return false;
}
hr = mDevice->CreatePixelShader((DWORD*)RGBAShaderPS,
getter_AddRefs(mRGBAPS));
if (FAILED(hr)) {
return false;
}
hr = mDevice->CreatePixelShader((DWORD*)ComponentPass1ShaderPS,
getter_AddRefs(mComponentPass1PS));
if (FAILED(hr)) {
return false;
}
hr = mDevice->CreatePixelShader((DWORD*)ComponentPass2ShaderPS,
getter_AddRefs(mComponentPass2PS));
if (FAILED(hr)) {
return false;
}
hr = mDevice->CreatePixelShader((DWORD*)YCbCrShaderPS,
getter_AddRefs(mYCbCrPS));
if (FAILED(hr)) {
return false;
}
hr = mDevice->CreatePixelShader((DWORD*)SolidColorShaderPS,
getter_AddRefs(mSolidColorPS));
if (FAILED(hr)) {
return false;
}
if (!CreateVertexBuffer()) {
return false;
}
hr = mDevice->SetStreamSource(0, mVB, 0, sizeof(vertex));
if (FAILED(hr)) {
return false;
}
D3DVERTEXELEMENT9 elements[] = {
{ 0, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT,
D3DDECLUSAGE_POSITION, 0 },
D3DDECL_END()
};
mDevice->CreateVertexDeclaration(elements, getter_AddRefs(mVD));
nsCOMPtr<nsIConsoleService>
console(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
D3DADAPTER_IDENTIFIER9 identifier;
mD3D9->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &identifier);
if (console) {
nsString msg;
msg +=
NS_LITERAL_STRING("Direct3D 9 DeviceManager Initialized Succesfully.\nDriver: ");
msg += NS_ConvertUTF8toUTF16(
nsDependentCString((const char*)identifier.Driver));
msg += NS_LITERAL_STRING("\nDescription: ");
msg += NS_ConvertUTF8toUTF16(
nsDependentCString((const char*)identifier.Description));
msg += NS_LITERAL_STRING("\nVersion: ");
msg += NS_ConvertUTF8toUTF16(
nsPrintfCString("%d.%d.%d.%d",
HIWORD(identifier.DriverVersion.HighPart),
LOWORD(identifier.DriverVersion.HighPart),
HIWORD(identifier.DriverVersion.LowPart),
LOWORD(identifier.DriverVersion.LowPart)));
console->LogStringMessage(msg.get());
}
return true;
}
void
DeviceManagerD3D9::SetupRenderState()
{
mDevice->SetStreamSource(0, mVB, 0, sizeof(vertex));
mDevice->SetVertexDeclaration(mVD);
mDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
mDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
mDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
mDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
mDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
mDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
mDevice->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);
mDevice->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA);
mDevice->SetRenderState(D3DRS_BLENDOPALPHA, D3DBLENDOP_ADD);
mDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
mDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
mDevice->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
mDevice->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
mDevice->SetSamplerState(2, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
mDevice->SetSamplerState(2, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
mDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
mDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
mDevice->SetSamplerState(1, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
mDevice->SetSamplerState(1, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
mDevice->SetSamplerState(2, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
mDevice->SetSamplerState(2, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
}
already_AddRefed<SwapChainD3D9>
DeviceManagerD3D9::CreateSwapChain(HWND hWnd)
{
nsRefPtr<SwapChainD3D9> swapChain = new SwapChainD3D9(this);
// See bug 604647. This line means that if we create a window while the
// device is lost LayerManager initialization will fail, this window
// will be permanently unaccelerated. This should be a rare situation
// though and the need for a low-risk fix for this bug outweighs the
// downside.
if (!VerifyReadyForRendering()) {
return nsnull;
}
if (!swapChain->Init(hWnd)) {
return nsnull;
}
return swapChain.forget();
}
void
DeviceManagerD3D9::SetShaderMode(ShaderMode aMode)
{
switch (aMode) {
case RGBLAYER:
mDevice->SetVertexShader(mLayerVS);
mDevice->SetPixelShader(mRGBPS);
break;
case RGBALAYER:
mDevice->SetVertexShader(mLayerVS);
mDevice->SetPixelShader(mRGBAPS);
break;
case COMPONENTLAYERPASS1:
mDevice->SetVertexShader(mLayerVS);
mDevice->SetPixelShader(mComponentPass1PS);
break;
case COMPONENTLAYERPASS2:
mDevice->SetVertexShader(mLayerVS);
mDevice->SetPixelShader(mComponentPass2PS);
break;
case YCBCRLAYER:
mDevice->SetVertexShader(mLayerVS);
mDevice->SetPixelShader(mYCbCrPS);
break;
case SOLIDCOLORLAYER:
mDevice->SetVertexShader(mLayerVS);
mDevice->SetPixelShader(mSolidColorPS);
break;
}
}
bool
DeviceManagerD3D9::VerifyReadyForRendering()
{
HRESULT hr = mDevice->TestCooperativeLevel();
if (SUCCEEDED(hr)) {
if (IsD3D9Ex()) {
hr = mDeviceEx->CheckDeviceState(mFocusWnd);
if (FAILED(hr)) {
mDeviceWasRemoved = true;
LayerManagerD3D9::OnDeviceManagerDestroy(this);
++mDeviceResetCount;
return false;
}
}
return true;
}
for(unsigned int i = 0; i < mLayersWithResources.Length(); i++) {
mLayersWithResources[i]->CleanResources();
}
for(unsigned int i = 0; i < mSwapChains.Length(); i++) {
mSwapChains[i]->Reset();
}
mVB = nsnull;
D3DPRESENT_PARAMETERS pp;
memset(&pp, 0, sizeof(D3DPRESENT_PARAMETERS));
pp.BackBufferWidth = 1;
pp.BackBufferHeight = 1;
pp.BackBufferFormat = D3DFMT_A8R8G8B8;
pp.SwapEffect = D3DSWAPEFFECT_DISCARD;
pp.Windowed = TRUE;
pp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
pp.hDeviceWindow = mFocusWnd;
hr = mDevice->Reset(&pp);
++mDeviceResetCount;
if (hr == D3DERR_DEVICELOST) {
/* It is not unusual for Reset to return DEVICELOST
* we're supposed to continue trying until we get
* DEVICENOTRESET and then Reset is supposed to succeed.
* Unfortunately, it seems like when we dock or undock
* DEVICELOST happens and we never get DEVICENOTRESET. */
HMONITOR hMonitorWindow;
hMonitorWindow = MonitorFromWindow(mFocusWnd, MONITOR_DEFAULTTOPRIMARY);
if (hMonitorWindow == mDeviceMonitor) {
/* The monitor has not changed. So, let's assume that the
* DEVICENOTRESET will be comming. */
/* jrmuizel: I'm not sure how to trigger this case. Usually, we get
* DEVICENOTRESET right away and Reset() succeeds without going through a
* set of DEVICELOSTs. This is presumeably because we don't call
* VerifyReadyForRendering when we don't have any reason to paint.
* Hopefully comparing HMONITORs is not overly aggressive. */
return false;
}
/* otherwise fall through and recreate the device */
}
if (FAILED(hr) || !CreateVertexBuffer()) {
mDeviceWasRemoved = true;
LayerManagerD3D9::OnDeviceManagerDestroy(this);
return false;
}
return true;
}
bool
DeviceManagerD3D9::VerifyCaps()
{
D3DCAPS9 caps;
HRESULT hr = mDevice->GetDeviceCaps(&caps);
if (FAILED(hr)) {
return false;
}
if (LACKS_CAP(caps.DevCaps, D3DDEVCAPS_TEXTUREVIDEOMEMORY)) {
return false;
}
if (LACKS_CAP(caps.PrimitiveMiscCaps, D3DPMISCCAPS_CULLNONE)) {
return false;
}
if (LACKS_CAP(caps.SrcBlendCaps, D3DPBLENDCAPS_ONE) ||
LACKS_CAP(caps.SrcBlendCaps, D3DBLEND_SRCALPHA) ||
LACKS_CAP(caps.DestBlendCaps, D3DPBLENDCAPS_INVSRCALPHA)) {
return false;
}
if (LACKS_CAP(caps.RasterCaps, D3DPRASTERCAPS_SCISSORTEST)) {
return false;
}
if (LACKS_CAP(caps.TextureCaps, D3DPTEXTURECAPS_ALPHA) ||
HAS_CAP(caps.TextureCaps, D3DPTEXTURECAPS_SQUAREONLY) ||
(HAS_CAP(caps.TextureCaps, D3DPTEXTURECAPS_POW2) &&
LACKS_CAP(caps.TextureCaps, D3DPTEXTURECAPS_NONPOW2CONDITIONAL))) {
return false;
}
if (LACKS_CAP(caps.TextureFilterCaps, D3DPTFILTERCAPS_MAGFLINEAR) ||
LACKS_CAP(caps.TextureFilterCaps, D3DPTFILTERCAPS_MINFLINEAR)) {
return false;
}
if (LACKS_CAP(caps.TextureAddressCaps, D3DPTADDRESSCAPS_CLAMP)) {
return false;
}
if (caps.MaxTextureHeight < 4096 ||
caps.MaxTextureWidth < 4096) {
return false;
}
if ((caps.PixelShaderVersion & 0xffff) < 0x200 ||
(caps.VertexShaderVersion & 0xffff) < 0x200) {
return false;
}
if (HAS_CAP(caps.Caps2, D3DCAPS2_DYNAMICTEXTURES)) {
mHasDynamicTextures = true;
}
return true;
}
bool
DeviceManagerD3D9::CreateVertexBuffer()
{
HRESULT hr;
hr = mDevice->CreateVertexBuffer(sizeof(vertex) * 4,
D3DUSAGE_WRITEONLY,
0,
D3DPOOL_DEFAULT,
getter_AddRefs(mVB),
NULL);
if (FAILED(hr)) {
return false;
}
vertex *vertices;
hr = mVB->Lock(0, 0, (void**)&vertices, 0);
if (FAILED(hr)) {
return false;
}
vertices[0].x = vertices[0].y = 0;
vertices[1].x = 1; vertices[1].y = 0;
vertices[2].x = 0; vertices[2].y = 1;
vertices[3].x = 1; vertices[3].y = 1;
mVB->Unlock();
return true;
}
} /* namespace layers */
} /* namespace mozilla */