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

2290 lines
75 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
#include "Interactive3dTextSample.h"
static const UINT sc_maxMsaaSampleCount = 4;
static const float sc_flatteningTolerance = .1f;
static const float sc_cursorWidth = 5.0f;
static const D3DXVECTOR3 sc_eyeLocation(0.0f, -100.0f, 400.0f);
static const D3DXVECTOR3 sc_eyeAt(0.0f, -50.0f, 0.0f);
static const D3DXVECTOR3 sc_eyeUp(0.0f, -1.0f, 0.0f);
//
// Note: Gabriola is a Win7 only font family. If the system cannot find this
// font, DWrite will fall back to another font family.
//
static const PCWSTR sc_fontFace = L"Gabriola";
static const float sc_fontSize = 96.0f;
/******************************************************************
* *
* WinMain *
* *
* Application entrypoint *
* *
******************************************************************/
int WINAPI WinMain(
HINSTANCE /*hInstance*/,
HINSTANCE /*hPrevInstance*/,
LPSTR /*lpCmdLine*/,
int /*nCmdShow*/
)
{
// Ignoring the return value because we want to continue running even in the
// unlikely event that HeapSetInformation fails.
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
if (SUCCEEDED(CoInitialize(NULL)))
{
{
Interactive3dTextSampleApp app;
if (SUCCEEDED(app.Initialize()))
{
app.RunMessageLoop();
}
}
CoUninitialize();
}
return 0;
}
/******************************************************************
* *
* NoRefComObject *
* *
* Template that creates dummy IUnknown method implementations. *
* This is useful in situations where one is absolutely sure *
* none of the IUnknown methods will be called (neither D2D nor *
* DWrite will call these methods on a ID2D1SimplifiedGeometrySink*
* or IDWriteTextRenderer). Using this technique also allows one *
* to stack allocate such objects. *
* *
******************************************************************/
template<class T>
class NoRefComObject : public T
{
public:
template<typename U>
NoRefComObject(U u)
: T(u)
{
}
template<typename U, typename V>
NoRefComObject(U u, V v)
: T(u, v)
{
}
template<typename U, typename V, typename W>
NoRefComObject(U u, V v, W w)
: T(u, v, w)
{
}
STDMETHOD_(ULONG, AddRef)(THIS)
{
return 0;
}
STDMETHOD_(ULONG, Release)(THIS)
{
return 0;
}
STDMETHOD(QueryInterface)(THIS_ REFIID /*riid*/, void** /*ppvObj*/)
{
return E_UNEXPECTED;
}
};
/******************************************************************
* *
* IsEmptyBounds *
* *
* Returns true if the bounds are empty *
* *
******************************************************************/
bool IsEmptyBounds(D2D1_RECT_F &bounds)
{
// Direct2D uses the convention that right>left is empty.
return bounds.left > bounds.right;
}
/******************************************************************
* *
* CreateEmptyGeometry *
* *
* Create a path geometry containing no figures. *
* *
******************************************************************/
HRESULT CreateEmptyGeometry(ID2D1Factory *pFactory, ID2D1Geometry **ppGeometry)
{
HRESULT hr;
ID2D1PathGeometry *pGeometry = NULL;
hr = pFactory->CreatePathGeometry(&pGeometry);
if (SUCCEEDED(hr))
{
ID2D1GeometrySink *pSink = NULL;
hr = pGeometry->Open(&pSink);
if (SUCCEEDED(hr))
{
hr = pSink->Close();
if (SUCCEEDED(hr))
{
*ppGeometry = pGeometry;
(*ppGeometry)->AddRef();
}
pSink->Release();
}
pGeometry->Release();
}
return hr;
}
/******************************************************************
* *
* PointSnapper *
* *
* Utility class for snapping points and vertices to grid points. *
* *
* This is useful when preparing and consuming tessellation data *
* (see notes further on in the code). *
* *
* Grid-points are spaced at 1/16 intervals and aligned on *
* integer boundaries. *
* *
******************************************************************/
class PointSnapper
{
public:
static HRESULT SnapGeometry(ID2D1Geometry *pGeometry, ID2D1Geometry **ppGeometry)
{
HRESULT hr;
ID2D1Factory *pFactory = NULL;
pGeometry->GetFactory(&pFactory);
ID2D1PathGeometry *pPathGeometry = NULL;
hr = pFactory->CreatePathGeometry(&pPathGeometry);
if (SUCCEEDED(hr))
{
ID2D1GeometrySink *pSink = NULL;
hr = pPathGeometry->Open(&pSink);
if (SUCCEEDED(hr))
{
NoRefComObject<PointSnappingSink> snapper(pSink);
hr = pGeometry->Simplify(
D2D1_GEOMETRY_SIMPLIFICATION_OPTION_LINES,
NULL, // world transform
&snapper
);
if (SUCCEEDED(hr))
{
hr = snapper.Close();
if (SUCCEEDED(hr))
{
*ppGeometry = pPathGeometry;
(*ppGeometry)->AddRef();
}
}
pSink->Release();
}
pPathGeometry->Release();
}
pFactory->Release();
return hr;
}
static float SnapCoordinate(float x)
{
return floorf(16.0f*x+.5f)/16.0f;
}
static D2D1_POINT_2F SnapPoint(D2D1_POINT_2F pt)
{
return D2D1::Point2F(
SnapCoordinate(pt.x),
SnapCoordinate(pt.y)
);
}
static D3DXVECTOR2 SnapPoint(D3DXVECTOR2 pt)
{
return D3DXVECTOR2(
SnapCoordinate(pt.x),
SnapCoordinate(pt.y)
);
}
private:
/******************************************************************
* *
* PointSnappingSink *
* *
* Internal sink used to implement SnapGeometry. *
* *
* Note: This class makes certain assumptions about it's usage *
* (e.g. no Beziers), which is why it's a private class. *
* *
******************************************************************/
class PointSnappingSink : public ID2D1SimplifiedGeometrySink
{
public:
PointSnappingSink(ID2D1SimplifiedGeometrySink *pSink)
: m_pSinkNoRef(pSink)
{
}
STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT * /*beziers*/, UINT /*beziersCount*/)
{
//
// Users should be sure to flatten their geometries prior to passing
// through a PointSnappingSink. It makes little sense snapping
// the control points of a Bezier, as the vertices from the
// flattened Bezier will almost certainly not be snapped.
//
}
STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *points, UINT pointsCount)
{
for (UINT i = 0; i < pointsCount; ++i)
{
D2D1_POINT_2F pt = SnapPoint(points[i]);
m_pSinkNoRef->AddLines(&pt, 1);
}
}
STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN figureBegin)
{
D2D1_POINT_2F pt = SnapPoint(startPoint);
m_pSinkNoRef->BeginFigure(pt, figureBegin);
}
STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END figureEnd)
{
m_pSinkNoRef->EndFigure(figureEnd);
}
STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE fillMode)
{
m_pSinkNoRef->SetFillMode(fillMode);
}
STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT vertexFlags)
{
m_pSinkNoRef->SetSegmentFlags(vertexFlags);
}
STDMETHOD(Close)()
{
return m_pSinkNoRef->Close();
}
private:
ID2D1SimplifiedGeometrySink *m_pSinkNoRef;
};
};
/******************************************************************
* *
* D2DFlatten *
* *
* Helper function for performing "flattening" -- transforming *
* a geometry with Beziers into one containing only line segments.*
* *
******************************************************************/
HRESULT D2DFlatten(
ID2D1Geometry *pGeometry,
float flatteningTolerance,
ID2D1Geometry **ppGeometry
)
{
HRESULT hr;
ID2D1Factory *pFactory = NULL;
pGeometry->GetFactory(&pFactory);
ID2D1PathGeometry *pPathGeometry = NULL;
hr = pFactory->CreatePathGeometry(&pPathGeometry);
if (SUCCEEDED(hr))
{
ID2D1GeometrySink *pSink = NULL;
hr = pPathGeometry->Open(&pSink);
if (SUCCEEDED(hr))
{
hr = pGeometry->Simplify(
D2D1_GEOMETRY_SIMPLIFICATION_OPTION_LINES,
NULL, // world transform
flatteningTolerance,
pSink
);
if (SUCCEEDED(hr))
{
hr = pSink->Close();
if (SUCCEEDED(hr))
{
*ppGeometry = pPathGeometry;
(*ppGeometry)->AddRef();
}
}
pSink->Release();
}
pPathGeometry->Release();
}
pFactory->Release();
return hr;
}
/******************************************************************
* *
* D2DOutline *
* *
* Helper function for performing "outlining" -- constructing an *
* equivalent geometry with no self-intersections. *
* *
* Note: This uses the default flattening tolerance and hence *
* should not be used with very small geometries. *
* *
******************************************************************/
HRESULT D2DOutline(
ID2D1Geometry *pGeometry,
ID2D1Geometry **ppGeometry
)
{
HRESULT hr;
ID2D1Factory *pFactory = NULL;
pGeometry->GetFactory(&pFactory);
ID2D1PathGeometry *pPathGeometry = NULL;
hr = pFactory->CreatePathGeometry(&pPathGeometry);
if (SUCCEEDED(hr))
{
ID2D1GeometrySink *pSink = NULL;
hr = pPathGeometry->Open(&pSink);
if (SUCCEEDED(hr))
{
hr = pGeometry->Outline(NULL, pSink);
if (SUCCEEDED(hr))
{
hr = pSink->Close();
if (SUCCEEDED(hr))
{
*ppGeometry = pPathGeometry;
(*ppGeometry)->AddRef();
}
}
pSink->Release();
}
pPathGeometry->Release();
}
pFactory->Release();
return hr;
}
/******************************************************************
* *
* D2DCombine *
* *
* Helper function for performing Boolean operations. *
* *
* Note: This uses the default flattening tolerance and hence *
* should not be used with very small geometries. *
* *
******************************************************************/
HRESULT D2DCombine(
D2D1_COMBINE_MODE combineMode,
ID2D1Geometry *pGeometry1,
ID2D1Geometry *pGeometry2,
ID2D1Geometry **ppGeometry
)
{
HRESULT hr;
ID2D1Factory *pFactory = NULL;
pGeometry1->GetFactory(&pFactory);
ID2D1PathGeometry *pPathGeometry = NULL;
hr = pFactory->CreatePathGeometry(&pPathGeometry);
if (SUCCEEDED(hr))
{
ID2D1GeometrySink *pSink = NULL;
hr = pPathGeometry->Open(&pSink);
if (SUCCEEDED(hr))
{
hr = pGeometry1->CombineWithGeometry(
pGeometry2,
combineMode,
NULL, // world transform
pSink
);
if (SUCCEEDED(hr))
{
hr = pSink->Close();
if (SUCCEEDED(hr))
{
*ppGeometry = pPathGeometry;
(*ppGeometry)->AddRef();
}
}
pSink->Release();
}
pPathGeometry->Release();
}
pFactory->Release();
return hr;
}
/******************************************************************
* *
* Extruder *
* *
* Class for extruding an ID2D1Geometry into 3-D. *
* *
******************************************************************/
class Extruder
{
public:
static HRESULT ExtrudeGeometry(
ID2D1Geometry *pGeometry,
float height,
CArray<SimpleVertex> &vertices
)
{
HRESULT hr;
//
// The basic idea here is to generate the side faces by walking the
// geometry and constructing quads, and use ID2D1Geometry::Tessellate
// to generate the front and back faces.
//
// There are two things to be careful of here:
//
// 1) We must not produce overlapping triangles, as this can cause
// "depth-buffer fighting".
// 2) The vertices on the front and back faces must perfectly align with
// the vertices on the side faces.
//
// Thankfully, D2D correctly handles self-intersections, which makes
// solving issue 1 easy.
//
// Issue 2 is more complicated, since the D2D tessellation algorithm
// will jitter vertices slightly. To get around this, we snap vertices
// to grid-points. To ensure that the tessellation jittering does cause
// a vertex to be snapped to a neighboring grid-point, we actually snap
// the vertices twice: once prior to tessellating and once after
// tessellating. As long as our grid points are spaced further than
// twice max-jitter distance, we can be sure the vertices will snap
// to the right spot.
//
ID2D1Geometry *pFlattenedGeometry = NULL;
//
// Flatten our geometry first so we don't have to worry about stitching
// together seams of Beziers.
//
hr = D2DFlatten(pGeometry, sc_flatteningTolerance, &pFlattenedGeometry);
if (SUCCEEDED(hr))
{
ID2D1Geometry *pOutlinedGeometry = NULL;
//
// D2DOutline will remove any self-intersections. This is important to
// ensure that the tessellator doesn't introduce new vertices (which
// can cause T-junctions).
//
hr = D2DOutline(pFlattenedGeometry, &pOutlinedGeometry);
if (SUCCEEDED(hr))
{
ID2D1Geometry *pSnappedGeometry = NULL;
hr = PointSnapper::SnapGeometry(pOutlinedGeometry, &pSnappedGeometry);
if (SUCCEEDED(hr))
{
NoRefComObject<ExtrudingSink> helper(height, &vertices);
hr = pSnappedGeometry->Tessellate(NULL, &helper);
if (SUCCEEDED(hr))
{
// Simplify is a convenient API for extracting the data out of a geometry.
hr = pOutlinedGeometry->Simplify(
D2D1_GEOMETRY_SIMPLIFICATION_OPTION_LINES,
NULL, // world transform
&helper
);
if (SUCCEEDED(hr))
{
//
// This Close() call is a little ambiguous, since it refers both to the
// ID2D1TessellationSink and to the ID2D1SimplifiedGeometrySink.
// Thankfully, it really doesn't matter with our ExtrudingSink.
//
hr = helper.Close();
}
}
pSnappedGeometry->Release();
}
pOutlinedGeometry->Release();
}
pFlattenedGeometry->Release();
}
return hr;
}
private:
/******************************************************************
* *
* ExtrudingSink *
* *
* Internal sink used to implement Extruder. *
* *
* Note: This class makes certain assumptions about it's usage *
* (e.g. no Beziers), which is why it's a private class. *
* *
* Note 2: Both ID2D1SimplifiedGeometrySink and *
* ID2D1TessellationSink define a Close() method, which is *
* bending the rules a bit. This is another reason why we are *
* significantly limiting its usage. *
* *
* *
******************************************************************/
class ExtrudingSink : public ID2D1SimplifiedGeometrySink, public ID2D1TessellationSink
{
public:
ExtrudingSink(float height, CArray<SimpleVertex> *pVertices)
: m_height(height), m_vertices(*pVertices), m_hr(S_OK)
{
}
STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT * /*beziers*/, UINT /*beziersCount*/)
{
//
// ExtrudingSink only handles line segments. Users should flatten
// their geometry prior to passing through an ExtrudingSink.
//
}
STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *points, UINT pointsCount)
{
if (SUCCEEDED(m_hr))
{
for (UINT i = 0; SUCCEEDED(m_hr) && i < pointsCount; ++i)
{
Vertex2D v;
v.pt = D3DXVECTOR2(points[i].x, points[i].y);
//
// Take care to ignore degenerate segments, as we will be
// unable to compute proper normals for them.
//
// Note: This doesn't handle near-degenerate segments, which
// should probably also be removed. The one complication here
// is that the segments should be removed from both the outline
// and the front/back tessellations.
//
if ((m_figureVertices.GetCount() == 0) ||
(v.pt.x != m_figureVertices.GetBack().pt.x) ||
(v.pt.y != m_figureVertices.GetBack().pt.y)
)
{
m_hr = m_figureVertices.Add(v);
}
}
}
}
STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN /*figureBegin*/)
{
if (SUCCEEDED(m_hr))
{
m_figureVertices.RemoveAll();
Vertex2D v = {
D3DXVECTOR2(startPoint.x, startPoint.y),
D3DXVECTOR2(0,0), // dummy
D3DXVECTOR2(0,0), // dummy
D3DXVECTOR2(0,0) // dummy
};
m_hr = m_figureVertices.Add(v);
}
}
STDMETHOD(Close)()
{
return m_hr;
}
STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END /*figureEnd*/)
{
if (SUCCEEDED(m_hr))
{
D3DXVECTOR2 front = m_figureVertices.GetFront().pt;
D3DXVECTOR2 back = m_figureVertices.GetBack().pt;
if (front.x == back.x && front.y == back.y)
{
m_figureVertices.RemoveLast();
}
// If we only have one vertex, then there is nothing to draw!
if (m_figureVertices.GetCount() > 1)
{
//
// We construct the triangles corresponding to the sides of
// the extruded object in 3 steps:
//
//
// Step 1:
//
// Snap vertices and calculate normals.
//
//
// Note: it is important that we compute normals *before*
// snapping the vertices, otherwise, the normals will become
// discretized, which will manifest itself as faceting.
//
for (UINT i = 0; i < m_figureVertices.GetCount(); ++i)
{
m_figureVertices[i].norm = GetNormal(i);
m_figureVertices[i].pt = PointSnapper::SnapPoint(m_figureVertices[i].pt);
}
//
// Step 2:
//
// Interpolate normals as appropriate.
//
for (UINT i = 0; i < m_figureVertices.GetCount(); ++i)
{
UINT h = (i+m_figureVertices.GetCount()-1)%m_figureVertices.GetCount();
D3DXVECTOR2 n1 = m_figureVertices[h].norm;
D3DXVECTOR2 n2 = m_figureVertices[i].norm;
//
// Take a dot-product to determine if the angle between
// the normals is small. If it is, then average them so we
// get a smooth transition from one face to the next.
//
if ( (n1.x * n2.x + n1.y * n2.y) > .5f )
{
D3DXVECTOR2 sum = m_figureVertices[i].norm + m_figureVertices[h].norm;
m_figureVertices[i].interpNorm1 = m_figureVertices[i].interpNorm2 =
Normalize(sum);
}
else
{
m_figureVertices[i].interpNorm1 = m_figureVertices[h].norm;
m_figureVertices[i].interpNorm2 = m_figureVertices[i].norm;
}
}
//
// Step 3:
//
// Output the triangles.
//
// interpNorm1 == end normal of previous segment
// interpNorm2 == begin normal of next segment
for (UINT i = 0; i < m_figureVertices.GetCount(); ++i)
{
UINT j = (i+1) % m_figureVertices.GetCount();
D3DXVECTOR2 pt = m_figureVertices[i].pt;
D3DXVECTOR2 nextPt = m_figureVertices[j].pt;
D3DXVECTOR2 ptNorm3 = m_figureVertices[i].interpNorm2;
D3DXVECTOR2 nextPtNorm2 = m_figureVertices[j].interpNorm1;
//
// These 6 vertices define two adjacent triangles that
// together form a quad.
//
SimpleVertex newVertices[6] =
{
{ D3DXVECTOR3( pt.x, pt.y, m_height/2 ), D3DXVECTOR3( ptNorm3.x, ptNorm3.y, 0.0f)},
{ D3DXVECTOR3( pt.x, pt.y, -m_height/2 ), D3DXVECTOR3( ptNorm3.x, ptNorm3.y, 0.0f)},
{ D3DXVECTOR3( nextPt.x, nextPt.y, -m_height/2 ), D3DXVECTOR3( nextPtNorm2.x, nextPtNorm2.y, 0.0f)},
{ D3DXVECTOR3( nextPt.x, nextPt.y, -m_height/2 ), D3DXVECTOR3( nextPtNorm2.x, nextPtNorm2.y, 0.0f)},
{ D3DXVECTOR3( nextPt.x, nextPt.y, m_height/2 ), D3DXVECTOR3( nextPtNorm2.x, nextPtNorm2.y, 0.0f)},
{ D3DXVECTOR3( pt.x, pt.y, m_height/2 ), D3DXVECTOR3( ptNorm3.x, ptNorm3.y, 0.0f)},
};
for (UINT n = 0; SUCCEEDED(m_hr) && n < ARRAYSIZE(newVertices); ++n)
{
m_hr = m_vertices.Add(newVertices[n]);
}
}
}
}
}
STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE /*fillMode*/)
{
// Do nothing
}
STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT /*vertexFlags*/)
{
// Do nothing
}
STDMETHOD_(void, AddTriangles)(const D2D1_TRIANGLE *triangles, UINT trianglesCount)
{
if (SUCCEEDED(m_hr))
{
//
// These triangles reprent the front and back faces of the extrusion.
//
for (UINT i = 0; i < trianglesCount; ++i)
{
D2D1_TRIANGLE tri = triangles[i];
D2D1_POINT_2F d1 = {tri.point2.x - tri.point1.x, tri.point2.y - tri.point1.y};
D2D1_POINT_2F d2 = {tri.point3.x - tri.point2.x, tri.point3.y - tri.point2.y};
tri.point1 = PointSnapper::SnapPoint(tri.point1);
tri.point2 = PointSnapper::SnapPoint(tri.point2);
tri.point3 = PointSnapper::SnapPoint(tri.point3);
//
// Currently, Tessellate does not guarantee the orientation
// of the triangles it produces, so we must check here.
//
float cross = d1.x * d2.y - d1.y*d2.x;
if (cross < 0)
{
D2D1_POINT_2F tmp = tri.point1;
tri.point1 = tri.point2;
tri.point2 = tmp;
}
SimpleVertex newVertices[] =
{
{ D3DXVECTOR3( tri.point1.x, tri.point1.y, m_height/2 ), D3DXVECTOR3( 0.0f, 0.0f, 1.0f )},
{ D3DXVECTOR3( tri.point2.x, tri.point2.y, m_height/2 ), D3DXVECTOR3( 0.0f, 0.0f, 1.0f )},
{ D3DXVECTOR3( tri.point3.x, tri.point3.y, m_height/2 ), D3DXVECTOR3( 0.0f, 0.0f, 1.0f )},
//
// Note: these points are listed in a different order since the orientation of the back
// face should be the opposite of the front face.
//
{ D3DXVECTOR3( tri.point2.x, tri.point2.y, -m_height/2 ), D3DXVECTOR3( 0.0f, 0.0f, -1.0f )},
{ D3DXVECTOR3( tri.point1.x, tri.point1.y, -m_height/2 ), D3DXVECTOR3( 0.0f, 0.0f, -1.0f )},
{ D3DXVECTOR3( tri.point3.x, tri.point3.y, -m_height/2 ), D3DXVECTOR3( 0.0f, 0.0f, -1.0f )},
};
for (UINT i = 0; SUCCEEDED(m_hr) && i < ARRAYSIZE(newVertices); ++i)
{
m_hr = m_vertices.Add(newVertices[i]);
}
}
}
}
private:
D3DXVECTOR2 GetNormal(UINT i)
{
UINT j = (i+1) % m_figureVertices.GetCount();
D3DXVECTOR2 pti = m_figureVertices[i].pt;
D3DXVECTOR2 ptj = m_figureVertices[j].pt;
D3DXVECTOR2 vecij = ptj - pti;
return Normalize(D3DXVECTOR2(vecij.y, vecij.x));
}
D3DXVECTOR2 Normalize(D3DXVECTOR2 pt)
{
return pt / sqrtf(pt.x*pt.x+pt.y*pt.y);
}
struct Vertex2D
{
D3DXVECTOR2 pt;
D3DXVECTOR2 norm;
D3DXVECTOR2 interpNorm1;
D3DXVECTOR2 interpNorm2;
};
HRESULT m_hr;
float m_height;
D2D1_POINT_2F m_lastPoint;
D2D1_POINT_2F m_startPoint;
CArray<SimpleVertex> &m_vertices;
CArray<Vertex2D> m_figureVertices;
};
};
/******************************************************************
* *
* OutlineRenderer *
* *
* A "TextRenderer" that extracts the glyph runs of a text *
* layout object and turns them into a geometry. *
* *
******************************************************************/
class OutlineRenderer : public IDWriteTextRenderer
{
public:
OutlineRenderer(ID2D1Factory *pFactory)
: m_pFactory(pFactory)
{
m_pFactory->AddRef();
m_pGeometry = NULL;
}
~OutlineRenderer()
{
SafeRelease(&m_pFactory);
SafeRelease(&m_pGeometry);
}
STDMETHOD(DrawGlyphRun)(
void* /*clientDrawingContext*/,
FLOAT baselineOriginX,
FLOAT baselineOriginY,
DWRITE_MEASURING_MODE /*measuringMode*/,
DWRITE_GLYPH_RUN const* glyphRun,
DWRITE_GLYPH_RUN_DESCRIPTION const* /*glyphRunDescription*/,
IUnknown* /*clientDrawingEffect*/
)
{
HRESULT hr;
ID2D1PathGeometry *pPathGeometry = NULL;
hr = m_pFactory->CreatePathGeometry(&pPathGeometry);
if (SUCCEEDED(hr))
{
ID2D1GeometrySink *pSink = NULL;
hr = pPathGeometry->Open(&pSink);
if (SUCCEEDED(hr))
{
hr = glyphRun->fontFace->GetGlyphRunOutline(
glyphRun->fontEmSize,
glyphRun->glyphIndices,
glyphRun->glyphAdvances,
glyphRun->glyphOffsets,
glyphRun->glyphCount,
glyphRun->isSideways,
(glyphRun->bidiLevel % 2) == 1,
pSink
);
if (SUCCEEDED(hr))
{
hr = pSink->Close();
if (SUCCEEDED(hr))
{
ID2D1TransformedGeometry *pTransformedGeometry = NULL;
hr = m_pFactory->CreateTransformedGeometry(
pPathGeometry,
D2D1::Matrix3x2F::Translation(baselineOriginX, baselineOriginY),
&pTransformedGeometry
);
if (SUCCEEDED(hr))
{
hr = AddGeometry(pTransformedGeometry);
pTransformedGeometry->Release();
}
}
}
pSink->Release();
}
pPathGeometry->Release();
}
return hr;
}
STDMETHOD(DrawUnderline)(
void* /*clientDrawingContext*/,
FLOAT /*baselineOriginX*/,
FLOAT /*baselineOriginY*/,
DWRITE_UNDERLINE const* /*underline*/,
IUnknown* /*clientDrawingEffect*/
)
{
// Implement this to add support for underlines. See the
// CustomTextRender in the DWrite PDC hands-on lab for
// an example on how to do this.
return E_NOTIMPL;
}
STDMETHOD(DrawStrikethrough)(
void* /*clientDrawingContext*/,
FLOAT /*baselineOriginX*/,
FLOAT /*baselineOriginY*/,
DWRITE_STRIKETHROUGH const* /*strikethrough*/,
IUnknown* /*clientDrawingEffect*/
)
{
// Implement this to add support for strikethroughs. See the
// CustomTextRender in the DWrite PDC hands-on lab for
// an example on how to do this.
return E_NOTIMPL;
}
STDMETHOD(DrawInlineObject)(
void* /*clientDrawingContext*/,
FLOAT /*originX*/,
FLOAT /*originY*/,
IDWriteInlineObject* /*inlineObject*/,
BOOL /*isSideways*/,
BOOL /*isRightToLeft*/,
IUnknown* /*clientDrawingEffect*/
)
{
// Implement this to add support for inline objects.
// See the CustomTextRender in the DWrite PDC hands-on lab for an
// example on how to do this.
return E_NOTIMPL;
}
STDMETHOD(IsPixelSnappingDisabled)(
void* /*clientDrawingContext*/,
BOOL* isDisabled
)
{
*isDisabled = TRUE;
return S_OK;
}
STDMETHOD(GetCurrentTransform)(
void* /*clientDrawingContext*/,
DWRITE_MATRIX* transform
)
{
DWRITE_MATRIX matrix = {1, 0, 0, 1, 0, 0};
*transform = matrix;
return S_OK;
}
STDMETHOD(GetPixelsPerDip)(
void* /*clientDrawingContext*/,
FLOAT* pixelsPerDip
)
{
*pixelsPerDip = 1.0f;
return S_OK;
}
HRESULT GetGeometry(
ID2D1Geometry **ppGeometry
)
{
HRESULT hr = S_OK;
if (m_pGeometry)
{
*ppGeometry = m_pGeometry;
(*ppGeometry)->AddRef();
}
else
{
ID2D1PathGeometry *pGeometry = NULL;
hr = m_pFactory->CreatePathGeometry(&pGeometry);
if (SUCCEEDED(hr))
{
ID2D1GeometrySink *pSink = NULL;
hr = pGeometry->Open(&pSink);
if (SUCCEEDED(hr))
{
hr = pSink->Close();
if (SUCCEEDED(hr))
{
*ppGeometry = pGeometry;
(*ppGeometry)->AddRef();
}
pSink->Release();
}
pGeometry->Release();
}
}
return hr;
}
protected:
HRESULT AddGeometry(
ID2D1Geometry *pGeometry
)
{
HRESULT hr = S_OK;
if (m_pGeometry)
{
ID2D1Geometry *pCombinedGeometry = NULL;
hr = D2DCombine(
D2D1_COMBINE_MODE_UNION,
m_pGeometry,
pGeometry,
&pCombinedGeometry
);
if (SUCCEEDED(hr))
{
SafeReplace(&m_pGeometry, pCombinedGeometry);
pCombinedGeometry->Release();
}
}
else
{
SafeReplace(&m_pGeometry, pGeometry);
}
return hr;
}
private:
ID2D1Factory *m_pFactory;
ID2D1Geometry *m_pGeometry;
};
/******************************************************************
* *
* Static Data *
* *
******************************************************************/
/*static*/ const D3D10_INPUT_ELEMENT_DESC Interactive3dTextSampleApp::s_InputLayout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0},
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0},
};
// This must be a multiple of 3
/* static*/ const UINT Interactive3dTextSampleApp::sc_vertexBufferCount = 3*1000;
/******************************************************************
* *
* Interactive3dTextSampleApp::Interactive3dTextSampleApp *
* *
* Constructor -- initialize member data *
* *
******************************************************************/
Interactive3dTextSampleApp::Interactive3dTextSampleApp() :
m_hwnd(NULL),
m_pD2DFactory(NULL),
m_pDWriteFactory(NULL),
m_pDevice(NULL),
m_pSwapChain(NULL),
m_pState(NULL),
m_pDepthStencil(NULL),
m_pDepthStencilView(NULL),
m_pRenderTargetView(NULL),
m_pShader(NULL),
m_pVertexBuffer(NULL),
m_pVertexLayout(NULL),
m_pTechniqueNoRef(NULL),
m_pWorldVariableNoRef(NULL),
m_pViewVariableNoRef(NULL),
m_pProjectionVariableNoRef(NULL),
m_pLightPosVariableNoRef(NULL),
m_pLightColorVariableNoRef(NULL),
m_pTextGeometry(NULL),
m_pTextLayout(NULL)
{
}
/******************************************************************
* *
* Interactive3dTextSampleApp::~Interactive3dTextSampleApp *
* *
* Destructor -- tear down member data *
* *
******************************************************************/
Interactive3dTextSampleApp::~Interactive3dTextSampleApp()
{
SafeRelease(&m_pD2DFactory);
SafeRelease(&m_pDWriteFactory);
SafeRelease(&m_pDevice);
SafeRelease(&m_pSwapChain);
SafeRelease(&m_pState);
SafeRelease(&m_pDepthStencil);
SafeRelease(&m_pDepthStencilView);
SafeRelease(&m_pRenderTargetView);
SafeRelease(&m_pShader);
SafeRelease(&m_pVertexBuffer);
SafeRelease(&m_pVertexLayout);
SafeRelease(&m_pTextGeometry);
SafeRelease(&m_pTextLayout);
}
/******************************************************************
* *
* DemoApp::Initialize *
* *
* Create application window and device-independent resources *
* *
******************************************************************/
HRESULT Interactive3dTextSampleApp::Initialize()
{
HRESULT hr;
hr = CreateDeviceIndependentResources();
if (SUCCEEDED(hr))
{
// Register window class
WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = Interactive3dTextSampleApp::WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = sizeof(LONG_PTR);
wcex.hInstance = HINST_THISCOMPONENT;
wcex.hbrBackground = NULL;
wcex.lpszMenuName = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.lpszClassName = L"D2DDemoApp";
RegisterClassEx(&wcex);
// Create the application window.
//
// This sample does not handle resize so we create the window such that
// it can't be resized.
//
// Because the CreateWindow function takes its size in pixels, we
// obtain the system DPI and use it to scale the window size.
FLOAT dpiX;
FLOAT dpiY;
m_pD2DFactory->GetDesktopDpi(&dpiX, &dpiY);
m_hwnd = CreateWindow(
L"D2DDemoApp",
L"Direct2D Demo App",
WS_OVERLAPPEDWINDOW & ~(WS_MAXIMIZEBOX | WS_SIZEBOX),
CW_USEDEFAULT,
CW_USEDEFAULT,
static_cast<UINT>(ceil(1024.f * dpiX / 96.f)),
static_cast<UINT>(ceil(480.f * dpiY / 96.f)),
NULL,
NULL,
HINST_THISCOMPONENT,
this
);
hr = m_hwnd ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
// Create a timer and receive WM_TIMER messages at a rough
// granularity of 33msecs. If you need a more precise timer,
// consider modifying the message loop and calling more precise
// timing functions.
SetTimer(
m_hwnd,
0, //timerId
33, //msecs
NULL //lpTimerProc
);
ShowWindow(m_hwnd, SW_SHOWNORMAL);
UpdateWindow(m_hwnd);
}
}
return hr;
}
/******************************************************************
* *
* Interactive3dTextSampleApp::CreateDeviceIndependentResources *
* *
* This method is used to create resources which are not bound *
* to any device. Their lifetime effectively extends for the *
* duration of the app. These resources include the D2D, *
* DWrite, and WIC factories; and a DWrite Text Format object *
* (used for identifying particular font characteristics) and *
* a D2D geometry. *
* *
******************************************************************/
HRESULT Interactive3dTextSampleApp::CreateDeviceIndependentResources()
{
HRESULT hr;
// Create D2D factory
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pD2DFactory);
if (SUCCEEDED(hr))
{
// Create DWrite factory
hr = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(m_pDWriteFactory),
reinterpret_cast<IUnknown **>(&m_pDWriteFactory)
);
}
//
// Add command-prompt-ish like text to the start of the string.
//
if (SUCCEEDED(hr))
{
hr = m_characters.Add('>');
}
if (SUCCEEDED(hr))
{
hr = m_characters.Add(' ');
}
if (SUCCEEDED(hr))
{
hr = UpdateTextGeometry();
}
return hr;
}
/******************************************************************
* *
* Interactive3dTextSampleApp::UpdateTextGeometry *
* *
* Generates a layout object and a geometric outline *
* corresponding to the current text string and stores the *
* results in m_pTextLayout and m_pTextGeometry. *
* *
******************************************************************/
HRESULT Interactive3dTextSampleApp::UpdateTextGeometry()
{
HRESULT hr;
IDWriteTextFormat *pFormat = NULL;
hr = m_pDWriteFactory->CreateTextFormat(
sc_fontFace,
NULL,
DWRITE_FONT_WEIGHT_EXTRA_BOLD,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
sc_fontSize,
L"", // locale name
&pFormat
);
if (SUCCEEDED(hr))
{
IDWriteTextLayout *pLayout = NULL;
hr = m_pDWriteFactory->CreateTextLayout(
&m_characters[0],
m_characters.GetCount(),
pFormat,
0.0f, // lineWidth (ignored because of NO_WRAP)
sc_fontSize, // lineHeight
&pLayout
);
if (SUCCEEDED(hr))
{
hr = pLayout->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
if (SUCCEEDED(hr))
{
IDWriteTypography *pTypography = NULL;
hr = m_pDWriteFactory->CreateTypography(&pTypography);
if (SUCCEEDED(hr))
{
DWRITE_FONT_FEATURE fontFeature =
{
DWRITE_FONT_FEATURE_TAG_STYLISTIC_SET_7,
1
};
hr = pTypography->AddFontFeature(fontFeature);
if (SUCCEEDED(hr))
{
DWRITE_TEXT_RANGE textRange = {0, m_characters.GetCount()};
hr = pLayout->SetTypography(pTypography, textRange);
}
pTypography->Release();
}
NoRefComObject<OutlineRenderer> renderer(m_pD2DFactory);
hr = pLayout->Draw(
NULL, // clientDrawingContext
&renderer,
0.0f, // originX
0.0f // originY
);
if (SUCCEEDED(hr))
{
ID2D1Geometry *pGeometry = NULL;;
hr = renderer.GetGeometry(&pGeometry);
if (SUCCEEDED(hr))
{
SafeReplace(&m_pTextGeometry, pGeometry);
SafeReplace(&m_pTextLayout, pLayout);
pGeometry->Release();
}
}
}
pLayout->Release();
}
pFormat->Release();
}
return hr;
}
/******************************************************************
* *
* Interactive3dTextSampleApp::GenerateTextOutline *
* *
* Returns a version of the text geometry that is horizontally *
* centered and vertically positioned so (0,0) is on the *
* base line. *
* *
******************************************************************/
HRESULT Interactive3dTextSampleApp::GenerateTextOutline(
bool includeCursor,
ID2D1Geometry **ppGeometry
)
{
HRESULT hr;
DWRITE_LINE_METRICS lineMetrics;
DWRITE_TEXT_METRICS textMetrics;
UINT actualNumLines;
//
// We're assuming here that the text doesn't wrap and doesn't contain
// newlines.
//
hr = m_pTextLayout->GetLineMetrics(
&lineMetrics,
1, // maxLineCount
&actualNumLines // ignored
);
if (SUCCEEDED(hr))
{
hr = m_pTextLayout->GetMetrics(&textMetrics);
if (SUCCEEDED(hr))
{
float offsetY = -lineMetrics.baseline;
float offsetX = -textMetrics.widthIncludingTrailingWhitespace / 2;
ID2D1TransformedGeometry *pTransformedGeometry = NULL;
hr = m_pD2DFactory->CreateTransformedGeometry(
m_pTextGeometry,
D2D1::Matrix3x2F::Translation(offsetX, offsetY),
&pTransformedGeometry
);
if (SUCCEEDED(hr))
{
ID2D1Geometry *pGeometry = NULL;
pGeometry = pTransformedGeometry;
pGeometry->AddRef();
if (includeCursor)
{
float x, y;
DWRITE_HIT_TEST_METRICS hitTestMetrics;
hr = m_pTextLayout->HitTestTextPosition(
lineMetrics.length,
TRUE, // isTrailingHit
&x,
&y,
&hitTestMetrics
);
if (SUCCEEDED(hr))
{
float width = sc_cursorWidth;
float left = x+offsetX;
ID2D1RectangleGeometry *pCursorGeometry = NULL;
hr = m_pD2DFactory->CreateRectangleGeometry(
D2D1::RectF(
left,
hitTestMetrics.top + offsetY,
left + width,
0.0f),
&pCursorGeometry
);
if (SUCCEEDED(hr))
{
ID2D1Geometry *pCombinedGeometry = NULL;
hr = D2DCombine(
D2D1_COMBINE_MODE_UNION,
pGeometry,
pCursorGeometry,
&pCombinedGeometry
);
if (SUCCEEDED(hr))
{
SafeReplace(&pGeometry, pCombinedGeometry);
pCombinedGeometry->Release();
}
pCursorGeometry->Release();
}
}
}
// transfer reference
*ppGeometry = pGeometry;
pGeometry = NULL;
}
pTransformedGeometry->Release();
}
}
return hr;
}
/******************************************************************
* *
* Interactive3dTextSampleApp::CreateDeviceResources *
* *
* This method is responsible for creating the D3D device and *
* all corresponding device-bound resources that are required to *
* render. *
* *
* Of the objects created in this method, 2 are interesting ... *
* *
* 1. D3D Device (m_pDevice) *
* *
* 2. DXGI Swap Chain (m_pSwapChain) -- Mapped to current *
* window (m_hwnd) *
* *
******************************************************************/
HRESULT Interactive3dTextSampleApp::CreateDeviceResources()
{
HRESULT hr = S_OK;
RECT rcClient;
UINT msaaQuality = 0;
UINT sampleCount = 0;
ID3D10Resource *pBackBufferResource = NULL;
ID3D10Device1 *pDevice = NULL;
IDXGIDevice *pDXGIDevice = NULL;
IDXGIAdapter *pAdapter = NULL;
IDXGIFactory *pDXGIFactory = NULL;
IDXGISurface *pSurface = NULL;
IDXGISurface *pBackBuffer = NULL;
Assert(m_hwnd);
GetClientRect(m_hwnd, &rcClient);
UINT nWidth = abs(rcClient.right - rcClient.left);
UINT nHeight = abs(rcClient.bottom - rcClient.top);
// If we don't have a device, need to create one now and all
// accompanying D3D resources
if (!m_pDevice)
{
UINT nDeviceFlags = 0;
// Create device
hr = CreateD3DDevice(
NULL, // adapter
D3D10_DRIVER_TYPE_HARDWARE,
nDeviceFlags,
&pDevice
);
if (FAILED(hr))
{
hr = CreateD3DDevice(
NULL,
D3D10_DRIVER_TYPE_WARP,
nDeviceFlags,
&pDevice
);
}
if (SUCCEEDED(hr))
{
hr = pDevice->QueryInterface(&m_pDevice);
}
if (SUCCEEDED(hr))
{
hr = pDevice->QueryInterface(&pDXGIDevice);
}
if (SUCCEEDED(hr))
{
hr = pDXGIDevice->GetAdapter(&pAdapter);
}
if (SUCCEEDED(hr))
{
hr = pAdapter->GetParent(IID_PPV_ARGS(&pDXGIFactory));
}
if (SUCCEEDED(hr))
{
msaaQuality = 0;
for (sampleCount = sc_maxMsaaSampleCount; SUCCEEDED(hr) && sampleCount > 0; --sampleCount)
{
hr = m_pDevice->CheckMultisampleQualityLevels(
DXGI_FORMAT_B8G8R8A8_UNORM,
sampleCount,
&msaaQuality
);
if (SUCCEEDED(hr))
{
if (msaaQuality > 0)
{
break;
}
}
}
}
if (SUCCEEDED(hr))
{
DXGI_SWAP_CHAIN_DESC swapDesc;
::ZeroMemory(&swapDesc, sizeof(swapDesc));
swapDesc.BufferDesc.Width = nWidth;
swapDesc.BufferDesc.Height = nHeight;
swapDesc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
swapDesc.SampleDesc.Count = sampleCount;
swapDesc.SampleDesc.Quality = msaaQuality-1;
swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapDesc.BufferCount = 1;
swapDesc.OutputWindow = m_hwnd;
swapDesc.Windowed = TRUE;
hr = pDXGIFactory->CreateSwapChain(m_pDevice, &swapDesc, &m_pSwapChain);
}
if (SUCCEEDED(hr))
{
// Create rasterizer state object
D3D10_RASTERIZER_DESC rsDesc;
rsDesc.AntialiasedLineEnable = FALSE;
rsDesc.CullMode = D3D10_CULL_BACK;
rsDesc.DepthBias = 0;
rsDesc.DepthBiasClamp = 0;
rsDesc.DepthClipEnable = TRUE;
rsDesc.FillMode = D3D10_FILL_SOLID;
rsDesc.FrontCounterClockwise = FALSE; // Must be FALSE for 10on9
rsDesc.MultisampleEnable = TRUE;
rsDesc.ScissorEnable = FALSE;
rsDesc.SlopeScaledDepthBias = 0;
hr = m_pDevice->CreateRasterizerState(&rsDesc, &m_pState);
}
if (SUCCEEDED(hr))
{
m_pDevice->RSSetState(m_pState);
msaaQuality = 0;
for (sampleCount = sc_maxMsaaSampleCount; SUCCEEDED(hr) && sampleCount > 0; --sampleCount)
{
hr = m_pDevice->CheckMultisampleQualityLevels(
DXGI_FORMAT_B8G8R8A8_UNORM,
sampleCount,
&msaaQuality
);
if (SUCCEEDED(hr))
{
if (msaaQuality > 0)
{
break;
}
}
}
}
if (SUCCEEDED(hr))
{
// Create views on the RT buffers and set them on the device
hr = m_pSwapChain->GetBuffer(
0,
IID_PPV_ARGS(&pBackBufferResource)
);
}
if (SUCCEEDED(hr))
{
D3D10_RENDER_TARGET_VIEW_DESC renderDesc;
renderDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
renderDesc.ViewDimension = D3D10_RTV_DIMENSION_TEXTURE2DMS;
renderDesc.Texture2D.MipSlice = 0;
hr = m_pDevice->CreateRenderTargetView(pBackBufferResource, &renderDesc, &m_pRenderTargetView);
}
if (SUCCEEDED(hr))
{
D3D10_TEXTURE2D_DESC texDesc;
texDesc.ArraySize = 1;
texDesc.BindFlags = D3D10_BIND_DEPTH_STENCIL;
texDesc.CPUAccessFlags = 0;
texDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
texDesc.Height = nHeight;
texDesc.Width = nWidth;
texDesc.MipLevels = 1;
texDesc.MiscFlags = 0;
texDesc.SampleDesc.Count = sampleCount;
texDesc.SampleDesc.Quality = msaaQuality-1;
texDesc.Usage = D3D10_USAGE_DEFAULT;
hr = m_pDevice->CreateTexture2D(&texDesc, NULL, &m_pDepthStencil);
if (SUCCEEDED(hr))
{
D3D10_DEPTH_STENCIL_VIEW_DESC depthViewDesc;
depthViewDesc.Format = texDesc.Format;
depthViewDesc.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2DMS;
depthViewDesc.Texture2D.MipSlice = 0;
hr = m_pDevice->CreateDepthStencilView(m_pDepthStencil, &depthViewDesc, &m_pDepthStencilView);
}
}
if (SUCCEEDED(hr))
{
ID3D10RenderTargetView *viewList[1] = {m_pRenderTargetView};
m_pDevice->OMSetRenderTargets(1, viewList, m_pDepthStencilView);
// Set a new viewport based on the new dimensions
D3D10_VIEWPORT viewport;
viewport.Width = nWidth;
viewport.Height = nHeight;
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.MinDepth = 0;
viewport.MaxDepth = 1;
m_pDevice->RSSetViewports(1, &viewport);
// Get a surface in the swap chain
hr = m_pSwapChain->GetBuffer(
0,
IID_PPV_ARGS(&pBackBuffer)
);
}
if (SUCCEEDED(hr))
{
// Load pixel shader
hr = LoadResourceShader(
m_pDevice,
MAKEINTRESOURCE(IDR_PIXEL_SHADER),
&m_pShader
);
}
if (SUCCEEDED(hr))
{
// Obtain the technique
m_pTechniqueNoRef = m_pShader->GetTechniqueByName("Render");
hr = m_pTechniqueNoRef ? S_OK : E_FAIL;
}
if (SUCCEEDED(hr))
{
// Obtain the variables
m_pWorldVariableNoRef = m_pShader->GetVariableByName("World")->AsMatrix();
hr = m_pWorldVariableNoRef ? S_OK : E_FAIL;
}
if (SUCCEEDED(hr))
{
m_pViewVariableNoRef = m_pShader->GetVariableByName("View")->AsMatrix();
hr = m_pViewVariableNoRef ? S_OK : E_FAIL;
}
if (SUCCEEDED(hr))
{
m_pProjectionVariableNoRef = m_pShader->GetVariableByName("Projection")->AsMatrix();
hr = m_pProjectionVariableNoRef ? S_OK : E_FAIL;
}
if (SUCCEEDED(hr))
{
m_pLightPosVariableNoRef = m_pShader->GetVariableByName("vLightPos")->AsVector();
hr = m_pLightPosVariableNoRef ? S_OK : E_FAIL;
}
if (SUCCEEDED(hr))
{
m_pLightColorVariableNoRef = m_pShader->GetVariableByName("vLightColor")->AsVector();
hr = m_pLightColorVariableNoRef ? S_OK : E_FAIL;
}
if (SUCCEEDED(hr))
{
// Define the input layout
UINT numElements = ARRAYSIZE(s_InputLayout);
// Create the input layout
D3D10_PASS_DESC PassDesc;
m_pTechniqueNoRef->GetPassByIndex(0)->GetDesc(&PassDesc);
hr = m_pDevice->CreateInputLayout(
s_InputLayout,
numElements,
PassDesc.pIAInputSignature,
PassDesc.IAInputSignatureSize,
&m_pVertexLayout
);
}
if (SUCCEEDED(hr))
{
// Set the input layout.
m_pDevice->IASetInputLayout(m_pVertexLayout);
D3D10_BUFFER_DESC bd;
bd.Usage = D3D10_USAGE_DYNAMIC;
bd.ByteWidth = sc_vertexBufferCount * sizeof(SimpleVertex);
bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
bd.MiscFlags = 0;
hr = m_pDevice->CreateBuffer(&bd, NULL, &m_pVertexBuffer);
}
if (SUCCEEDED(hr))
{
// Set the vertex buffer.
UINT stride = sizeof(SimpleVertex);
UINT offset = 0;
ID3D10Buffer *pVertexBuffer = m_pVertexBuffer;
m_pDevice->IASetVertexBuffers(
0, // StartSlot
1, // NumBuffers
&pVertexBuffer,
&stride,
&offset
);
// Set primitive topology.
m_pDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// Initialize the world matrices.
D3DMatrixIdentity(&m_WorldMatrix);
// Initialize the view matrix.
D3DMatrixLookAtLH(&m_ViewMatrix, &sc_eyeLocation, &sc_eyeAt, &sc_eyeUp);
// Initialize the projection matrix.
D3DMatrixPerspectiveFovLH(
&m_ProjectionMatrix,
(float)D3DX_PI * 0.24f, // fovy
nWidth / (FLOAT)nHeight, // aspect
0.1f, // zn
800.0f // zf
);
// Update variables that never change.
m_pViewVariableNoRef->SetMatrix((float*)&m_ViewMatrix);
m_pProjectionVariableNoRef->SetMatrix((float*)&m_ProjectionMatrix);
}
}
SafeRelease(&pBackBufferResource);
SafeRelease(&pDevice);
SafeRelease(&pDXGIDevice);
SafeRelease(&pAdapter);
SafeRelease(&pDXGIFactory);
SafeRelease(&pSurface);
SafeRelease(&pBackBuffer);
return hr;
}
/******************************************************************
* *
* Interactive3dTextSampleApp::DiscardDeviceResources *
* *
* Certain resources (eg. device, swap chain, RT) are bound to a *
* particular D3D device. Under certain conditions (eg. change *
* display mode, remoting, removing a video adapter), it is *
* necessary to discard device-specific resources. This method *
* just releases all of the device-bound resources that we're *
* holding onto. *
* *
******************************************************************/
void Interactive3dTextSampleApp::DiscardDeviceResources()
{
SafeRelease(&m_pDevice);
SafeRelease(&m_pSwapChain);
SafeRelease(&m_pState);
SafeRelease(&m_pDepthStencil);
SafeRelease(&m_pDepthStencilView);
SafeRelease(&m_pRenderTargetView);
SafeRelease(&m_pShader);
SafeRelease(&m_pVertexBuffer);
SafeRelease(&m_pVertexLayout);
}
/******************************************************************
* *
* Interactive3dTextSampleApp::RunMessageLoop *
* *
* This is the main message pump for the application *
* *
******************************************************************/
void Interactive3dTextSampleApp::RunMessageLoop()
{
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
/******************************************************************
* *
* Interactive3dTextSampleApp::OnChar *
* *
******************************************************************/
void Interactive3dTextSampleApp::OnChar(SHORT key)
{
if (key == '\r')
{
// swallow
}
else if (key == '\b')
{
if (m_characters.GetCount() > 2)
{
m_characters.RemoveLast();
}
}
else
{
// In the case of failure we will keep the previous characters, so we
// can safely ignore the return value.
m_characters.Add(key);
}
// In the case of failure we will keep the previous text geometry, so we can
// safely ignore the return value.
UpdateTextGeometry();
}
/******************************************************************
* *
* Interactive3dTextSampleApp::OnRender *
* *
* This method is called when the app needs to paint the window. *
* It uses a D2D RT to draw a gradient background into the swap *
* chain buffer. Then, it uses a separate D2D RT to draw a *
* 2D scene into a D3D texture. This texture is mapped onto a *
* simple planar 3D model and displayed using D3D. *
* *
******************************************************************/
HRESULT Interactive3dTextSampleApp::OnRender()
{
HRESULT hr;
static float t = 0.0f;
static DWORD dwTimeStart = 0;
hr = CreateDeviceResources();
if (SUCCEEDED(hr) && m_pRenderTargetView)
{
DWORD dwTimeCur = GetTickCount();
if (dwTimeStart == 0)
{
dwTimeStart = dwTimeCur;
}
t = (dwTimeCur - dwTimeStart) / 3000.0f;
float a = ((float)D3DX_PI)/4 * sinf(2*t);
// A silly way to get a blinking cursor, but it works!
bool showCursor = sinf(20*t) < 0;
ID2D1Geometry *pGeometry = NULL;
//
// Note: We are dynamically extruding the geometry every frame here.
// This is somewhat wasteful, but allows us to introduce more complex
// geometry animations in the future.
//
hr = GenerateTextOutline(showCursor, &pGeometry);
if (SUCCEEDED(hr))
{
CArray<SimpleVertex> vertices;
hr = Extruder::ExtrudeGeometry(
pGeometry,
24.0f, // height
vertices
);
if (SUCCEEDED(hr))
{
D3DMatrixRotationY(&m_WorldMatrix, a);
// Setup our lighting parameters
D3DXVECTOR4 vLightPos[3] =
{
D3DXVECTOR4( 1200.0f, -20.0f, 400.0f, 0.0f )
};
D3DXVECTOR4 vLightColors[3] =
{
D3DXVECTOR4( 0.9f, 0.0f, 0.0f, 1.0f )
};
//Swap chain will tell us how big the back buffer is
DXGI_SWAP_CHAIN_DESC swapDesc;
hr = m_pSwapChain->GetDesc(&swapDesc);
if (SUCCEEDED(hr))
{
m_pDevice->ClearDepthStencilView(m_pDepthStencilView, D3D10_CLEAR_DEPTH, 1, 0);
const float black[4] ={0};
m_pDevice->ClearRenderTargetView(m_pRenderTargetView, black);
m_pTechniqueNoRef->GetPassByIndex(0)->Apply(0);
// Update variables that change once per frame
m_pWorldVariableNoRef->SetMatrix((float*)&m_WorldMatrix);
// Update lighting variables
m_pLightPosVariableNoRef->SetFloatVectorArray((float*)vLightPos, 0, 1);
m_pLightColorVariableNoRef->SetFloatVectorArray((float*)vLightColors, 0, 1);
// Render the scene
m_pTechniqueNoRef->GetPassByIndex(0)->Apply(0);
UINT verticesLeft = vertices.GetCount();
UINT index = 0;
while (SUCCEEDED(hr) && verticesLeft > 0)
{
UINT verticesToCopy = min(verticesLeft,sc_vertexBufferCount);
void *pVertices;
hr = m_pVertexBuffer->Map(
D3D10_MAP_WRITE_DISCARD,
0, // MapFlags
&pVertices
);
if (SUCCEEDED(hr))
{
memcpy(pVertices, &vertices[index], verticesToCopy*sizeof(SimpleVertex));
m_pVertexBuffer->Unmap();
m_pDevice->Draw(verticesToCopy, 0);
verticesLeft -= verticesToCopy;
index += verticesToCopy;
}
}
if (SUCCEEDED(hr))
{
hr = m_pSwapChain->Present(1, 0);
}
}
}
pGeometry->Release();
}
}
// If the device is lost for any reason, we need to recreate it
if (hr == DXGI_ERROR_DEVICE_RESET ||
hr == DXGI_ERROR_DEVICE_REMOVED)
{
hr = S_OK;
DiscardDeviceResources();
}
return hr;
}
/******************************************************************
* *
* Interactive3dTextSampleApp::OnTimer *
* *
* *
******************************************************************/
void Interactive3dTextSampleApp::OnTimer()
{
InvalidateRect(m_hwnd, NULL, FALSE);
}
/******************************************************************
* *
* Interactive3dTextSampleApp::WndProc *
* *
* This static method handles our app's window messages *
* *
******************************************************************/
LRESULT CALLBACK Interactive3dTextSampleApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT result = 0;
if (message == WM_CREATE)
{
LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
Interactive3dTextSampleApp *pInteractive3dTextSampleApp = (Interactive3dTextSampleApp *)pcs->lpCreateParams;
::SetWindowLongPtrW(
hwnd,
GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(pInteractive3dTextSampleApp)
);
result = 1;
}
else
{
Interactive3dTextSampleApp *pInteractive3dTextSampleApp = reinterpret_cast<Interactive3dTextSampleApp *>(
::GetWindowLongPtrW(
hwnd,
GWLP_USERDATA
));
bool wasHandled = false;
if (pInteractive3dTextSampleApp)
{
switch (message)
{
case WM_PAINT:
case WM_DISPLAYCHANGE:
{
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
pInteractive3dTextSampleApp->OnRender();
EndPaint(hwnd, &ps);
}
result = 0;
wasHandled = true;
break;
case WM_CHAR:
{
pInteractive3dTextSampleApp->OnChar(static_cast<SHORT>(wParam));
}
result = 0;
wasHandled = true;
break;
case WM_TIMER:
{
pInteractive3dTextSampleApp->OnTimer();
}
result = 0;
wasHandled = true;
break;
case WM_DESTROY:
{
PostQuitMessage(0);
}
result = 1;
wasHandled = true;
break;
}
}
if (!wasHandled)
{
result = DefWindowProc(hwnd, message, wParam, lParam);
}
}
return result;
}
/******************************************************************
* *
* Interactive3dTextSampleApp::CreateD3DDevice *
* *
******************************************************************/
HRESULT Interactive3dTextSampleApp::CreateD3DDevice(
IDXGIAdapter *pAdapter,
D3D10_DRIVER_TYPE driverType,
UINT flags,
ID3D10Device1 **ppDevice
)
{
HRESULT hr = S_OK;
static const D3D10_FEATURE_LEVEL1 levelAttempts[] =
{
D3D10_FEATURE_LEVEL_10_0,
D3D10_FEATURE_LEVEL_9_3,
D3D10_FEATURE_LEVEL_9_2,
D3D10_FEATURE_LEVEL_9_1,
};
for (UINT level = 0; level < ARRAYSIZE(levelAttempts); level++)
{
ID3D10Device1 *pDevice = NULL;
hr = D3D10CreateDevice1(
pAdapter,
driverType,
NULL,
flags,
levelAttempts[level],
D3D10_1_SDK_VERSION,
&pDevice
);
if (SUCCEEDED(hr))
{
// transfer reference
*ppDevice = pDevice;
pDevice = NULL;
break;
}
}
if (FAILED(hr))
{
hr = D2DERR_NO_HARDWARE_DEVICE;
}
return hr;
}
/******************************************************************
* *
* Interactive3dTextSampleApp::LoadResourceShader *
* *
* This method loads and creates a pixel shader from a DLL *
* resource *
* *
******************************************************************/
HRESULT Interactive3dTextSampleApp::LoadResourceShader(
ID3D10Device *pDevice,
PCWSTR pszResource,
ID3D10Effect **ppShader
)
{
HRESULT hr;
HRSRC hResource = ::FindResource(HINST_THISCOMPONENT, pszResource, RT_RCDATA);
hr = hResource ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
HGLOBAL hResourceData = ::LoadResource(HINST_THISCOMPONENT, hResource);
hr = hResourceData ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
LPVOID pData = ::LockResource(hResourceData);
hr = pData ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
hr = ::D3D10CreateEffectFromMemory(
pData,
::SizeofResource(HINST_THISCOMPONENT, hResource),
0,
pDevice,
NULL,
ppShader
);
}
}
}
return hr;
}