diff -r 000000000000 -r 6474c204b198 gfx/2d/RadialGradientEffectD2D1.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gfx/2d/RadialGradientEffectD2D1.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,386 @@ +/* -*- 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 "RadialGradientEffectD2D1.h" + +#include "Logging.h" + +#include "ShadersD2D1.h" +#include "HelpersD2D.h" + +#include + +#define TEXTW(x) L##x +#define XML(X) TEXTW(#X) // This macro creates a single string from multiple lines of text. + +static const PCWSTR kXmlDescription = + XML( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + +// {FB947CDA-718E-40CC-AE7B-D255830D7D14} +static const GUID GUID_SampleRadialGradientPS = + {0xfb947cda, 0x718e, 0x40cc, {0xae, 0x7b, 0xd2, 0x55, 0x83, 0xd, 0x7d, 0x14}}; +// {2C468128-6546-453C-8E25-F2DF0DE10A0F} +static const GUID GUID_SampleRadialGradientA0PS = + {0x2c468128, 0x6546, 0x453c, {0x8e, 0x25, 0xf2, 0xdf, 0xd, 0xe1, 0xa, 0xf}}; + +namespace mozilla { +namespace gfx { + +RadialGradientEffectD2D1::RadialGradientEffectD2D1() + : mRefCount(0) + , mCenter1(D2D1::Vector2F(0, 0)) + , mCenter2(D2D1::Vector2F(0, 0)) + , mRadius1(0) + , mRadius2(0) + , mTransform(D2D1::IdentityMatrix()) + +{ +} + +IFACEMETHODIMP +RadialGradientEffectD2D1::Initialize(ID2D1EffectContext* pContextInternal, ID2D1TransformGraph* pTransformGraph) +{ + HRESULT hr; + + hr = pContextInternal->LoadPixelShader(GUID_SampleRadialGradientPS, SampleRadialGradientPS, sizeof(SampleRadialGradientPS)); + + if (FAILED(hr)) { + return hr; + } + + hr = pContextInternal->LoadPixelShader(GUID_SampleRadialGradientA0PS, SampleRadialGradientA0PS, sizeof(SampleRadialGradientA0PS)); + + if (FAILED(hr)) { + return hr; + } + + hr = pTransformGraph->SetSingleTransformNode(this); + + if (FAILED(hr)) { + return hr; + } + + mEffectContext = pContextInternal; + + return S_OK; +} + +IFACEMETHODIMP +RadialGradientEffectD2D1::PrepareForRender(D2D1_CHANGE_TYPE changeType) +{ + if (changeType == D2D1_CHANGE_TYPE_NONE) { + return S_OK; + } + + // We'll need to inverse transform our pixel, precompute inverse here. + Matrix mat = ToMatrix(mTransform); + if (!mat.Invert()) { + // Singular + return S_OK; + } + + if (!mStopCollection) { + return S_OK; + } + + D2D1_POINT_2F dc = D2D1::Point2F(mCenter2.x - mCenter1.x, mCenter2.y - mCenter2.y); + float dr = mRadius2 - mRadius1; + float A = dc.x * dc.x + dc.y * dc.y - dr * dr; + + HRESULT hr; + + if (A == 0) { + hr = mDrawInfo->SetPixelShader(GUID_SampleRadialGradientA0PS); + } else { + hr = mDrawInfo->SetPixelShader(GUID_SampleRadialGradientPS); + } + + if (FAILED(hr)) { + return hr; + } + + RefPtr tex = CreateGradientTexture(); + hr = mDrawInfo->SetResourceTexture(1, tex); + + if (FAILED(hr)) { + return hr; + } + + struct PSConstantBuffer + { + float diff[3]; + float padding; + float center1[2]; + float A; + float radius1; + float sq_radius1; + float padding2[3]; + float transform[8]; + }; + + PSConstantBuffer buffer = { { dc.x, dc.y, dr }, 0, + { mCenter1.x, mCenter1.y }, + A, mRadius1, mRadius1 * mRadius1, + { 0, 0, 0 }, { mat._11, mat._21, mat._31, 0, + mat._12, mat._22, mat._32, 0 } }; + + hr = mDrawInfo->SetPixelShaderConstantBuffer((BYTE*)&buffer, sizeof(buffer)); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +IFACEMETHODIMP +RadialGradientEffectD2D1::SetGraph(ID2D1TransformGraph* pGraph) +{ + return pGraph->SetSingleTransformNode(this); +} + +IFACEMETHODIMP_(ULONG) +RadialGradientEffectD2D1::AddRef() +{ + return ++mRefCount; +} + +IFACEMETHODIMP_(ULONG) +RadialGradientEffectD2D1::Release() +{ + if (!--mRefCount) { + delete this; + return 0; + } + return mRefCount; +} + +IFACEMETHODIMP +RadialGradientEffectD2D1::QueryInterface(const IID &aIID, void **aPtr) +{ + if (!aPtr) { + return E_POINTER; + } + + if (aIID == IID_IUnknown) { + *aPtr = static_cast(static_cast(this)); + } else if (aIID == IID_ID2D1EffectImpl) { + *aPtr = static_cast(this); + } else if (aIID == IID_ID2D1DrawTransform) { + *aPtr = static_cast(this); + } else if (aIID == IID_ID2D1Transform) { + *aPtr = static_cast(this); + } else if (aIID == IID_ID2D1TransformNode) { + *aPtr = static_cast(this); + } else { + return E_NOINTERFACE; + } + + static_cast(*aPtr)->AddRef(); + return S_OK; +} + +IFACEMETHODIMP +RadialGradientEffectD2D1::MapInputRectsToOutputRect(const D2D1_RECT_L* pInputRects, + const D2D1_RECT_L* pInputOpaqueSubRects, + UINT32 inputRectCount, + D2D1_RECT_L* pOutputRect, + D2D1_RECT_L* pOutputOpaqueSubRect) +{ + if (inputRectCount != 1) { + return E_INVALIDARG; + } + + *pOutputRect = *pInputRects; + *pOutputOpaqueSubRect = *pInputOpaqueSubRects; + return S_OK; +} + +IFACEMETHODIMP +RadialGradientEffectD2D1::MapOutputRectToInputRects(const D2D1_RECT_L* pOutputRect, + D2D1_RECT_L* pInputRects, + UINT32 inputRectCount) const +{ + if (inputRectCount != 1) { + return E_INVALIDARG; + } + + *pInputRects = *pOutputRect; + return S_OK; +} + +IFACEMETHODIMP +RadialGradientEffectD2D1::MapInvalidRect(UINT32 inputIndex, + D2D1_RECT_L invalidInputRect, + D2D1_RECT_L* pInvalidOutputRect) const +{ + MOZ_ASSERT(inputIndex = 0); + + *pInvalidOutputRect = invalidInputRect; + return S_OK; +} + +IFACEMETHODIMP +RadialGradientEffectD2D1::SetDrawInfo(ID2D1DrawInfo *pDrawInfo) +{ + mDrawInfo = pDrawInfo; + return S_OK; +} + +HRESULT +RadialGradientEffectD2D1::Register(ID2D1Factory1 *aFactory) +{ + D2D1_PROPERTY_BINDING bindings[] = { + D2D1_VALUE_TYPE_BINDING(L"StopCollection", &RadialGradientEffectD2D1::SetStopCollection, + &RadialGradientEffectD2D1::GetStopCollection), + D2D1_VALUE_TYPE_BINDING(L"Center1", &RadialGradientEffectD2D1::SetCenter1, &RadialGradientEffectD2D1::GetCenter1), + D2D1_VALUE_TYPE_BINDING(L"Center2", &RadialGradientEffectD2D1::SetCenter2, &RadialGradientEffectD2D1::GetCenter2), + D2D1_VALUE_TYPE_BINDING(L"Radius1", &RadialGradientEffectD2D1::SetRadius1, &RadialGradientEffectD2D1::GetRadius1), + D2D1_VALUE_TYPE_BINDING(L"Radius2", &RadialGradientEffectD2D1::SetRadius2, &RadialGradientEffectD2D1::GetRadius2), + D2D1_VALUE_TYPE_BINDING(L"Transform", &RadialGradientEffectD2D1::SetTransform, &RadialGradientEffectD2D1::GetTransform) + }; + HRESULT hr = aFactory->RegisterEffectFromString(CLSID_RadialGradientEffect, kXmlDescription, bindings, ARRAYSIZE(bindings), CreateEffect); + + if (FAILED(hr)) { + gfxWarning() << "Failed to register radial gradient effect."; + } + return hr; +} + +HRESULT __stdcall +RadialGradientEffectD2D1::CreateEffect(IUnknown **aEffectImpl) +{ + *aEffectImpl = static_cast(new RadialGradientEffectD2D1()); + (*aEffectImpl)->AddRef(); + + return S_OK; +} + +HRESULT +RadialGradientEffectD2D1::SetStopCollection(IUnknown *aStopCollection) +{ + if (SUCCEEDED(aStopCollection->QueryInterface((ID2D1GradientStopCollection**)byRef(mStopCollection)))) { + return S_OK; + } + + return E_INVALIDARG; +} + +TemporaryRef +RadialGradientEffectD2D1::CreateGradientTexture() +{ + std::vector rawStops; + rawStops.resize(mStopCollection->GetGradientStopCount()); + 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); + + // Note D2D expects RGBA here!! + texData[i * 4] = (char)(255.0f * newColor.r); + texData[i * 4 + 1] = (char)(255.0f * newColor.g); + texData[i * 4 + 2] = (char)(255.0f * newColor.b); + texData[i * 4 + 3] = (char)(255.0f * newColor.a); + } + + RefPtr tex; + + UINT32 width = 4096; + UINT32 stride = 4096 * 4; + D2D1_RESOURCE_TEXTURE_PROPERTIES props; + props.dimensions = 1; + props.extents = &width; + props.channelDepth = D2D1_CHANNEL_DEPTH_4; + props.bufferPrecision = D2D1_BUFFER_PRECISION_8BPC_UNORM; + props.filter = D2D1_FILTER_MIN_MAG_MIP_LINEAR; + D2D1_EXTEND_MODE extendMode = mStopCollection->GetExtendMode(); + props.extendModes = &extendMode; + + HRESULT hr = mEffectContext->CreateResourceTexture(nullptr, &props, &textureData.front(), &stride, 4096 * 4, byRef(tex)); + + if (FAILED(hr)) { + gfxWarning() << "Failed to create resource texture: " << hr; + } + + return tex; +} + +} +}