1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "RadialGradientEffectD2D1.h"
8 
9 #include "Logging.h"
10 
11 #include "ShadersD2D1.h"
12 #include "HelpersD2D.h"
13 
14 #include <vector>
15 
16 #define TEXTW(x) L##x
17 #define XML(X) \
18   TEXTW(#X)  // This macro creates a single string from multiple lines of text.
19 
20 static const PCWSTR kXmlDescription =
21     XML(
22         <?xml version='1.0'?>
23         <Effect>
24             <!-- System Properties -->
25             <Property name='DisplayName' type='string' value='RadialGradientEffect'/>
26             <Property name='Author' type='string' value='Mozilla'/>
27             <Property name='Category' type='string' value='Pattern effects'/>
28             <Property name='Description' type='string' value='This effect is used to render radial gradients in a manner compliant with the 2D Canvas specification.'/>
29             <Inputs>
30                 <Input name='Geometry'/>
31             </Inputs>
32             <Property name='StopCollection' type='iunknown'>
33               <Property name='DisplayName' type='string' value='Gradient stop collection'/>
34             </Property>
35             <Property name='Center1' type='vector2'>
36               <Property name='DisplayName' type='string' value='Inner circle center'/>
37             </Property>
38             <Property name='Center2' type='vector2'>
39               <Property name='DisplayName' type='string' value='Outer circle center'/>
40             </Property>
41             <Property name='Radius1' type='float'>
42               <Property name='DisplayName' type='string' value='Inner circle radius'/>
43             </Property>
44             <Property name='Radius2' type='float'>
45               <Property name='DisplayName' type='string' value='Outer circle radius'/>
46             </Property>
47             <Property name='Transform' type='matrix3x2'>
48               <Property name='DisplayName' type='string' value='Transform applied to the pattern'/>
49             </Property>
50 
51         </Effect>
52         );
53 
54 // {FB947CDA-718E-40CC-AE7B-D255830D7D14}
55 static const GUID GUID_SampleRadialGradientPS = {
56     0xfb947cda,
57     0x718e,
58     0x40cc,
59     {0xae, 0x7b, 0xd2, 0x55, 0x83, 0xd, 0x7d, 0x14}};
60 // {2C468128-6546-453C-8E25-F2DF0DE10A0F}
61 static const GUID GUID_SampleRadialGradientA0PS = {
62     0x2c468128, 0x6546, 0x453c, {0x8e, 0x25, 0xf2, 0xdf, 0xd, 0xe1, 0xa, 0xf}};
63 
64 namespace mozilla {
65 namespace gfx {
66 
RadialGradientEffectD2D1()67 RadialGradientEffectD2D1::RadialGradientEffectD2D1()
68     : mRefCount(0),
69       mCenter1(D2D1::Vector2F(0, 0)),
70       mCenter2(D2D1::Vector2F(0, 0)),
71       mRadius1(0),
72       mRadius2(0),
73       mTransform(D2D1::IdentityMatrix())
74 
75 {}
76 
77 IFACEMETHODIMP
Initialize(ID2D1EffectContext * pContextInternal,ID2D1TransformGraph * pTransformGraph)78 RadialGradientEffectD2D1::Initialize(ID2D1EffectContext* pContextInternal,
79                                      ID2D1TransformGraph* pTransformGraph) {
80   HRESULT hr;
81 
82   hr = pContextInternal->LoadPixelShader(GUID_SampleRadialGradientPS,
83                                          SampleRadialGradientPS,
84                                          sizeof(SampleRadialGradientPS));
85 
86   if (FAILED(hr)) {
87     return hr;
88   }
89 
90   hr = pContextInternal->LoadPixelShader(GUID_SampleRadialGradientA0PS,
91                                          SampleRadialGradientA0PS,
92                                          sizeof(SampleRadialGradientA0PS));
93 
94   if (FAILED(hr)) {
95     return hr;
96   }
97 
98   hr = pTransformGraph->SetSingleTransformNode(this);
99 
100   if (FAILED(hr)) {
101     return hr;
102   }
103 
104   mEffectContext = pContextInternal;
105 
106   return S_OK;
107 }
108 
109 IFACEMETHODIMP
PrepareForRender(D2D1_CHANGE_TYPE changeType)110 RadialGradientEffectD2D1::PrepareForRender(D2D1_CHANGE_TYPE changeType) {
111   if (changeType == D2D1_CHANGE_TYPE_NONE) {
112     return S_OK;
113   }
114 
115   // We'll need to inverse transform our pixel, precompute inverse here.
116   Matrix mat = ToMatrix(mTransform);
117   if (!mat.Invert()) {
118     // Singular
119     return S_OK;
120   }
121 
122   if (!mStopCollection) {
123     return S_OK;
124   }
125 
126   D2D1_POINT_2F dc =
127       D2D1::Point2F(mCenter2.x - mCenter1.x, mCenter2.y - mCenter1.y);
128   float dr = mRadius2 - mRadius1;
129   float A = dc.x * dc.x + dc.y * dc.y - dr * dr;
130 
131   HRESULT hr;
132 
133   if (A == 0) {
134     hr = mDrawInfo->SetPixelShader(GUID_SampleRadialGradientA0PS);
135   } else {
136     hr = mDrawInfo->SetPixelShader(GUID_SampleRadialGradientPS);
137   }
138 
139   if (FAILED(hr)) {
140     return hr;
141   }
142 
143   RefPtr<ID2D1ResourceTexture> tex = CreateGradientTexture();
144   hr = mDrawInfo->SetResourceTexture(1, tex);
145 
146   if (FAILED(hr)) {
147     return hr;
148   }
149 
150   struct PSConstantBuffer {
151     float diff[3];
152     float padding;
153     float center1[2];
154     float A;
155     float radius1;
156     float sq_radius1;
157     float repeat_correct;
158     float allow_odd;
159     float padding2[1];
160     float transform[8];
161   };
162 
163   PSConstantBuffer buffer = {
164       {dc.x, dc.y, dr},
165       0.0f,
166       {mCenter1.x, mCenter1.y},
167       A,
168       mRadius1,
169       mRadius1 * mRadius1,
170       mStopCollection->GetExtendMode() != D2D1_EXTEND_MODE_CLAMP ? 1.0f : 0.0f,
171       mStopCollection->GetExtendMode() == D2D1_EXTEND_MODE_MIRROR ? 1.0f : 0.0f,
172       {0.0f},
173       {mat._11, mat._21, mat._31, 0.0f, mat._12, mat._22, mat._32, 0.0f}};
174 
175   hr = mDrawInfo->SetPixelShaderConstantBuffer((BYTE*)&buffer, sizeof(buffer));
176 
177   if (FAILED(hr)) {
178     return hr;
179   }
180 
181   return S_OK;
182 }
183 
184 IFACEMETHODIMP
SetGraph(ID2D1TransformGraph * pGraph)185 RadialGradientEffectD2D1::SetGraph(ID2D1TransformGraph* pGraph) {
186   return pGraph->SetSingleTransformNode(this);
187 }
188 
IFACEMETHODIMP_(ULONG)189 IFACEMETHODIMP_(ULONG)
190 RadialGradientEffectD2D1::AddRef() { return ++mRefCount; }
191 
IFACEMETHODIMP_(ULONG)192 IFACEMETHODIMP_(ULONG)
193 RadialGradientEffectD2D1::Release() {
194   if (!--mRefCount) {
195     delete this;
196     return 0;
197   }
198   return mRefCount;
199 }
200 
201 IFACEMETHODIMP
QueryInterface(const IID & aIID,void ** aPtr)202 RadialGradientEffectD2D1::QueryInterface(const IID& aIID, void** aPtr) {
203   if (!aPtr) {
204     return E_POINTER;
205   }
206 
207   if (aIID == IID_IUnknown) {
208     *aPtr = static_cast<IUnknown*>(static_cast<ID2D1EffectImpl*>(this));
209   } else if (aIID == IID_ID2D1EffectImpl) {
210     *aPtr = static_cast<ID2D1EffectImpl*>(this);
211   } else if (aIID == IID_ID2D1DrawTransform) {
212     *aPtr = static_cast<ID2D1DrawTransform*>(this);
213   } else if (aIID == IID_ID2D1Transform) {
214     *aPtr = static_cast<ID2D1Transform*>(this);
215   } else if (aIID == IID_ID2D1TransformNode) {
216     *aPtr = static_cast<ID2D1TransformNode*>(this);
217   } else {
218     return E_NOINTERFACE;
219   }
220 
221   static_cast<IUnknown*>(*aPtr)->AddRef();
222   return S_OK;
223 }
224 
225 IFACEMETHODIMP
MapInputRectsToOutputRect(const D2D1_RECT_L * pInputRects,const D2D1_RECT_L * pInputOpaqueSubRects,UINT32 inputRectCount,D2D1_RECT_L * pOutputRect,D2D1_RECT_L * pOutputOpaqueSubRect)226 RadialGradientEffectD2D1::MapInputRectsToOutputRect(
227     const D2D1_RECT_L* pInputRects, const D2D1_RECT_L* pInputOpaqueSubRects,
228     UINT32 inputRectCount, D2D1_RECT_L* pOutputRect,
229     D2D1_RECT_L* pOutputOpaqueSubRect) {
230   if (inputRectCount != 1) {
231     return E_INVALIDARG;
232   }
233 
234   *pOutputRect = *pInputRects;
235   *pOutputOpaqueSubRect = *pInputOpaqueSubRects;
236   return S_OK;
237 }
238 
239 IFACEMETHODIMP
MapOutputRectToInputRects(const D2D1_RECT_L * pOutputRect,D2D1_RECT_L * pInputRects,UINT32 inputRectCount) const240 RadialGradientEffectD2D1::MapOutputRectToInputRects(
241     const D2D1_RECT_L* pOutputRect, D2D1_RECT_L* pInputRects,
242     UINT32 inputRectCount) const {
243   if (inputRectCount != 1) {
244     return E_INVALIDARG;
245   }
246 
247   *pInputRects = *pOutputRect;
248   return S_OK;
249 }
250 
251 IFACEMETHODIMP
MapInvalidRect(UINT32 inputIndex,D2D1_RECT_L invalidInputRect,D2D1_RECT_L * pInvalidOutputRect) const252 RadialGradientEffectD2D1::MapInvalidRect(
253     UINT32 inputIndex, D2D1_RECT_L invalidInputRect,
254     D2D1_RECT_L* pInvalidOutputRect) const {
255   MOZ_ASSERT(inputIndex == 0);
256 
257   *pInvalidOutputRect = invalidInputRect;
258   return S_OK;
259 }
260 
261 IFACEMETHODIMP
SetDrawInfo(ID2D1DrawInfo * pDrawInfo)262 RadialGradientEffectD2D1::SetDrawInfo(ID2D1DrawInfo* pDrawInfo) {
263   mDrawInfo = pDrawInfo;
264   return S_OK;
265 }
266 
267 HRESULT
Register(ID2D1Factory1 * aFactory)268 RadialGradientEffectD2D1::Register(ID2D1Factory1* aFactory) {
269   D2D1_PROPERTY_BINDING bindings[] = {
270       D2D1_VALUE_TYPE_BINDING(L"StopCollection",
271                               &RadialGradientEffectD2D1::SetStopCollection,
272                               &RadialGradientEffectD2D1::GetStopCollection),
273       D2D1_VALUE_TYPE_BINDING(L"Center1", &RadialGradientEffectD2D1::SetCenter1,
274                               &RadialGradientEffectD2D1::GetCenter1),
275       D2D1_VALUE_TYPE_BINDING(L"Center2", &RadialGradientEffectD2D1::SetCenter2,
276                               &RadialGradientEffectD2D1::GetCenter2),
277       D2D1_VALUE_TYPE_BINDING(L"Radius1", &RadialGradientEffectD2D1::SetRadius1,
278                               &RadialGradientEffectD2D1::GetRadius1),
279       D2D1_VALUE_TYPE_BINDING(L"Radius2", &RadialGradientEffectD2D1::SetRadius2,
280                               &RadialGradientEffectD2D1::GetRadius2),
281       D2D1_VALUE_TYPE_BINDING(L"Transform",
282                               &RadialGradientEffectD2D1::SetTransform,
283                               &RadialGradientEffectD2D1::GetTransform)};
284   HRESULT hr = aFactory->RegisterEffectFromString(
285       CLSID_RadialGradientEffect, kXmlDescription, bindings,
286       ARRAYSIZE(bindings), CreateEffect);
287 
288   if (FAILED(hr)) {
289     gfxWarning() << "Failed to register radial gradient effect.";
290   }
291   return hr;
292 }
293 
Unregister(ID2D1Factory1 * aFactory)294 void RadialGradientEffectD2D1::Unregister(ID2D1Factory1* aFactory) {
295   aFactory->UnregisterEffect(CLSID_RadialGradientEffect);
296 }
297 
CreateEffect(IUnknown ** aEffectImpl)298 HRESULT __stdcall RadialGradientEffectD2D1::CreateEffect(
299     IUnknown** aEffectImpl) {
300   *aEffectImpl = static_cast<ID2D1EffectImpl*>(new RadialGradientEffectD2D1());
301   (*aEffectImpl)->AddRef();
302 
303   return S_OK;
304 }
305 
306 HRESULT
SetStopCollection(IUnknown * aStopCollection)307 RadialGradientEffectD2D1::SetStopCollection(IUnknown* aStopCollection) {
308   if (SUCCEEDED(aStopCollection->QueryInterface(
309           (ID2D1GradientStopCollection**)getter_AddRefs(mStopCollection)))) {
310     return S_OK;
311   }
312 
313   return E_INVALIDARG;
314 }
315 
316 already_AddRefed<ID2D1ResourceTexture>
CreateGradientTexture()317 RadialGradientEffectD2D1::CreateGradientTexture() {
318   std::vector<D2D1_GRADIENT_STOP> rawStops;
319   rawStops.resize(mStopCollection->GetGradientStopCount());
320   mStopCollection->GetGradientStops(&rawStops.front(), rawStops.size());
321 
322   std::vector<unsigned char> textureData;
323   textureData.resize(4096 * 4);
324   unsigned char* texData = &textureData.front();
325 
326   float prevColorPos = 0;
327   float nextColorPos = 1.0f;
328   D2D1_COLOR_F prevColor = rawStops[0].color;
329   D2D1_COLOR_F nextColor = prevColor;
330 
331   if (rawStops.size() >= 2) {
332     nextColor = rawStops[1].color;
333     nextColorPos = rawStops[1].position;
334   }
335 
336   uint32_t stopPosition = 2;
337 
338   // Not the most optimized way but this will do for now.
339   for (int i = 0; i < 4096; i++) {
340     // The 4095 seems a little counter intuitive, but we want the gradient
341     // color at offset 0 at the first pixel, and at offset 1.0f at the last
342     // pixel.
343     float pos = float(i) / 4095;
344 
345     while (pos > nextColorPos) {
346       prevColor = nextColor;
347       prevColorPos = nextColorPos;
348       if (rawStops.size() > stopPosition) {
349         nextColor = rawStops[stopPosition].color;
350         nextColorPos = rawStops[stopPosition++].position;
351       } else {
352         nextColorPos = 1.0f;
353       }
354     }
355 
356     float interp;
357 
358     if (nextColorPos != prevColorPos) {
359       interp = (pos - prevColorPos) / (nextColorPos - prevColorPos);
360     } else {
361       interp = 0;
362     }
363 
364     Color newColor(prevColor.r + (nextColor.r - prevColor.r) * interp,
365                    prevColor.g + (nextColor.g - prevColor.g) * interp,
366                    prevColor.b + (nextColor.b - prevColor.b) * interp,
367                    prevColor.a + (nextColor.a - prevColor.a) * interp);
368 
369     // Note D2D expects RGBA here!!
370     texData[i * 4] = (char)(255.0f * newColor.r);
371     texData[i * 4 + 1] = (char)(255.0f * newColor.g);
372     texData[i * 4 + 2] = (char)(255.0f * newColor.b);
373     texData[i * 4 + 3] = (char)(255.0f * newColor.a);
374   }
375 
376   RefPtr<ID2D1ResourceTexture> tex;
377 
378   UINT32 width = 4096;
379   UINT32 stride = 4096 * 4;
380   D2D1_RESOURCE_TEXTURE_PROPERTIES props;
381   // Older shader models do not support 1D textures. So just use a width x 1
382   // texture.
383   props.dimensions = 2;
384   UINT32 dims[] = {width, 1};
385   props.extents = dims;
386   props.channelDepth = D2D1_CHANNEL_DEPTH_4;
387   props.bufferPrecision = D2D1_BUFFER_PRECISION_8BPC_UNORM;
388   props.filter = D2D1_FILTER_MIN_MAG_MIP_LINEAR;
389   D2D1_EXTEND_MODE extendMode[] = {mStopCollection->GetExtendMode(),
390                                    mStopCollection->GetExtendMode()};
391   props.extendModes = extendMode;
392 
393   HRESULT hr = mEffectContext->CreateResourceTexture(
394       nullptr, &props, &textureData.front(), &stride, 4096 * 4,
395       getter_AddRefs(tex));
396 
397   if (FAILED(hr)) {
398     gfxWarning() << "Failed to create resource texture: " << hexa(hr);
399   }
400 
401   return tex.forget();
402 }
403 
404 }  // namespace gfx
405 }  // namespace mozilla
406