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/. */
7 #include "RadialGradientEffectD2D1.h"
11 #include "ShadersD2D1.h"
12 #include "HelpersD2D.h"
18 TEXTW(#X) // This macro creates a single string from multiple lines of text.
20 static const PCWSTR kXmlDescription
=
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.'/>
30 <Input name
='Geometry'/>
32 <Property name
='StopCollection' type
='iunknown'>
33 <Property name
='DisplayName' type
='string' value
='Gradient stop collection'/>
35 <Property name
='Center1' type
='vector2'>
36 <Property name
='DisplayName' type
='string' value
='Inner circle center'/>
38 <Property name
='Center2' type
='vector2'>
39 <Property name
='DisplayName' type
='string' value
='Outer circle center'/>
41 <Property name
='Radius1' type
='float'>
42 <Property name
='DisplayName' type
='string' value
='Inner circle radius'/>
44 <Property name
='Radius2' type
='float'>
45 <Property name
='DisplayName' type
='string' value
='Outer circle radius'/>
47 <Property name
='Transform' type
='matrix3x2'>
48 <Property name
='DisplayName' type
='string' value
='Transform applied to the pattern'/>
54 // {FB947CDA-718E-40CC-AE7B-D255830D7D14}
55 static const GUID GUID_SampleRadialGradientPS
= {
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}};
67 RadialGradientEffectD2D1::RadialGradientEffectD2D1()
69 mCenter1(D2D1::Vector2F(0, 0)),
70 mCenter2(D2D1::Vector2F(0, 0)),
73 mTransform(D2D1::IdentityMatrix())
78 RadialGradientEffectD2D1::Initialize(ID2D1EffectContext
* pContextInternal
,
79 ID2D1TransformGraph
* pTransformGraph
) {
82 hr
= pContextInternal
->LoadPixelShader(GUID_SampleRadialGradientPS
,
83 SampleRadialGradientPS
,
84 sizeof(SampleRadialGradientPS
));
90 hr
= pContextInternal
->LoadPixelShader(GUID_SampleRadialGradientA0PS
,
91 SampleRadialGradientA0PS
,
92 sizeof(SampleRadialGradientA0PS
));
98 hr
= pTransformGraph
->SetSingleTransformNode(this);
104 mEffectContext
= pContextInternal
;
110 RadialGradientEffectD2D1::PrepareForRender(D2D1_CHANGE_TYPE changeType
) {
111 if (changeType
== D2D1_CHANGE_TYPE_NONE
) {
115 // We'll need to inverse transform our pixel, precompute inverse here.
116 Matrix mat
= ToMatrix(mTransform
);
122 if (!mStopCollection
) {
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
;
134 hr
= mDrawInfo
->SetPixelShader(GUID_SampleRadialGradientA0PS
);
136 hr
= mDrawInfo
->SetPixelShader(GUID_SampleRadialGradientPS
);
143 RefPtr
<ID2D1ResourceTexture
> tex
= CreateGradientTexture();
144 hr
= mDrawInfo
->SetResourceTexture(1, tex
);
150 struct PSConstantBuffer
{
157 float repeat_correct
;
163 PSConstantBuffer buffer
= {
166 {mCenter1
.x
, mCenter1
.y
},
170 mStopCollection
->GetExtendMode() != D2D1_EXTEND_MODE_CLAMP
? 1.0f
: 0.0f
,
171 mStopCollection
->GetExtendMode() == D2D1_EXTEND_MODE_MIRROR
? 1.0f
: 0.0f
,
173 {mat
._11
, mat
._21
, mat
._31
, 0.0f
, mat
._12
, mat
._22
, mat
._32
, 0.0f
}};
175 hr
= mDrawInfo
->SetPixelShaderConstantBuffer((BYTE
*)&buffer
, sizeof(buffer
));
185 RadialGradientEffectD2D1::SetGraph(ID2D1TransformGraph
* pGraph
) {
186 return pGraph
->SetSingleTransformNode(this);
189 IFACEMETHODIMP_(ULONG
)
190 RadialGradientEffectD2D1::AddRef() { return ++mRefCount
; }
192 IFACEMETHODIMP_(ULONG
)
193 RadialGradientEffectD2D1::Release() {
202 RadialGradientEffectD2D1::QueryInterface(const IID
& aIID
, void** aPtr
) {
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);
218 return E_NOINTERFACE
;
221 static_cast<IUnknown
*>(*aPtr
)->AddRef();
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) {
234 *pOutputRect
= *pInputRects
;
235 *pOutputOpaqueSubRect
= *pInputOpaqueSubRects
;
240 RadialGradientEffectD2D1::MapOutputRectToInputRects(
241 const D2D1_RECT_L
* pOutputRect
, D2D1_RECT_L
* pInputRects
,
242 UINT32 inputRectCount
) const {
243 if (inputRectCount
!= 1) {
247 *pInputRects
= *pOutputRect
;
252 RadialGradientEffectD2D1::MapInvalidRect(
253 UINT32 inputIndex
, D2D1_RECT_L invalidInputRect
,
254 D2D1_RECT_L
* pInvalidOutputRect
) const {
255 MOZ_ASSERT(inputIndex
== 0);
257 *pInvalidOutputRect
= invalidInputRect
;
262 RadialGradientEffectD2D1::SetDrawInfo(ID2D1DrawInfo
* pDrawInfo
) {
263 mDrawInfo
= pDrawInfo
;
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
);
289 gfxWarning() << "Failed to register radial gradient effect.";
294 void RadialGradientEffectD2D1::Unregister(ID2D1Factory1
* aFactory
) {
295 aFactory
->UnregisterEffect(CLSID_RadialGradientEffect
);
298 HRESULT __stdcall
RadialGradientEffectD2D1::CreateEffect(
299 IUnknown
** aEffectImpl
) {
300 *aEffectImpl
= static_cast<ID2D1EffectImpl
*>(new RadialGradientEffectD2D1());
301 (*aEffectImpl
)->AddRef();
307 RadialGradientEffectD2D1::SetStopCollection(IUnknown
* aStopCollection
) {
308 if (SUCCEEDED(aStopCollection
->QueryInterface(
309 (ID2D1GradientStopCollection
**)getter_AddRefs(mStopCollection
)))) {
316 already_AddRefed
<ID2D1ResourceTexture
>
317 RadialGradientEffectD2D1::CreateGradientTexture() {
318 std::vector
<D2D1_GRADIENT_STOP
> rawStops
;
319 rawStops
.resize(mStopCollection
->GetGradientStopCount());
320 mStopCollection
->GetGradientStops(&rawStops
.front(), rawStops
.size());
322 std::vector
<unsigned char> textureData
;
323 textureData
.resize(4096 * 4);
324 unsigned char* texData
= &textureData
.front();
326 float prevColorPos
= 0;
327 float nextColorPos
= 1.0f
;
328 D2D1_COLOR_F prevColor
= rawStops
[0].color
;
329 D2D1_COLOR_F nextColor
= prevColor
;
331 if (rawStops
.size() >= 2) {
332 nextColor
= rawStops
[1].color
;
333 nextColorPos
= rawStops
[1].position
;
336 uint32_t stopPosition
= 2;
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
343 float pos
= float(i
) / 4095;
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
;
358 if (nextColorPos
!= prevColorPos
) {
359 interp
= (pos
- prevColorPos
) / (nextColorPos
- prevColorPos
);
364 DeviceColor
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
);
369 // Note D2D expects RGBA here!!
370 texData
[i
* 4] = (unsigned char)(255.0f
* newColor
.r
);
371 texData
[i
* 4 + 1] = (unsigned char)(255.0f
* newColor
.g
);
372 texData
[i
* 4 + 2] = (unsigned char)(255.0f
* newColor
.b
);
373 texData
[i
* 4 + 3] = (unsigned char)(255.0f
* newColor
.a
);
376 RefPtr
<ID2D1ResourceTexture
> tex
;
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
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
;
393 HRESULT hr
= mEffectContext
->CreateResourceTexture(
394 nullptr, &props
, &textureData
.front(), &stride
, 4096 * 4,
395 getter_AddRefs(tex
));
398 gfxWarning() << "Failed to create resource texture: " << hexa(hr
);
405 } // namespace mozilla