...

воскресенье, 26 июля 2020 г.

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

/* 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 <https://www.gnu.org/licenses/gpl-2.0.html>. */

#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<ID3D11Device> Canvas::c_D3DDevice;
Microsoft::WRL::ComPtr<ID3D11DeviceContext> Canvas::c_D3DContext;
Microsoft::WRL::ComPtr<ID2D1Device> Canvas::c_D2DDevice;
Microsoft::WRL::ComPtr<IDXGIDevice1> Canvas::c_DxgiDevice;
Microsoft::WRL::ComPtr<ID2D1Factory1> Canvas::c_D2DFactory;
Microsoft::WRL::ComPtr<IDWriteFactory1> Canvas::c_DWFactory;
Microsoft::WRL::ComPtr<IWICImagingFactory> 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<IDXGIAdapter> 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<IDXGIFactory2> 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<ID2D1SolidColorBrush> 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<ID2D1BitmapBrush1> 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<ID2D1SolidColorBrush> 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<ID2D1GradientStopCollection> 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<ID2D1LinearGradientBrush> 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<ID2D1SolidColorBrush> 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

Let's block ads! (Why?)

Комментариев нет:

Отправить комментарий