/* -*- 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 "DrawTargetD2D.h" #include "SourceSurfaceD2D.h" #include "SourceSurfaceD2DTarget.h" #include "ShadersD2D.h" #include "PathD2D.h" #include "GradientStopsD2D.h" #include "ScaledFontDWrite.h" #include "ImageScaling.h" #include "Logging.h" #include "Tools.h" #include #include "mozilla/Constants.h" #include typedef HRESULT (WINAPI*D2D1CreateFactoryFunc)( D2D1_FACTORY_TYPE factoryType, REFIID iid, CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, void **factory ); typedef HRESULT (WINAPI*D3D10CreateEffectFromMemoryFunc)( void *pData, SIZE_T DataLength, UINT FXFlags, ID3D10Device *pDevice, ID3D10EffectPool *pEffectPool, ID3D10Effect **ppEffect ); typedef HRESULT (WINAPI*DWriteCreateFactoryFunc)( DWRITE_FACTORY_TYPE factoryType, REFIID iid, IUnknown **factory ); using namespace std; namespace mozilla { namespace gfx { struct Vertex { float x; float y; }; ID2D1Factory *DrawTargetD2D::mFactory; IDWriteFactory *DrawTargetD2D::mDWriteFactory; uint64_t DrawTargetD2D::mVRAMUsageDT; uint64_t DrawTargetD2D::mVRAMUsageSS; // Helper class to restore surface contents that was clipped out but may have // been altered by a drawing call. class AutoSaveRestoreClippedOut { public: AutoSaveRestoreClippedOut(DrawTargetD2D *aDT) : mDT(aDT) {} void Save() { if (!mDT->mPushedClips.size()) { return; } mDT->Flush(); RefPtr tmpTexture; IntSize size = mDT->mSize; SurfaceFormat format = mDT->mFormat; CD3D10_TEXTURE2D_DESC desc(DXGIFormat(format), size.width, size.height, 1, 1); desc.BindFlags = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE; HRESULT hr = mDT->mDevice->CreateTexture2D(&desc, nullptr, byRef(tmpTexture)); if (FAILED(hr)) { gfxWarning() << "Failed to create temporary texture to hold surface data."; } mDT->mDevice->CopyResource(tmpTexture, mDT->mTexture); D2D1_BITMAP_PROPERTIES props = D2D1::BitmapProperties(D2D1::PixelFormat(DXGIFormat(format), AlphaMode(format))); RefPtr surf; tmpTexture->QueryInterface((IDXGISurface**)byRef(surf)); hr = mDT->mRT->CreateSharedBitmap(IID_IDXGISurface, surf, &props, byRef(mOldSurfBitmap)); if (FAILED(hr)) { gfxWarning() << "Failed to create shared bitmap for old surface."; } IntRect clipBounds; mClippedArea = mDT->GetClippedGeometry(&clipBounds); if (!clipBounds.IsEqualEdges(IntRect(IntPoint(0, 0), mDT->mSize))) { // We still need to take into account clipBounds if it contains additional // clipping information. RefPtr rectGeom; factory()->CreateRectangleGeometry(D2D1::Rect(clipBounds.x, clipBounds.y, clipBounds.XMost(), clipBounds.YMost()), byRef(rectGeom)); mClippedArea = mDT->Intersect(mClippedArea, rectGeom); } } ID2D1Factory *factory() { return mDT->factory(); } ~AutoSaveRestoreClippedOut() { if (!mOldSurfBitmap) { return; } ID2D1RenderTarget *rt = mDT->mRT; // Write the area that was clipped out back to the surface. This all // happens in device space. rt->SetTransform(D2D1::IdentityMatrix()); mDT->mTransformDirty = true; RefPtr rectGeom; factory()->CreateRectangleGeometry( D2D1::RectF(0, 0, float(mDT->mSize.width), float(mDT->mSize.height)), byRef(rectGeom)); RefPtr invClippedArea; factory()->CreatePathGeometry(byRef(invClippedArea)); RefPtr sink; invClippedArea->Open(byRef(sink)); rectGeom->CombineWithGeometry(mClippedArea, D2D1_COMBINE_MODE_EXCLUDE, nullptr, sink); sink->Close(); RefPtr brush; rt->CreateBitmapBrush(mOldSurfBitmap, D2D1::BitmapBrushProperties(), D2D1::BrushProperties(), byRef(brush)); rt->FillGeometry(invClippedArea, brush); } private: DrawTargetD2D *mDT; // If we have an operator unbound by the source, this will contain a bitmap // with the old dest surface data. RefPtr mOldSurfBitmap; // This contains the area drawing is clipped to. RefPtr mClippedArea; }; DrawTargetD2D::DrawTargetD2D() : mCurrentCachedLayer(0) , mClipsArePushed(false) , mPrivateData(nullptr) { } DrawTargetD2D::~DrawTargetD2D() { if (mRT) { PopAllClips(); mRT->EndDraw(); mVRAMUsageDT -= GetByteSize(); } if (mTempRT) { mTempRT->EndDraw(); mVRAMUsageDT -= GetByteSize(); } if (mSnapshot) { // We may hold the only reference. MarkIndependent will clear mSnapshot; // keep the snapshot object alive so it doesn't get destroyed while // MarkIndependent is running. RefPtr deathGrip = mSnapshot; // mSnapshot can be treated as independent of this DrawTarget since we know // this DrawTarget won't change again. deathGrip->MarkIndependent(); // mSnapshot will be cleared now. } for (int i = 0; i < kLayerCacheSize; i++) { if (mCachedLayers[i]) { mCachedLayers[i] = nullptr; mVRAMUsageDT -= GetByteSize(); } } // Targets depending on us can break that dependency, since we're obviously not going to // be modified in the future. for (TargetSet::iterator iter = mDependentTargets.begin(); iter != mDependentTargets.end(); iter++) { (*iter)->mDependingOnTargets.erase(this); } // Our dependencies on other targets no longer matter. for (TargetSet::iterator iter = mDependingOnTargets.begin(); iter != mDependingOnTargets.end(); iter++) { (*iter)->mDependentTargets.erase(this); } } /* * DrawTarget Implementation */ TemporaryRef DrawTargetD2D::Snapshot() { if (!mSnapshot) { mSnapshot = new SourceSurfaceD2DTarget(this, mTexture, mFormat); Flush(); } return mSnapshot; } void DrawTargetD2D::Flush() { PopAllClips(); HRESULT hr = mRT->Flush(); if (FAILED(hr)) { gfxWarning() << "Error reported when trying to flush D2D rendertarget. Code: " << hr; } // We no longer depend on any target. for (TargetSet::iterator iter = mDependingOnTargets.begin(); iter != mDependingOnTargets.end(); iter++) { (*iter)->mDependentTargets.erase(this); } mDependingOnTargets.clear(); } void DrawTargetD2D::AddDependencyOnSource(SourceSurfaceD2DTarget* aSource) { if (aSource->mDrawTarget && !mDependingOnTargets.count(aSource->mDrawTarget)) { aSource->mDrawTarget->mDependentTargets.insert(this); mDependingOnTargets.insert(aSource->mDrawTarget); } } void DrawTargetD2D::DrawSurface(SourceSurface *aSurface, const Rect &aDest, const Rect &aSource, const DrawSurfaceOptions &aSurfOptions, const DrawOptions &aOptions) { RefPtr bitmap; ID2D1RenderTarget *rt = GetRTForOperation(aOptions.mCompositionOp, ColorPattern(Color())); PrepareForDrawing(rt); rt->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); Rect srcRect = aSource; switch (aSurface->GetType()) { case SURFACE_D2D1_BITMAP: { SourceSurfaceD2D *srcSurf = static_cast(aSurface); bitmap = srcSurf->GetBitmap(); if (!bitmap) { return; } } break; case SURFACE_D2D1_DRAWTARGET: { SourceSurfaceD2DTarget *srcSurf = static_cast(aSurface); bitmap = srcSurf->GetBitmap(mRT); AddDependencyOnSource(srcSurf); } break; default: { RefPtr srcSurf = aSurface->GetDataSurface(); if (!srcSurf) { gfxDebug() << "Not able to deal with non-data source surface."; return; } if (aSource.width > rt->GetMaximumBitmapSize() || aSource.height > rt->GetMaximumBitmapSize()) { gfxDebug() << "Bitmap source larger than texture size specified. DrawBitmap will silently fail."; // Don't know how to deal with this yet. return; } int stride = srcSurf->Stride(); unsigned char *data = srcSurf->GetData() + (uint32_t)aSource.y * stride + (uint32_t)aSource.x * BytesPerPixel(srcSurf->GetFormat()); D2D1_BITMAP_PROPERTIES props = D2D1::BitmapProperties(D2D1::PixelFormat(DXGIFormat(srcSurf->GetFormat()), AlphaMode(srcSurf->GetFormat()))); mRT->CreateBitmap(D2D1::SizeU(UINT32(aSource.width), UINT32(aSource.height)), data, stride, props, byRef(bitmap)); srcRect.x -= (uint32_t)aSource.x; srcRect.y -= (uint32_t)aSource.y; } break; } rt->DrawBitmap(bitmap, D2DRect(aDest), aOptions.mAlpha, D2DFilter(aSurfOptions.mFilter), D2DRect(srcRect)); FinalizeRTForOperation(aOptions.mCompositionOp, ColorPattern(Color()), aDest); } void DrawTargetD2D::DrawSurfaceWithShadow(SourceSurface *aSurface, const Point &aDest, const Color &aColor, const Point &aOffset, Float aSigma, CompositionOp aOperator) { RefPtr srView = nullptr; if (aSurface->GetType() != SURFACE_D2D1_DRAWTARGET) { return; } SetScissorToRect(nullptr); // XXX - This function is way too long, it should be split up soon to make // it more graspable! Flush(); AutoSaveRestoreClippedOut restoreClippedOut(this); if (!IsOperatorBoundByMask(aOperator)) { restoreClippedOut.Save(); } srView = static_cast(aSurface)->GetSRView(); EnsureViews(); if (!mTempRTView) { // This view is only needed in this path. HRESULT hr = mDevice->CreateRenderTargetView(mTempTexture, nullptr, byRef(mTempRTView)); if (FAILED(hr)) { gfxWarning() << "Failure to create RenderTargetView. Code: " << hr; return; } } RefPtr destRTView = mRTView; RefPtr destTexture; HRESULT hr; RefPtr maskTexture; RefPtr maskSRView; IntRect clipBounds; if (mPushedClips.size()) { EnsureClipMaskTexture(&clipBounds); mDevice->CreateShaderResourceView(mCurrentClipMaskTexture, nullptr, byRef(maskSRView)); } IntSize srcSurfSize; ID3D10RenderTargetView *rtViews; D3D10_VIEWPORT viewport; UINT stride = sizeof(Vertex); UINT offset = 0; ID3D10Buffer *buff = mPrivateData->mVB; mDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); mDevice->IASetVertexBuffers(0, 1, &buff, &stride, &offset); mDevice->IASetInputLayout(mPrivateData->mInputLayout); mPrivateData->mEffect->GetVariableByName("QuadDesc")->AsVector()-> SetFloatVector(ShaderConstantRectD3D10(-1.0f, 1.0f, 2.0f, -2.0f)); mPrivateData->mEffect->GetVariableByName("TexCoords")->AsVector()-> SetFloatVector(ShaderConstantRectD3D10(0, 0, 1.0f, 1.0f)); // If we create a downsampled source surface we need to correct aOffset for that. Point correctedOffset = aOffset + aDest; // The 'practical' scaling factors. Float dsFactorX = 1.0f; Float dsFactorY = 1.0f; if (aSigma > 1.7f) { // In this case 9 samples of our original will not cover it. Generate the // mip levels for the original and create a downsampled version from // them. We generate a version downsampled so that a kernel for a sigma // of 1.7 will produce the right results. float blurWeights[9] = { 0.234671f, 0.197389f, 0.197389f, 0.117465f, 0.117465f, 0.049456f, 0.049456f, 0.014732f, 0.014732f }; mPrivateData->mEffect->GetVariableByName("BlurWeights")->SetRawValue(blurWeights, 0, sizeof(blurWeights)); CD3D10_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM, aSurface->GetSize().width, aSurface->GetSize().height); desc.BindFlags = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE; desc.MiscFlags = D3D10_RESOURCE_MISC_GENERATE_MIPS; RefPtr mipTexture; hr = mDevice->CreateTexture2D(&desc, nullptr, byRef(mipTexture)); if (FAILED(hr)) { gfxWarning() << "Failure to create temporary texture. Size: " << aSurface->GetSize() << " Code: " << hr; return; } IntSize dsSize = IntSize(int32_t(aSurface->GetSize().width * (1.7f / aSigma)), int32_t(aSurface->GetSize().height * (1.7f / aSigma))); if (dsSize.width < 1) { dsSize.width = 1; } if (dsSize.height < 1) { dsSize.height = 1; } dsFactorX = dsSize.width / Float(aSurface->GetSize().width); dsFactorY = dsSize.height / Float(aSurface->GetSize().height); correctedOffset.x *= dsFactorX; correctedOffset.y *= dsFactorY; desc = CD3D10_TEXTURE2D_DESC(DXGI_FORMAT_B8G8R8A8_UNORM, dsSize.width, dsSize.height, 1, 1); desc.BindFlags = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE; RefPtr tmpDSTexture; hr = mDevice->CreateTexture2D(&desc, nullptr, byRef(tmpDSTexture)); if (FAILED(hr)) { gfxWarning() << "Failure to create temporary texture. Size: " << dsSize << " Code: " << hr; return; } D3D10_BOX box; box.left = box.top = box.front = 0; box.back = 1; box.right = aSurface->GetSize().width; box.bottom = aSurface->GetSize().height; mDevice->CopySubresourceRegion(mipTexture, 0, 0, 0, 0, static_cast(aSurface)->mTexture, 0, &box); mDevice->CreateShaderResourceView(mipTexture, nullptr, byRef(srView)); mDevice->GenerateMips(srView); RefPtr dsRTView; RefPtr dsSRView; mDevice->CreateRenderTargetView(tmpDSTexture, nullptr, byRef(dsRTView)); mDevice->CreateShaderResourceView(tmpDSTexture, nullptr, byRef(dsSRView)); // We're not guaranteed the texture we created will be empty, we've // seen old content at least on NVidia drivers. float color[4] = { 0, 0, 0, 0 }; mDevice->ClearRenderTargetView(dsRTView, color); rtViews = dsRTView; mDevice->OMSetRenderTargets(1, &rtViews, nullptr); viewport.MaxDepth = 1; viewport.MinDepth = 0; viewport.Height = dsSize.height; viewport.Width = dsSize.width; viewport.TopLeftX = 0; viewport.TopLeftY = 0; mDevice->RSSetViewports(1, &viewport); mPrivateData->mEffect->GetVariableByName("tex")->AsShaderResource()->SetResource(srView); mPrivateData->mEffect->GetTechniqueByName("SampleTexture")-> GetPassByIndex(0)->Apply(0); mDevice->OMSetBlendState(GetBlendStateForOperator(OP_OVER), nullptr, 0xffffffff); mDevice->Draw(4, 0); srcSurfSize = dsSize; srView = dsSRView; } else { // In this case generate a kernel to draw the blur directly to the temp // surf in one direction and to final in the other. float blurWeights[9]; float normalizeFactor = 1.0f; if (aSigma != 0) { normalizeFactor = 1.0f / Float(sqrt(2 * M_PI * pow(aSigma, 2))); } blurWeights[0] = normalizeFactor; // XXX - We should actually optimize for Sigma = 0 here. We could use a // much simpler shader and save a lot of texture lookups. for (int i = 1; i < 9; i += 2) { if (aSigma != 0) { blurWeights[i] = blurWeights[i + 1] = normalizeFactor * exp(-pow(float((i + 1) / 2), 2) / (2 * pow(aSigma, 2))); } else { blurWeights[i] = blurWeights[i + 1] = 0; } } mPrivateData->mEffect->GetVariableByName("BlurWeights")->SetRawValue(blurWeights, 0, sizeof(blurWeights)); viewport.MaxDepth = 1; viewport.MinDepth = 0; viewport.Height = aSurface->GetSize().height; viewport.Width = aSurface->GetSize().width; viewport.TopLeftX = 0; viewport.TopLeftY = 0; mDevice->RSSetViewports(1, &viewport); srcSurfSize = aSurface->GetSize(); } // We may need to draw to a different intermediate surface if our temp // texture isn't big enough. bool needBiggerTemp = srcSurfSize.width > mSize.width || srcSurfSize.height > mSize.height; RefPtr tmpRTView; RefPtr tmpSRView; RefPtr tmpTexture; IntSize tmpSurfSize = mSize; if (!needBiggerTemp) { tmpRTView = mTempRTView; tmpSRView = mSRView; // There could still be content here! float color[4] = { 0, 0, 0, 0 }; mDevice->ClearRenderTargetView(tmpRTView, color); } else { CD3D10_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM, srcSurfSize.width, srcSurfSize.height, 1, 1); desc.BindFlags = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE; mDevice->CreateTexture2D(&desc, nullptr, byRef(tmpTexture)); mDevice->CreateRenderTargetView(tmpTexture, nullptr, byRef(tmpRTView)); mDevice->CreateShaderResourceView(tmpTexture, nullptr, byRef(tmpSRView)); tmpSurfSize = srcSurfSize; } rtViews = tmpRTView; mDevice->OMSetRenderTargets(1, &rtViews, nullptr); mPrivateData->mEffect->GetVariableByName("tex")->AsShaderResource()->SetResource(srView); // Premultiplied! float shadowColor[4] = { aColor.r * aColor.a, aColor.g * aColor.a, aColor.b * aColor.a, aColor.a }; mPrivateData->mEffect->GetVariableByName("ShadowColor")->AsVector()-> SetFloatVector(shadowColor); float pixelOffset = 1.0f / float(srcSurfSize.width); float blurOffsetsH[9] = { 0, pixelOffset, -pixelOffset, 2.0f * pixelOffset, -2.0f * pixelOffset, 3.0f * pixelOffset, -3.0f * pixelOffset, 4.0f * pixelOffset, - 4.0f * pixelOffset }; pixelOffset = 1.0f / float(tmpSurfSize.height); float blurOffsetsV[9] = { 0, pixelOffset, -pixelOffset, 2.0f * pixelOffset, -2.0f * pixelOffset, 3.0f * pixelOffset, -3.0f * pixelOffset, 4.0f * pixelOffset, - 4.0f * pixelOffset }; mPrivateData->mEffect->GetVariableByName("BlurOffsetsH")-> SetRawValue(blurOffsetsH, 0, sizeof(blurOffsetsH)); mPrivateData->mEffect->GetVariableByName("BlurOffsetsV")-> SetRawValue(blurOffsetsV, 0, sizeof(blurOffsetsV)); mPrivateData->mEffect->GetTechniqueByName("SampleTextureWithShadow")-> GetPassByIndex(0)->Apply(0); mDevice->Draw(4, 0); viewport.MaxDepth = 1; viewport.MinDepth = 0; viewport.Height = mSize.height; viewport.Width = mSize.width; viewport.TopLeftX = 0; viewport.TopLeftY = 0; mDevice->RSSetViewports(1, &viewport); mPrivateData->mEffect->GetVariableByName("tex")->AsShaderResource()->SetResource(tmpSRView); rtViews = destRTView; mDevice->OMSetRenderTargets(1, &rtViews, nullptr); Point shadowDest = aDest + aOffset; mPrivateData->mEffect->GetVariableByName("QuadDesc")->AsVector()-> SetFloatVector(ShaderConstantRectD3D10(-1.0f + ((shadowDest.x / mSize.width) * 2.0f), 1.0f - (shadowDest.y / mSize.height * 2.0f), (Float(aSurface->GetSize().width) / mSize.width) * 2.0f, (-Float(aSurface->GetSize().height) / mSize.height) * 2.0f)); mPrivateData->mEffect->GetVariableByName("TexCoords")->AsVector()-> SetFloatVector(ShaderConstantRectD3D10(0, 0, Float(srcSurfSize.width) / tmpSurfSize.width, Float(srcSurfSize.height) / tmpSurfSize.height)); if (mPushedClips.size()) { mPrivateData->mEffect->GetVariableByName("mask")->AsShaderResource()->SetResource(maskSRView); mPrivateData->mEffect->GetVariableByName("MaskTexCoords")->AsVector()-> SetFloatVector(ShaderConstantRectD3D10(shadowDest.x / mSize.width, shadowDest.y / mSize.height, Float(aSurface->GetSize().width) / mSize.width, Float(aSurface->GetSize().height) / mSize.height)); mPrivateData->mEffect->GetTechniqueByName("SampleTextureWithShadow")-> GetPassByIndex(2)->Apply(0); SetScissorToRect(&clipBounds); } else { mPrivateData->mEffect->GetTechniqueByName("SampleTextureWithShadow")-> GetPassByIndex(1)->Apply(0); } mDevice->OMSetBlendState(GetBlendStateForOperator(aOperator), nullptr, 0xffffffff); mDevice->Draw(4, 0); mPrivateData->mEffect->GetVariableByName("QuadDesc")->AsVector()-> SetFloatVector(ShaderConstantRectD3D10(-1.0f + ((aDest.x / mSize.width) * 2.0f), 1.0f - (aDest.y / mSize.height * 2.0f), (Float(aSurface->GetSize().width) / mSize.width) * 2.0f, (-Float(aSurface->GetSize().height) / mSize.height) * 2.0f)); mPrivateData->mEffect->GetVariableByName("tex")->AsShaderResource()->SetResource(static_cast(aSurface)->GetSRView()); mPrivateData->mEffect->GetVariableByName("TexCoords")->AsVector()-> SetFloatVector(ShaderConstantRectD3D10(0, 0, 1.0f, 1.0f)); if (mPushedClips.size()) { mPrivateData->mEffect->GetVariableByName("MaskTexCoords")->AsVector()-> SetFloatVector(ShaderConstantRectD3D10(aDest.x / mSize.width, aDest.y / mSize.height, Float(aSurface->GetSize().width) / mSize.width, Float(aSurface->GetSize().height) / mSize.height)); mPrivateData->mEffect->GetTechniqueByName("SampleMaskedTexture")-> GetPassByIndex(0)->Apply(0); // We've set the scissor rect here for the previous draw call. } else { mPrivateData->mEffect->GetTechniqueByName("SampleTexture")-> GetPassByIndex(0)->Apply(0); } mDevice->OMSetBlendState(GetBlendStateForOperator(aOperator), nullptr, 0xffffffff); mDevice->Draw(4, 0); } void DrawTargetD2D::ClearRect(const Rect &aRect) { MarkChanged(); FlushTransformToRT(); PopAllClips(); AutoSaveRestoreClippedOut restoreClippedOut(this); restoreClippedOut.Save(); bool needsClip = false; needsClip = aRect.x > 0 || aRect.y > 0 || aRect.XMost() < mSize.width || aRect.YMost() < mSize.height; if (needsClip) { mRT->PushAxisAlignedClip(D2DRect(aRect), D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); } mRT->Clear(D2D1::ColorF(0, 0.0f)); if (needsClip) { mRT->PopAxisAlignedClip(); } return; } void DrawTargetD2D::CopySurface(SourceSurface *aSurface, const IntRect &aSourceRect, const IntPoint &aDestination) { MarkChanged(); Rect srcRect(Float(aSourceRect.x), Float(aSourceRect.y), Float(aSourceRect.width), Float(aSourceRect.height)); Rect dstRect(Float(aDestination.x), Float(aDestination.y), Float(aSourceRect.width), Float(aSourceRect.height)); mRT->SetTransform(D2D1::IdentityMatrix()); mTransformDirty = true; mRT->PushAxisAlignedClip(D2DRect(dstRect), D2D1_ANTIALIAS_MODE_ALIASED); mRT->Clear(D2D1::ColorF(0, 0.0f)); mRT->PopAxisAlignedClip(); RefPtr bitmap; switch (aSurface->GetType()) { case SURFACE_D2D1_BITMAP: { SourceSurfaceD2D *srcSurf = static_cast(aSurface); bitmap = srcSurf->GetBitmap(); } break; case SURFACE_D2D1_DRAWTARGET: { SourceSurfaceD2DTarget *srcSurf = static_cast(aSurface); bitmap = srcSurf->GetBitmap(mRT); AddDependencyOnSource(srcSurf); } break; default: return; } if (!bitmap) { return; } mRT->DrawBitmap(bitmap, D2DRect(dstRect), 1.0f, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, D2DRect(srcRect)); } void DrawTargetD2D::FillRect(const Rect &aRect, const Pattern &aPattern, const DrawOptions &aOptions) { ID2D1RenderTarget *rt = GetRTForOperation(aOptions.mCompositionOp, aPattern); PrepareForDrawing(rt); rt->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); RefPtr brush = CreateBrushForPattern(aPattern, aOptions.mAlpha); if (brush) { rt->FillRectangle(D2DRect(aRect), brush); } FinalizeRTForOperation(aOptions.mCompositionOp, aPattern, aRect); } void DrawTargetD2D::StrokeRect(const Rect &aRect, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions) { ID2D1RenderTarget *rt = GetRTForOperation(aOptions.mCompositionOp, aPattern); PrepareForDrawing(rt); rt->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); RefPtr brush = CreateBrushForPattern(aPattern, aOptions.mAlpha); RefPtr strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions); if (brush && strokeStyle) { rt->DrawRectangle(D2DRect(aRect), brush, aStrokeOptions.mLineWidth, strokeStyle); } FinalizeRTForOperation(aOptions.mCompositionOp, aPattern, aRect); } void DrawTargetD2D::StrokeLine(const Point &aStart, const Point &aEnd, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions) { ID2D1RenderTarget *rt = GetRTForOperation(aOptions.mCompositionOp, aPattern); PrepareForDrawing(rt); rt->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); RefPtr brush = CreateBrushForPattern(aPattern, aOptions.mAlpha); RefPtr strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions); if (brush && strokeStyle) { rt->DrawLine(D2DPoint(aStart), D2DPoint(aEnd), brush, aStrokeOptions.mLineWidth, strokeStyle); } FinalizeRTForOperation(aOptions.mCompositionOp, aPattern, Rect(0, 0, Float(mSize.width), Float(mSize.height))); } void DrawTargetD2D::Stroke(const Path *aPath, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions) { if (aPath->GetBackendType() != BACKEND_DIRECT2D) { gfxDebug() << *this << ": Ignoring drawing call for incompatible path."; return; } const PathD2D *d2dPath = static_cast(aPath); ID2D1RenderTarget *rt = GetRTForOperation(aOptions.mCompositionOp, aPattern); PrepareForDrawing(rt); rt->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); RefPtr brush = CreateBrushForPattern(aPattern, aOptions.mAlpha); RefPtr strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions); if (brush && strokeStyle) { rt->DrawGeometry(d2dPath->mGeometry, brush, aStrokeOptions.mLineWidth, strokeStyle); } FinalizeRTForOperation(aOptions.mCompositionOp, aPattern, Rect(0, 0, Float(mSize.width), Float(mSize.height))); } void DrawTargetD2D::Fill(const Path *aPath, const Pattern &aPattern, const DrawOptions &aOptions) { if (aPath->GetBackendType() != BACKEND_DIRECT2D) { gfxDebug() << *this << ": Ignoring drawing call for incompatible path."; return; } const PathD2D *d2dPath = static_cast(aPath); ID2D1RenderTarget *rt = GetRTForOperation(aOptions.mCompositionOp, aPattern); PrepareForDrawing(rt); rt->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); RefPtr brush = CreateBrushForPattern(aPattern, aOptions.mAlpha); if (brush) { rt->FillGeometry(d2dPath->mGeometry, brush); } Rect bounds; if (aOptions.mCompositionOp != OP_OVER) { D2D1_RECT_F d2dbounds; d2dPath->mGeometry->GetBounds(D2D1::IdentityMatrix(), &d2dbounds); bounds = ToRect(d2dbounds); } FinalizeRTForOperation(aOptions.mCompositionOp, aPattern, bounds); } void DrawTargetD2D::FillGlyphs(ScaledFont *aFont, const GlyphBuffer &aBuffer, const Pattern &aPattern, const DrawOptions &aOptions, const GlyphRenderingOptions* aRenderOptions) { if (aFont->GetType() != FONT_DWRITE) { gfxDebug() << *this << ": Ignoring drawing call for incompatible font."; return; } ScaledFontDWrite *font = static_cast(aFont); IDWriteRenderingParams *params = nullptr; if (aRenderOptions) { if (aRenderOptions->GetType() != FONT_DWRITE) { gfxDebug() << *this << ": Ignoring incompatible GlyphRenderingOptions."; // This should never happen. MOZ_ASSERT(false); } else { params = static_cast(aRenderOptions)->mParams; } } if (mFormat == FORMAT_B8G8R8A8 && mPermitSubpixelAA && aOptions.mCompositionOp == OP_OVER && aPattern.GetType() == PATTERN_COLOR) { if (FillGlyphsManual(font, aBuffer, static_cast(&aPattern)->mColor, params, aOptions)) { return; } } ID2D1RenderTarget *rt = GetRTForOperation(aOptions.mCompositionOp, aPattern); PrepareForDrawing(rt); if (rt != mRT || params != mTextRenderingParams) { rt->SetTextRenderingParams(params); if (rt == mRT) { mTextRenderingParams = params; } } RefPtr brush = CreateBrushForPattern(aPattern, aOptions.mAlpha); AutoDWriteGlyphRun autoRun; DWriteGlyphRunFromGlyphs(aBuffer, font, &autoRun); if (brush) { rt->DrawGlyphRun(D2D1::Point2F(), &autoRun, brush); } FinalizeRTForOperation(aOptions.mCompositionOp, aPattern, Rect(0, 0, (Float)mSize.width, (Float)mSize.height)); } void DrawTargetD2D::Mask(const Pattern &aSource, const Pattern &aMask, const DrawOptions &aOptions) { ID2D1RenderTarget *rt = GetRTForOperation(aOptions.mCompositionOp, aSource); PrepareForDrawing(rt); RefPtr brush = CreateBrushForPattern(aSource, aOptions.mAlpha); RefPtr maskBrush = CreateBrushForPattern(aMask, 1.0f); RefPtr layer; layer = GetCachedLayer(); rt->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), nullptr, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, D2D1::IdentityMatrix(), 1.0f, maskBrush), layer); Rect rect(0, 0, (Float)mSize.width, (Float)mSize.height); Matrix mat = mTransform; mat.Invert(); rt->FillRectangle(D2DRect(mat.TransformBounds(rect)), brush); PopCachedLayer(rt); FinalizeRTForOperation(aOptions.mCompositionOp, aSource, Rect(0, 0, (Float)mSize.width, (Float)mSize.height)); } void DrawTargetD2D::PushClip(const Path *aPath) { if (aPath->GetBackendType() != BACKEND_DIRECT2D) { gfxDebug() << *this << ": Ignoring clipping call for incompatible path."; return; } mCurrentClipMaskTexture = nullptr; mCurrentClippedGeometry = nullptr; RefPtr pathD2D = static_cast(const_cast(aPath)); PushedClip clip; clip.mTransform = D2DMatrix(mTransform); clip.mPath = pathD2D; pathD2D->mGeometry->GetBounds(clip.mTransform, &clip.mBounds); clip.mLayer = GetCachedLayer(); mPushedClips.push_back(clip); // The transform of clips is relative to the world matrix, since we use the total // transform for the clips, make the world matrix identity. mRT->SetTransform(D2D1::IdentityMatrix()); mTransformDirty = true; if (mClipsArePushed) { PushD2DLayer(mRT, pathD2D->mGeometry, clip.mLayer, clip.mTransform); } } void DrawTargetD2D::PushClipRect(const Rect &aRect) { mCurrentClipMaskTexture = nullptr; mCurrentClippedGeometry = nullptr; if (!mTransform.IsRectilinear()) { // Whoops, this isn't a rectangle in device space, Direct2D will not deal // with this transform the way we want it to. // See remarks: http://msdn.microsoft.com/en-us/library/dd316860%28VS.85%29.aspx RefPtr pathBuilder = CreatePathBuilder(); pathBuilder->MoveTo(aRect.TopLeft()); pathBuilder->LineTo(aRect.TopRight()); pathBuilder->LineTo(aRect.BottomRight()); pathBuilder->LineTo(aRect.BottomLeft()); pathBuilder->Close(); RefPtr path = pathBuilder->Finish(); return PushClip(path); } PushedClip clip; Rect rect = mTransform.TransformBounds(aRect); IntRect intRect; clip.mIsPixelAligned = rect.ToIntRect(&intRect); // Do not store the transform, just store the device space rectangle directly. clip.mBounds = D2DRect(rect); mPushedClips.push_back(clip); mRT->SetTransform(D2D1::IdentityMatrix()); mTransformDirty = true; if (mClipsArePushed) { mRT->PushAxisAlignedClip(clip.mBounds, clip.mIsPixelAligned ? D2D1_ANTIALIAS_MODE_ALIASED : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); } } void DrawTargetD2D::PopClip() { mCurrentClipMaskTexture = nullptr; mCurrentClippedGeometry = nullptr; if (mClipsArePushed) { if (mPushedClips.back().mLayer) { PopCachedLayer(mRT); } else { mRT->PopAxisAlignedClip(); } } mPushedClips.pop_back(); } TemporaryRef DrawTargetD2D::CreateSourceSurfaceFromData(unsigned char *aData, const IntSize &aSize, int32_t aStride, SurfaceFormat aFormat) const { RefPtr newSurf = new SourceSurfaceD2D(); if (!newSurf->InitFromData(aData, aSize, aStride, aFormat, mRT)) { return nullptr; } return newSurf; } TemporaryRef DrawTargetD2D::OptimizeSourceSurface(SourceSurface *aSurface) const { // Unsupported! return nullptr; } TemporaryRef DrawTargetD2D::CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const { if (aSurface.mType != NATIVE_SURFACE_D3D10_TEXTURE) { gfxDebug() << *this << ": Failure to create source surface from non-D3D10 texture native surface."; return nullptr; } RefPtr newSurf = new SourceSurfaceD2D(); if (!newSurf->InitFromTexture(static_cast(aSurface.mSurface), aSurface.mFormat, mRT)) { gfxWarning() << *this << ": Failed to create SourceSurface from texture."; return nullptr; } return newSurf; } TemporaryRef DrawTargetD2D::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const { RefPtr newTarget = new DrawTargetD2D(); if (!newTarget->Init(aSize, aFormat)) { gfxDebug() << *this << ": Failed to create optimal draw target. Size: " << aSize; return nullptr; } return newTarget; } TemporaryRef DrawTargetD2D::CreatePathBuilder(FillRule aFillRule) const { RefPtr path; HRESULT hr = factory()->CreatePathGeometry(byRef(path)); if (FAILED(hr)) { gfxWarning() << "Failed to create Direct2D Path Geometry. Code: " << hr; return nullptr; } RefPtr sink; hr = path->Open(byRef(sink)); if (FAILED(hr)) { gfxWarning() << "Failed to access Direct2D Path Geometry. Code: " << hr; return nullptr; } if (aFillRule == FILL_WINDING) { sink->SetFillMode(D2D1_FILL_MODE_WINDING); } return new PathBuilderD2D(sink, path, aFillRule); } TemporaryRef DrawTargetD2D::CreateGradientStops(GradientStop *rawStops, uint32_t aNumStops, ExtendMode aExtendMode) const { D2D1_GRADIENT_STOP *stops = new D2D1_GRADIENT_STOP[aNumStops]; for (uint32_t i = 0; i < aNumStops; i++) { stops[i].position = rawStops[i].offset; stops[i].color = D2DColor(rawStops[i].color); } RefPtr stopCollection; HRESULT hr = mRT->CreateGradientStopCollection(stops, aNumStops, D2D1_GAMMA_2_2, D2DExtend(aExtendMode), byRef(stopCollection)); delete [] stops; if (FAILED(hr)) { gfxWarning() << "Failed to create GradientStopCollection. Code: " << hr; return nullptr; } return new GradientStopsD2D(stopCollection); } void* DrawTargetD2D::GetNativeSurface(NativeSurfaceType aType) { if (aType != NATIVE_SURFACE_D3D10_TEXTURE) { return nullptr; } return mTexture; } /* * Public functions */ bool DrawTargetD2D::Init(const IntSize &aSize, SurfaceFormat aFormat) { HRESULT hr; mSize = aSize; mFormat = aFormat; if (!Factory::GetDirect3D10Device()) { gfxDebug() << "Failed to Init Direct2D DrawTarget (No D3D10 Device set.)"; return false; } mDevice = Factory::GetDirect3D10Device(); CD3D10_TEXTURE2D_DESC desc(DXGIFormat(aFormat), mSize.width, mSize.height, 1, 1); desc.BindFlags = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE; hr = mDevice->CreateTexture2D(&desc, nullptr, byRef(mTexture)); if (FAILED(hr)) { gfxDebug() << "Failed to init Direct2D DrawTarget. Size: " << mSize << " Code: " << hr; return false; } if (!InitD2DRenderTarget()) { return false; } mRT->Clear(D2D1::ColorF(0, 0)); return true; } bool DrawTargetD2D::Init(ID3D10Texture2D *aTexture, SurfaceFormat aFormat) { HRESULT hr; mTexture = aTexture; mFormat = aFormat; if (!mTexture) { gfxDebug() << "No valid texture for Direct2D draw target initialization."; return false; } RefPtr device; mTexture->GetDevice(byRef(device)); hr = device->QueryInterface((ID3D10Device1**)byRef(mDevice)); if (FAILED(hr)) { gfxWarning() << "Failed to get D3D10 device from texture."; return false; } D3D10_TEXTURE2D_DESC desc; mTexture->GetDesc(&desc); mSize.width = desc.Width; mSize.height = desc.Height; return InitD2DRenderTarget(); } // {0D398B49-AE7B-416F-B26D-EA3C137D1CF7} static const GUID sPrivateDataD2D = { 0xd398b49, 0xae7b, 0x416f, { 0xb2, 0x6d, 0xea, 0x3c, 0x13, 0x7d, 0x1c, 0xf7 } }; bool DrawTargetD2D::InitD3D10Data() { HRESULT hr; UINT privateDataSize; privateDataSize = sizeof(mPrivateData); hr = mDevice->GetPrivateData(sPrivateDataD2D, &privateDataSize, &mPrivateData); if (SUCCEEDED(hr)) { return true; } mPrivateData = new PrivateD3D10DataD2D; D3D10CreateEffectFromMemoryFunc createD3DEffect; HMODULE d3dModule = LoadLibraryW(L"d3d10_1.dll"); createD3DEffect = (D3D10CreateEffectFromMemoryFunc) GetProcAddress(d3dModule, "D3D10CreateEffectFromMemory"); hr = createD3DEffect((void*)d2deffect, sizeof(d2deffect), 0, mDevice, nullptr, byRef(mPrivateData->mEffect)); if (FAILED(hr)) { gfxWarning() << "Failed to initialize Direct2D required effects. Code: " << hr; return false; } privateDataSize = sizeof(mPrivateData); mDevice->SetPrivateData(sPrivateDataD2D, privateDataSize, &mPrivateData); D3D10_INPUT_ELEMENT_DESC layout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 }, }; D3D10_PASS_DESC passDesc; mPrivateData->mEffect->GetTechniqueByName("SampleTexture")->GetPassByIndex(0)->GetDesc(&passDesc); hr = mDevice->CreateInputLayout(layout, sizeof(layout) / sizeof(D3D10_INPUT_ELEMENT_DESC), passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, byRef(mPrivateData->mInputLayout)); if (FAILED(hr)) { gfxWarning() << "Failed to initialize Direct2D required InputLayout. Code: " << hr; return false; } D3D10_SUBRESOURCE_DATA data; Vertex vertices[] = { {0.0, 0.0}, {1.0, 0.0}, {0.0, 1.0}, {1.0, 1.0} }; data.pSysMem = vertices; CD3D10_BUFFER_DESC bufferDesc(sizeof(vertices), D3D10_BIND_VERTEX_BUFFER); hr = mDevice->CreateBuffer(&bufferDesc, &data, byRef(mPrivateData->mVB)); if (FAILED(hr)) { gfxWarning() << "Failed to initialize Direct2D required VertexBuffer. Code: " << hr; return false; } return true; } /* * Private helpers */ uint32_t DrawTargetD2D::GetByteSize() const { return mSize.width * mSize.height * BytesPerPixel(mFormat); } TemporaryRef DrawTargetD2D::GetCachedLayer() { RefPtr layer; if (mCurrentCachedLayer < 5) { if (!mCachedLayers[mCurrentCachedLayer]) { mRT->CreateLayer(byRef(mCachedLayers[mCurrentCachedLayer])); mVRAMUsageDT += GetByteSize(); } layer = mCachedLayers[mCurrentCachedLayer]; } else { mRT->CreateLayer(byRef(layer)); } mCurrentCachedLayer++; return layer; } void DrawTargetD2D::PopCachedLayer(ID2D1RenderTarget *aRT) { aRT->PopLayer(); mCurrentCachedLayer--; } bool DrawTargetD2D::InitD2DRenderTarget() { if (!factory()) { return false; } mRT = CreateRTForTexture(mTexture, mFormat); if (!mRT) { return false; } mRT->BeginDraw(); if (mFormat == FORMAT_B8G8R8X8) { mRT->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE); } mVRAMUsageDT += GetByteSize(); return InitD3D10Data(); } void DrawTargetD2D::PrepareForDrawing(ID2D1RenderTarget *aRT) { if (!mClipsArePushed || aRT == mTempRT) { if (mPushedClips.size()) { // The transform of clips is relative to the world matrix, since we use the total // transform for the clips, make the world matrix identity. aRT->SetTransform(D2D1::IdentityMatrix()); if (aRT == mRT) { mTransformDirty = true; mClipsArePushed = true; } PushClipsToRT(aRT); } } FlushTransformToRT(); MarkChanged(); if (aRT == mTempRT) { mTempRT->SetTransform(D2DMatrix(mTransform)); } } void DrawTargetD2D::MarkChanged() { if (mSnapshot) { if (mSnapshot->hasOneRef()) { // Just destroy it, since no-one else knows about it. mSnapshot = nullptr; } else { mSnapshot->DrawTargetWillChange(); // The snapshot will no longer depend on this target. MOZ_ASSERT(!mSnapshot); } } if (mDependentTargets.size()) { // Copy mDependentTargets since the Flush()es below will modify it. TargetSet tmpTargets = mDependentTargets; for (TargetSet::iterator iter = tmpTargets.begin(); iter != tmpTargets.end(); iter++) { (*iter)->Flush(); } // The Flush() should have broken all dependencies on this target. MOZ_ASSERT(!mDependentTargets.size()); } } ID3D10BlendState* DrawTargetD2D::GetBlendStateForOperator(CompositionOp aOperator) { if (mPrivateData->mBlendStates[aOperator]) { return mPrivateData->mBlendStates[aOperator]; } D3D10_BLEND_DESC desc; memset(&desc, 0, sizeof(D3D10_BLEND_DESC)); desc.AlphaToCoverageEnable = FALSE; desc.BlendEnable[0] = TRUE; desc.RenderTargetWriteMask[0] = D3D10_COLOR_WRITE_ENABLE_ALL; desc.BlendOp = desc.BlendOpAlpha = D3D10_BLEND_OP_ADD; switch (aOperator) { case OP_ADD: desc.SrcBlend = desc.SrcBlendAlpha = D3D10_BLEND_ONE; desc.DestBlend = desc.DestBlendAlpha = D3D10_BLEND_ONE; break; case OP_IN: desc.SrcBlend = desc.SrcBlendAlpha = D3D10_BLEND_DEST_ALPHA; desc.DestBlend = desc.DestBlendAlpha = D3D10_BLEND_ZERO; break; case OP_OUT: desc.SrcBlend = desc.SrcBlendAlpha = D3D10_BLEND_INV_DEST_ALPHA; desc.DestBlend = desc.DestBlendAlpha = D3D10_BLEND_ZERO; break; case OP_ATOP: desc.SrcBlend = desc.SrcBlendAlpha = D3D10_BLEND_DEST_ALPHA; desc.DestBlend = desc.DestBlendAlpha = D3D10_BLEND_INV_SRC_ALPHA; break; case OP_DEST_IN: desc.SrcBlend = desc.SrcBlendAlpha = D3D10_BLEND_ZERO; desc.DestBlend = desc.DestBlendAlpha = D3D10_BLEND_SRC_ALPHA; break; case OP_DEST_OUT: desc.SrcBlend = desc.SrcBlendAlpha = D3D10_BLEND_ZERO; desc.DestBlend = desc.DestBlendAlpha = D3D10_BLEND_INV_SRC_ALPHA; break; case OP_DEST_ATOP: desc.SrcBlend = desc.SrcBlendAlpha = D3D10_BLEND_INV_DEST_ALPHA; desc.DestBlend = desc.DestBlendAlpha = D3D10_BLEND_SRC_ALPHA; break; case OP_DEST_OVER: desc.SrcBlend = desc.SrcBlendAlpha = D3D10_BLEND_INV_DEST_ALPHA; desc.DestBlend = desc.DestBlendAlpha = D3D10_BLEND_ONE; break; case OP_XOR: desc.SrcBlend = desc.SrcBlendAlpha = D3D10_BLEND_INV_DEST_ALPHA; desc.DestBlend = desc.DestBlendAlpha = D3D10_BLEND_INV_SRC_ALPHA; break; case OP_SOURCE: desc.SrcBlend = desc.SrcBlendAlpha = D3D10_BLEND_ONE; desc.DestBlend = desc.DestBlendAlpha = D3D10_BLEND_ZERO; break; default: desc.SrcBlend = desc.SrcBlendAlpha = D3D10_BLEND_ONE; desc.DestBlend = desc.DestBlendAlpha = D3D10_BLEND_INV_SRC_ALPHA; } mDevice->CreateBlendState(&desc, byRef(mPrivateData->mBlendStates[aOperator])); return mPrivateData->mBlendStates[aOperator]; } /* This function prepares the temporary RT for drawing and returns it when a * drawing operation other than OVER is required. */ ID2D1RenderTarget* DrawTargetD2D::GetRTForOperation(CompositionOp aOperator, const Pattern &aPattern) { if (aOperator == OP_OVER && !IsPatternSupportedByD2D(aPattern)) { return mRT; } PopAllClips(); if (mTempRT) { mTempRT->Clear(D2D1::ColorF(0, 0)); return mTempRT; } EnsureViews(); if (!mRTView || !mSRView) { gfxDebug() << *this << ": Failed to get required views. Defaulting to OP_OVER."; return mRT; } mTempRT = CreateRTForTexture(mTempTexture, FORMAT_B8G8R8A8); if (!mTempRT) { return mRT; } mVRAMUsageDT += GetByteSize(); mTempRT->BeginDraw(); mTempRT->Clear(D2D1::ColorF(0, 0)); return mTempRT; } /* This function blends back the content of a drawing operation (drawn to an * empty surface with OVER, so the surface now contains the source operation * contents) to the rendertarget using the requested composition operation. * In order to respect clip for operations which are unbound by their mask, * the old content of the surface outside the clipped area may be blended back * to the surface. */ void DrawTargetD2D::FinalizeRTForOperation(CompositionOp aOperator, const Pattern &aPattern, const Rect &aBounds) { if (aOperator == OP_OVER && !IsPatternSupportedByD2D(aPattern)) { return; } if (!mTempRT) { return; } PopClipsFromRT(mTempRT); mRT->Flush(); mTempRT->Flush(); AutoSaveRestoreClippedOut restoreClippedOut(this); bool needsWriteBack = !IsOperatorBoundByMask(aOperator) && mPushedClips.size(); if (needsWriteBack) { restoreClippedOut.Save(); } ID3D10RenderTargetView *rtViews = mRTView; mDevice->OMSetRenderTargets(1, &rtViews, nullptr); UINT stride = sizeof(Vertex); UINT offset = 0; ID3D10Buffer *buff = mPrivateData->mVB; mDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); mDevice->IASetVertexBuffers(0, 1, &buff, &stride, &offset); mDevice->IASetInputLayout(mPrivateData->mInputLayout); D3D10_VIEWPORT viewport; viewport.MaxDepth = 1; viewport.MinDepth = 0; viewport.Height = mSize.height; viewport.Width = mSize.width; viewport.TopLeftX = 0; viewport.TopLeftY = 0; mDevice->RSSetViewports(1, &viewport); mPrivateData->mEffect->GetVariableByName("QuadDesc")->AsVector()-> SetFloatVector(ShaderConstantRectD3D10(-1.0f, 1.0f, 2.0f, -2.0f)); if (!IsPatternSupportedByD2D(aPattern)) { mPrivateData->mEffect->GetVariableByName("TexCoords")->AsVector()-> SetFloatVector(ShaderConstantRectD3D10(0, 0, 1.0f, 1.0f)); mPrivateData->mEffect->GetVariableByName("tex")->AsShaderResource()->SetResource(mSRView); mPrivateData->mEffect->GetTechniqueByName("SampleTexture")->GetPassByIndex(0)->Apply(0); } else if (aPattern.GetType() == PATTERN_RADIAL_GRADIENT) { const RadialGradientPattern *pat = static_cast(&aPattern); if (pat->mCenter1 == pat->mCenter2 && pat->mRadius1 == pat->mRadius2) { // Draw nothing! return; } mPrivateData->mEffect->GetVariableByName("mask")->AsShaderResource()->SetResource(mSRView); SetupEffectForRadialGradient(pat); } mDevice->OMSetBlendState(GetBlendStateForOperator(aOperator), nullptr, 0xffffffff); SetScissorToRect(nullptr); mDevice->Draw(4, 0); } TemporaryRef DrawTargetD2D::ConvertRectToGeometry(const D2D1_RECT_F& aRect) { RefPtr rectGeom; factory()->CreateRectangleGeometry(&aRect, byRef(rectGeom)); return rectGeom.forget(); } TemporaryRef DrawTargetD2D::GetTransformedGeometry(ID2D1Geometry *aGeometry, const D2D1_MATRIX_3X2_F &aTransform) { RefPtr tmpGeometry; factory()->CreatePathGeometry(byRef(tmpGeometry)); RefPtr currentSink; tmpGeometry->Open(byRef(currentSink)); aGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, aTransform, currentSink); currentSink->Close(); return tmpGeometry; } TemporaryRef DrawTargetD2D::Intersect(ID2D1Geometry *aGeometryA, ID2D1Geometry *aGeometryB) { RefPtr pathGeom; factory()->CreatePathGeometry(byRef(pathGeom)); RefPtr sink; pathGeom->Open(byRef(sink)); aGeometryA->CombineWithGeometry(aGeometryB, D2D1_COMBINE_MODE_INTERSECT, nullptr, sink); sink->Close(); return pathGeom; } static D2D1_RECT_F IntersectRect(const D2D1_RECT_F& aRect1, const D2D1_RECT_F& aRect2) { D2D1_RECT_F result; result.left = max(aRect1.left, aRect2.left); result.top = max(aRect1.top, aRect2.top); result.right = min(aRect1.right, aRect2.right); result.bottom = min(aRect1.bottom, aRect2.bottom); return result; } TemporaryRef DrawTargetD2D::GetClippedGeometry(IntRect *aClipBounds) { if (mCurrentClippedGeometry) { *aClipBounds = mCurrentClipBounds; return mCurrentClippedGeometry; } mCurrentClipBounds = IntRect(IntPoint(0, 0), mSize); // if pathGeom is null then pathRect represents the path. RefPtr pathGeom; D2D1_RECT_F pathRect; bool pathRectIsAxisAligned = false; std::vector::iterator iter = mPushedClips.begin(); if (iter->mPath) { pathGeom = GetTransformedGeometry(iter->mPath->GetGeometry(), iter->mTransform); } else { pathRect = iter->mBounds; pathRectIsAxisAligned = iter->mIsPixelAligned; } iter++; for (;iter != mPushedClips.end(); iter++) { // Do nothing but add it to the current clip bounds. if (!iter->mPath && iter->mIsPixelAligned) { mCurrentClipBounds.IntersectRect(mCurrentClipBounds, IntRect(int32_t(iter->mBounds.left), int32_t(iter->mBounds.top), int32_t(iter->mBounds.right - iter->mBounds.left), int32_t(iter->mBounds.bottom - iter->mBounds.top))); continue; } if (!pathGeom) { if (pathRectIsAxisAligned) { mCurrentClipBounds.IntersectRect(mCurrentClipBounds, IntRect(int32_t(pathRect.left), int32_t(pathRect.top), int32_t(pathRect.right - pathRect.left), int32_t(pathRect.bottom - pathRect.top))); } if (iter->mPath) { // See if pathRect needs to go into the path geometry. if (!pathRectIsAxisAligned) { pathGeom = ConvertRectToGeometry(pathRect); } else { pathGeom = GetTransformedGeometry(iter->mPath->GetGeometry(), iter->mTransform); } } else { pathRect = IntersectRect(pathRect, iter->mBounds); pathRectIsAxisAligned = false; continue; } } RefPtr newGeom; factory()->CreatePathGeometry(byRef(newGeom)); RefPtr currentSink; newGeom->Open(byRef(currentSink)); if (iter->mPath) { pathGeom->CombineWithGeometry(iter->mPath->GetGeometry(), D2D1_COMBINE_MODE_INTERSECT, iter->mTransform, currentSink); } else { RefPtr rectGeom = ConvertRectToGeometry(iter->mBounds); pathGeom->CombineWithGeometry(rectGeom, D2D1_COMBINE_MODE_INTERSECT, D2D1::IdentityMatrix(), currentSink); } currentSink->Close(); pathGeom = newGeom.forget(); } // For now we need mCurrentClippedGeometry to always be non-NULL. This method // might seem a little strange but it is just fine, if pathGeom is NULL // pathRect will always still contain 1 clip unaccounted for regardless of // mCurrentClipBounds. if (!pathGeom) { pathGeom = ConvertRectToGeometry(pathRect); } mCurrentClippedGeometry = pathGeom.forget(); *aClipBounds = mCurrentClipBounds; return mCurrentClippedGeometry; } TemporaryRef DrawTargetD2D::CreateRTForTexture(ID3D10Texture2D *aTexture, SurfaceFormat aFormat) { HRESULT hr; RefPtr surface; RefPtr rt; hr = aTexture->QueryInterface((IDXGISurface**)byRef(surface)); if (FAILED(hr)) { gfxWarning() << "Failed to QI texture to surface."; return nullptr; } D3D10_TEXTURE2D_DESC desc; aTexture->GetDesc(&desc); D2D1_ALPHA_MODE alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; if (aFormat == FORMAT_B8G8R8X8 && aTexture == mTexture) { alphaMode = D2D1_ALPHA_MODE_IGNORE; } D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(desc.Format, alphaMode)); hr = factory()->CreateDxgiSurfaceRenderTarget(surface, props, byRef(rt)); if (FAILED(hr)) { gfxWarning() << "Failed to create D2D render target for texture."; return nullptr; } return rt; } void DrawTargetD2D::EnsureViews() { if (mTempTexture && mSRView && mRTView) { return; } HRESULT hr; CD3D10_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM, mSize.width, mSize.height, 1, 1); desc.BindFlags = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE; hr = mDevice->CreateTexture2D(&desc, nullptr, byRef(mTempTexture)); if (FAILED(hr)) { gfxWarning() << *this << "Failed to create temporary texture for rendertarget. Size: " << mSize << " Code: " << hr; return; } hr = mDevice->CreateShaderResourceView(mTempTexture, nullptr, byRef(mSRView)); if (FAILED(hr)) { gfxWarning() << *this << "Failed to create shader resource view for temp texture. Code: " << hr; return; } hr = mDevice->CreateRenderTargetView(mTexture, nullptr, byRef(mRTView)); if (FAILED(hr)) { gfxWarning() << *this << "Failed to create rendertarget view for temp texture. Code: " << hr; } } void DrawTargetD2D::PopAllClips() { if (mClipsArePushed) { PopClipsFromRT(mRT); mClipsArePushed = false; } } void DrawTargetD2D::PushClipsToRT(ID2D1RenderTarget *aRT) { for (std::vector::iterator iter = mPushedClips.begin(); iter != mPushedClips.end(); iter++) { if (iter->mLayer) { PushD2DLayer(aRT, iter->mPath->mGeometry, iter->mLayer, iter->mTransform); } else { aRT->PushAxisAlignedClip(iter->mBounds, iter->mIsPixelAligned ? D2D1_ANTIALIAS_MODE_ALIASED : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); } } } void DrawTargetD2D::PopClipsFromRT(ID2D1RenderTarget *aRT) { for (int i = mPushedClips.size() - 1; i >= 0; i--) { if (mPushedClips[i].mLayer) { aRT->PopLayer(); } else { aRT->PopAxisAlignedClip(); } } } void DrawTargetD2D::EnsureClipMaskTexture(IntRect *aBounds) { if (mCurrentClipMaskTexture || mPushedClips.empty()) { *aBounds = mCurrentClipBounds; return; } RefPtr geometry = GetClippedGeometry(aBounds); CD3D10_TEXTURE2D_DESC desc(DXGI_FORMAT_A8_UNORM, mSize.width, mSize.height, 1, 1); desc.BindFlags = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE; HRESULT hr = mDevice->CreateTexture2D(&desc, nullptr, byRef(mCurrentClipMaskTexture)); if (FAILED(hr)) { gfxWarning() << "Failed to create texture for ClipMask!"; return; } RefPtr rt = CreateRTForTexture(mCurrentClipMaskTexture, FORMAT_A8); if (!rt) { gfxWarning() << "Failed to create RT for ClipMask!"; return; } RefPtr brush; rt->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), byRef(brush)); rt->BeginDraw(); rt->Clear(D2D1::ColorF(0, 0)); rt->FillGeometry(geometry, brush); rt->EndDraw(); } bool DrawTargetD2D::FillGlyphsManual(ScaledFontDWrite *aFont, const GlyphBuffer &aBuffer, const Color &aColor, IDWriteRenderingParams *aParams, const DrawOptions &aOptions) { HRESULT hr; RefPtr params; if (aParams) { params = aParams; } else { mRT->GetTextRenderingParams(byRef(params)); } DWRITE_RENDERING_MODE renderMode = DWRITE_RENDERING_MODE_DEFAULT; if (params) { hr = aFont->mFontFace->GetRecommendedRenderingMode( (FLOAT)aFont->GetSize(), 1.0f, DWRITE_MEASURING_MODE_NATURAL, params, &renderMode); if (FAILED(hr)) { // this probably never happens, but let's play it safe renderMode = DWRITE_RENDERING_MODE_DEFAULT; } } // Deal with rendering modes CreateGlyphRunAnalysis doesn't accept. switch (renderMode) { case DWRITE_RENDERING_MODE_ALIASED: // ClearType texture creation will fail in this mode, so bail out return false; case DWRITE_RENDERING_MODE_DEFAULT: // As per DWRITE_RENDERING_MODE documentation, pick Natural for font // sizes under 16 ppem if (aFont->GetSize() < 16.0f) { renderMode = DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL; } else { renderMode = DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC; } break; case DWRITE_RENDERING_MODE_OUTLINE: renderMode = DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC; break; default: break; } DWRITE_MEASURING_MODE measureMode = renderMode <= DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC ? DWRITE_MEASURING_MODE_GDI_CLASSIC : renderMode == DWRITE_RENDERING_MODE_CLEARTYPE_GDI_NATURAL ? DWRITE_MEASURING_MODE_GDI_NATURAL : DWRITE_MEASURING_MODE_NATURAL; DWRITE_MATRIX mat = DWriteMatrixFromMatrix(mTransform); AutoDWriteGlyphRun autoRun; DWriteGlyphRunFromGlyphs(aBuffer, aFont, &autoRun); RefPtr analysis; hr = GetDWriteFactory()->CreateGlyphRunAnalysis(&autoRun, 1.0f, &mat, renderMode, measureMode, 0, 0, byRef(analysis)); if (FAILED(hr)) { return false; } RECT bounds; hr = analysis->GetAlphaTextureBounds(DWRITE_TEXTURE_CLEARTYPE_3x1, &bounds); if (bounds.bottom <= bounds.top || bounds.right <= bounds.left) { // DWrite seems to do this sometimes. I'm not 100% sure why. See bug 758980. gfxDebug() << "Empty alpha texture bounds! Falling back to regular drawing."; return false; } IntRect rectBounds(bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top); IntRect surfBounds(IntPoint(0, 0), mSize); rectBounds.IntersectRect(rectBounds, surfBounds); if (rectBounds.IsEmpty()) { // Nothing to do. return true; } RefPtr tex = CreateTextureForAnalysis(analysis, rectBounds); if (!tex) { return false; } RefPtr srView; hr = mDevice->CreateShaderResourceView(tex, nullptr, byRef(srView)); if (FAILED(hr)) { return false; } MarkChanged(); // Prepare our background texture for drawing. PopAllClips(); mRT->Flush(); SetupStateForRendering(); ID3D10EffectTechnique *technique = mPrivateData->mEffect->GetTechniqueByName("SampleTextTexture"); mPrivateData->mEffect->GetVariableByName("QuadDesc")->AsVector()-> SetFloatVector(ShaderConstantRectD3D10(-1.0f + ((Float(rectBounds.x) / mSize.width) * 2.0f), 1.0f - (Float(rectBounds.y) / mSize.height * 2.0f), (Float(rectBounds.width) / mSize.width) * 2.0f, (-Float(rectBounds.height) / mSize.height) * 2.0f)); mPrivateData->mEffect->GetVariableByName("TexCoords")->AsVector()-> SetFloatVector(ShaderConstantRectD3D10(0, 0, 1.0f, 1.0f)); FLOAT color[4] = { aColor.r, aColor.g, aColor.b, aColor.a }; mPrivateData->mEffect->GetVariableByName("TextColor")->AsVector()-> SetFloatVector(color); mPrivateData->mEffect->GetVariableByName("tex")->AsShaderResource()->SetResource(srView); bool isMasking = false; IntRect clipBoundsStorage; IntRect *clipBounds = nullptr; if (!mPushedClips.empty()) { clipBounds = &clipBoundsStorage; RefPtr geom = GetClippedGeometry(clipBounds); RefPtr rectGeom; factory()->CreateRectangleGeometry(D2D1::RectF(Float(rectBounds.x), Float(rectBounds.y), Float(rectBounds.width + rectBounds.x), Float(rectBounds.height + rectBounds.y)), byRef(rectGeom)); D2D1_GEOMETRY_RELATION relation; if (FAILED(geom->CompareWithGeometry(rectGeom, D2D1::IdentityMatrix(), &relation)) || relation != D2D1_GEOMETRY_RELATION_CONTAINS ) { isMasking = true; } } if (isMasking) { clipBounds = &clipBoundsStorage; EnsureClipMaskTexture(clipBounds); RefPtr srViewMask; hr = mDevice->CreateShaderResourceView(mCurrentClipMaskTexture, nullptr, byRef(srViewMask)); if (FAILED(hr)) { return false; } mPrivateData->mEffect->GetVariableByName("mask")->AsShaderResource()->SetResource(srViewMask); mPrivateData->mEffect->GetVariableByName("MaskTexCoords")->AsVector()-> SetFloatVector(ShaderConstantRectD3D10(Float(rectBounds.x) / mSize.width, Float(rectBounds.y) / mSize.height, Float(rectBounds.width) / mSize.width, Float(rectBounds.height) / mSize.height)); technique->GetPassByIndex(1)->Apply(0); } else { technique->GetPassByIndex(0)->Apply(0); } RefPtr rtView; ID3D10RenderTargetView *rtViews; mDevice->CreateRenderTargetView(mTexture, nullptr, byRef(rtView)); rtViews = rtView; mDevice->OMSetRenderTargets(1, &rtViews, nullptr); SetScissorToRect(clipBounds); mDevice->Draw(4, 0); return true; } TemporaryRef DrawTargetD2D::CreateBrushForPattern(const Pattern &aPattern, Float aAlpha) { if (IsPatternSupportedByD2D(aPattern)) { RefPtr colBrush; mRT->CreateSolidColorBrush(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), byRef(colBrush)); return colBrush; } if (aPattern.GetType() == PATTERN_COLOR) { RefPtr colBrush; Color color = static_cast(&aPattern)->mColor; mRT->CreateSolidColorBrush(D2D1::ColorF(color.r, color.g, color.b, color.a), D2D1::BrushProperties(aAlpha), byRef(colBrush)); return colBrush; } else if (aPattern.GetType() == PATTERN_LINEAR_GRADIENT) { RefPtr gradBrush; const LinearGradientPattern *pat = static_cast(&aPattern); GradientStopsD2D *stops = static_cast(pat->mStops.get()); if (!stops) { gfxDebug() << "No stops specified for gradient pattern."; return nullptr; } if (pat->mBegin == pat->mEnd) { RefPtr colBrush; uint32_t stopCount = stops->mStopCollection->GetGradientStopCount(); vector d2dStops(stopCount); stops->mStopCollection->GetGradientStops(&d2dStops.front(), stopCount); mRT->CreateSolidColorBrush(d2dStops.back().color, D2D1::BrushProperties(aAlpha), byRef(colBrush)); return colBrush; } mRT->CreateLinearGradientBrush(D2D1::LinearGradientBrushProperties(D2DPoint(pat->mBegin), D2DPoint(pat->mEnd)), D2D1::BrushProperties(aAlpha, D2DMatrix(pat->mMatrix)), stops->mStopCollection, byRef(gradBrush)); return gradBrush; } else if (aPattern.GetType() == PATTERN_RADIAL_GRADIENT) { RefPtr gradBrush; const RadialGradientPattern *pat = static_cast(&aPattern); GradientStopsD2D *stops = static_cast(pat->mStops.get()); if (!stops) { gfxDebug() << "No stops specified for gradient pattern."; return nullptr; } // This will not be a complex radial gradient brush. mRT->CreateRadialGradientBrush( D2D1::RadialGradientBrushProperties(D2DPoint(pat->mCenter1), D2D1::Point2F(), pat->mRadius2, pat->mRadius2), D2D1::BrushProperties(aAlpha, D2DMatrix(pat->mMatrix)), stops->mStopCollection, byRef(gradBrush)); return gradBrush; } else if (aPattern.GetType() == PATTERN_SURFACE) { RefPtr bmBrush; const SurfacePattern *pat = static_cast(&aPattern); if (!pat->mSurface) { gfxDebug() << "No source surface specified for surface pattern"; return nullptr; } RefPtr bitmap; Matrix mat = pat->mMatrix; switch (pat->mSurface->GetType()) { case SURFACE_D2D1_BITMAP: { SourceSurfaceD2D *surf = static_cast(pat->mSurface.get()); bitmap = surf->mBitmap; if (!bitmap) { return nullptr; } } break; case SURFACE_D2D1_DRAWTARGET: { SourceSurfaceD2DTarget *surf = static_cast(pat->mSurface.get()); bitmap = surf->GetBitmap(mRT); AddDependencyOnSource(surf); } break; default: { RefPtr dataSurf = pat->mSurface->GetDataSurface(); if (!dataSurf) { gfxWarning() << "Invalid surface type."; return nullptr; } bitmap = CreatePartialBitmapForSurface(dataSurf, mat, pat->mExtendMode); if (!bitmap) { return nullptr; } } break; } mRT->CreateBitmapBrush(bitmap, D2D1::BitmapBrushProperties(D2DExtend(pat->mExtendMode), D2DExtend(pat->mExtendMode), D2DFilter(pat->mFilter)), D2D1::BrushProperties(aAlpha, D2DMatrix(mat)), byRef(bmBrush)); return bmBrush; } gfxWarning() << "Invalid pattern type detected."; return nullptr; } TemporaryRef DrawTargetD2D::CreateStrokeStyleForOptions(const StrokeOptions &aStrokeOptions) { RefPtr style; D2D1_CAP_STYLE capStyle; D2D1_LINE_JOIN joinStyle; switch (aStrokeOptions.mLineCap) { case CAP_BUTT: capStyle = D2D1_CAP_STYLE_FLAT; break; case CAP_ROUND: capStyle = D2D1_CAP_STYLE_ROUND; break; case CAP_SQUARE: capStyle = D2D1_CAP_STYLE_SQUARE; break; } switch (aStrokeOptions.mLineJoin) { case JOIN_MITER: joinStyle = D2D1_LINE_JOIN_MITER; break; case JOIN_MITER_OR_BEVEL: joinStyle = D2D1_LINE_JOIN_MITER_OR_BEVEL; break; case JOIN_ROUND: joinStyle = D2D1_LINE_JOIN_ROUND; break; case JOIN_BEVEL: joinStyle = D2D1_LINE_JOIN_BEVEL; break; } HRESULT hr; if (aStrokeOptions.mDashPattern) { typedef vector FloatVector; // D2D "helpfully" multiplies the dash pattern by the line width. // That's not what cairo does, or is what 's dash wants. // So fix the multiplication in advance. Float lineWidth = aStrokeOptions.mLineWidth; FloatVector dash(aStrokeOptions.mDashPattern, aStrokeOptions.mDashPattern + aStrokeOptions.mDashLength); for (FloatVector::iterator it = dash.begin(); it != dash.end(); ++it) { *it /= lineWidth; } hr = factory()->CreateStrokeStyle( D2D1::StrokeStyleProperties(capStyle, capStyle, capStyle, joinStyle, aStrokeOptions.mMiterLimit, D2D1_DASH_STYLE_CUSTOM, aStrokeOptions.mDashOffset), &dash[0], // data() is not C++98, although it's in recent gcc // and VC10's STL dash.size(), byRef(style)); } else { hr = factory()->CreateStrokeStyle( D2D1::StrokeStyleProperties(capStyle, capStyle, capStyle, joinStyle, aStrokeOptions.mMiterLimit), nullptr, 0, byRef(style)); } if (FAILED(hr)) { gfxWarning() << "Failed to create Direct2D stroke style."; } return style; } TemporaryRef DrawTargetD2D::CreateGradientTexture(const GradientStopsD2D *aStops) { CD3D10_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM, 4096, 1, 1, 1); std::vector rawStops; rawStops.resize(aStops->mStopCollection->GetGradientStopCount()); aStops->mStopCollection->GetGradientStops(&rawStops.front(), rawStops.size()); std::vector textureData; textureData.resize(4096 * 4); unsigned char *texData = &textureData.front(); float prevColorPos = 0; float nextColorPos = 1.0f; D2D1_COLOR_F prevColor = rawStops[0].color; D2D1_COLOR_F nextColor = prevColor; if (rawStops.size() >= 2) { nextColor = rawStops[1].color; nextColorPos = rawStops[1].position; } uint32_t stopPosition = 2; // Not the most optimized way but this will do for now. for (int i = 0; i < 4096; i++) { // The 4095 seems a little counter intuitive, but we want the gradient // color at offset 0 at the first pixel, and at offset 1.0f at the last // pixel. float pos = float(i) / 4095; while (pos > nextColorPos) { prevColor = nextColor; prevColorPos = nextColorPos; if (rawStops.size() > stopPosition) { nextColor = rawStops[stopPosition].color; nextColorPos = rawStops[stopPosition++].position; } else { nextColorPos = 1.0f; } } float interp; if (nextColorPos != prevColorPos) { interp = (pos - prevColorPos) / (nextColorPos - prevColorPos); } else { interp = 0; } Color newColor(prevColor.r + (nextColor.r - prevColor.r) * interp, prevColor.g + (nextColor.g - prevColor.g) * interp, prevColor.b + (nextColor.b - prevColor.b) * interp, prevColor.a + (nextColor.a - prevColor.a) * interp); texData[i * 4] = (char)(255.0f * newColor.b); texData[i * 4 + 1] = (char)(255.0f * newColor.g); texData[i * 4 + 2] = (char)(255.0f * newColor.r); texData[i * 4 + 3] = (char)(255.0f * newColor.a); } D3D10_SUBRESOURCE_DATA data; data.pSysMem = &textureData.front(); data.SysMemPitch = 4096 * 4; RefPtr tex; mDevice->CreateTexture2D(&desc, &data, byRef(tex)); return tex; } TemporaryRef DrawTargetD2D::CreateTextureForAnalysis(IDWriteGlyphRunAnalysis *aAnalysis, const IntRect &aBounds) { HRESULT hr; uint32_t bufferSize = aBounds.width * aBounds.height * 3; RECT bounds; bounds.left = aBounds.x; bounds.top = aBounds.y; bounds.right = aBounds.x + aBounds.width; bounds.bottom = aBounds.y + aBounds.height; // Add one byte so we can safely read a 32-bit int when copying the last // 3 bytes. BYTE *texture = new BYTE[bufferSize + 1]; hr = aAnalysis->CreateAlphaTexture(DWRITE_TEXTURE_CLEARTYPE_3x1, &bounds, texture, bufferSize); if (FAILED(hr)) { delete [] texture; return nullptr; } int alignedBufferSize = aBounds.width * aBounds.height * 4; // Create a one-off immutable texture from system memory. BYTE *alignedTextureData = new BYTE[alignedBufferSize]; for (int y = 0; y < aBounds.height; y++) { for (int x = 0; x < aBounds.width; x++) { // Copy 3 Bpp source to 4 Bpp destination memory used for // texture creation. D3D10 has no 3 Bpp texture format we can // use. // // Since we don't care what ends up in the alpha pixel of the // destination, therefor we can simply copy a normal 32 bit // integer each time, filling the alpha pixel of the destination // with the first subpixel of the next pixel from the source. *((int*)(alignedTextureData + (y * aBounds.width + x) * 4)) = *((int*)(texture + (y * aBounds.width + x) * 3)); } } D3D10_SUBRESOURCE_DATA data; CD3D10_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM, aBounds.width, aBounds.height, 1, 1); desc.Usage = D3D10_USAGE_IMMUTABLE; data.SysMemPitch = aBounds.width * 4; data.pSysMem = alignedTextureData; RefPtr tex; hr = mDevice->CreateTexture2D(&desc, &data, byRef(tex)); delete [] alignedTextureData; delete [] texture; if (FAILED(hr)) { return nullptr; } return tex; } TemporaryRef DrawTargetD2D::CreatePartialBitmapForSurface(DataSourceSurface *aSurface, Matrix &aMatrix, ExtendMode aExtendMode) { RefPtr bitmap; // This is where things get complicated. The source surface was // created for a surface that was too large to fit in a texture. // We'll need to figure out if we can work with a partial upload // or downsample in software. Matrix transform = mTransform; Matrix invTransform = transform = aMatrix * transform; if (!invTransform.Invert()) { // Singular transform, nothing to be drawn. return nullptr; } Rect rect(0, 0, Float(mSize.width), Float(mSize.height)); // Calculate the rectangle of the source mapped to our surface. rect = invTransform.TransformBounds(rect); rect.RoundOut(); IntSize size = aSurface->GetSize(); Rect uploadRect(0, 0, Float(size.width), Float(size.height)); // Limit the uploadRect as much as possible without supporting discontiguous uploads // // region we will paint from // uploadRect // .---------------. .---------------. resulting uploadRect // | |rect | | // | .---------. .----. .----. .---------------. // | | | ----> | | | | ----> | | // | '---------' '----' '----' '---------------' // '---------------' '---------------' // // if (uploadRect.Contains(rect)) { // Extend mode is irrelevant, the displayed rect is completely contained // by the source bitmap. uploadRect = rect; } else if (aExtendMode == EXTEND_CLAMP && uploadRect.Intersects(rect)) { // Calculate the rectangle on the source bitmap that touches our // surface, and upload that, for EXTEND_CLAMP we can actually guarantee // correct behaviour in this case. uploadRect = uploadRect.Intersect(rect); // We now proceed to check if we can limit at least one dimension of the // upload rect safely without looking at extend mode. } else if (rect.x >= 0 && rect.XMost() < size.width) { uploadRect.x = rect.x; uploadRect.width = rect.width; } else if (rect.y >= 0 && rect.YMost() < size.height) { uploadRect.y = rect.y; uploadRect.height = rect.height; } int stride = aSurface->Stride(); if (uploadRect.width <= mRT->GetMaximumBitmapSize() && uploadRect.height <= mRT->GetMaximumBitmapSize()) { // A partial upload will suffice. mRT->CreateBitmap(D2D1::SizeU(uint32_t(uploadRect.width), uint32_t(uploadRect.height)), aSurface->GetData() + int(uploadRect.x) * 4 + int(uploadRect.y) * stride, stride, D2D1::BitmapProperties(D2DPixelFormat(aSurface->GetFormat())), byRef(bitmap)); aMatrix.Translate(uploadRect.x, uploadRect.y); return bitmap; } else { int Bpp = BytesPerPixel(aSurface->GetFormat()); if (Bpp != 4) { // This shouldn't actually happen in practice! MOZ_ASSERT(false); return nullptr; } ImageHalfScaler scaler(aSurface->GetData(), stride, size); // Calculate the maximum width/height of the image post transform. Point topRight = transform * Point(Float(size.width), 0); Point topLeft = transform * Point(0, 0); Point bottomRight = transform * Point(Float(size.width), Float(size.height)); Point bottomLeft = transform * Point(0, Float(size.height)); IntSize scaleSize; scaleSize.width = int32_t(max(Distance(topRight, topLeft), Distance(bottomRight, bottomLeft))); scaleSize.height = int32_t(max(Distance(topRight, bottomRight), Distance(topLeft, bottomLeft))); if (unsigned(scaleSize.width) > mRT->GetMaximumBitmapSize()) { // Ok, in this case we'd really want a downscale of a part of the bitmap, // perhaps we can do this later but for simplicity let's do something // different here and assume it's good enough, this should be rare! scaleSize.width = 4095; } if (unsigned(scaleSize.height) > mRT->GetMaximumBitmapSize()) { scaleSize.height = 4095; } scaler.ScaleForSize(scaleSize); IntSize newSize = scaler.GetSize(); mRT->CreateBitmap(D2D1::SizeU(newSize.width, newSize.height), scaler.GetScaledData(), scaler.GetStride(), D2D1::BitmapProperties(D2DPixelFormat(aSurface->GetFormat())), byRef(bitmap)); aMatrix.Scale(Float(size.width / newSize.width), Float(size.height / newSize.height)); return bitmap; } } void DrawTargetD2D::SetupEffectForRadialGradient(const RadialGradientPattern *aPattern) { mPrivateData->mEffect->GetTechniqueByName("SampleRadialGradient")->GetPassByIndex(0)->Apply(0); mPrivateData->mEffect->GetVariableByName("MaskTexCoords")->AsVector()-> SetFloatVector(ShaderConstantRectD3D10(0, 0, 1.0f, 1.0f)); float dimensions[] = { float(mSize.width), float(mSize.height), 0, 0 }; mPrivateData->mEffect->GetVariableByName("dimensions")->AsVector()-> SetFloatVector(dimensions); const GradientStopsD2D *stops = static_cast(aPattern->mStops.get()); RefPtr tex = CreateGradientTexture(stops); RefPtr srView; mDevice->CreateShaderResourceView(tex, nullptr, byRef(srView)); mPrivateData->mEffect->GetVariableByName("tex")->AsShaderResource()->SetResource(srView); Point dc = aPattern->mCenter2 - aPattern->mCenter1; float dr = aPattern->mRadius2 - aPattern->mRadius1; float diffv[] = { dc.x, dc.y, dr, 0 }; mPrivateData->mEffect->GetVariableByName("diff")->AsVector()-> SetFloatVector(diffv); float center1[] = { aPattern->mCenter1.x, aPattern->mCenter1.y, dr, 0 }; mPrivateData->mEffect->GetVariableByName("center1")->AsVector()-> SetFloatVector(center1); mPrivateData->mEffect->GetVariableByName("radius1")->AsScalar()-> SetFloat(aPattern->mRadius1); mPrivateData->mEffect->GetVariableByName("sq_radius1")->AsScalar()-> SetFloat(pow(aPattern->mRadius1, 2)); Matrix invTransform = mTransform; if (!invTransform.Invert()) { // Bail if the matrix is singular. return; } float matrix[] = { invTransform._11, invTransform._12, 0, 0, invTransform._21, invTransform._22, 0, 0, invTransform._31, invTransform._32, 1.0f, 0, 0, 0, 0, 1.0f }; mPrivateData->mEffect->GetVariableByName("DeviceSpaceToUserSpace")-> AsMatrix()->SetMatrix(matrix); float A = dc.x * dc.x + dc.y * dc.y - dr * dr; uint32_t offset = 0; switch (stops->mStopCollection->GetExtendMode()) { case D2D1_EXTEND_MODE_WRAP: offset = 1; break; case D2D1_EXTEND_MODE_MIRROR: offset = 2; break; default: gfxWarning() << "This shouldn't happen! Invalid extend mode for gradient stops."; } if (A == 0) { mPrivateData->mEffect->GetTechniqueByName("SampleRadialGradient")-> GetPassByIndex(offset * 2 + 1)->Apply(0); } else { mPrivateData->mEffect->GetVariableByName("A")->AsScalar()->SetFloat(A); mPrivateData->mEffect->GetTechniqueByName("SampleRadialGradient")-> GetPassByIndex(offset * 2)->Apply(0); } } void DrawTargetD2D::SetupStateForRendering() { UINT stride = sizeof(Vertex); UINT offset = 0; ID3D10Buffer *buff = mPrivateData->mVB; mDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); mDevice->IASetVertexBuffers(0, 1, &buff, &stride, &offset); mDevice->IASetInputLayout(mPrivateData->mInputLayout); D3D10_VIEWPORT viewport; viewport.MaxDepth = 1; viewport.MinDepth = 0; viewport.Height = mSize.height; viewport.Width = mSize.width; viewport.TopLeftX = 0; viewport.TopLeftY = 0; mDevice->RSSetViewports(1, &viewport); } ID2D1Factory* DrawTargetD2D::factory() { if (mFactory) { return mFactory; } D2D1CreateFactoryFunc createD2DFactory; HMODULE d2dModule = LoadLibraryW(L"d2d1.dll"); createD2DFactory = (D2D1CreateFactoryFunc) GetProcAddress(d2dModule, "D2D1CreateFactory"); if (!createD2DFactory) { gfxWarning() << "Failed to locate D2D1CreateFactory function."; return nullptr; } D2D1_FACTORY_OPTIONS options; #ifdef _DEBUG options.debugLevel = D2D1_DEBUG_LEVEL_WARNING; #else options.debugLevel = D2D1_DEBUG_LEVEL_NONE; #endif HRESULT hr = createD2DFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, __uuidof(ID2D1Factory), &options, (void**)&mFactory); if (FAILED(hr)) { gfxWarning() << "Failed to create Direct2D factory."; } return mFactory; } IDWriteFactory* DrawTargetD2D::GetDWriteFactory() { if (mDWriteFactory) { return mDWriteFactory; } DWriteCreateFactoryFunc createDWriteFactory; HMODULE dwriteModule = LoadLibraryW(L"dwrite.dll"); createDWriteFactory = (DWriteCreateFactoryFunc) GetProcAddress(dwriteModule, "DWriteCreateFactory"); if (!createDWriteFactory) { gfxWarning() << "Failed to locate DWriteCreateFactory function."; return nullptr; } HRESULT hr = createDWriteFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast(&mDWriteFactory)); if (FAILED(hr)) { gfxWarning() << "Failed to create DWrite Factory."; } return mDWriteFactory; } void DrawTargetD2D::SetScissorToRect(IntRect *aRect) { D3D10_RECT rect; if (aRect) { rect.left = aRect->x; rect.right = aRect->XMost(); rect.top = aRect->y; rect.bottom = aRect->YMost(); } else { rect.left = rect.top = INT32_MIN; rect.right = rect.bottom = INT32_MAX; } mDevice->RSSetScissorRects(1, &rect); } void DrawTargetD2D::PushD2DLayer(ID2D1RenderTarget *aRT, ID2D1Geometry *aGeometry, ID2D1Layer *aLayer, const D2D1_MATRIX_3X2_F &aTransform) { D2D1_LAYER_OPTIONS options = D2D1_LAYER_OPTIONS_NONE; D2D1_LAYER_OPTIONS1 options1 = D2D1_LAYER_OPTIONS1_NONE; if (mFormat == FORMAT_B8G8R8X8) { options = D2D1_LAYER_OPTIONS_INITIALIZE_FOR_CLEARTYPE; options1 = D2D1_LAYER_OPTIONS1_IGNORE_ALPHA | D2D1_LAYER_OPTIONS1_INITIALIZE_FROM_BACKGROUND; } RefPtr dc; HRESULT hr = aRT->QueryInterface(IID_ID2D1DeviceContext, (void**)((ID2D1DeviceContext**)byRef(dc))); if (FAILED(hr)) { aRT->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), aGeometry, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, aTransform, 1.0, nullptr, options), aLayer); } else { dc->PushLayer(D2D1::LayerParameters1(D2D1::InfiniteRect(), aGeometry, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, aTransform, 1.0, nullptr, options1), aLayer); } } } }