diff options
Diffstat (limited to 'gfx/layers/d3d9/CompositorD3D9.cpp')
-rw-r--r-- | gfx/layers/d3d9/CompositorD3D9.cpp | 1045 |
1 files changed, 1045 insertions, 0 deletions
diff --git a/gfx/layers/d3d9/CompositorD3D9.cpp b/gfx/layers/d3d9/CompositorD3D9.cpp new file mode 100644 index 000000000..0f7e942c1 --- /dev/null +++ b/gfx/layers/d3d9/CompositorD3D9.cpp @@ -0,0 +1,1045 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CompositorD3D9.h" +#include "LayerManagerD3D9Shaders.h" +#include "gfxWindowsPlatform.h" +#include "nsIWidget.h" +#include "mozilla/layers/ImageHost.h" +#include "mozilla/layers/ContentHost.h" +#include "mozilla/layers/Effects.h" +#include "nsWindowsHelpers.h" +#include "Nv3DVUtils.h" +#include "gfxFailure.h" +#include "mozilla/layers/LayerManagerComposite.h" +#include "gfxPrefs.h" +#include "gfxCrashReporterUtils.h" +#include "gfxUtils.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/widget/WinCompositorWidget.h" +#include "D3D9SurfaceImage.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +CompositorD3D9::CompositorD3D9(CompositorBridgeParent* aParent, widget::CompositorWidget* aWidget) + : Compositor(aWidget, aParent) + , mDeviceResetCount(0) + , mFailedResetAttempts(0) +{ +} + +CompositorD3D9::~CompositorD3D9() +{ + mSwapChain = nullptr; + mDeviceManager = nullptr; +} + +bool +CompositorD3D9::Initialize(nsCString* const out_failureReason) +{ + ScopedGfxFeatureReporter reporter("D3D9 Layers"); + + mDeviceManager = DeviceManagerD3D9::Get(); + if (!mDeviceManager) { + *out_failureReason = "FEATURE_FAILURE_D3D9_DEVICE_MANAGER"; + return false; + } + + mSwapChain = mDeviceManager->CreateSwapChain(mWidget->AsWindows()->GetHwnd()); + if (!mSwapChain) { + *out_failureReason = "FEATURE_FAILURE_D3D9_SWAP_CHAIN"; + return false; + } + + if (!mWidget->InitCompositor(this)) { + *out_failureReason = "FEATURE_FAILURE_D3D9_INIT_COMPOSITOR"; + return false; + } + + reporter.SetSuccessful(); + + return true; +} + +TextureFactoryIdentifier +CompositorD3D9::GetTextureFactoryIdentifier() +{ + TextureFactoryIdentifier ident; + ident.mMaxTextureSize = GetMaxTextureSize(); + ident.mParentBackend = LayersBackend::LAYERS_D3D9; + ident.mParentProcessType = XRE_GetProcessType(); + ident.mSupportsComponentAlpha = SupportsEffect(EffectTypes::COMPONENT_ALPHA); + return ident; +} + +bool +CompositorD3D9::CanUseCanvasLayerForSize(const IntSize &aSize) +{ + int32_t maxTextureSize = GetMaxTextureSize(); + + if (aSize.width > maxTextureSize || aSize.height > maxTextureSize) { + return false; + } + + return true; +} + +int32_t +CompositorD3D9::GetMaxTextureSize() const +{ + return mDeviceManager ? mDeviceManager->GetMaxTextureSize() : INT32_MAX; +} + +already_AddRefed<DataTextureSource> +CompositorD3D9::CreateDataTextureSource(TextureFlags aFlags) +{ + return MakeAndAddRef<DataTextureSourceD3D9>(SurfaceFormat::UNKNOWN, this, aFlags); +} + +already_AddRefed<CompositingRenderTarget> +CompositorD3D9::CreateRenderTarget(const gfx::IntRect &aRect, + SurfaceInitMode aInit) +{ + MOZ_ASSERT(aRect.width != 0 && aRect.height != 0, "Trying to create a render target of invalid size"); + + if (aRect.width * aRect.height == 0) { + return nullptr; + } + + if (!mDeviceManager) { + return nullptr; + } + + RefPtr<IDirect3DTexture9> texture; + HRESULT hr = device()->CreateTexture(aRect.width, aRect.height, 1, + D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, + D3DPOOL_DEFAULT, getter_AddRefs(texture), + nullptr); + if (FAILED(hr)) { + ReportFailure(NS_LITERAL_CSTRING("CompositorD3D9::CreateRenderTarget: Failed to create texture"), + hr); + return nullptr; + } + + return MakeAndAddRef<CompositingRenderTargetD3D9>(texture, aInit, aRect); +} + +already_AddRefed<IDirect3DTexture9> +CompositorD3D9::CreateTexture(const gfx::IntRect& aRect, + const CompositingRenderTarget* aSource, + const gfx::IntPoint& aSourcePoint) +{ + MOZ_ASSERT(aRect.width != 0 && aRect.height != 0, "Trying to create a render target of invalid size"); + + if (aRect.width * aRect.height == 0) { + return nullptr; + } + + if (!mDeviceManager) { + return nullptr; + } + + RefPtr<IDirect3DTexture9> texture; + HRESULT hr = device()->CreateTexture(aRect.width, aRect.height, 1, + D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, + D3DPOOL_DEFAULT, getter_AddRefs(texture), + nullptr); + if (FAILED(hr)) { + ReportFailure(NS_LITERAL_CSTRING("CompositorD3D9::CreateRenderTargetFromSource: Failed to create texture"), + hr); + return nullptr; + } + + if (aSource) { + RefPtr<IDirect3DSurface9> sourceSurface = + static_cast<const CompositingRenderTargetD3D9*>(aSource)->GetD3D9Surface(); + + RefPtr<IDirect3DSurface9> destSurface; + hr = texture->GetSurfaceLevel(0, getter_AddRefs(destSurface)); + if (FAILED(hr)) { + NS_WARNING("Failed to get texture surface level for dest."); + } + + if (sourceSurface && destSurface) { + RECT sourceRect; + sourceRect.left = aSourcePoint.x; + sourceRect.right = aSourcePoint.x + aRect.width; + sourceRect.top = aSourcePoint.y; + sourceRect.bottom = aSourcePoint.y + aRect.height; + RECT destRect; + destRect.left = 0; + destRect.right = aRect.width; + destRect.top = 0; + destRect.bottom = aRect.height; + + // copy the source to the dest + hr = device()->StretchRect(sourceSurface, + &sourceRect, + destSurface, + &destRect, + D3DTEXF_NONE); + if (FAILED(hr)) { + ReportFailure(NS_LITERAL_CSTRING("CompositorD3D9::CreateRenderTargetFromSource: Failed to update texture"), + hr); + } + } + } + + return texture.forget(); +} + +already_AddRefed<CompositingRenderTarget> +CompositorD3D9::CreateRenderTargetFromSource(const gfx::IntRect &aRect, + const CompositingRenderTarget *aSource, + const gfx::IntPoint &aSourcePoint) +{ + RefPtr<IDirect3DTexture9> texture = CreateTexture(aRect, aSource, aSourcePoint); + + if (!texture) { + return nullptr; + } + + return MakeAndAddRef<CompositingRenderTargetD3D9>(texture, + INIT_MODE_NONE, + aRect); +} + +void +CompositorD3D9::SetRenderTarget(CompositingRenderTarget *aRenderTarget) +{ + MOZ_ASSERT(aRenderTarget && mDeviceManager); + RefPtr<CompositingRenderTargetD3D9> oldRT = mCurrentRT; + mCurrentRT = static_cast<CompositingRenderTargetD3D9*>(aRenderTarget); + mCurrentRT->BindRenderTarget(device()); + PrepareViewport(mCurrentRT->GetSize()); +} + +static DeviceManagerD3D9::ShaderMode +ShaderModeForEffectType(EffectTypes aEffectType, gfx::SurfaceFormat aFormat) +{ + switch (aEffectType) { + case EffectTypes::SOLID_COLOR: + return DeviceManagerD3D9::SOLIDCOLORLAYER; + case EffectTypes::RENDER_TARGET: + return DeviceManagerD3D9::RGBALAYER; + case EffectTypes::RGB: + if (aFormat == SurfaceFormat::B8G8R8A8 || aFormat == SurfaceFormat::R8G8B8A8) + return DeviceManagerD3D9::RGBALAYER; + return DeviceManagerD3D9::RGBLAYER; + case EffectTypes::YCBCR: + return DeviceManagerD3D9::YCBCRLAYER; + } + + MOZ_CRASH("GFX: Bad effect type"); +} + +void +CompositorD3D9::ClearRect(const gfx::Rect& aRect) +{ + D3DRECT rect; + rect.x1 = aRect.X(); + rect.y1 = aRect.Y(); + rect.x2 = aRect.XMost(); + rect.y2 = aRect.YMost(); + + device()->Clear(1, &rect, D3DCLEAR_TARGET, + 0x00000000, 0, 0); +} + +void +CompositorD3D9::DrawQuad(const gfx::Rect &aRect, + const gfx::IntRect &aClipRect, + const EffectChain &aEffectChain, + gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) +{ + if (!mDeviceManager) { + return; + } + + IDirect3DDevice9* d3d9Device = device(); + MOZ_ASSERT(d3d9Device, "We should be able to get a device now"); + + MOZ_ASSERT(mCurrentRT, "No render target"); + d3d9Device->SetVertexShaderConstantF(CBmLayerTransform, &aTransform._11, 4); + + IntPoint origin = mCurrentRT->GetOrigin(); + float renderTargetOffset[] = { float(origin.x), float(origin.y), 0, 0 }; + d3d9Device->SetVertexShaderConstantF(CBvRenderTargetOffset, + renderTargetOffset, + 1); + d3d9Device->SetVertexShaderConstantF(CBvLayerQuad, + ShaderConstantRect(aRect.x, + aRect.y, + aRect.width, + aRect.height), + 1); + + if (aEffectChain.mPrimaryEffect->mType != EffectTypes::SOLID_COLOR) { + float opacity[4]; + /* + * We always upload a 4 component float, but the shader will use only the + * first component since it's declared as a 'float'. + */ + opacity[0] = aOpacity; + d3d9Device->SetPixelShaderConstantF(CBfLayerOpacity, opacity, 1); + } + + bool isPremultiplied = true; + + MaskType maskType = MaskType::MaskNone; + + if (aEffectChain.mSecondaryEffects[EffectTypes::MASK]) { + maskType = MaskType::Mask; + } + + gfx::Rect backdropDest; + gfx::IntRect backdropRect; + gfx::Matrix4x4 backdropTransform; + RefPtr<IDirect3DTexture9> backdropTexture; + gfx::CompositionOp blendMode = gfx::CompositionOp::OP_OVER; + + if (aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE]) { + EffectBlendMode *blendEffect = + static_cast<EffectBlendMode*>(aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE].get()); + blendMode = blendEffect->mBlendMode; + + // Pixel Shader Model 2.0 is too limited to perform blending in the same way + // as Direct3D 11 - there are too many instructions, and we don't have + // configurable shaders (as we do with OGL) that would avoid a huge shader + // matrix. + // + // Instead, we use a multi-step process for blending on D3D9: + // (1) Capture the backdrop into a temporary surface. + // (2) Render the effect chain onto the backdrop, with OP_SOURCE. + // (3) Capture the backdrop again into another surface - these are our source pixels. + // (4) Perform a final blend step using software. + // (5) Blit the blended result back to the render target. + if (BlendOpIsMixBlendMode(blendMode)) { + backdropRect = ComputeBackdropCopyRect( + aRect, aClipRect, aTransform, &backdropTransform, &backdropDest); + + // If this fails, don't set a blend op. + backdropTexture = CreateTexture(backdropRect, mCurrentRT, backdropRect.TopLeft()); + if (!backdropTexture) { + blendMode = gfx::CompositionOp::OP_OVER; + } + } + } + + RECT scissor; + scissor.left = aClipRect.x; + scissor.right = aClipRect.XMost(); + scissor.top = aClipRect.y; + scissor.bottom = aClipRect.YMost(); + d3d9Device->SetScissorRect(&scissor); + + uint32_t maskTexture = 0; + switch (aEffectChain.mPrimaryEffect->mType) { + case EffectTypes::SOLID_COLOR: + { + // output color is premultiplied, so we need to adjust all channels. + Color layerColor = + static_cast<EffectSolidColor*>(aEffectChain.mPrimaryEffect.get())->mColor; + float color[4]; + color[0] = layerColor.r * layerColor.a * aOpacity; + color[1] = layerColor.g * layerColor.a * aOpacity; + color[2] = layerColor.b * layerColor.a * aOpacity; + color[3] = layerColor.a * aOpacity; + + d3d9Device->SetPixelShaderConstantF(CBvColor, color, 1); + + maskTexture = mDeviceManager + ->SetShaderMode(DeviceManagerD3D9::SOLIDCOLORLAYER, maskType); + } + break; + case EffectTypes::RENDER_TARGET: + case EffectTypes::RGB: + { + TexturedEffect* texturedEffect = + static_cast<TexturedEffect*>(aEffectChain.mPrimaryEffect.get()); + + Rect textureCoords = texturedEffect->mTextureCoords; + d3d9Device->SetVertexShaderConstantF(CBvTextureCoords, + ShaderConstantRect( + textureCoords.x, + textureCoords.y, + textureCoords.width, + textureCoords.height), + 1); + + SetSamplerForSamplingFilter(texturedEffect->mSamplingFilter); + + TextureSourceD3D9* source = texturedEffect->mTexture->AsSourceD3D9(); + d3d9Device->SetTexture(0, source->GetD3D9Texture()); + + maskTexture = mDeviceManager + ->SetShaderMode(ShaderModeForEffectType(aEffectChain.mPrimaryEffect->mType, + texturedEffect->mTexture->GetFormat()), + maskType); + + isPremultiplied = texturedEffect->mPremultiplied; + } + break; + case EffectTypes::YCBCR: + { + EffectYCbCr* ycbcrEffect = + static_cast<EffectYCbCr*>(aEffectChain.mPrimaryEffect.get()); + + SetSamplerForSamplingFilter(SamplingFilter::LINEAR); + + Rect textureCoords = ycbcrEffect->mTextureCoords; + d3d9Device->SetVertexShaderConstantF(CBvTextureCoords, + ShaderConstantRect( + textureCoords.x, + textureCoords.y, + textureCoords.width, + textureCoords.height), + 1); + + const int Y = 0, Cb = 1, Cr = 2; + TextureSource* source = ycbcrEffect->mTexture; + + if (!source) { + NS_WARNING("No texture to composite"); + return; + } + + if (!source->GetSubSource(Y) || !source->GetSubSource(Cb) || !source->GetSubSource(Cr)) { + // This can happen if we failed to upload the textures, most likely + // because of unsupported dimensions (we don't tile YCbCr textures). + return; + } + + + float* yuvToRgb = gfxUtils::Get4x3YuvColorMatrix(ycbcrEffect->mYUVColorSpace); + d3d9Device->SetPixelShaderConstantF(CBmYuvColorMatrix, yuvToRgb, 3); + + TextureSourceD3D9* sourceY = source->GetSubSource(Y)->AsSourceD3D9(); + TextureSourceD3D9* sourceCb = source->GetSubSource(Cb)->AsSourceD3D9(); + TextureSourceD3D9* sourceCr = source->GetSubSource(Cr)->AsSourceD3D9(); + + + MOZ_ASSERT(sourceY->GetD3D9Texture()); + MOZ_ASSERT(sourceCb->GetD3D9Texture()); + MOZ_ASSERT(sourceCr->GetD3D9Texture()); + + /* + * Send 3d control data and metadata + */ + if (mDeviceManager->GetNv3DVUtils()) { + Nv_Stereo_Mode mode; + switch (source->AsSourceD3D9()->GetStereoMode()) { + case StereoMode::LEFT_RIGHT: + mode = NV_STEREO_MODE_LEFT_RIGHT; + break; + case StereoMode::RIGHT_LEFT: + mode = NV_STEREO_MODE_RIGHT_LEFT; + break; + case StereoMode::BOTTOM_TOP: + mode = NV_STEREO_MODE_BOTTOM_TOP; + break; + case StereoMode::TOP_BOTTOM: + mode = NV_STEREO_MODE_TOP_BOTTOM; + break; + case StereoMode::MONO: + mode = NV_STEREO_MODE_MONO; + break; + } + + // Send control data even in mono case so driver knows to leave stereo mode. + mDeviceManager->GetNv3DVUtils()->SendNv3DVControl(mode, true, FIREFOX_3DV_APP_HANDLE); + + if (source->AsSourceD3D9()->GetStereoMode() != StereoMode::MONO) { + mDeviceManager->GetNv3DVUtils()->SendNv3DVControl(mode, true, FIREFOX_3DV_APP_HANDLE); + + RefPtr<IDirect3DSurface9> renderTarget; + d3d9Device->GetRenderTarget(0, getter_AddRefs(renderTarget)); + mDeviceManager->GetNv3DVUtils()->SendNv3DVMetaData((unsigned int)aRect.width, + (unsigned int)aRect.height, + (HANDLE)(sourceY->GetD3D9Texture()), + (HANDLE)(renderTarget)); + } + } + + // Linear scaling is default here, adhering to mFilter is difficult since + // presumably even with point filtering we'll still want chroma upsampling + // to be linear. In the current approach we can't. + device()->SetTexture(Y, sourceY->GetD3D9Texture()); + device()->SetTexture(Cb, sourceCb->GetD3D9Texture()); + device()->SetTexture(Cr, sourceCr->GetD3D9Texture()); + maskTexture = mDeviceManager->SetShaderMode(DeviceManagerD3D9::YCBCRLAYER, maskType); + } + break; + case EffectTypes::COMPONENT_ALPHA: + { + MOZ_ASSERT(gfxPrefs::ComponentAlphaEnabled()); + EffectComponentAlpha* effectComponentAlpha = + static_cast<EffectComponentAlpha*>(aEffectChain.mPrimaryEffect.get()); + TextureSourceD3D9* sourceOnWhite = effectComponentAlpha->mOnWhite->AsSourceD3D9(); + TextureSourceD3D9* sourceOnBlack = effectComponentAlpha->mOnBlack->AsSourceD3D9(); + + Rect textureCoords = effectComponentAlpha->mTextureCoords; + d3d9Device->SetVertexShaderConstantF(CBvTextureCoords, + ShaderConstantRect( + textureCoords.x, + textureCoords.y, + textureCoords.width, + textureCoords.height), + 1); + + SetSamplerForSamplingFilter(effectComponentAlpha->mSamplingFilter); + + maskTexture = mDeviceManager->SetShaderMode(DeviceManagerD3D9::COMPONENTLAYERPASS1, maskType); + SetMask(aEffectChain, maskTexture); + d3d9Device->SetTexture(0, sourceOnBlack->GetD3D9Texture()); + d3d9Device->SetTexture(1, sourceOnWhite->GetD3D9Texture()); + d3d9Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO); + d3d9Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCCOLOR); + d3d9Device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); + + maskTexture = mDeviceManager->SetShaderMode(DeviceManagerD3D9::COMPONENTLAYERPASS2, maskType); + SetMask(aEffectChain, maskTexture); + d3d9Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE); + d3d9Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE); + d3d9Device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); + + // Restore defaults + d3d9Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE); + d3d9Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + d3d9Device->SetTexture(1, nullptr); + } + return; + default: + NS_WARNING("Unknown shader type"); + return; + } + + SetMask(aEffectChain, maskTexture); + + if (BlendOpIsMixBlendMode(blendMode)) { + // Use SOURCE instead of OVER to get the original source pixels without + // having to render to another intermediate target. + d3d9Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO); + } + if (!isPremultiplied) { + d3d9Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + } + + d3d9Device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); + + // Restore defaults. + if (BlendOpIsMixBlendMode(blendMode)) { + d3d9Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + } + if (!isPremultiplied) { + d3d9Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE); + } + + // Final pass - if mix-blending, do it now that we have the backdrop and + // source textures. + if (BlendOpIsMixBlendMode(blendMode)) { + FinishMixBlend( + backdropRect, + backdropDest, + backdropTransform, + backdropTexture, + blendMode); + } +} + +void +CompositorD3D9::SetMask(const EffectChain &aEffectChain, uint32_t aMaskTexture) +{ + EffectMask *maskEffect = + static_cast<EffectMask*>(aEffectChain.mSecondaryEffects[EffectTypes::MASK].get()); + if (!maskEffect) { + return; + } + + TextureSourceD3D9 *source = maskEffect->mMaskTexture->AsSourceD3D9(); + + device()->SetTexture(aMaskTexture, source->GetD3D9Texture()); + + const gfx::Matrix4x4& maskTransform = maskEffect->mMaskTransform; + NS_ASSERTION(maskTransform.Is2D(), "How did we end up with a 3D transform here?!"); + Rect bounds = Rect(Point(), Size(maskEffect->mSize)); + bounds = maskTransform.As2D().TransformBounds(bounds); + + device()->SetVertexShaderConstantF(DeviceManagerD3D9::sMaskQuadRegister, + ShaderConstantRect(bounds.x, + bounds.y, + bounds.width, + bounds.height), + 1); +} + +/** + * In the next few methods we call |mParent->InvalidateRemoteLayers()| - that has + * a few uses - if our device or swap chain is not ready, it causes us to try + * to render again, that means we keep trying to get a good device and swap + * chain and don't block the main thread (which we would if we kept trying in + * a busy loop because this is likely to happen in a sync transaction). + * If we had to recreate our device, then we have new textures and we + * need to reupload everything (not just what is currently invalid) from the + * client side. That means we need to invalidate everything on the client. + * If we just reset and didn't need to recreate, then we don't need to reupload + * our textures, but we do need to redraw the whole window, which means we still + * need to invalidate everything. + * Currently we probably do this complete invalidation too much. But it is better + * to do that than to miss an invalidation which would result in a black layer + * (or multiple layers) until the user moves the mouse. The unnecessary invalidtion + * only happens when the device is reset, so that should be pretty rare and when + * other things are happening so the user does not expect super performance. + */ + +bool +CompositorD3D9::EnsureSwapChain() +{ + MOZ_ASSERT(mDeviceManager, "Don't call EnsureSwapChain without a device manager"); + + if (!mSwapChain) { + mSwapChain = mDeviceManager->CreateSwapChain(mWidget->AsWindows()->GetHwnd()); + // We could not create a swap chain, return false + if (!mSwapChain) { + // Check the state of the device too + DeviceManagerState state = mDeviceManager->VerifyReadyForRendering(); + if (state == DeviceMustRecreate) { + mDeviceManager = nullptr; + } + mParent->InvalidateRemoteLayers(); + return false; + } + } + + // We have a swap chain, lets initialise it + DeviceManagerState state = mSwapChain->PrepareForRendering(); + if (state == DeviceOK) { + mFailedResetAttempts = 0; + return true; + } + // Swap chain could not be initialised, handle the failure + if (state == DeviceMustRecreate) { + mDeviceManager = nullptr; + mSwapChain = nullptr; + } + mParent->InvalidateRemoteLayers(); + return false; +} + +void +CompositorD3D9::CheckResetCount() +{ + if (mDeviceResetCount != mDeviceManager->GetDeviceResetCount()) { + mParent->InvalidateRemoteLayers(); + } + mDeviceResetCount = mDeviceManager->GetDeviceResetCount(); +} + +bool +CompositorD3D9::Ready() +{ + if (mDeviceManager) { + if (EnsureSwapChain()) { + // We don't need to call VerifyReadyForRendering because that is + // called by mSwapChain->PrepareForRendering() via EnsureSwapChain(). + CheckResetCount(); + return true; + } + return false; + } + + NS_ASSERTION(!mCurrentRT && !mDefaultRT, + "Shouldn't have any render targets around, they must be released before our device"); + mSwapChain = nullptr; + + mDeviceManager = DeviceManagerD3D9::Get(); + if (!mDeviceManager) { + FailedToResetDevice(); + mParent->InvalidateRemoteLayers(); + return false; + } + if (EnsureSwapChain()) { + CheckResetCount(); + return true; + } + return false; +} + +void +CompositorD3D9::FailedToResetDevice() { + mFailedResetAttempts += 1; + // 10 is a totally arbitrary number that we may want to increase or decrease + // depending on how things behave in the wild. + if (mFailedResetAttempts > 10) { + mFailedResetAttempts = 0; + DeviceManagerDx::Get()->NotifyD3D9DeviceReset(); + gfxCriticalNote << "[D3D9] Unable to get a working D3D9 Compositor"; + } +} + +void +CompositorD3D9::BeginFrame(const nsIntRegion& aInvalidRegion, + const IntRect *aClipRectIn, + const IntRect& aRenderBounds, + const nsIntRegion& aOpaqueRegion, + IntRect *aClipRectOut, + IntRect *aRenderBoundsOut) +{ + MOZ_ASSERT(mDeviceManager && mSwapChain); + + mDeviceManager->SetupRenderState(); + + EnsureSize(); + + device()->Clear(0, nullptr, D3DCLEAR_TARGET, 0x00000000, 0, 0); + device()->BeginScene(); + + if (aClipRectOut) { + *aClipRectOut = IntRect(0, 0, mSize.width, mSize.height); + } + if (aRenderBoundsOut) { + *aRenderBoundsOut = IntRect(0, 0, mSize.width, mSize.height); + } + + RECT r; + if (aClipRectIn) { + r.left = (LONG)aClipRectIn->x; + r.top = (LONG)aClipRectIn->y; + r.right = (LONG)(aClipRectIn->x + aClipRectIn->width); + r.bottom = (LONG)(aClipRectIn->y + aClipRectIn->height); + } else { + r.left = r.top = 0; + r.right = mSize.width; + r.bottom = mSize.height; + } + device()->SetScissorRect(&r); + + RefPtr<IDirect3DSurface9> backBuffer = mSwapChain->GetBackBuffer(); + mDefaultRT = new CompositingRenderTargetD3D9(backBuffer, + INIT_MODE_CLEAR, + IntRect(0, 0, mSize.width, mSize.height)); + SetRenderTarget(mDefaultRT); +} + +void +CompositorD3D9::EndFrame() +{ + if (mDeviceManager) { + device()->EndScene(); + + LayoutDeviceIntSize oldSize = mSize; + EnsureSize(); + if (oldSize == mSize) { + if (mTarget) { + PaintToTarget(); + } else { + mSwapChain->Present(); + } + } + } + + Compositor::EndFrame(); + + mCurrentRT = nullptr; + mDefaultRT = nullptr; +} + +void +CompositorD3D9::PrepareViewport(const gfx::IntSize& aSize) +{ + Matrix4x4 viewMatrix; + /* + * Matrix to transform to viewport space ( <-1.0, 1.0> topleft, + * <1.0, -1.0> bottomright) + */ + viewMatrix._11 = 2.0f / aSize.width; + viewMatrix._22 = -2.0f / aSize.height; + viewMatrix._41 = -1.0f; + viewMatrix._42 = 1.0f; + viewMatrix._33 = 0.0f; + + HRESULT hr = device()->SetVertexShaderConstantF(CBmProjection, &viewMatrix._11, 4); + + if (FAILED(hr)) { + NS_WARNING("Failed to set projection matrix"); + } +} + +bool +CompositorD3D9::SupportsEffect(EffectTypes aEffect) +{ + if (aEffect == EffectTypes::COMPONENT_ALPHA && + !mDeviceManager->HasComponentAlpha()) { + return false; + } + + return Compositor::SupportsEffect(aEffect); +} + +void +CompositorD3D9::EnsureSize() +{ + mSize = mWidget->GetClientSize(); +} + +void +CompositorD3D9::SetSamplerForSamplingFilter(SamplingFilter aSamplingFilter) +{ + switch (aSamplingFilter) { + case SamplingFilter::LINEAR: + device()->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); + device()->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); + return; + case SamplingFilter::POINT: + device()->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT); + device()->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT); + return; + default: + device()->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); + device()->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); + } +} + +void +CompositorD3D9::PaintToTarget() +{ + if (!mDeviceManager) { + return; + } + + RefPtr<IDirect3DSurface9> backBuff; + RefPtr<IDirect3DSurface9> destSurf; + device()->GetRenderTarget(0, getter_AddRefs(backBuff)); + + D3DSURFACE_DESC desc; + backBuff->GetDesc(&desc); + + device()->CreateOffscreenPlainSurface(desc.Width, desc.Height, + D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, + getter_AddRefs(destSurf), nullptr); + + device()->GetRenderTargetData(backBuff, destSurf); + + D3DLOCKED_RECT rect; + HRESULT hr = destSurf->LockRect(&rect, nullptr, D3DLOCK_READONLY); + if (FAILED(hr) || !rect.pBits) { + gfxCriticalError() << "Failed to lock rect in paint to target D3D9 " << hexa(hr); + return; + } + RefPtr<DataSourceSurface> sourceSurface = + Factory::CreateWrappingDataSourceSurface((uint8_t*)rect.pBits, + rect.Pitch, + IntSize(desc.Width, desc.Height), + SurfaceFormat::B8G8R8A8); + mTarget->CopySurface(sourceSurface, + IntRect(0, 0, desc.Width, desc.Height), + IntPoint(-mTargetBounds.x, -mTargetBounds.y)); + mTarget->Flush(); + destSurf->UnlockRect(); +} + +void +CompositorD3D9::ReportFailure(const nsACString &aMsg, HRESULT aCode) +{ + // We could choose to abort here when hr == E_OUTOFMEMORY. + nsCString msg; + msg.Append(aMsg); + msg.AppendLiteral(" Error code: "); + msg.AppendInt(uint32_t(aCode)); + NS_WARNING(msg.BeginReading()); + + gfx::LogFailure(msg); +} + +static inline already_AddRefed<IDirect3DSurface9> +GetSurfaceOfTexture(IDirect3DTexture9* aTexture) +{ + RefPtr<IDirect3DSurface9> surface; + HRESULT hr = aTexture->GetSurfaceLevel(0, getter_AddRefs(surface)); + if (FAILED(hr)) { + gfxCriticalNote << "Failed to grab texture surface " << hexa(hr); + return nullptr; + } + return surface.forget(); +} + +static inline already_AddRefed<IDirect3DSurface9> +CreateDataSurfaceForTexture(IDirect3DDevice9* aDevice, + IDirect3DSurface9* aSource, + const D3DSURFACE_DESC& aDesc) +{ + RefPtr<IDirect3DSurface9> dest; + HRESULT hr = aDevice->CreateOffscreenPlainSurface( + aDesc.Width, aDesc.Height, + aDesc.Format, D3DPOOL_SYSTEMMEM, + getter_AddRefs(dest), nullptr); + if (FAILED(hr) || !dest) { + gfxCriticalNote << "Failed to create offscreen plain surface " << hexa(hr); + return nullptr; + } + + hr = aDevice->GetRenderTargetData(aSource, dest); + if (FAILED(hr)) { + gfxCriticalNote << "Failed to get render target data " << hexa(hr); + return nullptr; + } + + return dest.forget(); +} + +class AutoSurfaceLock +{ + public: + AutoSurfaceLock(IDirect3DSurface9* aSurface, DWORD aFlags = 0) { + PodZero(&mRect); + + HRESULT hr = aSurface->LockRect(&mRect, nullptr, aFlags); + if (FAILED(hr)) { + gfxCriticalNote << "Failed to lock surface rect " << hexa(hr); + return; + } + mSurface = aSurface; + } + ~AutoSurfaceLock() { + if (mSurface) { + mSurface->UnlockRect(); + } + } + + bool Okay() const { + return !!mSurface; + } + int Pitch() const { + MOZ_ASSERT(Okay()); + return mRect.Pitch; + } + uint8_t* Bits() const { + MOZ_ASSERT(Okay()); + return reinterpret_cast<uint8_t*>(mRect.pBits); + } + + private: + RefPtr<IDirect3DSurface9> mSurface; + D3DLOCKED_RECT mRect; +}; + +void +CompositorD3D9::FinishMixBlend(const gfx::IntRect& aBackdropRect, + const gfx::Rect& aBackdropDest, + const gfx::Matrix4x4& aBackdropTransform, + RefPtr<IDirect3DTexture9> aBackdrop, + gfx::CompositionOp aBlendMode) +{ + HRESULT hr; + + RefPtr<IDirect3DTexture9> source = + CreateTexture(aBackdropRect, mCurrentRT, aBackdropRect.TopLeft()); + if (!source) { + return; + } + + // Slow path - do everything in software. Unfortunately this requires + // a lot of copying, since we have to readback the source and backdrop, + // then upload the blended result, then blit it back. + + IDirect3DDevice9* d3d9Device = device(); + + // Query geometry/format of the two surfaces. + D3DSURFACE_DESC backdropDesc, sourceDesc; + if (FAILED(aBackdrop->GetLevelDesc(0, &backdropDesc)) || + FAILED(source->GetLevelDesc(0, &sourceDesc))) + { + gfxCriticalNote << "Failed to query mix-blend texture descriptor"; + return; + } + + MOZ_ASSERT(backdropDesc.Format == D3DFMT_A8R8G8B8); + MOZ_ASSERT(sourceDesc.Format == D3DFMT_A8R8G8B8); + + // Acquire a temporary data surface for the backdrop texture. + RefPtr<IDirect3DSurface9> backdropSurface = GetSurfaceOfTexture(aBackdrop); + if (!backdropSurface) { + return; + } + RefPtr<IDirect3DSurface9> tmpBackdrop = + CreateDataSurfaceForTexture(d3d9Device, backdropSurface, backdropDesc); + if (!tmpBackdrop) { + return; + } + + // New scope for locks and temporary surfaces. + { + // Acquire a temporary data surface for the source texture. + RefPtr<IDirect3DSurface9> sourceSurface = GetSurfaceOfTexture(source); + if (!sourceSurface) { + return; + } + RefPtr<IDirect3DSurface9> tmpSource = + CreateDataSurfaceForTexture(d3d9Device, sourceSurface, sourceDesc); + if (!tmpSource) { + return; + } + + // Perform the readback and blend in software. + AutoSurfaceLock backdropLock(tmpBackdrop); + AutoSurfaceLock sourceLock(tmpSource, D3DLOCK_READONLY); + if (!backdropLock.Okay() || !sourceLock.Okay()) { + return; + } + + RefPtr<DataSourceSurface> source = Factory::CreateWrappingDataSourceSurface( + sourceLock.Bits(), sourceLock.Pitch(), + gfx::IntSize(sourceDesc.Width, sourceDesc.Height), + SurfaceFormat::B8G8R8A8); + + RefPtr<DrawTarget> dest = Factory::CreateDrawTargetForData( + BackendType::CAIRO, + backdropLock.Bits(), + gfx::IntSize(backdropDesc.Width, backdropDesc.Height), + backdropLock.Pitch(), + SurfaceFormat::B8G8R8A8); + + // The backdrop rect is rounded out - account for any difference between + // it and the actual destination. + gfx::Rect destRect( + aBackdropDest.x - aBackdropRect.x, + aBackdropDest.y - aBackdropRect.y, + aBackdropDest.width, + aBackdropDest.height); + + dest->DrawSurface( + source, destRect, destRect, + gfx::DrawSurfaceOptions(), + gfx::DrawOptions(1.0f, aBlendMode)); + } + + // Upload the new blended surface to the backdrop texture. + d3d9Device->UpdateSurface(tmpBackdrop, nullptr, backdropSurface, nullptr); + + // Finally, drop in the new backdrop. We don't need to do another + // DrawPrimitive() since the software blend will have included the + // final OP_OVER step for us. + RECT destRect = { + aBackdropRect.x, aBackdropRect.y, + aBackdropRect.XMost(), aBackdropRect.YMost() + }; + hr = d3d9Device->StretchRect(backdropSurface, + nullptr, + mCurrentRT->GetD3D9Surface(), + &destRect, + D3DTEXF_NONE); + if (FAILED(hr)) { + gfxCriticalNote << "StretcRect with mix-blend failed " << hexa(hr); + } +} + +} +} |