diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/2d/ConicGradientEffectD2D1.cpp | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/gfx/2d/ConicGradientEffectD2D1.cpp b/gfx/2d/ConicGradientEffectD2D1.cpp new file mode 100644 index 0000000000..d94059e3d2 --- /dev/null +++ b/gfx/2d/ConicGradientEffectD2D1.cpp @@ -0,0 +1,375 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "ConicGradientEffectD2D1.h" + +#include "Logging.h" + +#include "ShadersD2D1.h" +#include "HelpersD2D.h" + +#include <vector> + +#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( + <?xml version='1.0'?> + <Effect> + <!-- System Properties --> + <Property name='DisplayName' type='string' value='ConicGradientEffect'/> + <Property name='Author' type='string' value='Mozilla'/> + <Property name='Category' type='string' value='Pattern effects'/> + <Property name='Description' type='string' value='This effect is used to render CSS conic gradients.'/> + <Inputs> + <Input name='Geometry'/> + </Inputs> + <Property name='StopCollection' type='iunknown'> + <Property name='DisplayName' type='string' value='Gradient stop collection'/> + </Property> + <Property name='Center' type='vector2'> + <Property name='DisplayName' type='string' value='Gradient center'/> + </Property> + <Property name='Angle' type='vector2'> + <Property name='DisplayName' type='string' value='Gradient angle'/> + </Property> + <Property name='StartOffset' type='float'> + <Property name='DisplayName' type='string' value='Start stop offset'/> + </Property> + <Property name='EndOffset' type='float'> + <Property name='DisplayName' type='string' value='End stop offset'/> + </Property> + <Property name='Transform' type='matrix3x2'> + <Property name='DisplayName' type='string' value='Transform applied to the pattern'/> + </Property> + + </Effect> + ); + +// {091fda1d-857e-4b1e-828f-1c839d9b7897} +static const GUID GUID_SampleConicGradientPS = { + 0x091fda1d, + 0x857e, + 0x4b1e, + {0x82, 0x8f, 0x1c, 0x83, 0x9d, 0x9b, 0x78, 0x97}}; + +namespace mozilla { +namespace gfx { + +ConicGradientEffectD2D1::ConicGradientEffectD2D1() + : mRefCount(0), + mCenter(D2D1::Vector2F(0, 0)), + mAngle(0), + mStartOffset(0), + mEndOffset(0), + mTransform(D2D1::IdentityMatrix()) + +{} + +IFACEMETHODIMP +ConicGradientEffectD2D1::Initialize(ID2D1EffectContext* pContextInternal, + ID2D1TransformGraph* pTransformGraph) { + HRESULT hr; + + hr = pContextInternal->LoadPixelShader(GUID_SampleConicGradientPS, + SampleConicGradientPS, + sizeof(SampleConicGradientPS)); + + if (FAILED(hr)) { + return hr; + } + + hr = pTransformGraph->SetSingleTransformNode(this); + + if (FAILED(hr)) { + return hr; + } + + mEffectContext = pContextInternal; + + return S_OK; +} + +IFACEMETHODIMP +ConicGradientEffectD2D1::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; + } + + HRESULT hr = mDrawInfo->SetPixelShader(GUID_SampleConicGradientPS); + + if (FAILED(hr)) { + return hr; + } + + RefPtr<ID2D1ResourceTexture> tex = CreateGradientTexture(); + hr = mDrawInfo->SetResourceTexture(1, tex); + + if (FAILED(hr)) { + return hr; + } + + struct PSConstantBuffer { + float center[2]; + float angle; + float start_offset; + float end_offset; + float repeat_correct; + float allow_odd; + float padding[1]; + float transform[8]; + }; + + PSConstantBuffer buffer = { + {mCenter.x, mCenter.y}, + mAngle, + mStartOffset, + mEndOffset, + mStopCollection->GetExtendMode() != D2D1_EXTEND_MODE_CLAMP ? 1.0f : 0.0f, + mStopCollection->GetExtendMode() == D2D1_EXTEND_MODE_MIRROR ? 1.0f : 0.0f, + {0.0f}, + {mat._11, mat._21, mat._31, 0.0f, mat._12, mat._22, mat._32, 0.0f}}; + + hr = mDrawInfo->SetPixelShaderConstantBuffer((BYTE*)&buffer, sizeof(buffer)); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +IFACEMETHODIMP +ConicGradientEffectD2D1::SetGraph(ID2D1TransformGraph* pGraph) { + return pGraph->SetSingleTransformNode(this); +} + +IFACEMETHODIMP_(ULONG) +ConicGradientEffectD2D1::AddRef() { return ++mRefCount; } + +IFACEMETHODIMP_(ULONG) +ConicGradientEffectD2D1::Release() { + if (!--mRefCount) { + delete this; + return 0; + } + return mRefCount; +} + +IFACEMETHODIMP +ConicGradientEffectD2D1::QueryInterface(const IID& aIID, void** aPtr) { + if (!aPtr) { + return E_POINTER; + } + + if (aIID == IID_IUnknown) { + *aPtr = static_cast<IUnknown*>(static_cast<ID2D1EffectImpl*>(this)); + } else if (aIID == IID_ID2D1EffectImpl) { + *aPtr = static_cast<ID2D1EffectImpl*>(this); + } else if (aIID == IID_ID2D1DrawTransform) { + *aPtr = static_cast<ID2D1DrawTransform*>(this); + } else if (aIID == IID_ID2D1Transform) { + *aPtr = static_cast<ID2D1Transform*>(this); + } else if (aIID == IID_ID2D1TransformNode) { + *aPtr = static_cast<ID2D1TransformNode*>(this); + } else { + return E_NOINTERFACE; + } + + static_cast<IUnknown*>(*aPtr)->AddRef(); + return S_OK; +} + +IFACEMETHODIMP +ConicGradientEffectD2D1::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 +ConicGradientEffectD2D1::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 +ConicGradientEffectD2D1::MapInvalidRect(UINT32 inputIndex, + D2D1_RECT_L invalidInputRect, + D2D1_RECT_L* pInvalidOutputRect) const { + MOZ_ASSERT(inputIndex == 0); + + *pInvalidOutputRect = invalidInputRect; + return S_OK; +} + +IFACEMETHODIMP +ConicGradientEffectD2D1::SetDrawInfo(ID2D1DrawInfo* pDrawInfo) { + mDrawInfo = pDrawInfo; + return S_OK; +} + +HRESULT +ConicGradientEffectD2D1::Register(ID2D1Factory1* aFactory) { + D2D1_PROPERTY_BINDING bindings[] = { + D2D1_VALUE_TYPE_BINDING(L"StopCollection", + &ConicGradientEffectD2D1::SetStopCollection, + &ConicGradientEffectD2D1::GetStopCollection), + D2D1_VALUE_TYPE_BINDING(L"Center", &ConicGradientEffectD2D1::SetCenter, + &ConicGradientEffectD2D1::GetCenter), + D2D1_VALUE_TYPE_BINDING(L"Angle", &ConicGradientEffectD2D1::SetAngle, + &ConicGradientEffectD2D1::GetAngle), + D2D1_VALUE_TYPE_BINDING(L"StartOffset", + &ConicGradientEffectD2D1::SetStartOffset, + &ConicGradientEffectD2D1::GetStartOffset), + D2D1_VALUE_TYPE_BINDING(L"EndOffset", + &ConicGradientEffectD2D1::SetEndOffset, + &ConicGradientEffectD2D1::GetEndOffset), + D2D1_VALUE_TYPE_BINDING(L"Transform", + &ConicGradientEffectD2D1::SetTransform, + &ConicGradientEffectD2D1::GetTransform)}; + HRESULT hr = aFactory->RegisterEffectFromString( + CLSID_ConicGradientEffect, kXmlDescription, bindings, ARRAYSIZE(bindings), + CreateEffect); + + if (FAILED(hr)) { + gfxWarning() << "Failed to register radial gradient effect."; + } + return hr; +} + +void ConicGradientEffectD2D1::Unregister(ID2D1Factory1* aFactory) { + aFactory->UnregisterEffect(CLSID_ConicGradientEffect); +} + +HRESULT __stdcall ConicGradientEffectD2D1::CreateEffect( + IUnknown** aEffectImpl) { + *aEffectImpl = static_cast<ID2D1EffectImpl*>(new ConicGradientEffectD2D1()); + (*aEffectImpl)->AddRef(); + + return S_OK; +} + +HRESULT +ConicGradientEffectD2D1::SetStopCollection(IUnknown* aStopCollection) { + if (SUCCEEDED(aStopCollection->QueryInterface( + (ID2D1GradientStopCollection**)getter_AddRefs(mStopCollection)))) { + return S_OK; + } + + return E_INVALIDARG; +} + +already_AddRefed<ID2D1ResourceTexture> +ConicGradientEffectD2D1::CreateGradientTexture() { + std::vector<D2D1_GRADIENT_STOP> rawStops; + rawStops.resize(mStopCollection->GetGradientStopCount()); + mStopCollection->GetGradientStops(&rawStops.front(), rawStops.size()); + + std::vector<unsigned char> textureData; + textureData.resize(4096 * 4); + unsigned char* texData = &textureData.front(); + + float prevColorPos = 0; + float nextColorPos = rawStops[0].position; + D2D1_COLOR_F prevColor = rawStops[0].color; + D2D1_COLOR_F nextColor = prevColor; + uint32_t stopPosition = 1; + + // 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; + } + + DeviceColor 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<ID2D1ResourceTexture> tex; + + UINT32 width = 4096; + UINT32 stride = 4096 * 4; + D2D1_RESOURCE_TEXTURE_PROPERTIES props; + // Older shader models do not support 1D textures. So just use a width x 1 + // texture. + props.dimensions = 2; + UINT32 dims[] = {width, 1}; + props.extents = dims; + 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(), + mStopCollection->GetExtendMode()}; + props.extendModes = extendMode; + + HRESULT hr = mEffectContext->CreateResourceTexture( + nullptr, &props, &textureData.front(), &stride, 4096 * 4, + getter_AddRefs(tex)); + + if (FAILED(hr)) { + gfxWarning() << "Failed to create resource texture: " << hexa(hr); + } + + return tex.forget(); +} + +} // namespace gfx +} // namespace mozilla |