rainmeter-studio/Common/Gfx/CanvasD2D.cpp
Birunthan Mohanathas 5f03d7b235 Gfx: Fix rendering issues in some cases
It seems that Direct2D does not expect the underlying pixel data of the render target to change outside of the target draw commands while the render target exists. As a result, Direct2D may in some cases overwrite changes to the pixel data. This change changes the render target to be (re)created for each batch of Direct2D drawing operations as a temporary solution. As a side effect, a DIB section is now used as the render target bitmap.
2013-03-26 21:29:05 +02:00

393 lines
9.8 KiB
C++

/*
Copyright (C) 2013 Birunthan Mohanathas
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "CanvasD2D.h"
#include "TextFormatD2D.h"
#include "WICBitmapLockGDIP.h"
#include "../../Library/Litestep.h"
template<class T>
inline void SafeRelease(T** t)
{
if (*t)
{
(*t)->Release();
*t = nullptr;
}
}
namespace {
D2D1_COLOR_F ToColorF(const Gdiplus::Color& color)
{
return D2D1::ColorF(color.GetR() / 255.0f, color.GetG() / 255.0f, color.GetB() / 255.0f, color.GetA() / 255.0f);
}
D2D1_RECT_F ToRectF(const Gdiplus::Rect& rect)
{
return D2D1::RectF(rect.X, rect.Y, rect.X + rect.Width, rect.Y + rect.Height);
}
D2D1_RECT_F ToRectF(const Gdiplus::RectF& rect)
{
return D2D1::RectF(rect.X, rect.Y, rect.X + rect.Width, rect.Y + rect.Height);
}
} // namespace
namespace Gfx {
UINT CanvasD2D::c_Instances = 0;
ID2D1Factory* CanvasD2D::c_D2D = nullptr;
IDWriteFactory* CanvasD2D::c_DW = nullptr;
IWICImagingFactory* CanvasD2D::c_WIC = nullptr;
CanvasD2D::CanvasD2D() : Canvas(),
m_Target(),
m_Bitmap(),
m_GdipGraphics(),
m_GdipBitmap(),
m_TextAntiAliasing(false)
{
Initialize();
}
CanvasD2D::~CanvasD2D()
{
DiscardDeviceResources();
Finalize();
}
bool CanvasD2D::Initialize()
{
++c_Instances;
if (c_Instances == 1)
{
D2D1_FACTORY_OPTIONS fo = {};
#ifdef _DEBUG
fo.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif
HRESULT hr = D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
fo,
&c_D2D);
if (FAILED(hr)) return false;
hr = CoCreateInstance(
CLSID_WICImagingFactory,
nullptr,
CLSCTX_INPROC_SERVER,
IID_IWICImagingFactory,
(LPVOID*)&c_WIC);
if (FAILED(hr)) return false;
hr = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),
(IUnknown**)&c_DW);
if (FAILED(hr)) return false;
}
return true;
}
void CanvasD2D::Finalize()
{
--c_Instances;
if (c_Instances == 0)
{
SafeRelease(&c_D2D);
SafeRelease(&c_WIC);
SafeRelease(&c_DW);
}
}
void CanvasD2D::DiscardDeviceResources()
{
SafeRelease(&m_Target);
delete m_GdipGraphics;
m_GdipGraphics = nullptr;
delete m_GdipBitmap;
m_GdipBitmap = nullptr;
}
void CanvasD2D::Resize(int w, int h)
{
__super::Resize(w, h);
DiscardDeviceResources();
m_Bitmap.Resize(w, h);
m_GdipBitmap = new Gdiplus::Bitmap(w, h, w * 4, PixelFormat32bppPARGB, m_Bitmap.GetData());
m_GdipGraphics = new Gdiplus::Graphics(m_GdipBitmap);
}
bool CanvasD2D::BeginDraw()
{
return true;
}
void CanvasD2D::EndDraw()
{
EndTargetDraw();
}
bool CanvasD2D::BeginTargetDraw()
{
if (m_Target) return true;
const D2D1_PIXEL_FORMAT format = D2D1::PixelFormat(
DXGI_FORMAT_B8G8R8A8_UNORM,
D2D1_ALPHA_MODE_PREMULTIPLIED);
const D2D1_RENDER_TARGET_PROPERTIES properties = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
format,
0.0f, // Default DPI
0.0f, // Default DPI
D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE);
// A new Direct2D render target must be created for each sequence of Direct2D draw operations
// since we use GDI+ to render to the same pixel data. Without creating a new render target
// each time, it has been found that Direct2D may overwrite the draws by GDI+ since it is
// unaware of the changes made by GDI+. By creating a new render target and then releasing it
// before the next GDI+ draw operations, we ensure that the pixel data result is as expected
// Once GDI+ drawing is no longer needed, we change to recreate the render target only when the
// bitmap size is changed.
HRESULT hr = c_D2D->CreateWicBitmapRenderTarget(&m_Bitmap, properties, &m_Target);
if (SUCCEEDED(hr))
{
SetTextAntiAliasing(m_TextAntiAliasing);
m_Target->BeginDraw();
return true;
}
return false;
}
void CanvasD2D::EndTargetDraw()
{
if (m_Target)
{
m_Target->EndDraw();
SafeRelease(&m_Target);
}
}
Gdiplus::Graphics& CanvasD2D::BeginGdiplusContext()
{
EndTargetDraw();
return *m_GdipGraphics;
}
void CanvasD2D::EndGdiplusContext()
{
}
HDC CanvasD2D::GetDC()
{
EndTargetDraw();
HDC dcMemory = CreateCompatibleDC(nullptr);
SelectObject(dcMemory, m_Bitmap.GetHandle());
return dcMemory;
}
void CanvasD2D::ReleaseDC(HDC dc)
{
DeleteDC(dc);
}
bool CanvasD2D::IsTransparentPixel(int x, int y)
{
if (!(x >= 0 && y >= 0 && x < m_W && y < m_H)) return false;
bool transparent = true;
DWORD* data = (DWORD*)m_Bitmap.GetData();
if (data)
{
DWORD pixel = data[y * m_W + x]; // Top-down DIB.
transparent = (pixel & 0xFF000000) != 0;
}
return transparent;
}
void CanvasD2D::SetAntiAliasing(bool enable)
{
// TODO: Set m_Target aliasing?
m_GdipGraphics->SetSmoothingMode(
enable ? Gdiplus::SmoothingModeHighQuality : Gdiplus::SmoothingModeNone);
m_GdipGraphics->SetPixelOffsetMode(
enable ? Gdiplus::PixelOffsetModeHighQuality : Gdiplus::PixelOffsetModeDefault);
}
void CanvasD2D::SetTextAntiAliasing(bool enable)
{
// TODO: Add support for D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE?
m_TextAntiAliasing = enable;
if (m_Target)
{
m_Target->SetTextAntialiasMode(
m_TextAntiAliasing ? D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE : D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
}
}
void CanvasD2D::Clear(const Gdiplus::Color& color)
{
if (!BeginTargetDraw()) return;
m_Target->Clear(ToColorF(color));
}
void CanvasD2D::DrawTextW(const WCHAR* str, UINT strLen, const TextFormat& format, Gdiplus::RectF& rect, const Gdiplus::SolidBrush& brush)
{
if (!BeginTargetDraw()) return;
Gdiplus::Color color;
brush.GetColor(&color);
ID2D1SolidColorBrush* solidBrush;
m_Target->CreateSolidColorBrush(ToColorF(color), &solidBrush);
bool right = ((TextFormatD2D&)format).GetHorizontalAlignment() == Gfx::HorizontalAlignment::Right;
// TODO: Draw cached layout?
//m_Target->DrawTextLayout(
// D2D1::Point2F(right ? rect.X - 2 : rect.X + 2.0f, rect.Y - 1.0f),
// textLayout,
// solidBrush);
m_Target->DrawTextW(
str,
strLen,
((TextFormatD2D&)format).m_TextFormat,
D2D1::RectF(right ? rect.X - 2 : rect.X + 2.0f, rect.Y - 1.0f, (right ? rect.X - 2 : rect.X + 2.0f) + rect.Width, rect.Y + rect.Height - 1.0f),
solidBrush,
D2D1_DRAW_TEXT_OPTIONS_NONE);
solidBrush->Release();
}
bool CanvasD2D::MeasureTextW(const WCHAR* str, UINT strLen, const TextFormat& format, Gdiplus::RectF& rect)
{
IDWriteTextLayout* textLayout;
HRESULT hr = c_DW->CreateTextLayout(
str,
strLen,
((TextFormatD2D&)format).m_TextFormat,
10000,
10000,
&textLayout);
if (SUCCEEDED(hr))
{
DWRITE_TEXT_METRICS metrics;
textLayout->GetMetrics(&metrics);
rect.Width = metrics.width + 5.0f;
rect.Height = metrics.height + 1.0f; // 1.0f to get same result as GDI+.
textLayout->Release();
return true;
}
return false;
}
bool CanvasD2D::MeasureTextLinesW(const WCHAR* str, UINT strLen, const TextFormat& format, Gdiplus::RectF& rect, UINT& lines)
{
((TextFormatD2D&)format).m_TextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP);
IDWriteTextLayout* textLayout;
HRESULT hr = c_DW->CreateTextLayout(
str,
strLen,
((TextFormatD2D&)format).m_TextFormat,
rect.Width,
10000,
&textLayout);
if (SUCCEEDED(hr))
{
DWRITE_TEXT_METRICS metrics;
textLayout->GetMetrics(&metrics);
rect.Width = metrics.width + 5.0f;
rect.Height = metrics.height + 1.0f; // 1.0f to get same result as GDI+.
lines = metrics.lineCount;
textLayout->Release();
return true;
}
return false;
}
void CanvasD2D::DrawBitmap(Gdiplus::Bitmap* bitmap, const Gdiplus::Rect& dstRect, const Gdiplus::Rect& srcRect)
{
if (!BeginTargetDraw()) return;
// The D2D DrawBitmap seems to perform exactly like Gdiplus::Graphics::DrawImage since we are
// not using a hardware accelerated render target. Nevertheless, we will use it to avoid
// the EndDraw() call needed for GDI+ drawing.
WICBitmapLockGDIP* bitmapLock = new WICBitmapLockGDIP();
Gdiplus::Status status = bitmap->LockBits(
&srcRect, Gdiplus::ImageLockModeRead, PixelFormat32bppPARGB, bitmapLock->GetBitmapData());
if (status == Gdiplus::Ok)
{
D2D1_BITMAP_PROPERTIES props = D2D1::BitmapProperties(
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED));
ID2D1Bitmap* d2dBitmap;
HRESULT hr = m_Target->CreateSharedBitmap(__uuidof(IWICBitmapLock), bitmapLock, &props, &d2dBitmap);
if (SUCCEEDED(hr))
{
auto rDst = ToRectF(dstRect);
auto rSrc = ToRectF(srcRect);
m_Target->DrawBitmap(d2dBitmap, rDst, 1.0F, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, rSrc);
d2dBitmap->Release();
}
// D2D will still use the pixel data after this call (at the next Flush() or EndDraw()).
bitmap->UnlockBits(bitmapLock->GetBitmapData());
}
bitmapLock->Release();
}
void CanvasD2D::FillRectangle(Gdiplus::Rect& rect, const Gdiplus::SolidBrush& brush)
{
if (!BeginTargetDraw()) return;
Gdiplus::Color color;
brush.GetColor(&color);
ID2D1SolidColorBrush* solidBrush;
m_Target->CreateSolidColorBrush(ToColorF(color), &solidBrush);
m_Target->FillRectangle(ToRectF(rect), solidBrush);
solidBrush->Release();
}
} // namespace Gfx