From be70d283dcb2f26e396420432b2af83e3bd16881 Mon Sep 17 00:00:00 2001 From: Bas Schouten Date: Tue, 15 May 2012 16:57:51 +0200 Subject: [PATCH] Bug 717393 - Part 4: Add code for drawing subpixel AA to transparent surfaces. r=jrmuizel --- gfx/2d/2D.h | 10 ++ gfx/2d/DrawTargetD2D.cpp | 307 ++++++++++++++++++++++++++++++++++++++- gfx/2d/DrawTargetD2D.h | 13 ++ gfx/2d/ShadersD2D.fx | 61 +++++++- 4 files changed, 385 insertions(+), 6 deletions(-) diff --git a/gfx/2d/2D.h b/gfx/2d/2D.h index e9005ae130d..00aaa083f1a 100644 --- a/gfx/2d/2D.h +++ b/gfx/2d/2D.h @@ -792,11 +792,21 @@ public: const IntRect &GetOpaqueRect() const { return mOpaqueRect; } + + void SetPermitSubpixelAA(bool aPermitSubpixelAA) { + mPermitSubpixelAA = aPermitSubpixelAA; + } + + bool GetPermitSubpixelAA() { + return mPermitSubpixelAA; + } + protected: UserData mUserData; Matrix mTransform; IntRect mOpaqueRect; bool mTransformDirty : 1; + bool mPermitSubpixelAA : 1; SurfaceFormat mFormat; }; diff --git a/gfx/2d/DrawTargetD2D.cpp b/gfx/2d/DrawTargetD2D.cpp index d922faafd78..8f14e1c5f91 100644 --- a/gfx/2d/DrawTargetD2D.cpp +++ b/gfx/2d/DrawTargetD2D.cpp @@ -47,6 +47,8 @@ #include "Tools.h" #include +#include + #ifndef M_PI #define M_PI 3.14159265358979323846 #endif @@ -67,6 +69,12 @@ typedef HRESULT (WINAPI*D3D10CreateEffectFromMemoryFunc)( ID3D10Effect **ppEffect ); +typedef HRESULT (WINAPI*DWriteCreateFactoryFunc)( + DWRITE_FACTORY_TYPE factoryType, + REFIID iid, + IUnknown **factory +); + using namespace std; namespace mozilla { @@ -78,6 +86,7 @@ struct Vertex { }; ID2D1Factory *DrawTargetD2D::mFactory; +IDWriteFactory *DrawTargetD2D::mDWriteFactory; // Helper class to restore surface contents that was clipped out but may have // been altered by a drawing call. @@ -871,10 +880,6 @@ DrawTargetD2D::FillGlyphs(ScaledFont *aFont, ScaledFontDWrite *font = static_cast(aFont); - ID2D1RenderTarget *rt = GetRTForOperation(aOptions.mCompositionOp, aPattern); - - PrepareForDrawing(rt); - IDWriteRenderingParams *params = NULL; if (aRenderOptions) { if (aRenderOptions->GetType() != FONT_DWRITE) { @@ -886,6 +891,19 @@ DrawTargetD2D::FillGlyphs(ScaledFont *aFont, } } + 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); + rt->SetTextRenderingParams(params); RefPtr brush = CreateBrushForPattern(aPattern, aOptions.mAlpha); @@ -1702,7 +1720,7 @@ DrawTargetD2D::PopClipsFromRT(ID2D1RenderTarget *aRT) void DrawTargetD2D::EnsureClipMaskTexture() { - if (mCurrentClipMaskTexture) { + if (mCurrentClipMaskTexture || mPushedClips.empty()) { return; } @@ -1737,6 +1755,172 @@ DrawTargetD2D::EnsureClipMaskTexture() 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->mSize, + 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->mSize < 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); + 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, NULL, 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; + + if (!mPushedClips.empty()) { + RefPtr geom = GetClippedGeometry(); + + RefPtr rectGeom; + factory()->CreateRectangleGeometry(D2D1::RectF(rectBounds.x, rectBounds.y, + rectBounds.width + rectBounds.x, + 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) { + EnsureClipMaskTexture(); + + RefPtr srViewMask; + hr = mDevice->CreateShaderResourceView(mCurrentClipMaskTexture, NULL, 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, NULL, byRef(rtView)); + + rtViews = rtView; + mDevice->OMSetRenderTargets(1, &rtViews, NULL); + + mDevice->Draw(4, 0); +} TemporaryRef DrawTargetD2D::CreateBrushForPattern(const Pattern &aPattern, Float aAlpha) @@ -1999,6 +2183,70 @@ DrawTargetD2D::CreateGradientTexture(const GradientStopsD2D *aStops) 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 NULL; + } + + 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 NULL; + } + + return tex; +} TemporaryRef DrawTargetD2D::CreatePartialBitmapForSurface(SourceSurfaceD2D *aSurface, Matrix &aMatrix) { @@ -2173,6 +2421,28 @@ DrawTargetD2D::SetupEffectForRadialGradient(const RadialGradientPattern *aPatter } } +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() { @@ -2209,5 +2479,32 @@ DrawTargetD2D::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 NULL; + } + + HRESULT hr = createDWriteFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), + reinterpret_cast(&mDWriteFactory)); + + if (FAILED(hr)) { + gfxWarning() << "Failed to create DWrite Factory."; + } + + return mDWriteFactory; +} + } } diff --git a/gfx/2d/DrawTargetD2D.h b/gfx/2d/DrawTargetD2D.h index 30d35a84018..aa40441ba36 100644 --- a/gfx/2d/DrawTargetD2D.h +++ b/gfx/2d/DrawTargetD2D.h @@ -52,12 +52,15 @@ #include #endif +struct IDWriteFactory; + namespace mozilla { namespace gfx { class SourceSurfaceD2DTarget; class SourceSurfaceD2D; class GradientStopsD2D; +class ScaledFontDWrite; struct PrivateD3D10DataD2D { @@ -153,6 +156,7 @@ public: static ID2D1Factory *factory(); static TemporaryRef CreateStrokeStyleForOptions(const StrokeOptions &aStrokeOptions); + static IDWriteFactory *GetDWriteFactory(); operator std::string() const { std::stringstream stream; @@ -194,12 +198,19 @@ private: // a mask corresponding with the current DrawTarget clip. void EnsureClipMaskTexture(); + bool FillGlyphsManual(ScaledFontDWrite *aFont, + const GlyphBuffer &aBuffer, + const Color &aColor, + IDWriteRenderingParams *aParams, + const DrawOptions &aOptions = DrawOptions()); + TemporaryRef CreateRTForTexture(ID3D10Texture2D *aTexture, SurfaceFormat aFormat); TemporaryRef GetClippedGeometry(); TemporaryRef CreateBrushForPattern(const Pattern &aPattern, Float aAlpha = 1.0f); TemporaryRef CreateGradientTexture(const GradientStopsD2D *aStops); + TemporaryRef CreateTextureForAnalysis(IDWriteGlyphRunAnalysis *aAnalysis, const IntRect &aBounds); // This creates a partially uploaded bitmap for a SourceSurfaceD2D that is // too big to fit in a bitmap. It adjusts the passed Matrix to accomodate the @@ -207,6 +218,7 @@ private: TemporaryRef CreatePartialBitmapForSurface(SourceSurfaceD2D *aSurface, Matrix &aMatrix); void SetupEffectForRadialGradient(const RadialGradientPattern *aPattern); + void SetupStateForRendering(); static const uint32_t test = 4; @@ -247,6 +259,7 @@ private: bool mClipsArePushed; PrivateD3D10DataD2D *mPrivateData; static ID2D1Factory *mFactory; + static IDWriteFactory *mDWriteFactory; }; } diff --git a/gfx/2d/ShadersD2D.fx b/gfx/2d/ShadersD2D.fx index 5483402aaae..2cc07aeeed4 100644 --- a/gfx/2d/ShadersD2D.fx +++ b/gfx/2d/ShadersD2D.fx @@ -14,6 +14,7 @@ cbuffer cb0 float4 QuadDesc; float4 TexCoords; float4 MaskTexCoords; + float4 TextColor; } cbuffer cb1 @@ -50,6 +51,12 @@ struct VS_RADIAL_OUTPUT float2 PixelCoord : TEXCOORD1; }; +struct PS_TEXT_OUTPUT +{ + float4 color; + float4 alpha; +}; + Texture2D tex; Texture2D mask; @@ -113,6 +120,19 @@ BlendState ShadowBlendV RenderTargetWriteMask[0] = 0xF; }; +BlendState bTextBlend +{ + AlphaToCoverageEnable = FALSE; + BlendEnable[0] = TRUE; + SrcBlend = Src1_Color; + DestBlend = Inv_Src1_Color; + BlendOp = Add; + SrcBlendAlpha = Src1_Alpha; + DestBlendAlpha = Inv_Src1_Alpha; + BlendOpAlpha = Add; + RenderTargetWriteMask[0] = 0x0F; // All +}; + VS_OUTPUT SampleTextureVS(float3 pos : POSITION) { VS_OUTPUT Output; @@ -278,6 +298,26 @@ float4 SampleMaskShadowVPS( VS_OUTPUT In) : SV_Target return outputColor * mask.Sample(sMaskSampler, In.MaskTexCoord).a; }; +PS_TEXT_OUTPUT SampleTextTexturePS( VS_OUTPUT In) : SV_Target +{ + PS_TEXT_OUTPUT output; + output.color = TextColor; + output.alpha.rgba = tex.Sample(sSampler, In.TexCoord).bgrg * TextColor.a; + return output; +}; + +PS_TEXT_OUTPUT SampleTextTexturePSMasked( VS_OUTPUT In) : SV_Target +{ + PS_TEXT_OUTPUT output; + + float maskValue = mask.Sample(sMaskSampler, In.MaskTexCoord).a; + + output.color = TextColor * maskValue; + output.alpha.rgba = tex.Sample(sSampler, In.TexCoord).bgrg * TextColor.a * maskValue; + + return output; +}; + technique10 SampleTexture { pass P0 @@ -375,4 +415,23 @@ technique10 SampleTextureWithShadow SetGeometryShader(NULL); SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleMaskShadowVPS())); } - } +} + +technique10 SampleTextTexture +{ + pass Unmasked + { + SetBlendState(bTextBlend, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF ); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleTextTexturePS())); + } + pass Masked + { + SetBlendState(bTextBlend, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF ); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleTextTexturePSMasked())); + } +} +