2025-11-28 00:35:46 +09:00

1387 lines
41 KiB
C++

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved
//
// The following is a relatively simple implementation of realizations, built
// on top of Direct2D's mesh and opacity mask primitives (meshes for aliased and
// multi-sampled anti-aliased rendering and opacity masks for per-primitive
// anti-aliased rendering).
//
// One IGeometryRealization object can hold up to 4 "sub-realizations",
// corresponding to the matrix {Aliased, PPAA} x {Filled, Stroked}. The user
// can specify which sub-realizations to generate by passing in the appropriate
// REALIZATION_CREATION_OPTIONS.
//
// The implementation of the PPAA realizations is somewhat primitive. It will
// attempt to reuse existing bitmaps when possible, but it will not, for
// instance, attempt to use a single bitmap to store multiple realizations
// ("atlasing"). An atlased implementation would be somewhat more performant,
// as the number of state switches that Direct2D would have to make when
// interleaving realizations of different geometries would be greatly reduced.
//
// Another limitation in the PPAA implementation below is that it can use very
// large amounts of video-memory even for very simple primitives. Consider, for
// instance, a thin diagonal line stretching from the top-left corner of the
// render-target to the bottom-right. To store this as a single opacity mask, a
// bitmap the size of the entire render target must be created, even though the
// area of the stroke is very small. This can also have a severe impact on
// performance, as the video card has to waste numerous cycles rendering fully-
// transparent pixels.
//
// A more sophisticated implementation of PPAA realizations would divide the
// geometry up into a grid of bitmaps. Grid cells that contained no
// content or were fully covered by the geometry could be optimized away. The
// partially covered grid cells could be atlased for a further boost in
// performance.
//
#ifndef _NO_PRECOMPILED_HEADER_
#include "stdafx.h"
#endif
//+-----------------------------------------------------------------------------
//
// Class:
// GeometryRealization
//
//------------------------------------------------------------------------------
class GeometryRealization : public IGeometryRealization
{
public:
STDMETHOD(Fill)(
ID2D1RenderTarget *pRT,
ID2D1Brush *pBrush,
REALIZATION_RENDER_MODE mode
);
STDMETHOD(Draw)(
ID2D1RenderTarget *pRT,
ID2D1Brush *pBrush,
REALIZATION_RENDER_MODE mode
);
STDMETHOD(Update)(
ID2D1Geometry *pGeometry,
REALIZATION_CREATION_OPTIONS options,
CONST D2D1_MATRIX_3X2_F *pWorldTransform,
float strokeWidth,
ID2D1StrokeStyle *pIStrokeStyle
);
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
STDMETHOD(QueryInterface)(
REFIID iid,
void ** ppvObject
);
static HRESULT Create(
ID2D1RenderTarget *pRT,
UINT maxRealizationDimension,
ID2D1Geometry *pGeometry,
REALIZATION_CREATION_OPTIONS options,
CONST D2D1_MATRIX_3X2_F *pWorldTransform,
float strokeWidth,
ID2D1StrokeStyle *pIStrokeStyle,
IGeometryRealization **ppRealization
);
// These methods are declared protected for white-box unit-testing.
protected:
// Non-interface methods
GeometryRealization();
~GeometryRealization();
HRESULT Initialize(
ID2D1RenderTarget *pRT,
UINT maxRealizationDimension,
ID2D1Geometry *pGeometry,
REALIZATION_CREATION_OPTIONS options,
CONST D2D1_MATRIX_3X2_F *pWorldTransform,
float strokeWidth,
ID2D1StrokeStyle *pIStrokeStyle
);
static HRESULT GenerateOpacityMask(
bool fill,
ID2D1RenderTarget *pBaseRT,
UINT maxRealizationDimension,
ID2D1BitmapRenderTarget **ppBitmapRT,
ID2D1Geometry *pIGeometry,
const D2D1_MATRIX_3X2_F *pWorldTransform,
float strokeWidth,
ID2D1StrokeStyle *pStrokeStyle,
D2D1_RECT_F *pMaskDestBounds,
D2D1_RECT_F *pMaskSourceBounds
);
HRESULT RenderToTarget(
bool fill,
ID2D1RenderTarget *pRT,
ID2D1Brush *pBrush,
REALIZATION_RENDER_MODE mode
);
ID2D1Mesh *m_pFillMesh;
ID2D1Mesh *m_pStrokeMesh;
ID2D1BitmapRenderTarget *m_pFillRT;
ID2D1BitmapRenderTarget *m_pStrokeRT;
ID2D1Geometry *m_pGeometry;
ID2D1StrokeStyle *m_pStrokeStyle;
float m_strokeWidth;
D2D1_RECT_F m_fillMaskDestBounds;
D2D1_RECT_F m_fillMaskSourceBounds;
D2D1_RECT_F m_strokeMaskDestBounds;
D2D1_RECT_F m_strokeMaskSourceBounds;
ID2D1RenderTarget *m_pRT;
bool m_realizationTransformIsIdentity;
D2D1::Matrix3x2F m_realizationTransform;
D2D1::Matrix3x2F m_realizationTransformInv;
BOOL m_swRT;
UINT m_maxRealizationDimension;
ULONG volatile m_cRef;
};
//+-----------------------------------------------------------------------------
//
// Class:
// GeometryRealizationFactory
//
//------------------------------------------------------------------------------
class GeometryRealizationFactory : public IGeometryRealizationFactory
{
public:
STDMETHOD(CreateGeometryRealization)(
IGeometryRealization **ppRealization
);
STDMETHOD(CreateGeometryRealization)(
ID2D1Geometry *pGeometry,
REALIZATION_CREATION_OPTIONS options,
CONST D2D1_MATRIX_3X2_F *pWorldTransform,
float strokeWidth,
ID2D1StrokeStyle *pIStrokeStyle,
IGeometryRealization **ppRealization
);
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
STDMETHOD(QueryInterface)(
REFIID iid,
void ** ppvObject
);
// Non-interface methods
static HRESULT Create(
ID2D1RenderTarget *pRT,
UINT maxRealizationDimension,
IGeometryRealizationFactory **ppFactory
);
// These methods are declared protected for white-box unit-testing.
protected:
GeometryRealizationFactory();
~GeometryRealizationFactory();
HRESULT Initialize(
ID2D1RenderTarget *pRT,
UINT maxRealizationDimension
);
ID2D1RenderTarget *m_pRT;
ULONG volatile m_cRef;
UINT m_maxRealizationDimension;
};
// The maximum granularity of bitmap sizes we allow for AA realizations.
static const UINT sc_bitmapChunkSize = 64;
//+-----------------------------------------------------------------------------
//
// Function:
// CreateGeometryRealizationFactory
//
//------------------------------------------------------------------------------
HRESULT CreateGeometryRealizationFactory(
ID2D1RenderTarget *pRT,
UINT maxRealizationDimension,
IGeometryRealizationFactory **ppFactory
)
{
return GeometryRealizationFactory::Create(
pRT,
maxRealizationDimension,
ppFactory
);
}
//+-----------------------------------------------------------------------------
//
// Function:
// CreateGeometryRealizationFactory
//
//------------------------------------------------------------------------------
HRESULT CreateGeometryRealizationFactory(
ID2D1RenderTarget *pRT,
IGeometryRealizationFactory **ppFactory
)
{
return CreateGeometryRealizationFactory(
pRT,
0xffffffff, // maxRealizationDimension
ppFactory
);
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealizationFactory::Create
//
//------------------------------------------------------------------------------
/* static */
HRESULT GeometryRealizationFactory::Create(
ID2D1RenderTarget *pRT,
UINT maxRealizationDimension,
IGeometryRealizationFactory **ppFactory
)
{
HRESULT hr = S_OK;
GeometryRealizationFactory *pFactory = NULL;
pFactory = new (std::nothrow) GeometryRealizationFactory();
hr = pFactory ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = pFactory->Initialize(pRT, maxRealizationDimension);
if (SUCCEEDED(hr))
{
*ppFactory = pFactory;
(*ppFactory)->AddRef();
}
pFactory->Release();
}
return hr;
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealizationFactory::GeometryRealizationFactory
//
//------------------------------------------------------------------------------
GeometryRealizationFactory::GeometryRealizationFactory() :
m_cRef(1),
m_pRT(NULL)
{
}
//+-----------------------------------------------------------------------------
//
// Method:
// ~GeometryRealizationFactory::GeometryRealizationFactory
//
//------------------------------------------------------------------------------
GeometryRealizationFactory::~GeometryRealizationFactory()
{
SafeRelease(&m_pRT);
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealizationFactory::Initialize
//
//------------------------------------------------------------------------------
HRESULT GeometryRealizationFactory::Initialize(
ID2D1RenderTarget *pRT,
UINT maxRealizationDimension
)
{
HRESULT hr = S_OK;
if (maxRealizationDimension == 0)
{
//
// 0-sized bitmaps aren't very useful for realizations, and
// DXGI surface render targets don't support them, anyway.
//
hr = E_INVALIDARG;
}
if (SUCCEEDED(hr))
{
m_pRT = pRT;
m_pRT->AddRef();
m_maxRealizationDimension = min(
pRT->GetMaximumBitmapSize(),
maxRealizationDimension
);
}
return hr;
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealizationFactory::CreateGeometryRealization
//
//------------------------------------------------------------------------------
STDMETHODIMP GeometryRealizationFactory::CreateGeometryRealization(
ID2D1Geometry *pGeometry,
REALIZATION_CREATION_OPTIONS options,
CONST D2D1_MATRIX_3X2_F *pWorldTransform,
float strokeWidth,
ID2D1StrokeStyle *pIStrokeStyle,
IGeometryRealization **ppRealization
)
{
return GeometryRealization::Create(
m_pRT,
m_maxRealizationDimension,
pGeometry,
options,
pWorldTransform,
strokeWidth,
pIStrokeStyle,
ppRealization
);
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealizationFactory::CreateGeometryRealization
//
//------------------------------------------------------------------------------
STDMETHODIMP GeometryRealizationFactory::CreateGeometryRealization(
IGeometryRealization **ppRealization
)
{
return GeometryRealization::Create(
m_pRT,
m_maxRealizationDimension,
NULL, // pGeometry
REALIZATION_CREATION_OPTIONS_ALIASED, // ignored.
NULL,
0.0f, // strokeWidth
NULL, // pIStrokeStyle
ppRealization
);
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealization::Create
//
//------------------------------------------------------------------------------
/* static */
HRESULT GeometryRealization::Create(
ID2D1RenderTarget *pRT,
UINT maxRealizationDimension,
ID2D1Geometry *pGeometry,
REALIZATION_CREATION_OPTIONS options,
CONST D2D1_MATRIX_3X2_F *pWorldTransform,
float strokeWidth,
ID2D1StrokeStyle *pIStrokeStyle,
IGeometryRealization **ppRealization
)
{
HRESULT hr = S_OK;
GeometryRealization *pRealization = NULL;
pRealization = new (std::nothrow) GeometryRealization();
hr = pRealization ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = pRealization->Initialize(
pRT,
maxRealizationDimension,
pGeometry,
options,
pWorldTransform,
strokeWidth,
pIStrokeStyle
);
if (SUCCEEDED(hr))
{
*ppRealization = pRealization;
(*ppRealization)->AddRef();
}
pRealization->Release();
}
return hr;
}
//+----------------------------------------------------------------------------
//
// Method:
// GeometryRealization::GeometryRealization
//
//-----------------------------------------------------------------------------
GeometryRealization::GeometryRealization() :
m_cRef(1),
m_pFillMesh(NULL),
m_pStrokeMesh(NULL),
m_pFillRT(NULL),
m_pStrokeRT(NULL),
m_pGeometry(NULL),
m_pStrokeStyle(NULL),
m_pRT(NULL)
{
}
//+----------------------------------------------------------------------------
//
// Method:
// GeometryRealization::GeometryRealization
//
//-----------------------------------------------------------------------------
GeometryRealization::~GeometryRealization()
{
SafeRelease(&m_pFillMesh);
SafeRelease(&m_pStrokeMesh);
SafeRelease(&m_pFillRT);
SafeRelease(&m_pStrokeRT);
SafeRelease(&m_pGeometry);
SafeRelease(&m_pStrokeStyle);
SafeRelease(&m_pRT);
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealization::Fill
//
//------------------------------------------------------------------------------
STDMETHODIMP GeometryRealization::Fill(
ID2D1RenderTarget *pRT,
ID2D1Brush *pBrush,
REALIZATION_RENDER_MODE mode
)
{
return RenderToTarget(
true, // => fill
pRT,
pBrush,
mode
);
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealization::Draw
//
//------------------------------------------------------------------------------
STDMETHODIMP GeometryRealization::Draw(
ID2D1RenderTarget *pRT,
ID2D1Brush *pBrush,
REALIZATION_RENDER_MODE mode
)
{
return RenderToTarget(
false, // => stroke
pRT,
pBrush,
mode
);
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealization::Update
//
// Description:
// Discard the current realization's contents and replace with new
// contents.
//
// Note: This method attempts to reuse the existing bitmaps (but will
// replace the bitmaps if they aren't large enough). Since the cost of
// destroying a texture can be surprisingly astronomical, using this
// method can be substantially more performant than recreating a new
// realization every time.
//
// Note: Here, pWorldTransform is the transform that the realization will
// be optimized for. If, at the time of rendering, the render target's
// transform is the same as the pWorldTransform passed in here then the
// realization will look identical to the unrealized version. Otherwise,
// quality will be degraded.
//
//------------------------------------------------------------------------------
STDMETHODIMP GeometryRealization::Update(
ID2D1Geometry *pGeometry,
REALIZATION_CREATION_OPTIONS options,
CONST D2D1_MATRIX_3X2_F *pWorldTransform,
float strokeWidth,
ID2D1StrokeStyle *pIStrokeStyle
)
{
HRESULT hr = S_OK;
if (pWorldTransform)
{
m_realizationTransform = *D2D1::Matrix3x2F::ReinterpretBaseType(pWorldTransform);
m_realizationTransformIsIdentity = (m_realizationTransform.IsIdentity());
}
else
{
m_realizationTransform = D2D1::Matrix3x2F::Identity();
m_realizationTransformIsIdentity = true;
}
//
// We're about to create our realizations with the world transform applied
// to them. When we go to actually render the realization, though, we'll
// want to "undo" this realization and instead apply the render target's
// current transform.
//
// Note: we keep track to see if the passed in realization transform is the
// identity. This is a small optimization that saves us from having to
// multiply matrices when we go to draw the realization.
//
m_realizationTransformInv = m_realizationTransform;
m_realizationTransformInv.Invert();
if ((options & REALIZATION_CREATION_OPTIONS_UNREALIZED) || m_swRT)
{
SafeReplace(&m_pGeometry, pGeometry);
SafeReplace(&m_pStrokeStyle, pIStrokeStyle);
m_strokeWidth = strokeWidth;
}
if (options & REALIZATION_CREATION_OPTIONS_ANTI_ALIASED)
{
//
// Antialiased realizations are implemented using opacity masks.
//
if (options & REALIZATION_CREATION_OPTIONS_FILLED)
{
hr = GenerateOpacityMask(
true, // => filled
m_pRT,
m_maxRealizationDimension,
IN OUT &m_pFillRT,
pGeometry,
pWorldTransform,
strokeWidth,
pIStrokeStyle,
&m_fillMaskDestBounds,
&m_fillMaskSourceBounds
);
}
if (SUCCEEDED(hr) && options & REALIZATION_CREATION_OPTIONS_STROKED)
{
hr = GenerateOpacityMask(
false, // => stroked
m_pRT,
m_maxRealizationDimension,
IN OUT &m_pStrokeRT,
pGeometry,
pWorldTransform,
strokeWidth,
pIStrokeStyle,
&m_strokeMaskDestBounds,
&m_strokeMaskSourceBounds
);
}
}
if (SUCCEEDED(hr) && options & REALIZATION_CREATION_OPTIONS_ALIASED)
{
//
// Aliased realizations are implemented using meshes.
//
if (options & REALIZATION_CREATION_OPTIONS_FILLED)
{
ID2D1Mesh *pMesh = NULL;
hr = m_pRT->CreateMesh(&pMesh);
if (SUCCEEDED(hr))
{
ID2D1TessellationSink *pSink = NULL;
hr = pMesh->Open(&pSink);
if (SUCCEEDED(hr))
{
hr = pGeometry->Tessellate(
pWorldTransform,
pSink
);
if (SUCCEEDED(hr))
{
hr = pSink->Close();
if (SUCCEEDED(hr))
{
SafeReplace(&m_pFillMesh, pMesh);
}
}
pSink->Release();
}
pMesh->Release();
}
}
if (SUCCEEDED(hr) && options & REALIZATION_CREATION_OPTIONS_STROKED)
{
//
// In order generate the mesh corresponding to the stroke of a
// geometry, we first "widen" the geometry and then tessellate the
// result.
//
ID2D1Factory *pFactory = NULL;
m_pRT->GetFactory(&pFactory);
ID2D1PathGeometry *pPathGeometry = NULL;
hr = pFactory->CreatePathGeometry(&pPathGeometry);
if (SUCCEEDED(hr))
{
ID2D1GeometrySink *pGeometrySink = NULL;
hr = pPathGeometry->Open(&pGeometrySink);
if (SUCCEEDED(hr))
{
hr = pGeometry->Widen(
strokeWidth,
pIStrokeStyle,
pWorldTransform,
pGeometrySink
);
if (SUCCEEDED(hr))
{
hr = pGeometrySink->Close();
if (SUCCEEDED(hr))
{
ID2D1Mesh *pMesh = NULL;
hr = m_pRT->CreateMesh(&pMesh);
if (SUCCEEDED(hr))
{
ID2D1TessellationSink *pSink = NULL;
hr = pMesh->Open(&pSink);
if (SUCCEEDED(hr))
{
hr = pPathGeometry->Tessellate(
NULL, // world transform (already handled in Widen)
pSink
);
if (SUCCEEDED(hr))
{
hr = pSink->Close();
if (SUCCEEDED(hr))
{
SafeReplace(&m_pStrokeMesh, pMesh);
}
}
pSink->Release();
}
pMesh->Release();
}
}
}
pGeometrySink->Release();
}
pPathGeometry->Release();
}
pFactory->Release();
}
}
return hr;
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealization::RenderToTarget
//
//------------------------------------------------------------------------------
HRESULT GeometryRealization::RenderToTarget(
bool fill,
ID2D1RenderTarget *pRT,
ID2D1Brush *pBrush,
REALIZATION_RENDER_MODE mode
)
{
HRESULT hr = S_OK;
D2D1_ANTIALIAS_MODE originalAAMode = pRT->GetAntialiasMode();
D2D1_MATRIX_3X2_F originalTransform;
if (((mode == REALIZATION_RENDER_MODE_DEFAULT) && m_swRT) ||
(mode == REALIZATION_RENDER_MODE_FORCE_UNREALIZED)
)
{
if (!m_pGeometry)
{
// We're being asked to render the geometry unrealized, but we
// weren't created with REALIZATION_CREATION_OPTIONS_UNREALIZED.
hr = E_FAIL;
}
if (SUCCEEDED(hr))
{
if (fill)
{
pRT->FillGeometry(
m_pGeometry,
pBrush
);
}
else
{
pRT->DrawGeometry(
m_pGeometry,
pBrush,
m_strokeWidth,
m_pStrokeStyle
);
}
}
}
else
{
if (originalAAMode != D2D1_ANTIALIAS_MODE_ALIASED)
{
pRT->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
}
if (!m_realizationTransformIsIdentity)
{
pRT->GetTransform(&originalTransform);
pRT->SetTransform(m_realizationTransformInv * originalTransform);
}
if (originalAAMode == D2D1_ANTIALIAS_MODE_PER_PRIMITIVE)
{
if (fill)
{
if (!m_pFillRT)
{
hr = E_FAIL;
}
if (SUCCEEDED(hr))
{
ID2D1Bitmap *pBitmap = NULL;
m_pFillRT->GetBitmap(&pBitmap);
//
// Note: The antialias mode must be set to aliased prior to calling
// FillOpacityMask.
//
pRT->FillOpacityMask(
pBitmap,
pBrush,
D2D1_OPACITY_MASK_CONTENT_GRAPHICS,
&m_fillMaskDestBounds,
&m_fillMaskSourceBounds
);
pBitmap->Release();
}
}
else
{
if (!m_pStrokeRT)
{
hr = E_FAIL;
}
if (SUCCEEDED(hr))
{
ID2D1Bitmap *pBitmap = NULL;
m_pStrokeRT->GetBitmap(&pBitmap);
//
// Note: The antialias mode must be set to aliased prior to calling
// FillOpacityMask.
//
pRT->FillOpacityMask(
pBitmap,
pBrush,
D2D1_OPACITY_MASK_CONTENT_GRAPHICS,
&m_strokeMaskDestBounds,
&m_strokeMaskSourceBounds
);
pBitmap->Release();
}
}
}
else
{
if (fill)
{
if (!m_pFillMesh)
{
hr = E_FAIL;
}
if (SUCCEEDED(hr))
{
pRT->FillMesh(
m_pFillMesh,
pBrush
);
}
}
else
{
if (!m_pStrokeMesh)
{
hr = E_FAIL;
}
if (SUCCEEDED(hr))
{
pRT->FillMesh(
m_pStrokeMesh,
pBrush
);
}
}
}
if (SUCCEEDED(hr))
{
pRT->SetAntialiasMode(originalAAMode);
if (!m_realizationTransformIsIdentity)
{
pRT->SetTransform(originalTransform);
}
}
}
return hr;
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealization::Initialize
//
//------------------------------------------------------------------------------
HRESULT GeometryRealization::Initialize(
ID2D1RenderTarget *pRT,
UINT maxRealizationDimension,
ID2D1Geometry *pGeometry,
REALIZATION_CREATION_OPTIONS options,
CONST D2D1_MATRIX_3X2_F *pWorldTransform,
float strokeWidth,
ID2D1StrokeStyle *pIStrokeStyle
)
{
HRESULT hr = S_OK;
m_pRT = pRT;
m_pRT->AddRef();
m_swRT = pRT->IsSupported(
D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_SOFTWARE
));
m_maxRealizationDimension = maxRealizationDimension;
if (pGeometry)
{
hr = Update(
pGeometry,
options,
pWorldTransform,
strokeWidth,
pIStrokeStyle
);
}
return hr;
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealization::GenerateOpacityMask
//
// Notes:
// This method is the trickiest part of doing realizations. Conceptually,
// we're creating a grayscale bitmap that represents the geometry. We'll
// reuse an existing bitmap if we can, but if not, we'll create the
// smallest possible bitmap that contains the geometry. In either, case,
// though, we'll keep track of the portion of the bitmap we actually used
// (the source bounds), so when we go to draw the realization, we don't
// end up drawing a bunch of superfluous transparent pixels.
//
// We also have to keep track of the "dest" bounds, as more than likely
// the bitmap has to be translated by some amount before being drawn.
//
//------------------------------------------------------------------------------
/* static */
HRESULT GeometryRealization::GenerateOpacityMask(
bool fill,
ID2D1RenderTarget *pBaseRT,
UINT maxRealizationDimension,
ID2D1BitmapRenderTarget **ppBitmapRT,
ID2D1Geometry *pIGeometry,
const D2D1_MATRIX_3X2_F *pWorldTransform,
float strokeWidth,
ID2D1StrokeStyle *pStrokeStyle,
D2D1_RECT_F *pMaskDestBounds,
D2D1_RECT_F *pMaskSourceBounds
)
{
HRESULT hr = S_OK;
D2D1_RECT_F bounds;
D2D1_RECT_F inflatedPixelBounds;
D2D1_SIZE_U inflatedIntegerPixelSize;
D2D1_SIZE_U currentRTSize;
D2D1_MATRIX_3X2_F translateMatrix;
float dpiX, dpiY;
float scaleX = 1.0f;
float scaleY = 1.0f;
ID2D1BitmapRenderTarget *pCompatRT = NULL;
SafeReplace(&pCompatRT, *ppBitmapRT);
ID2D1SolidColorBrush *pBrush = NULL;
hr = pBaseRT->CreateSolidColorBrush(
D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f),
&pBrush
);
if (SUCCEEDED(hr))
{
pBaseRT->GetDpi(&dpiX, &dpiY);
if (fill)
{
hr = pIGeometry->GetBounds(
pWorldTransform,
&bounds
);
}
else
{
hr = pIGeometry->GetWidenedBounds(
strokeWidth,
pStrokeStyle,
pWorldTransform,
&bounds
);
}
if (SUCCEEDED(hr))
{
//
// A rect where left > right is defined to be empty.
//
// The slightly baroque expression used below is an idiom that also
// correctly handles NaNs (i.e., if any of the coordinates of the bounds is
// a NaN, we want to treat the bounds as empty)
//
if (
!(bounds.left <= bounds.right) ||
!(bounds.top <= bounds.bottom)
)
{
// Bounds are empty or ill-defined.
// Make up a fake bounds
inflatedPixelBounds.top = 0.0f;
inflatedPixelBounds.left = 0.0f;
inflatedPixelBounds.bottom = 1.0f;
inflatedPixelBounds.right = 1.0f;
}
else
{
//
// We inflate the pixel bounds by 1 in each direction to ensure we have
// a border of completely transparent pixels around the geometry. This
// ensures that when the realization is stretched the alpha ramp still
// smoothly falls off to 0 rather than being clipped by the rect.
//
inflatedPixelBounds.top = floorf(bounds.top*dpiY/96)-1.0f;
inflatedPixelBounds.left = floorf(bounds.left*dpiX/96)-1.0f;
inflatedPixelBounds.bottom = ceilf(bounds.bottom*dpiY/96)+1.0f;
inflatedPixelBounds.right = ceilf(bounds.right*dpiX/96)+1.0f;
}
//
// Compute the width and height of the underlying bitmap we will need.
// Note: We round up the width and height to be a multiple of
// sc_bitmapChunkSize. We do this primarily to ensure that we aren't
// constantly reallocating bitmaps in the case where a realization is being
// zoomed in on slowly and updated frequently.
//
inflatedIntegerPixelSize = D2D1::SizeU(
static_cast<UINT>(inflatedPixelBounds.right - inflatedPixelBounds.left),
static_cast<UINT>(inflatedPixelBounds.bottom - inflatedPixelBounds.top)
);
// Round up
inflatedIntegerPixelSize.width =
(inflatedIntegerPixelSize.width + sc_bitmapChunkSize - 1)/sc_bitmapChunkSize * sc_bitmapChunkSize;
// Round up
inflatedIntegerPixelSize.height =
(inflatedIntegerPixelSize.height + sc_bitmapChunkSize - 1)/sc_bitmapChunkSize * sc_bitmapChunkSize;
//
// Compute the bounds we will pass to FillOpacityMask (which are in Device
// Independent Pixels).
//
// Note: The DIP bounds do *not* use the rounded coordinates, since this
// would cause us to render superfluous, fully-transparent pixels, which
// would hurt fill rate.
//
D2D1_RECT_F inflatedDipBounds = D2D1::RectF(
inflatedPixelBounds.left * 96/dpiX,
inflatedPixelBounds.top * 96/dpiY,
inflatedPixelBounds.right * 96/dpiX,
inflatedPixelBounds.bottom * 96/dpiY
);
if (pCompatRT)
{
currentRTSize = pCompatRT->GetPixelSize();
}
else
{
// This will force the creation of a new target
currentRTSize = D2D1::SizeU(0,0);
}
//
// We need to ensure that our desired render target size isn't larger than
// the max allowable bitmap size. If it is, we need to scale the bitmap
// down by the appropriate amount.
//
if (inflatedIntegerPixelSize.width > maxRealizationDimension)
{
scaleX = maxRealizationDimension/static_cast<float>(inflatedIntegerPixelSize.width);
inflatedIntegerPixelSize.width = maxRealizationDimension;
}
if (inflatedIntegerPixelSize.height > maxRealizationDimension)
{
scaleY = maxRealizationDimension/static_cast<float>(inflatedIntegerPixelSize.height);
inflatedIntegerPixelSize.height = maxRealizationDimension;
}
//
// If the necessary pixel dimensions are less than half the existing
// bitmap's dimensions (in either direction), force the bitmap to be
// reallocated to save memory.
//
// Note: The fact that we use > rather than >= is important for a subtle
// reason: We'd like to have the property that repeated small changes in
// geometry size do not cause repeated reallocations of memory. >= does not
// ensure this property in the case where the geometry size is close to
// sc_bitmapChunkSize, but > does.
//
// Example:
//
// Assume sc_bitmapChunkSize is 64 and the initial geometry width is 63
// pixels. This will get rounded up to 64, and we will allocate a bitmap
// with width 64. Now, say, we zoom in slightly, so the new geometry width
// becomes 65 pixels. This will get rounded up to 128 pixels, and a new
// bitmap will be allocated. Now, say the geometry drops back down to 63
// pixels. This will get rounded up to 64. If we used >=, this would cause
// another reallocation. Since we use >, on the other hand, the 128 pixel
// bitmap will be reused.
//
if (currentRTSize.width > 2*inflatedIntegerPixelSize.width ||
currentRTSize.height > 2*inflatedIntegerPixelSize.height
)
{
SafeRelease(&pCompatRT);
currentRTSize.width = currentRTSize.height = 0;
}
if (inflatedIntegerPixelSize.width > currentRTSize.width ||
inflatedIntegerPixelSize.height > currentRTSize.height
)
{
SafeRelease(&pCompatRT);
}
if (!pCompatRT)
{
//
// Make sure our new rendertarget is strictly larger than before.
//
currentRTSize.width =
max(inflatedIntegerPixelSize.width, currentRTSize.width);
currentRTSize.height =
max(inflatedIntegerPixelSize.height, currentRTSize.height);
D2D1_PIXEL_FORMAT alphaOnlyFormat =
D2D1::PixelFormat(
DXGI_FORMAT_A8_UNORM,
D2D1_ALPHA_MODE_PREMULTIPLIED
);
hr = pBaseRT->CreateCompatibleRenderTarget(
NULL, // desiredSize
&currentRTSize,
&alphaOnlyFormat,
D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE,
&pCompatRT
);
}
if (SUCCEEDED(hr))
{
//
// Translate the geometry so it is flush against the left and top
// sides of the render target.
//
translateMatrix =
D2D1::Matrix3x2F::Translation(
-inflatedDipBounds.left,
-inflatedDipBounds.top
) *
D2D1::Matrix3x2F::Scale(
scaleX,
scaleY
);
if (pWorldTransform)
{
pCompatRT->SetTransform(
*pWorldTransform * translateMatrix
);
}
else
{
pCompatRT->SetTransform(
translateMatrix
);
}
//
// Render the geometry.
//
pCompatRT->BeginDraw();
pCompatRT->Clear(
D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f)
);
if (fill)
{
pCompatRT->FillGeometry(
pIGeometry,
pBrush
);
}
else
{
pCompatRT->DrawGeometry(
pIGeometry,
pBrush,
strokeWidth,
pStrokeStyle
);
}
hr = pCompatRT->EndDraw();
if (SUCCEEDED(hr))
{
//
// Report back the source and dest bounds (to be used as input parameters
// to FillOpacityMask.
//
*pMaskDestBounds = inflatedDipBounds;
*pMaskSourceBounds = D2D1::Rect<float>(
0.0f,
0.0f,
static_cast<float>(inflatedDipBounds.right - inflatedDipBounds.left)*scaleX,
static_cast<float>(inflatedDipBounds.bottom - inflatedDipBounds.top)*scaleY
);
if (*ppBitmapRT != pCompatRT)
{
SafeReplace(ppBitmapRT, pCompatRT);
}
}
}
}
pBrush->Release();
}
SafeRelease(&pCompatRT);
return hr;
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealizationFactory::AddRef
//
//------------------------------------------------------------------------------
STDMETHODIMP_(ULONG) GeometryRealizationFactory::AddRef()
{
return InterlockedIncrement(reinterpret_cast<LONG volatile *>(&m_cRef));
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealizationFactory::Release
//
//------------------------------------------------------------------------------
STDMETHODIMP_(ULONG) GeometryRealizationFactory::Release()
{
ULONG cRef = static_cast<ULONG>(
InterlockedDecrement(reinterpret_cast<LONG volatile *>(&m_cRef)));
if(0 == cRef)
{
delete this;
}
return cRef;
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealizationFactory::QueryInterface
//
//------------------------------------------------------------------------------
STDMETHODIMP GeometryRealizationFactory::QueryInterface(
REFIID iid,
void ** ppvObject
)
{
HRESULT hr = S_OK;
if (__uuidof(IUnknown) == iid)
{
*ppvObject = static_cast<IUnknown*>(this);
AddRef();
}
else if (__uuidof(IGeometryRealizationFactory) == iid)
{
*ppvObject = static_cast<IGeometryRealizationFactory*>(this);
AddRef();
}
else
{
*ppvObject = NULL;
hr = E_NOINTERFACE;
}
return hr;
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealization::AddRef
//
//------------------------------------------------------------------------------
STDMETHODIMP_(ULONG)
GeometryRealization::AddRef()
{
return InterlockedIncrement(reinterpret_cast<LONG volatile *>(&m_cRef));
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealization::Release
//
//------------------------------------------------------------------------------
STDMETHODIMP_(ULONG)
GeometryRealization::Release()
{
ULONG cRef = static_cast<ULONG>(
InterlockedDecrement(reinterpret_cast<LONG volatile *>(&m_cRef)));
if(0 == cRef)
{
delete this;
}
return cRef;
}
//+-----------------------------------------------------------------------------
//
// Method:
// GeometryRealization::QueryInterface
//
//------------------------------------------------------------------------------
STDMETHODIMP
GeometryRealization::QueryInterface(
REFIID iid,
void ** ppvObject
)
{
HRESULT hr = S_OK;
if (__uuidof(IUnknown) == iid)
{
*ppvObject = static_cast<IUnknown*>(this);
AddRef();
}
else if (__uuidof(IGeometryRealization) == iid)
{
*ppvObject = static_cast<IGeometryRealization*>(this);
AddRef();
}
else
{
*ppvObject = NULL;
hr = E_NOINTERFACE;
}
return hr;
}