summaryrefslogtreecommitdiffstats
path: root/gfx/2d/ConicGradientEffectD2D1.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--gfx/2d/ConicGradientEffectD2D1.cpp375
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