Merge branch 'd2d-no-copy' into 'master'

Draft: Implement zero-copy rendering path for Direct2D

See merge request cairo/cairo!607
This commit is contained in:
Luca Bacci 2026-03-03 17:51:08 +00:00
commit 9d5d038674
5 changed files with 3000 additions and 17 deletions

View file

@ -181,6 +181,7 @@ cairo_feature_sources = {
],
'cairo-dwrite-font': [
'win32/cairo-dwrite-font.cpp',
'win32/wicbitmapfordata.cpp',
],
'cairo-script': [
'cairo-script-surface.c',

View file

@ -46,6 +46,7 @@
#include "cairo-truetype-subset-private.h"
#include "cairo-scaled-font-subsets-private.h"
#include "cairo-dwrite.h"
#include "wicbitmapfordata.hpp"
#include <utility>
#include <float.h>
@ -1201,18 +1202,30 @@ _cairo_dwrite_scaled_font_init_glyph_color_surface(cairo_dwrite_scaled_font_t *s
if (FAILED(hr))
return _cairo_dwrite_error (hr, "TranslateColorGlyphRun failed");
/* We have a color glyph(s). Use Direct2D to render it to a bitmap */
// We have a color glyph(s). Use Direct2D to render it to a bitmap
// Technically we don't need to initialize WIC, because we use our
// own IWICBitmap inmplementation. However it's unclear whether
// Direct2D uses other parts of WIC internally, and may assume that
// COM is initialized. Technically, it shouldn't assume anything
// about COM, since WIC can be used in a COM-free way via proxy
// functions, but better be safe.
if (!WICImagingFactory::Instance() || !D2DFactory::Instance())
return _cairo_dwrite_error (hr, "Instance failed");
RefPtr<IWICBitmap> bitmap;
hr = WICImagingFactory::Instance()->CreateBitmap ((UINT)width,
(UINT)height,
GUID_WICPixelFormat32bppPBGRA,
WICBitmapCacheOnLoad,
&bitmap);
if (FAILED(hr))
return _cairo_dwrite_error (hr, "CreateBitmap failed");
cairo_surface_t *image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
unsigned char *data = cairo_image_surface_get_data (image);
int stride = cairo_image_surface_get_stride (image);
WICBitmapForData bitmap(data,
static_cast<UINT> (width),
static_cast<UINT> (height),
static_cast<UINT> (stride),
4,
GUID_WICPixelFormat32bppPBGRA,
nullptr);
//if (bitmap.in_error ())
// return (cairo_int_status_t)_cairo_error (CAIRO_STATUS_DWRITE_ERROR);
D2D1_RENDER_TARGET_PROPERTIES properties = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
@ -1225,7 +1238,7 @@ _cairo_dwrite_scaled_font_init_glyph_color_surface(cairo_dwrite_scaled_font_t *s
D2D1_FEATURE_LEVEL_DEFAULT);
RefPtr<ID2D1RenderTarget> rt;
hr = D2DFactory::Instance()->CreateWicBitmapRenderTarget (bitmap, properties, &rt);
hr = D2DFactory::Instance()->CreateWicBitmapRenderTarget (&bitmap, properties, &rt);
if (FAILED(hr))
return _cairo_dwrite_error (hr, "CreateWicBitmapRenderTarget failed");
@ -1339,13 +1352,8 @@ _cairo_dwrite_scaled_font_init_glyph_color_surface(cairo_dwrite_scaled_font_t *s
if (FAILED(hr))
return _cairo_dwrite_error (hr, "EndDraw failed");
cairo_surface_t *image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
int stride = cairo_image_surface_get_stride (image);
WICRect rect = { 0, 0, width, height };
bitmap->CopyPixels(&rect,
stride,
height * stride,
cairo_image_surface_get_data (image));
//dc4->Flush ();
cairo_surface_mark_dirty (image);
cairo_surface_set_device_offset (image, -x1, -y1);
_cairo_scaled_glyph_set_color_surface (scaled_glyph,

2062
src/win32/optional.hpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,597 @@
/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */
/* Cairo - a vector graphics library with display and print output
*
* Copyright © 2025 Luca Bacci
*
* This library is free software; you can redistribute it and/or
* modify it either under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation
* (the "LGPL") or, at your option, under the terms of the Mozilla
* Public License Version 1.1 (the "MPL"). If you do not alter this
* notice, a recipient may use your version of this file under either
* the MPL or the LGPL.
*
* You should have received a copy of the LGPL along with this library
* in the file COPYING-LGPL-2.1; if not, write to the Free Software
* Foundation < licensing@fsf.org >. You should have received a copy of
* the MPL along with this library in the file COPYING-MPL-1.1
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* https://www.mozilla.org/MPL/
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
* OF ANY KIND, either express or implied. See the LGPL or the MPL for
* the specific language governing rights and limitations.
*
* The Original Code is the cairo graphics library.
*
* Contributor(s):
* Luca Bacci <luca.bacci@outlook.com>
*/
#include "cairoint.h"
#include "wicbitmapfordata.hpp"
#include <windows.h>
#include <wincodec.h>
#include <cstdlib>
#include <cstdio>
#include <cstdint>
#include <cstring>
#include <cstddef>
#include <cassert>
#include <intrin.h>
#include <new>
#if _WIN32_WINNT < _WIN32_WINNT_WIN8
# define CHECK_FAST_FAIL IsProcessorFeaturePresent (PF_FASTFAIL_AVAILABLE)
#else
# define CHECK_FAST_FAIL 1
#endif
#ifndef FAST_FAIL_INVALID_REFERENCE_COUNT
#define FAST_FAIL_INVALID_REFERENCE_COUNT 14
#endif
constexpr double DEFAULT_DPI = 96.0;
static void
print_unhandled_iid (const char *context,
REFIID riid);
thread_local ULONG
WICBitmapForData::s_thread_instances_count;
thread_local ULONG
WICBitmapLockForData::s_thread_instances_count;
// WICBitmapForData
WICBitmapForData::WICBitmapForData (unsigned char * const data,
UINT width,
UINT height,
UINT stride,
UINT pixel_size,
WICPixelFormatGUID wic_pixel_format,
deleter_t deleter) noexcept
: m_data (data)
, m_width (width)
, m_height (height)
, m_stride (stride)
, m_pixel_size (pixel_size)
, m_wic_pixel_format (wic_pixel_format)
, m_dpi_x (DEFAULT_DPI)
, m_dpi_y (DEFAULT_DPI)
, m_write_lock ()
, m_read_locks_count (0)
, m_reference_count (deleter ? 1 : 0)
, m_deleter (deleter)
, m_thread_id (GetCurrentThreadId ())
{
assert (m_data != nullptr);
assert (m_width != 0);
assert (m_height != 0);
assert (m_pixel_size != 0);
assert (m_stride / m_pixel_size >= m_width);
assert (UINT_MAX / stride >= m_height);
// Technically, should be m_stride * (m_height - 1) + m_row_size
assert (UINTPTR_MAX - (std::uintptr_t)m_data >= m_stride * m_height);
s_thread_instances_count++;
}
WICBitmapForData::~WICBitmapForData () noexcept
{
method_check ();
assert (!have_locks ());
assert (m_reference_count == 0);
s_thread_instances_count--;
}
IFACEMETHODIMP
WICBitmapForData::QueryInterface (REFIID riid,
void **ppvObject) noexcept
{
method_check ();
if (riid == IID_IUnknown ||
riid == IID_IWICBitmapSource ||
riid == IID_IWICBitmap)
{
AddRef();
*ppvObject = this;
return S_OK;
}
print_unhandled_iid ("WICBitmapForData", riid);
*ppvObject = nullptr;
return E_NOINTERFACE;
}
IFACEMETHODIMP_(ULONG)
WICBitmapForData::AddRef () noexcept
{
method_check ();
if (m_reference_count >= s_reference_count_limit) {
if (CHECK_FAST_FAIL) {
__fastfail (FAST_FAIL_INVALID_REFERENCE_COUNT);
}
std::abort ();
}
m_reference_count++;
return m_reference_count;
}
IFACEMETHODIMP_(ULONG)
WICBitmapForData::Release () noexcept
{
method_check ();
if (m_reference_count == 0 || m_reference_count > s_reference_count_limit) {
if (CHECK_FAST_FAIL) {
__fastfail (FAST_FAIL_INVALID_REFERENCE_COUNT);
}
std::abort ();
}
m_reference_count--;
assert (m_reference_count >= m_read_locks_count);
if (m_reference_count == 0) {
if (m_deleter) {
m_deleter (this);
}
// Note: the object has been destroyed, do not access any
// member method, call any member function, or use this.
return 0;
}
return m_reference_count;
}
// IWICBitmapSource
IFACEMETHODIMP
WICBitmapForData::CopyPalette (IWICPalette *pIPalette) noexcept
{
method_check ();
return WINCODEC_ERR_PALETTEUNAVAILABLE;
}
IFACEMETHODIMP
WICBitmapForData::CopyPixels (const WICRect *prc,
UINT cbStride,
UINT cbBufferSize,
BYTE *pbBuffer) noexcept
{
method_check ();
if (have_write_locks ()) {
return WINCODEC_ERR_ALREADYLOCKED;
}
UINT x = 0;
UINT y = 0;
UINT width = m_width;
UINT height = m_height;
if (prc) {
if (!check_sub_rectangle (*prc))
return E_INVALIDARG;
x = static_cast<UINT> (prc->X);
y = static_cast<UINT> (prc->Y);
width = static_cast<UINT> (prc->Width);
height = static_cast<UINT> (prc->Height);
}
const unsigned char *src_iter = get_address_for_point (x, y);
unsigned char *dst_iter = pbBuffer;
const UINT row_size = m_width * m_pixel_size;
for (UINT i = 0; i < height; i++) {
std::memcpy (dst_iter, src_iter, row_size);
src_iter += m_stride;
dst_iter += cbStride;
}
return S_OK;
}
IFACEMETHODIMP
WICBitmapForData::GetPixelFormat (WICPixelFormatGUID *pPixelFormat) noexcept
{
method_check ();
*pPixelFormat = m_wic_pixel_format;
return S_OK;
}
IFACEMETHODIMP
WICBitmapForData::GetResolution (double *pDpiX,
double *pDpiY) noexcept
{
method_check ();
*pDpiX = m_dpi_x;
*pDpiY = m_dpi_y;
return S_OK;
}
IFACEMETHODIMP
WICBitmapForData::GetSize (UINT *puiWidth,
UINT *puiHeight) noexcept
{
method_check ();
*puiWidth = m_width;
*puiHeight = m_height;
return S_OK;
}
IFACEMETHODIMP
WICBitmapForData::Lock (const WICRect *prcLock,
DWORD flags,
IWICBitmapLock **ppILock) noexcept
{
method_check ();
*ppILock = nullptr;
UINT x = 0;
UINT y = 0;
UINT width = m_width;
UINT height = m_height;
if (prcLock) {
if (!check_sub_rectangle (*prcLock))
return E_INVALIDARG;
x = static_cast<UINT> (prcLock->X);
y = static_cast<UINT> (prcLock->Y);
width = static_cast<UINT> (prcLock->Width);
height = static_cast<UINT> (prcLock->Height);
}
const bool is_write_lock = (flags & WICBitmapLockWrite) != 0;
if (is_write_lock) {
if (have_locks ()) {
return WINCODEC_ERR_ALREADYLOCKED;
}
AddRef ();
const auto& deleter = [](WICBitmapLockForData *lock) noexcept {
WICBitmapForData& bitmap = lock->m_parent_bitmap;
assert (bitmap.m_write_lock.has_value ());
bitmap.m_write_lock.reset ();
bitmap.Release ();
};
// There can only be one write lock, and we made it a member of this class
m_write_lock.emplace(*this, x, y, width, height, deleter);
*ppILock = &m_write_lock.value ();
}
else {
if (have_write_locks ()) {
return WINCODEC_ERR_ALREADYLOCKED;
}
if (m_read_locks_count >= s_reference_count_limit - 1) {
if (CHECK_FAST_FAIL) {
__fastfail (FAST_FAIL_INVALID_REFERENCE_COUNT);
}
std::abort ();
}
AddRef ();
m_read_locks_count++;
const auto& deleter = [](WICBitmapLockForData *lock) noexcept {
WICBitmapForData& bitmap = lock->m_parent_bitmap;
delete lock;
lock = nullptr;
if (bitmap.m_read_locks_count == 0) {
if (CHECK_FAST_FAIL) {
__fastfail (FAST_FAIL_INVALID_REFERENCE_COUNT);
}
std::abort ();
}
bitmap.m_read_locks_count--;
bitmap.Release ();
};
// Read locks are allocated on the heap
*ppILock = new (std::nothrow) WICBitmapLockForData (*this, x, y, width, height, deleter);
if (*ppILock == nullptr) {
Release ();
m_read_locks_count--;
return E_OUTOFMEMORY;
}
}
return S_OK;
}
IFACEMETHODIMP
WICBitmapForData::SetPalette (IWICPalette *pIPalette) noexcept
{
method_check ();
return WINCODEC_ERR_PALETTEUNAVAILABLE;
}
IFACEMETHODIMP
WICBitmapForData::SetResolution (double dpiX,
double dpiY) noexcept
{
method_check ();
m_dpi_x = dpiX;
m_dpi_y = dpiY;
return S_OK;
}
unsigned char *
WICBitmapForData::get_address_for_point (UINT x, UINT y) const noexcept
{
static_assert (sizeof (*m_data) == 1, "");
return m_data + (y * m_stride) + (m_pixel_size * x);
}
bool
WICBitmapForData::check_sub_rectangle (const WICRect& rectangle) const noexcept
{
return rectangle.X >= 0 &&
rectangle.Y >= 0 &&
rectangle.Width > 0 &&
rectangle.Height > 0 &&
(UINT)rectangle.X < m_width &&
(UINT)rectangle.Y < m_height &&
m_width - (UINT)rectangle.X <= (UINT)rectangle.Width &&
m_height - (UINT)rectangle.Y <= (UINT)rectangle.Height;
}
bool
WICBitmapForData::have_write_locks () const noexcept
{
return m_write_lock.has_value ();
}
bool
WICBitmapForData::have_read_locks () const noexcept
{
return m_read_locks_count != 0;
}
bool
WICBitmapForData::have_locks () const noexcept
{
return have_read_locks () || have_write_locks ();
}
void
WICBitmapForData::method_check () const noexcept
{
assert (m_thread_id == GetCurrentThreadId ());
}
// WICBitmapLockForData
WICBitmapLockForData::WICBitmapLockForData (WICBitmapForData& parent_bitmap,
UINT x,
UINT y,
UINT width,
UINT height,
deleter_t deleter) noexcept
: m_parent_bitmap (parent_bitmap)
, m_x (x)
, m_y (y)
, m_width (width)
, m_height (height)
, m_reference_count (1)
, m_deleter (deleter)
, m_thread_id (GetCurrentThreadId ())
{
assert (m_x < m_parent_bitmap.m_width);
assert (m_y < m_parent_bitmap.m_height);
assert (m_width <= m_parent_bitmap.m_width);
assert (m_height <= m_parent_bitmap.m_height);
assert (m_parent_bitmap.m_width - m_width >= m_x);
assert (m_parent_bitmap.m_height - m_height >= m_y);
s_thread_instances_count++;
}
WICBitmapLockForData::~WICBitmapLockForData () noexcept
{
method_check ();
assert (m_reference_count == 0);
s_thread_instances_count--;
}
IFACEMETHODIMP
WICBitmapLockForData::QueryInterface (REFIID riid,
void **ppvObject) noexcept
{
method_check ();
if (riid == IID_IUnknown ||
riid == IID_IWICBitmapLock)
{
AddRef();
*ppvObject = this;
return S_OK;
}
print_unhandled_iid ("WICBitmapLockForData", riid);
*ppvObject = nullptr;
return E_NOINTERFACE;
}
IFACEMETHODIMP_(ULONG)
WICBitmapLockForData::AddRef () noexcept
{
method_check ();
if (m_reference_count >= s_reference_count_limit) {
if (CHECK_FAST_FAIL) {
__fastfail (FAST_FAIL_INVALID_REFERENCE_COUNT);
}
std::abort ();
}
m_reference_count++;
return m_reference_count;
}
IFACEMETHODIMP_(ULONG)
WICBitmapLockForData::Release () noexcept
{
method_check ();
if (m_reference_count == 0) {
if (CHECK_FAST_FAIL) {
__fastfail (FAST_FAIL_INVALID_REFERENCE_COUNT);
}
std::abort ();
}
m_reference_count--;
if (m_reference_count == 0) {
if (m_deleter) {
m_deleter (this);
}
// Note: the object has been destroyed, do not access any
// member method, call any member function, or use this.
return 0;
}
return m_reference_count;
}
IFACEMETHODIMP
WICBitmapLockForData::GetDataPointer (UINT *pcbBufferSize,
WICInProcPointer *ppbData) noexcept
{
method_check ();
const UINT stride = m_parent_bitmap.m_stride;
const UINT pixel_size = m_parent_bitmap.m_pixel_size;
*pcbBufferSize = stride * (m_height - 1) + (pixel_size * m_width);
*ppbData = m_parent_bitmap.get_address_for_point (m_x, m_y);
return S_OK;
}
IFACEMETHODIMP
WICBitmapLockForData::GetPixelFormat (WICPixelFormatGUID *pPixelFormat) noexcept
{
method_check ();
*pPixelFormat = m_parent_bitmap.m_wic_pixel_format;
return S_OK;
}
IFACEMETHODIMP
WICBitmapLockForData::GetSize (UINT *puiWidth,
UINT *puiHeight) noexcept
{
method_check ();
*puiWidth = m_width;
*puiHeight = m_height;
return S_OK;
}
IFACEMETHODIMP
WICBitmapLockForData::GetStride (UINT *pcbStride) noexcept
{
method_check ();
*pcbStride = m_parent_bitmap.m_stride;
return S_OK;
}
void
WICBitmapLockForData::method_check () const noexcept
{
assert (m_thread_id == GetCurrentThreadId ());
}
// Utils
static void
print_unhandled_iid (const char *context,
REFIID riid)
{
static_assert (alignof (GUID) % sizeof (unsigned short) == 0, "");
static_assert (offsetof (GUID, Data4) % sizeof (unsigned short) == 0, "");
unsigned short *data4 = (unsigned short *)riid.Data4;
std::fprintf (stderr,
"%s: %s {%08lX-%04hX-%04hX-%04hX-%04hX%04hX%04hX}\n",
context, "Could not handle QueryInterface for IID",
riid.Data1, riid.Data2, riid.Data3,
data4[0], data4[1], data4[2], data4[3]);
}

View file

@ -0,0 +1,315 @@
/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */
/* Cairo - a vector graphics library with display and print output
*
* Copyright © 2025 Luca Bacci
*
* This library is free software; you can redistribute it and/or
* modify it either under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation
* (the "LGPL") or, at your option, under the terms of the Mozilla
* Public License Version 1.1 (the "MPL"). If you do not alter this
* notice, a recipient may use your version of this file under either
* the MPL or the LGPL.
*
* You should have received a copy of the LGPL along with this library
* in the file COPYING-LGPL-2.1; if not, write to the Free Software
* Foundation, Inc., 31 Milk Street, # 960789 Boston, MA 02196 USA.
* You should have received a copy of the MPL along with this library
* in the file COPYING-MPL-1.1
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* https://www.mozilla.org/MPL/
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
* OF ANY KIND, either express or implied. See the LGPL or the MPL for
* the specific language governing rights and limitations.
*
* The Original Code is the cairo graphics library.
*
* Contributor(s):
* Luca Bacci <luca.bacci@outlook.com>
*/
#pragma once
#include "cairoint.h"
#include <limits>
#include <windows.h>
#include <wincodec.h>
#include "optional.hpp"
/* WICBitmapForData (bitmap), WICBitmapLockForData (lock).
*
* WICBitmapForData is an implementation of IWICBitmap that wraps an
* existing memory buffer. This is needed to make Direct2D render
* directly on in-memory graphics data that is created externally,
* provided that pixel formats match. Note that WIC supports many
* pixel formats natively [1], but Direct2D WIC render targets support
* only a few selected formats [2].
*
* Constructor:
*
* WICBitmapForData (unsigned char * const data,
* UINT width,
* UINT height,
* UINT stride,
* UINT pixel_size,
* WICPixelFormatGUID wic_pixel_format,
* deleter_t deleter);
*
* @data: pointer to the memory buffer containing graphics data
*
* @width: the image width (cannot be zero)
*
* @height: the image height (cannot be zero)
*
* @stride: the size (in bytes) to move from one row to the next
*
* @pixel_size: pizel size (in bytes). As this argument is a byte size,
* WICBitmapForData cannot support packed pixel formats
* (e.g A1). Supporting such formats would complicate the
* implementation, and is not needed for Direct2D anyway.
*
* @wic_pixel_format: the WICPixelFormatGUID of the image data
*
* @deleter: a deleter callback with signature:
*
* void (*)(WICBitmapForData*) noexcept
*
* It will be called once the reference count of the object
* drops to zero. If lifetime should be managed externally,
* pass nullptr.
*
* Notes:
*
* The pixel size could be derived automatically from the pixel format
* GUID, however that's a quite elaborated process, which involve
* potential runtime activation of COM objects due to the extensible
* nature of WIC. That can be slow to do repeatedly, and renders the
* constructor failable, adding complexity.
*
* WICBitmapForData can be allocated by whatever mean, thanks to the
* deleter construct-time argument. This was added because most COM
* objects trigger their destruction internally from the Release()
* method, however, the object doesn't know how the user allocated it.
* The user passes a deleter callback which is responsible for deleting
* the instance.
*
* In some cases, lifetime (deletion) of the object is outside the control
* of the object user; for example, when the object is allocated on the
* stack or within another structure that it's not owned. In such cases,
* a nullptr deleter can ber specified. Internal reference counting is
* kept for debugging purposes, but won't trigger destruction. The user
* need not drop its own initial reference count.
*
* WICBitmapForData only supports pixel formats where pixels are byte-aligned.
* That is, bits-per-pixel must be a multiple of eight. See the @pixel_size
* argument description.
*
* WICBitmapForData is meant for use from a single thread only.
*
* References:
*
* [1] - https://learn.microsoft.com/en-us/windows/win32/wic/-wic-codec-native-pixel-formats
* [2] - https://learn.microsoft.com/en-us/windows/win32/direct2d/supported-pixel-formats-and-alpha-modes#supported-formats-for-wic-bitmap-render-target
*/
class WICBitmapForData;
class WICBitmapLockForData;
#ifndef _MSC_VER
// mingw-w64 headers don't have WICInProcPointer
typedef BYTE* WICInProcPointer;
#endif
// We define the lock class first, as we want to make the
// lock class (write lock) a member of the bitmap class.
// https://stackoverflow.com/questions/27489558/how-do-i-create-an-alias-for-a-noexcept-function-pointer
void lock_deleter_prototype (WICBitmapLockForData*) noexcept;
class WICBitmapLockForData final
: public IWICBitmapLock
{
public:
// Cannot use noexcept in using declarations, so we use
// a function declaration and decltype as a workaround.
using deleter_t = decltype (&lock_deleter_prototype);
// TODO: Use a functor / callable object
// Constructor
explicit WICBitmapLockForData (WICBitmapForData& parent_bitmap,
UINT x,
UINT y,
UINT width,
UINT height,
deleter_t deleter) noexcept;
public:
// Disable copy
WICBitmapLockForData (const WICBitmapLockForData&) = delete;
WICBitmapLockForData& operator= (const WICBitmapLockForData&) = delete;
// Destructor
~WICBitmapLockForData () noexcept;
// IUnknown
IFACEMETHOD (QueryInterface) (REFIID riid,
void **ppvObject) noexcept override;
IFACEMETHOD_(ULONG, AddRef) () noexcept override;
IFACEMETHOD_(ULONG, Release) () noexcept override;
// IWICBitmapLock
IFACEMETHOD (GetDataPointer) (UINT *pcbBufferSize,
WICInProcPointer *ppbData) noexcept override;
IFACEMETHOD (GetPixelFormat) (WICPixelFormatGUID *pPixelFormat) noexcept override;
IFACEMETHOD (GetSize) (UINT *puiWidth,
UINT *puiHeight) noexcept override;
IFACEMETHOD (GetStride) (UINT *pcbStride) noexcept override;
private:
void method_check () const noexcept;
private:
WICBitmapForData& m_parent_bitmap;
const UINT m_x;
const UINT m_y;
const UINT m_width;
const UINT m_height;
ULONG m_reference_count;
const deleter_t m_deleter;
const DWORD m_thread_id;
friend class WICBitmapForData;
private:
static constexpr ULONG s_reference_count_limit {
std::numeric_limits<ULONG>::max()
};
static thread_local ULONG s_thread_instances_count;
};
void bitmap_deleter_prototype (WICBitmapForData*) noexcept;
class WICBitmapForData final
: public IWICBitmap
{
public:
// Disable copy
WICBitmapForData (const WICBitmapForData&) = delete;
WICBitmapForData& operator= (const WICBitmapForData&) = delete;
// Cannot use noexcept in using declarations, so we use
// a function declaration and decltype as a workaround.
using deleter_t = decltype (&bitmap_deleter_prototype);
// TODO: Use a functor / callable object
// Constructor
explicit WICBitmapForData (unsigned char * const data,
UINT width,
UINT height,
UINT stride,
UINT pixel_size,
WICPixelFormatGUID wic_pixel_format,
deleter_t deleter) noexcept;
// Destructor
~WICBitmapForData () noexcept;
// IUnknown
IFACEMETHOD (QueryInterface) (REFIID riid,
void **ppvObject) noexcept override;
IFACEMETHOD_(ULONG, AddRef) () noexcept override;
IFACEMETHOD_(ULONG, Release) () noexcept override;
// IWICBitmapSource
IFACEMETHOD (CopyPalette) (IWICPalette *pIPalette) noexcept override;
IFACEMETHOD (CopyPixels) (const WICRect *prc,
UINT cbStride,
UINT cbBufferSize,
BYTE *pbBuffer) noexcept override;
IFACEMETHOD (GetPixelFormat) (WICPixelFormatGUID *pPixelFormat) noexcept override;
IFACEMETHOD (GetResolution) (double *pDpiX,
double *pDpiY) noexcept override;
IFACEMETHOD (GetSize) (UINT *puiWidth,
UINT *puiHeight) noexcept override;
// IWICBitmap
IFACEMETHOD (Lock) (const WICRect *prcLock,
DWORD flags,
IWICBitmapLock **ppILock) noexcept override;
IFACEMETHOD (SetPalette) (IWICPalette *pIPalette) noexcept override;
IFACEMETHOD (SetResolution) (double dpiX,
double dpiY) noexcept override;
private:
unsigned char * get_address_for_point (UINT x,
UINT y) const noexcept;
bool check_sub_rectangle (const WICRect& rectangle) const noexcept;
bool have_write_locks () const noexcept;
bool have_read_locks () const noexcept;
bool have_locks () const noexcept;
// Check for public methods
void method_check () const noexcept;
private:
unsigned char * const m_data;
const UINT m_width;
const UINT m_height;
const UINT m_stride;
const UINT m_pixel_size;
const WICPixelFormatGUID m_wic_pixel_format;
double m_dpi_x;
double m_dpi_y;
// There can only be one write lock at a time. Make it
// a member so that we avoid an allocation on the heap.
tl::optional<WICBitmapLockForData> m_write_lock;
// Keeps track of how many read locks are outstanding.
ULONG m_read_locks_count;
ULONG m_reference_count;
const deleter_t m_deleter;
// The thread ID at construction time. This class is
// meant to be used by one thread only, so we check
// the thread ID in all public methods.
const DWORD m_thread_id;
friend class WICBitmapLockForData;
private:
static constexpr ULONG s_reference_count_limit {
std::numeric_limits<ULONG>::max()
};
static thread_local ULONG s_thread_instances_count;
};