michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "RadialGradientEffectD2D1.h" michael@0: michael@0: #include "Logging.h" michael@0: michael@0: #include "ShadersD2D1.h" michael@0: #include "HelpersD2D.h" michael@0: michael@0: #include michael@0: michael@0: #define TEXTW(x) L##x michael@0: #define XML(X) TEXTW(#X) // This macro creates a single string from multiple lines of text. michael@0: michael@0: static const PCWSTR kXmlDescription = michael@0: XML( michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: ); michael@0: michael@0: // {FB947CDA-718E-40CC-AE7B-D255830D7D14} michael@0: static const GUID GUID_SampleRadialGradientPS = michael@0: {0xfb947cda, 0x718e, 0x40cc, {0xae, 0x7b, 0xd2, 0x55, 0x83, 0xd, 0x7d, 0x14}}; michael@0: // {2C468128-6546-453C-8E25-F2DF0DE10A0F} michael@0: static const GUID GUID_SampleRadialGradientA0PS = michael@0: {0x2c468128, 0x6546, 0x453c, {0x8e, 0x25, 0xf2, 0xdf, 0xd, 0xe1, 0xa, 0xf}}; michael@0: michael@0: namespace mozilla { michael@0: namespace gfx { michael@0: michael@0: RadialGradientEffectD2D1::RadialGradientEffectD2D1() michael@0: : mRefCount(0) michael@0: , mCenter1(D2D1::Vector2F(0, 0)) michael@0: , mCenter2(D2D1::Vector2F(0, 0)) michael@0: , mRadius1(0) michael@0: , mRadius2(0) michael@0: , mTransform(D2D1::IdentityMatrix()) michael@0: michael@0: { michael@0: } michael@0: michael@0: IFACEMETHODIMP michael@0: RadialGradientEffectD2D1::Initialize(ID2D1EffectContext* pContextInternal, ID2D1TransformGraph* pTransformGraph) michael@0: { michael@0: HRESULT hr; michael@0: michael@0: hr = pContextInternal->LoadPixelShader(GUID_SampleRadialGradientPS, SampleRadialGradientPS, sizeof(SampleRadialGradientPS)); michael@0: michael@0: if (FAILED(hr)) { michael@0: return hr; michael@0: } michael@0: michael@0: hr = pContextInternal->LoadPixelShader(GUID_SampleRadialGradientA0PS, SampleRadialGradientA0PS, sizeof(SampleRadialGradientA0PS)); michael@0: michael@0: if (FAILED(hr)) { michael@0: return hr; michael@0: } michael@0: michael@0: hr = pTransformGraph->SetSingleTransformNode(this); michael@0: michael@0: if (FAILED(hr)) { michael@0: return hr; michael@0: } michael@0: michael@0: mEffectContext = pContextInternal; michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: IFACEMETHODIMP michael@0: RadialGradientEffectD2D1::PrepareForRender(D2D1_CHANGE_TYPE changeType) michael@0: { michael@0: if (changeType == D2D1_CHANGE_TYPE_NONE) { michael@0: return S_OK; michael@0: } michael@0: michael@0: // We'll need to inverse transform our pixel, precompute inverse here. michael@0: Matrix mat = ToMatrix(mTransform); michael@0: if (!mat.Invert()) { michael@0: // Singular michael@0: return S_OK; michael@0: } michael@0: michael@0: if (!mStopCollection) { michael@0: return S_OK; michael@0: } michael@0: michael@0: D2D1_POINT_2F dc = D2D1::Point2F(mCenter2.x - mCenter1.x, mCenter2.y - mCenter2.y); michael@0: float dr = mRadius2 - mRadius1; michael@0: float A = dc.x * dc.x + dc.y * dc.y - dr * dr; michael@0: michael@0: HRESULT hr; michael@0: michael@0: if (A == 0) { michael@0: hr = mDrawInfo->SetPixelShader(GUID_SampleRadialGradientA0PS); michael@0: } else { michael@0: hr = mDrawInfo->SetPixelShader(GUID_SampleRadialGradientPS); michael@0: } michael@0: michael@0: if (FAILED(hr)) { michael@0: return hr; michael@0: } michael@0: michael@0: RefPtr tex = CreateGradientTexture(); michael@0: hr = mDrawInfo->SetResourceTexture(1, tex); michael@0: michael@0: if (FAILED(hr)) { michael@0: return hr; michael@0: } michael@0: michael@0: struct PSConstantBuffer michael@0: { michael@0: float diff[3]; michael@0: float padding; michael@0: float center1[2]; michael@0: float A; michael@0: float radius1; michael@0: float sq_radius1; michael@0: float padding2[3]; michael@0: float transform[8]; michael@0: }; michael@0: michael@0: PSConstantBuffer buffer = { { dc.x, dc.y, dr }, 0, michael@0: { mCenter1.x, mCenter1.y }, michael@0: A, mRadius1, mRadius1 * mRadius1, michael@0: { 0, 0, 0 }, { mat._11, mat._21, mat._31, 0, michael@0: mat._12, mat._22, mat._32, 0 } }; michael@0: michael@0: hr = mDrawInfo->SetPixelShaderConstantBuffer((BYTE*)&buffer, sizeof(buffer)); michael@0: michael@0: if (FAILED(hr)) { michael@0: return hr; michael@0: } michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: IFACEMETHODIMP michael@0: RadialGradientEffectD2D1::SetGraph(ID2D1TransformGraph* pGraph) michael@0: { michael@0: return pGraph->SetSingleTransformNode(this); michael@0: } michael@0: michael@0: IFACEMETHODIMP_(ULONG) michael@0: RadialGradientEffectD2D1::AddRef() michael@0: { michael@0: return ++mRefCount; michael@0: } michael@0: michael@0: IFACEMETHODIMP_(ULONG) michael@0: RadialGradientEffectD2D1::Release() michael@0: { michael@0: if (!--mRefCount) { michael@0: delete this; michael@0: return 0; michael@0: } michael@0: return mRefCount; michael@0: } michael@0: michael@0: IFACEMETHODIMP michael@0: RadialGradientEffectD2D1::QueryInterface(const IID &aIID, void **aPtr) michael@0: { michael@0: if (!aPtr) { michael@0: return E_POINTER; michael@0: } michael@0: michael@0: if (aIID == IID_IUnknown) { michael@0: *aPtr = static_cast(static_cast(this)); michael@0: } else if (aIID == IID_ID2D1EffectImpl) { michael@0: *aPtr = static_cast(this); michael@0: } else if (aIID == IID_ID2D1DrawTransform) { michael@0: *aPtr = static_cast(this); michael@0: } else if (aIID == IID_ID2D1Transform) { michael@0: *aPtr = static_cast(this); michael@0: } else if (aIID == IID_ID2D1TransformNode) { michael@0: *aPtr = static_cast(this); michael@0: } else { michael@0: return E_NOINTERFACE; michael@0: } michael@0: michael@0: static_cast(*aPtr)->AddRef(); michael@0: return S_OK; michael@0: } michael@0: michael@0: IFACEMETHODIMP michael@0: RadialGradientEffectD2D1::MapInputRectsToOutputRect(const D2D1_RECT_L* pInputRects, michael@0: const D2D1_RECT_L* pInputOpaqueSubRects, michael@0: UINT32 inputRectCount, michael@0: D2D1_RECT_L* pOutputRect, michael@0: D2D1_RECT_L* pOutputOpaqueSubRect) michael@0: { michael@0: if (inputRectCount != 1) { michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: *pOutputRect = *pInputRects; michael@0: *pOutputOpaqueSubRect = *pInputOpaqueSubRects; michael@0: return S_OK; michael@0: } michael@0: michael@0: IFACEMETHODIMP michael@0: RadialGradientEffectD2D1::MapOutputRectToInputRects(const D2D1_RECT_L* pOutputRect, michael@0: D2D1_RECT_L* pInputRects, michael@0: UINT32 inputRectCount) const michael@0: { michael@0: if (inputRectCount != 1) { michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: *pInputRects = *pOutputRect; michael@0: return S_OK; michael@0: } michael@0: michael@0: IFACEMETHODIMP michael@0: RadialGradientEffectD2D1::MapInvalidRect(UINT32 inputIndex, michael@0: D2D1_RECT_L invalidInputRect, michael@0: D2D1_RECT_L* pInvalidOutputRect) const michael@0: { michael@0: MOZ_ASSERT(inputIndex = 0); michael@0: michael@0: *pInvalidOutputRect = invalidInputRect; michael@0: return S_OK; michael@0: } michael@0: michael@0: IFACEMETHODIMP michael@0: RadialGradientEffectD2D1::SetDrawInfo(ID2D1DrawInfo *pDrawInfo) michael@0: { michael@0: mDrawInfo = pDrawInfo; michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT michael@0: RadialGradientEffectD2D1::Register(ID2D1Factory1 *aFactory) michael@0: { michael@0: D2D1_PROPERTY_BINDING bindings[] = { michael@0: D2D1_VALUE_TYPE_BINDING(L"StopCollection", &RadialGradientEffectD2D1::SetStopCollection, michael@0: &RadialGradientEffectD2D1::GetStopCollection), michael@0: D2D1_VALUE_TYPE_BINDING(L"Center1", &RadialGradientEffectD2D1::SetCenter1, &RadialGradientEffectD2D1::GetCenter1), michael@0: D2D1_VALUE_TYPE_BINDING(L"Center2", &RadialGradientEffectD2D1::SetCenter2, &RadialGradientEffectD2D1::GetCenter2), michael@0: D2D1_VALUE_TYPE_BINDING(L"Radius1", &RadialGradientEffectD2D1::SetRadius1, &RadialGradientEffectD2D1::GetRadius1), michael@0: D2D1_VALUE_TYPE_BINDING(L"Radius2", &RadialGradientEffectD2D1::SetRadius2, &RadialGradientEffectD2D1::GetRadius2), michael@0: D2D1_VALUE_TYPE_BINDING(L"Transform", &RadialGradientEffectD2D1::SetTransform, &RadialGradientEffectD2D1::GetTransform) michael@0: }; michael@0: HRESULT hr = aFactory->RegisterEffectFromString(CLSID_RadialGradientEffect, kXmlDescription, bindings, ARRAYSIZE(bindings), CreateEffect); michael@0: michael@0: if (FAILED(hr)) { michael@0: gfxWarning() << "Failed to register radial gradient effect."; michael@0: } michael@0: return hr; michael@0: } michael@0: michael@0: HRESULT __stdcall michael@0: RadialGradientEffectD2D1::CreateEffect(IUnknown **aEffectImpl) michael@0: { michael@0: *aEffectImpl = static_cast(new RadialGradientEffectD2D1()); michael@0: (*aEffectImpl)->AddRef(); michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT michael@0: RadialGradientEffectD2D1::SetStopCollection(IUnknown *aStopCollection) michael@0: { michael@0: if (SUCCEEDED(aStopCollection->QueryInterface((ID2D1GradientStopCollection**)byRef(mStopCollection)))) { michael@0: return S_OK; michael@0: } michael@0: michael@0: return E_INVALIDARG; michael@0: } michael@0: michael@0: TemporaryRef michael@0: RadialGradientEffectD2D1::CreateGradientTexture() michael@0: { michael@0: std::vector rawStops; michael@0: rawStops.resize(mStopCollection->GetGradientStopCount()); michael@0: mStopCollection->GetGradientStops(&rawStops.front(), rawStops.size()); michael@0: michael@0: std::vector textureData; michael@0: textureData.resize(4096 * 4); michael@0: unsigned char *texData = &textureData.front(); michael@0: michael@0: float prevColorPos = 0; michael@0: float nextColorPos = 1.0f; michael@0: D2D1_COLOR_F prevColor = rawStops[0].color; michael@0: D2D1_COLOR_F nextColor = prevColor; michael@0: michael@0: if (rawStops.size() >= 2) { michael@0: nextColor = rawStops[1].color; michael@0: nextColorPos = rawStops[1].position; michael@0: } michael@0: michael@0: uint32_t stopPosition = 2; michael@0: michael@0: // Not the most optimized way but this will do for now. michael@0: for (int i = 0; i < 4096; i++) { michael@0: // The 4095 seems a little counter intuitive, but we want the gradient michael@0: // color at offset 0 at the first pixel, and at offset 1.0f at the last michael@0: // pixel. michael@0: float pos = float(i) / 4095; michael@0: michael@0: while (pos > nextColorPos) { michael@0: prevColor = nextColor; michael@0: prevColorPos = nextColorPos; michael@0: if (rawStops.size() > stopPosition) { michael@0: nextColor = rawStops[stopPosition].color; michael@0: nextColorPos = rawStops[stopPosition++].position; michael@0: } else { michael@0: nextColorPos = 1.0f; michael@0: } michael@0: } michael@0: michael@0: float interp; michael@0: michael@0: if (nextColorPos != prevColorPos) { michael@0: interp = (pos - prevColorPos) / (nextColorPos - prevColorPos); michael@0: } else { michael@0: interp = 0; michael@0: } michael@0: michael@0: Color newColor(prevColor.r + (nextColor.r - prevColor.r) * interp, michael@0: prevColor.g + (nextColor.g - prevColor.g) * interp, michael@0: prevColor.b + (nextColor.b - prevColor.b) * interp, michael@0: prevColor.a + (nextColor.a - prevColor.a) * interp); michael@0: michael@0: // Note D2D expects RGBA here!! michael@0: texData[i * 4] = (char)(255.0f * newColor.r); michael@0: texData[i * 4 + 1] = (char)(255.0f * newColor.g); michael@0: texData[i * 4 + 2] = (char)(255.0f * newColor.b); michael@0: texData[i * 4 + 3] = (char)(255.0f * newColor.a); michael@0: } michael@0: michael@0: RefPtr tex; michael@0: michael@0: UINT32 width = 4096; michael@0: UINT32 stride = 4096 * 4; michael@0: D2D1_RESOURCE_TEXTURE_PROPERTIES props; michael@0: props.dimensions = 1; michael@0: props.extents = &width; michael@0: props.channelDepth = D2D1_CHANNEL_DEPTH_4; michael@0: props.bufferPrecision = D2D1_BUFFER_PRECISION_8BPC_UNORM; michael@0: props.filter = D2D1_FILTER_MIN_MAG_MIP_LINEAR; michael@0: D2D1_EXTEND_MODE extendMode = mStopCollection->GetExtendMode(); michael@0: props.extendModes = &extendMode; michael@0: michael@0: HRESULT hr = mEffectContext->CreateResourceTexture(nullptr, &props, &textureData.front(), &stride, 4096 * 4, byRef(tex)); michael@0: michael@0: if (FAILED(hr)) { michael@0: gfxWarning() << "Failed to create resource texture: " << hr; michael@0: } michael@0: michael@0: return tex; michael@0: } michael@0: michael@0: } michael@0: }