/* -*- 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 "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) { 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; } 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 CompositorD3D9::CreateDataTextureSource(TextureFlags aFlags) { return MakeAndAddRef(SurfaceFormat::UNKNOWN, this, aFlags); } already_AddRefed 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 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(texture, aInit, aRect); } already_AddRefed 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 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 sourceSurface = static_cast(aSource)->GetD3D9Surface(); RefPtr 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 CompositorD3D9::CreateRenderTargetFromSource(const gfx::IntRect &aRect, const CompositingRenderTarget *aSource, const gfx::IntPoint &aSourcePoint) { RefPtr texture = CreateTexture(aRect, aSource, aSourcePoint); if (!texture) { return nullptr; } return MakeAndAddRef(texture, INIT_MODE_NONE, aRect); } void CompositorD3D9::SetRenderTarget(CompositingRenderTarget *aRenderTarget) { MOZ_ASSERT(aRenderTarget && mDeviceManager); RefPtr oldRT = mCurrentRT; mCurrentRT = static_cast(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 backdropTexture; gfx::CompositionOp blendMode = gfx::CompositionOp::OP_OVER; if (aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE]) { EffectBlendMode *blendEffect = static_cast(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(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(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(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 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(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(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 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 backBuff; RefPtr 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 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 GetSurfaceOfTexture(IDirect3DTexture9* aTexture) { RefPtr 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 CreateDataSurfaceForTexture(IDirect3DDevice9* aDevice, IDirect3DSurface9* aSource, const D3DSURFACE_DESC& aDesc) { RefPtr 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(mRect.pBits); } private: RefPtr mSurface; D3DLOCKED_RECT mRect; }; void CompositorD3D9::FinishMixBlend(const gfx::IntRect& aBackdropRect, const gfx::Rect& aBackdropDest, const gfx::Matrix4x4& aBackdropTransform, RefPtr aBackdrop, gfx::CompositionOp aBlendMode) { HRESULT hr; RefPtr 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 backdropSurface = GetSurfaceOfTexture(aBackdrop); if (!backdropSurface) { return; } RefPtr 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 sourceSurface = GetSurfaceOfTexture(source); if (!sourceSurface) { return; } RefPtr 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 source = Factory::CreateWrappingDataSourceSurface( sourceLock.Bits(), sourceLock.Pitch(), gfx::IntSize(sourceDesc.Width, sourceDesc.Height), SurfaceFormat::B8G8R8A8); RefPtr 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); } } } }