[Из песочницы] Direct2D в Rainmeter

?v=1
/* Copyright (C) 2013 Rainmeter Project Developers
 *
 * This Source Code Form is subject to the terms of the GNU General Public
 * License; either version 2 of the License, or (at your option) any later
 * version. If a copy of the GPL was not distributed with this file, You can
 * obtain one at . */

#include "StdAfx.h"
#include "Canvas.h"
#include "TextFormatD2D.h"
#include "D2DBitmap.h"
#include "RenderTexture.h"
#include "Util/D2DUtil.h"
#include "Util/DWriteFontCollectionLoader.h"
#include "../../Library/Util.h"
#include "../../Library/Logger.h"

namespace Gfx {

UINT Canvas::c_Instances = 0;
D3D_FEATURE_LEVEL Canvas::c_FeatureLevel;
Microsoft::WRL::ComPtr Canvas::c_D3DDevice;
Microsoft::WRL::ComPtr Canvas::c_D3DContext;
Microsoft::WRL::ComPtr Canvas::c_D2DDevice;
Microsoft::WRL::ComPtr Canvas::c_DxgiDevice;
Microsoft::WRL::ComPtr Canvas::c_D2DFactory;
Microsoft::WRL::ComPtr Canvas::c_DWFactory;
Microsoft::WRL::ComPtr Canvas::c_WICFactory;

Canvas::Canvas() :
	m_W(0),
	m_H(0),
	m_MaxBitmapSize(0U),
	m_IsDrawing(false),
	m_EnableDrawAfterGdi(false),
	m_TextAntiAliasing(false),
	m_CanUseAxisAlignClip(true)
{
	Initialize(true);
}

Canvas::~Canvas()
{
	Finalize();
}

bool Canvas::LogComError(HRESULT hr)
{
	_com_error err(hr);
	LogErrorF(L"Error 0x%08x: %s", hr, err.ErrorMessage());
	return false;
}

bool Canvas::Initialize(bool hardwareAccelerated)
{
	++c_Instances;
	if (c_Instances == 1U)
	{
		// Required for Direct2D interopability.
		UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

#ifdef _DEBUG
		creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

		auto tryCreateContext = [&](D3D_DRIVER_TYPE driverType,
			const D3D_FEATURE_LEVEL* levels, UINT numLevels)
		{
			return D3D11CreateDevice(
				nullptr,
				driverType,
				nullptr,
				creationFlags,
				levels,
				numLevels,
				D3D11_SDK_VERSION,
				c_D3DDevice.GetAddressOf(),
				&c_FeatureLevel,
				c_D3DContext.GetAddressOf());
		};

		// D3D selects the best feature level automatically and sets it
		// to |c_FeatureLevel|. First, we try to use the hardware driver
		// and if that fails, we try the WARP rasterizer for cases
		// where there is no graphics card or other failures.
		const D3D_FEATURE_LEVEL levels[] = 
		{
			D3D_FEATURE_LEVEL_11_1,
			D3D_FEATURE_LEVEL_11_0,
			D3D_FEATURE_LEVEL_10_1,
			D3D_FEATURE_LEVEL_10_0,
			D3D_FEATURE_LEVEL_9_3,
			D3D_FEATURE_LEVEL_9_2,
			D3D_FEATURE_LEVEL_9_1
		};

		HRESULT hr = E_FAIL;
		if (hardwareAccelerated)
		{
			hr = tryCreateContext(D3D_DRIVER_TYPE_HARDWARE, levels, _countof(levels));
			if (hr == E_INVALIDARG)
			{
				hr = tryCreateContext(D3D_DRIVER_TYPE_HARDWARE, &levels[1], _countof(levels) - 1);
			}
		}

		if (FAILED(hr))
		{
			hr = tryCreateContext(D3D_DRIVER_TYPE_WARP, nullptr, 0U);
			if (FAILED(hr)) return false;
		}

		hr = c_D3DDevice.As(&c_DxgiDevice);
		if (FAILED(hr)) return false;

		D2D1_FACTORY_OPTIONS fo = {};
#ifdef _DEBUG
		fo.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif

		hr = D2D1CreateFactory(
			D2D1_FACTORY_TYPE_SINGLE_THREADED,
			fo,
			c_D2DFactory.GetAddressOf());
		if (FAILED(hr)) return false;

		hr = c_D2DFactory->CreateDevice(
			c_DxgiDevice.Get(),
			c_D2DDevice.GetAddressOf());
		if (FAILED(hr)) return false;

		hr = CoCreateInstance(
			CLSID_WICImagingFactory,
			nullptr,
			CLSCTX_INPROC_SERVER,
			IID_IWICImagingFactory,
			(LPVOID*)c_WICFactory.GetAddressOf());
		if (FAILED(hr)) return false;

		hr = DWriteCreateFactory(
			DWRITE_FACTORY_TYPE_SHARED,
			__uuidof(c_DWFactory),
			(IUnknown**)c_DWFactory.GetAddressOf());
		if (FAILED(hr)) return false;

		hr = c_DWFactory->RegisterFontCollectionLoader(Util::DWriteFontCollectionLoader::GetInstance());
		if (FAILED(hr)) return false;
	}

	return true;
}

void Canvas::Finalize()
{
	--c_Instances;
	if (c_Instances == 0U)
	{
		c_D3DDevice.Reset();
		c_D3DContext.Reset();
		c_D2DDevice.Reset();
		c_DxgiDevice.Reset();
		c_D2DFactory.Reset();
		c_WICFactory.Reset();

		if (c_DWFactory)
		{
			c_DWFactory->UnregisterFontCollectionLoader(Util::DWriteFontCollectionLoader::GetInstance());
			c_DWFactory.Reset();
		}
	}
}

bool Canvas::InitializeRenderTarget(HWND hwnd)
{
	DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 };
	swapChainDesc.Width = 1U;
	swapChainDesc.Height = 1U;
	swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
	swapChainDesc.Stereo = false;
	swapChainDesc.SampleDesc.Count = 1U;
	swapChainDesc.SampleDesc.Quality = 0U;
	swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	swapChainDesc.BufferCount = 2U;
	swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
	swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_GDI_COMPATIBLE;
	swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;

	Microsoft::WRL::ComPtr dxgiAdapter;
	HRESULT hr = c_DxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf());
	if (FAILED(hr)) return LogComError(hr);

	// Ensure that DXGI does not queue more than one frame at a time.
	hr = c_DxgiDevice->SetMaximumFrameLatency(1U);
	if (FAILED(hr)) return LogComError(hr);

	Microsoft::WRL::ComPtr dxgiFactory;
	hr = dxgiAdapter->GetParent(IID_PPV_ARGS(dxgiFactory.GetAddressOf()));
	if (FAILED(hr)) return LogComError(hr);

	hr = dxgiFactory->CreateSwapChainForHwnd(
		c_DxgiDevice.Get(),
		hwnd,
		&swapChainDesc,
		nullptr,
		nullptr,
		m_SwapChain.ReleaseAndGetAddressOf());
	if (FAILED(hr)) return LogComError(hr);

	hr = CreateRenderTarget();
	if (FAILED(hr)) return LogComError(hr);

	return CreateTargetBitmap(0U, 0U);
}

void Canvas::Resize(int w, int h)
{
	// Truncate the size of the skin if it's too big.
	if (w > (int)m_MaxBitmapSize) w = (int)m_MaxBitmapSize;
	if (h > (int)m_MaxBitmapSize) h = (int)m_MaxBitmapSize;

	m_W = w;
	m_H = h;

	// Check if target, targetbitmap, backbuffer, swap chain are valid?

	// Unmap all resources tied to the swap chain.
	m_Target->SetTarget(nullptr);
	m_TargetBitmap.Reset();
	m_BackBuffer.Reset();

	// Resize swap chain.
	HRESULT hr = m_SwapChain->ResizeBuffers(
		0U,
		(UINT)w,
		(UINT)h,
		DXGI_FORMAT_B8G8R8A8_UNORM,
		DXGI_SWAP_CHAIN_FLAG_GDI_COMPATIBLE);
	if (FAILED(hr)) return;

	CreateTargetBitmap((UINT32)w, (UINT32)h);
}

bool Canvas::BeginDraw()
{
	if (!m_Target)
	{
		HRESULT hr = CreateRenderTarget();
		if (FAILED(hr))
		{
			m_IsDrawing = false;
			return false;
		}

		// Recreate target bitmap
		Resize(m_W, m_H);
	}

	m_Target->BeginDraw();
	m_IsDrawing = true;
	return true;
}

void Canvas::EndDraw()
{
	HRESULT hr = m_Target->EndDraw();
	if (FAILED(hr))
	{
		m_Target.Reset();
	}

	m_IsDrawing = false;
}

HDC Canvas::GetDC()
{
	if (m_IsDrawing)
	{
		m_EnableDrawAfterGdi = true;
		m_IsDrawing = false;
		EndDraw();
	}

	HDC hdc;
	HRESULT hr = m_BackBuffer->GetDC(FALSE, &hdc);
	if (FAILED(hr)) return nullptr;

	return hdc;
}

void Canvas::ReleaseDC()
{
	m_BackBuffer->ReleaseDC(nullptr);

	if (m_EnableDrawAfterGdi)
	{
		m_EnableDrawAfterGdi = false;
		m_IsDrawing = true;
		BeginDraw();
	}
}

bool Canvas::IsTransparentPixel(int x, int y)
{
	if (!(x >= 0 && y >= 0 && x < m_W && y < m_H)) return false;

	auto pixel = GetPixel(GetDC(), x, y);
	ReleaseDC();

	return (pixel & 0xFF000000) == 0;
}

void Canvas::GetTransform(D2D1_MATRIX_3X2_F* matrix)
{
	if (m_Target)
	{
		m_Target->GetTransform(matrix);
	}
}

void Canvas::SetTransform(const D2D1_MATRIX_3X2_F& matrix)
{
	m_Target->SetTransform(matrix);

	m_CanUseAxisAlignClip =
		(matrix.m11 ==  1.0f && matrix.m12 ==  0.0f && matrix.m21 ==  0.0f && matrix.m22 ==  1.0f) ||	// Angle: 0
		(matrix.m11 ==  0.0f && matrix.m12 ==  1.0f && matrix.m21 == -1.0f && matrix.m22 ==  0.0f) ||	// Angle: 90
		(matrix.m11 == -1.0f && matrix.m12 ==  0.0f && matrix.m21 ==  0.0f && matrix.m22 == -1.0f) ||	// Angle: 180
		(matrix.m11 ==  0.0f && matrix.m12 == -1.0f && matrix.m21 ==  1.0f && matrix.m22 ==  0.0f);		// Angle: 270
}

void Canvas::ResetTransform()
{
	m_Target->SetTransform(D2D1::Matrix3x2F::Identity());
}

bool Canvas::SetTarget(RenderTexture* texture)
{
	auto bitmap = texture->GetBitmap();
	if (bitmap->m_Segments.size() == 0) return false;

	auto image = bitmap->m_Segments[0].GetBitmap();
	m_Target->SetTarget(image);
	return true;
}

void Canvas::ResetTarget()
{
	m_Target->SetTarget(m_TargetBitmap.Get());
}
void Canvas::SetAntiAliasing(bool enable)
{
	m_Target->SetAntialiasMode(enable ? D2D1_ANTIALIAS_MODE_PER_PRIMITIVE : D2D1_ANTIALIAS_MODE_ALIASED);
}

void Canvas::SetTextAntiAliasing(bool enable)
{
	// TODO: Add support for D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE?
	m_TextAntiAliasing = enable;
	m_Target->SetTextAntialiasMode(enable ? D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE : D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
}

void Canvas::Clear(const D2D1_COLOR_F& color)
{
	if (!m_Target) return;

	m_Target->Clear(color);
}
void Canvas::DrawTextW(const std::wstring& srcStr, const TextFormat& format, const D2D1_RECT_F& rect,
	const D2D1_COLOR_F& color, bool applyInlineFormatting)
{
	Microsoft::WRL::ComPtr solidBrush;
	HRESULT hr = m_Target->CreateSolidColorBrush(color, solidBrush.GetAddressOf());
	if (FAILED(hr)) return;

	TextFormatD2D& formatD2D = (TextFormatD2D&)format;

	static std::wstring str;
	str = srcStr;
	formatD2D.ApplyInlineCase(str);

	if (!formatD2D.CreateLayout(
		m_Target.Get(),
		str,
		rect.right - rect.left,
		rect.bottom - rect.top,
		!m_AccurateText && m_TextAntiAliasing)) return;

	D2D1_POINT_2F drawPosition;
	drawPosition.x = [&]()
	{
		if (!m_AccurateText)
		{
			const float xOffset = formatD2D.m_TextFormat->GetFontSize() / 6.0f;
			switch (formatD2D.GetHorizontalAlignment())
			{
			case HorizontalAlignment::Left: return rect.left + xOffset;
			case HorizontalAlignment::Right: return rect.left - xOffset;
			}
		}

		return rect.left;
	} ();

	drawPosition.y = [&]()
	{
		// GDI+ compatibility.
		float yPos = rect.top - formatD2D.m_LineGap;
		switch (formatD2D.GetVerticalAlignment())
		{
		case VerticalAlignment::Bottom: yPos -= formatD2D.m_ExtraHeight; break;
		case VerticalAlignment::Center: yPos -= formatD2D.m_ExtraHeight / 2.0f; break;
		}

		return yPos;
	} ();

	if (formatD2D.m_Trimming)
	{
		D2D1_RECT_F clipRect = rect;

		if (m_CanUseAxisAlignClip)
		{
			m_Target->PushAxisAlignedClip(clipRect, D2D1_ANTIALIAS_MODE_ALIASED);
		}
		else
		{
			const D2D1_LAYER_PARAMETERS1 layerParams =
				D2D1::LayerParameters1(clipRect, nullptr, D2D1_ANTIALIAS_MODE_ALIASED);
			m_Target->PushLayer(layerParams, nullptr);
		}
	}

	// When different "effects" are used with inline coloring options, we need to
	// remove the previous inline coloring, then reapply them (if needed) - instead
	// of destroying/recreating the text layout.
	UINT32 strLen = (UINT32)str.length();
	formatD2D.ResetInlineColoring(solidBrush.Get(), strLen);
	if (applyInlineFormatting)
	{
		formatD2D.ApplyInlineColoring(m_Target.Get(), &drawPosition);

		// Draw any 'shadow' effects
		formatD2D.ApplyInlineShadow(m_Target.Get(), solidBrush.Get(), strLen, drawPosition);
	}

	m_Target->DrawTextLayout(drawPosition, formatD2D.m_TextLayout.Get(), solidBrush.Get());

	if (applyInlineFormatting)
	{
		// Inline gradients require the drawing position, so in case that position
		// changes, we need a way to reset it after drawing time so on the next
		// iteration it will know the correct position.
		formatD2D.ResetGradientPosition(&drawPosition);
	}

	if (formatD2D.m_Trimming)
	{
		if (m_CanUseAxisAlignClip)
		{
			m_Target->PopAxisAlignedClip();
		}
		else
		{
			m_Target->PopLayer();
		}
	}
}
bool Canvas::MeasureTextW(const std::wstring& str, const TextFormat& format, D2D1_SIZE_F& size)
{
	TextFormatD2D& formatD2D = (TextFormatD2D&)format;

	static std::wstring formatStr;
	formatStr = str;
	formatD2D.ApplyInlineCase(formatStr);

	const DWRITE_TEXT_METRICS metrics = formatD2D.GetMetrics(formatStr, !m_AccurateText);
	size.width = metrics.width;
	size.height = metrics.height;
	return true;
}

bool Canvas::MeasureTextLinesW(const std::wstring& str, const TextFormat& format, D2D1_SIZE_F& size, UINT32& lines)
{
	TextFormatD2D& formatD2D = (TextFormatD2D&)format;
	formatD2D.m_TextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP);

	static std::wstring formatStr;
	formatStr = str;
	formatD2D.ApplyInlineCase(formatStr);

	const DWRITE_TEXT_METRICS metrics = formatD2D.GetMetrics(formatStr, !m_AccurateText, size.width);
	size.width = metrics.width;
	size.height = metrics.height;
	lines = metrics.lineCount;

	if (size.height > 0.0f)
	{
		// GDI+ draws multi-line text even though the last line may be clipped slightly at the
		// bottom. This is a workaround to emulate that behaviour.
		size.height += 1.0f;
	}
	else
	{
		// GDI+ compatibility: Zero height text has no visible lines.
		lines = 0U;
	}
	return true;
}

void Canvas::DrawBitmap(const D2DBitmap* bitmap, const D2D1_RECT_F& dstRect, const D2D1_RECT_F& srcRect)
{
	auto& segments = bitmap->m_Segments;
	for (auto seg : segments)
	{
		const auto rSeg = seg.GetRect();
		D2D1_RECT_F rSrc = (rSeg.left < rSeg.right && rSeg.top < rSeg.bottom) ?
			D2D1::RectF(
				max(rSeg.left, srcRect.left),
				max(rSeg.top, srcRect.top),
				min(rSeg.right + rSeg.left, srcRect.right),
				min(rSeg.bottom + rSeg.top, srcRect.bottom)) :
			D2D1::RectF();
		if (rSrc.left == rSrc.right || rSrc.top == rSrc.bottom) continue;

		const D2D1_RECT_F rDst = D2D1::RectF(
			(rSrc.left   - srcRect.left) / (srcRect.right  - srcRect.left) * (dstRect.right  - dstRect.left) + dstRect.left,
			(rSrc.top    - srcRect.top)  / (srcRect.bottom - srcRect.top)  * (dstRect.bottom - dstRect.top)  + dstRect.top,
			(rSrc.right  - srcRect.left) / (srcRect.right  - srcRect.left) * (dstRect.right  - dstRect.left) + dstRect.left,
			(rSrc.bottom - srcRect.top)  / (srcRect.bottom - srcRect.top)  * (dstRect.bottom - dstRect.top)  + dstRect.top);

		while (rSrc.top >= m_MaxBitmapSize)
		{
			rSrc.bottom -= m_MaxBitmapSize;
			rSrc.top -= m_MaxBitmapSize;
		}

		while (rSrc.left >= m_MaxBitmapSize)
		{
			rSrc.right -= m_MaxBitmapSize;
			rSrc.left -= m_MaxBitmapSize;
		}

		m_Target->DrawBitmap(seg.GetBitmap(), rDst, 1.0f, D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC, &rSrc);
	}
}

void Canvas::DrawTiledBitmap(const D2DBitmap* bitmap, const D2D1_RECT_F& dstRect, const D2D1_RECT_F& srcRect)
{
	const FLOAT width = (FLOAT)bitmap->m_Width;
	const FLOAT height = (FLOAT)bitmap->m_Height;

	FLOAT x = dstRect.left;
	FLOAT y = dstRect.top;

	while (y < dstRect.bottom)
	{
		const FLOAT w = (dstRect.right - x) > width ? width : (dstRect.right - x);
		const FLOAT h = (dstRect.bottom - y) > height ? height : (dstRect.bottom - y);

		const auto dst = D2D1::RectF(x, y, x + w, y + h);
		const auto src = D2D1::RectF(0.0f, 0.0f, w, h);
		DrawBitmap(bitmap, dst, src);

		x += width;
		if (x >= dstRect.right && y < dstRect.bottom)
		{
			x = dstRect.left;
			y += height;
		}
	}
}

void Canvas::DrawMaskedBitmap(const D2DBitmap* bitmap, const D2DBitmap* maskBitmap, const D2D1_RECT_F& dstRect,
	const D2D1_RECT_F& srcRect, const D2D1_RECT_F& srcRect2)
{
	if (!bitmap || !maskBitmap) return;

	// Create bitmap brush from original |bitmap|.
	Microsoft::WRL::ComPtr brush;
	D2D1_BITMAP_BRUSH_PROPERTIES1 propertiesXClampYClamp = D2D1::BitmapBrushProperties1(
		D2D1_EXTEND_MODE_CLAMP,
		D2D1_EXTEND_MODE_CLAMP,
		D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC);

	const FLOAT width = (FLOAT)bitmap->m_Width;
	const FLOAT height = (FLOAT)bitmap->m_Height;

	auto getRectSubRegion = [&width, &height](const D2D1_RECT_F& r1, const D2D1_RECT_F& r2) -> D2D1_RECT_F
	{
		return D2D1::RectF(
			r1.left / width * r2.right + r2.left,
			r1.top / height * r2.bottom + r2.top,
			(r1.right - r1.left) / width * r2.right,
			(r1.bottom - r1.top) / height * r2.bottom);
	};

	for (auto bseg : bitmap->m_Segments)
	{
		const auto rSeg = bseg.GetRect();
		const auto rDst = getRectSubRegion(rSeg, dstRect);
		const auto rSrc = getRectSubRegion(rSeg, srcRect);

		FLOAT s2Width = srcRect2.right - srcRect2.left;
		FLOAT s2Height = srcRect2.bottom - srcRect2.top;

		// "Move" and "scale" the |bitmap| to match the destination.
		D2D1_MATRIX_3X2_F translateMask = D2D1::Matrix3x2F::Translation(-srcRect2.left, -srcRect2.top);
		D2D1_MATRIX_3X2_F translate = D2D1::Matrix3x2F::Translation(rDst.left, rDst.top);
		D2D1_MATRIX_3X2_F scale = D2D1::Matrix3x2F::Scale(
			D2D1::SizeF((rDst.right - rDst.left) / s2Width, (rDst.bottom - rDst.top) / s2Height));
		D2D1_BRUSH_PROPERTIES brushProps = D2D1::BrushProperties(1.0f, translateMask * scale * translate);

		HRESULT hr = m_Target->CreateBitmapBrush(
			bseg.GetBitmap(),
			propertiesXClampYClamp,
			brushProps,
			brush.ReleaseAndGetAddressOf());
		if (FAILED(hr)) return;

		const auto aaMode = m_Target->GetAntialiasMode();
		m_Target->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); // required

		for (auto mseg : maskBitmap->m_Segments)
		{
			const auto rmSeg = mseg.GetRect();
			const auto rmDst = getRectSubRegion(rmSeg, dstRect);
			const auto rmSrc = getRectSubRegion(rmSeg, srcRect);

			// If no overlap, don't draw
			if ((rmDst.left < (rDst.left + rDst.right) &&
				(rmDst.right + rmDst.left) > rDst.left &&
				rmDst.top > (rmDst.top + rmDst.bottom) &&
				(rmDst.top + rmDst.bottom) < rmDst.top)) continue;

			m_Target->FillOpacityMask(
				mseg.GetBitmap(),
				brush.Get(),
				D2D1_OPACITY_MASK_CONTENT_GRAPHICS,
				&rDst,
				&rSrc);
		}

		m_Target->SetAntialiasMode(aaMode);
	}
}

void Canvas::FillRectangle(const D2D1_RECT_F& rect, const D2D1_COLOR_F& color)
{
	Microsoft::WRL::ComPtr solidBrush;
	HRESULT hr = m_Target->CreateSolidColorBrush(color, solidBrush.GetAddressOf());
	if (SUCCEEDED(hr))
	{
		m_Target->FillRectangle(rect, solidBrush.Get());
	}
}

void Canvas::FillGradientRectangle(const D2D1_RECT_F& rect, const D2D1_COLOR_F& color1, const D2D1_COLOR_F& color2, const FLOAT& angle)
{
	// D2D requires 2 points to draw the gradient along where GDI+ just requires a rectangle. To
	// mimic GDI+ for SolidColor2, we have to find and swap the starting and ending points of where
	// the gradient touches edge of the bounding rectangle. Normally we would offset the ending
	// point by 180, but we do this on starting point for SolidColor2. This differs from the other
	// D2D gradient options below:
	//  Gfx::TextInlineFormat_GradientColor::BuildGradientBrushes
	//  Gfx::Shape::CreateLinearGradient
	D2D1_POINT_2F start = Util::FindEdgePoint(angle + 180.0f, rect.left, rect.top, rect.right, rect.bottom);
	D2D1_POINT_2F end = Util::FindEdgePoint(angle, rect.left, rect.top, rect.right, rect.bottom);

	Microsoft::WRL::ComPtr pGradientStops;

	D2D1_GRADIENT_STOP gradientStops[2];
	gradientStops[0].color = color1;
	gradientStops[0].position = 0.0f;
	gradientStops[1].color = color2;
	gradientStops[1].position = 1.0f;

	HRESULT hr = m_Target->CreateGradientStopCollection(
		gradientStops,
		2U,
		D2D1_GAMMA_2_2,
		D2D1_EXTEND_MODE_CLAMP,
		pGradientStops.GetAddressOf());
	if (FAILED(hr)) return;

	Microsoft::WRL::ComPtr brush;
	hr = m_Target->CreateLinearGradientBrush(
		D2D1::LinearGradientBrushProperties(start, end),
		pGradientStops.Get(),
		brush.GetAddressOf());
	if (FAILED(hr)) return;

	m_Target->FillRectangle(rect, brush.Get());
}

void Canvas::DrawLine(const D2D1_COLOR_F& color, FLOAT x1, FLOAT y1, FLOAT x2, FLOAT y2, FLOAT strokeWidth)
{
	Microsoft::WRL::ComPtr solidBrush;
	HRESULT hr = m_Target->CreateSolidColorBrush(color, solidBrush.GetAddressOf());
	if (FAILED(hr)) return;

	m_Target->DrawLine(D2D1::Point2F(x1, y1), D2D1::Point2F(x2, y2), solidBrush.Get(), strokeWidth);
}

void Canvas::DrawGeometry(Shape& shape, int xPos, int yPos)
{
	D2D1_MATRIX_3X2_F worldTransform;
	m_Target->GetTransform(&worldTransform);
	m_Target->SetTransform(
		shape.GetShapeMatrix() *
		D2D1::Matrix3x2F::Translation((FLOAT)xPos, (FLOAT)yPos) *
		worldTransform);

	auto fill = shape.GetFillBrush(m_Target.Get());
	if (fill)
	{
		m_Target->FillGeometry(shape.m_Shape.Get(), fill.Get());
	}

	auto stroke = shape.GetStrokeFillBrush(m_Target.Get());
	if (stroke)
	{
		m_Target->DrawGeometry(
			shape.m_Shape.Get(),
			stroke.Get(),
			shape.m_StrokeWidth,
			shape.m_StrokeStyle.Get());
	}

	m_Target->SetTransform(worldTransform);
}

HRESULT Canvas::CreateRenderTarget()
{
	HRESULT hr = E_FAIL;
	if (c_D2DDevice)
	{
		c_D2DDevice->ClearResources();

		hr = c_D2DDevice->CreateDeviceContext(
			D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS,
			m_Target.ReleaseAndGetAddressOf());
		if (FAILED(hr))
		{
			hr = c_D2DDevice->CreateDeviceContext(
				D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
				m_Target.ReleaseAndGetAddressOf());
		}
	}

	// Hardware accelerated targets have a hard limit to the size of bitmaps they can support.
	// The size will depend on the D3D feature level of the driver used. The WARP software
	// renderer has a limit of 16MP (16*1024*1024 = 16777216).

	// https://docs.microsoft.com/en-us/windows/desktop/direct3d11/overviews-direct3d-11-devices-downlevel-intro#overview-for-each-feature-level
	// Max Texture Dimension
	// D3D_FEATURE_LEVEL_11_1 = 16348
	// D3D_FEATURE_LEVEL_11_0 = 16348
	// D3D_FEATURE_LEVEL_10_1 = 8192
	// D3D_FEATURE_LEVEL_10_0 = 8192
	// D3D_FEATURE_LEVEL_9_3  = 4096
	// D3D_FEATURE_LEVEL_9_2  = 2048
	// D3D_FEATURE_LEVEL_9_1  = 2048

	if (SUCCEEDED(hr))
	{
		m_MaxBitmapSize = m_Target->GetMaximumBitmapSize();
	}

	return hr;
}

bool Canvas::CreateTargetBitmap(UINT32 width, UINT32 height)
{
	HRESULT hr = m_SwapChain->GetBuffer(0U, IID_PPV_ARGS(m_BackBuffer.GetAddressOf()));
	if (FAILED(hr)) return LogComError(hr);

	D2D1_BITMAP_PROPERTIES1 bProps = D2D1::BitmapProperties1(
		D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
		D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED));

	hr = m_Target->CreateBitmapFromDxgiSurface(
		m_BackBuffer.Get(),
		&bProps,
		m_TargetBitmap.GetAddressOf());
	if (FAILED(hr)) return LogComError(hr);

	m_Target->SetTarget(m_TargetBitmap.Get());
	return true;
}

}  // namespace Gfx


© Habrahabr.ru