diff --git a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt
index 0a88f13551..90dc653ef2 100644
--- a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt
+++ b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt
@@ -1981,6 +1981,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_graphics/native/juce_CoreGraphicsHelpers_mac.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp"
"../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.h"
+ "../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContextImpl_windows.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp"
"../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.cpp"
@@ -4633,6 +4634,7 @@ set_source_files_properties(
"../../../../../modules/juce_graphics/native/juce_CoreGraphicsHelpers_mac.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp"
"../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.h"
+ "../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContextImpl_windows.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp"
"../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.cpp"
diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj
index 90c63aed28..59ad6bd368 100644
--- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj
+++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj
@@ -4417,6 +4417,7 @@
+
diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters
index 407c1812c3..94eb8b9d79 100644
--- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters
+++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters
@@ -7575,6 +7575,9 @@
JUCE Modules\juce_graphics\native
+
+ JUCE Modules\juce_graphics\native
+
JUCE Modules\juce_graphics\native
diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj
index c8de13cd52..6fac20fd12 100644
--- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj
+++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj
@@ -4417,6 +4417,7 @@
+
diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters
index 306f410f93..11e2245669 100644
--- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters
+++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters
@@ -7575,6 +7575,9 @@
JUCE Modules\juce_graphics\native
+
+ JUCE Modules\juce_graphics\native
+
JUCE Modules\juce_graphics\native
diff --git a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt
index 3379993892..4bab31179a 100644
--- a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt
+++ b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt
@@ -1743,6 +1743,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_graphics/native/juce_CoreGraphicsHelpers_mac.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp"
"../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.h"
+ "../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContextImpl_windows.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp"
"../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.cpp"
@@ -4009,6 +4010,7 @@ set_source_files_properties(
"../../../../../modules/juce_graphics/native/juce_CoreGraphicsHelpers_mac.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp"
"../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.h"
+ "../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContextImpl_windows.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp"
"../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.cpp"
diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj
index d6ef99bb44..22507bbedb 100644
--- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj
+++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj
@@ -3830,6 +3830,7 @@
+
diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters
index 6ec05a8ce6..1059e8fb48 100644
--- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters
+++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters
@@ -6534,6 +6534,9 @@
JUCE Modules\juce_graphics\native
+
+ JUCE Modules\juce_graphics\native
+
JUCE Modules\juce_graphics\native
diff --git a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt
index 20b3245a97..7e1b111abd 100644
--- a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt
+++ b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt
@@ -1873,6 +1873,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_graphics/native/juce_CoreGraphicsHelpers_mac.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp"
"../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.h"
+ "../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContextImpl_windows.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp"
"../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.cpp"
@@ -4292,6 +4293,7 @@ set_source_files_properties(
"../../../../../modules/juce_graphics/native/juce_CoreGraphicsHelpers_mac.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp"
"../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.h"
+ "../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContextImpl_windows.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp"
"../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.cpp"
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj
index 078896cc5e..d15d3031c7 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj
+++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj
@@ -4063,6 +4063,7 @@
+
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters
index abd33a38fa..9eb6e1444f 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters
+++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters
@@ -6969,6 +6969,9 @@
JUCE Modules\juce_graphics\native
+
+ JUCE Modules\juce_graphics\native
+
JUCE Modules\juce_graphics\native
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj
index 2c3212530a..87e69c0e4a 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj
+++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj
@@ -4063,6 +4063,7 @@
+
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters
index 5dab64aca9..3877e0f4df 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters
+++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters
@@ -6969,6 +6969,9 @@
JUCE Modules\juce_graphics\native
+
+ JUCE Modules\juce_graphics\native
+
JUCE Modules\juce_graphics\native
diff --git a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt
index 1dcfe277c3..b7a2342797 100644
--- a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt
+++ b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt
@@ -1762,6 +1762,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_graphics/native/juce_CoreGraphicsHelpers_mac.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp"
"../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.h"
+ "../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContextImpl_windows.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp"
"../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.cpp"
@@ -4108,6 +4109,7 @@ set_source_files_properties(
"../../../../../modules/juce_graphics/native/juce_CoreGraphicsHelpers_mac.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp"
"../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.h"
+ "../../../../../modules/juce_graphics/native/juce_Direct2DGraphicsContextImpl_windows.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp"
"../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h"
"../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.cpp"
diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj
index f262f0ac2e..274e9bc408 100644
--- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj
+++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj
@@ -3928,6 +3928,7 @@
+
diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters
index 01b685d236..5d3f1262a6 100644
--- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters
+++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters
@@ -6696,6 +6696,9 @@
JUCE Modules\juce_graphics\native
+
+ JUCE Modules\juce_graphics\native
+
JUCE Modules\juce_graphics\native
diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj
index 4acf2f40fb..c229899bf6 100644
--- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj
+++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj
@@ -2628,6 +2628,7 @@
+
diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters
index e8c1a479e2..b78294ea20 100644
--- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters
+++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters
@@ -4338,6 +4338,9 @@
JUCE Modules\juce_graphics\native
+
+ JUCE Modules\juce_graphics\native
+
JUCE Modules\juce_graphics\native
diff --git a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj
index 5429c32d50..1ac9b9172c 100644
--- a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj
+++ b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj
@@ -2628,6 +2628,7 @@
+
diff --git a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters
index 51f71a607b..966e4ed212 100644
--- a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters
+++ b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters
@@ -4338,6 +4338,9 @@
JUCE Modules\juce_graphics\native
+
+ JUCE Modules\juce_graphics\native
+
JUCE Modules\juce_graphics\native
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj
index 75d794d9c9..cc3e678b2a 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj
+++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj
@@ -4175,6 +4175,7 @@
+
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters
index 70b7fa0b40..4a9a72339f 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters
+++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters
@@ -7128,6 +7128,9 @@
JUCE Modules\juce_graphics\native
+
+ JUCE Modules\juce_graphics\native
+
JUCE Modules\juce_graphics\native
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj
index 9599888aa2..3054c0bb13 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj
+++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj
@@ -4175,6 +4175,7 @@
+
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters
index fa0be06296..f9b988ae7d 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters
+++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters
@@ -7128,6 +7128,9 @@
JUCE Modules\juce_graphics\native
+
+ JUCE Modules\juce_graphics\native
+
JUCE Modules\juce_graphics\native
diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj
index 83f5d676bc..dd97aecc27 100644
--- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj
+++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj
@@ -3904,6 +3904,7 @@
+
diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj.filters b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj.filters
index 1e1bd7677a..f0d92e587e 100644
--- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj.filters
+++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj.filters
@@ -6663,6 +6663,9 @@
JUCE Modules\juce_graphics\native
+
+ JUCE Modules\juce_graphics\native
+
JUCE Modules\juce_graphics\native
diff --git a/modules/juce_graphics/juce_graphics.cpp b/modules/juce_graphics/juce_graphics.cpp
index cb552afd01..61052fe7a5 100644
--- a/modules/juce_graphics/juce_graphics.cpp
+++ b/modules/juce_graphics/juce_graphics.cpp
@@ -225,6 +225,7 @@ extern "C"
#include "native/juce_Direct2DHwndContext_windows.h"
#include "native/juce_DirectX_windows.h"
#include "native/juce_Direct2DImage_windows.h"
+ #include "native/juce_Direct2DGraphicsContextImpl_windows.h"
#include "native/juce_Direct2DImageContext_windows.h"
#include "native/juce_DirectWriteTypeface_windows.cpp"
diff --git a/modules/juce_graphics/native/juce_Direct2DGraphicsContextImpl_windows.h b/modules/juce_graphics/native/juce_Direct2DGraphicsContextImpl_windows.h
new file mode 100644
index 0000000000..4f30f9d5a3
--- /dev/null
+++ b/modules/juce_graphics/native/juce_Direct2DGraphicsContextImpl_windows.h
@@ -0,0 +1,895 @@
+/*
+ ==============================================================================
+
+ This file is part of the JUCE framework.
+ Copyright (c) Raw Material Software Limited
+
+ JUCE is an open source framework subject to commercial or open source
+ licensing.
+
+ By downloading, installing, or using the JUCE framework, or combining the
+ JUCE framework with any other source code, object code, content or any other
+ copyrightable work, you agree to the terms of the JUCE End User Licence
+ Agreement, and all incorporated terms including the JUCE Privacy Policy and
+ the JUCE Website Terms of Service, as applicable, which will bind you. If you
+ do not agree to the terms of these agreements, we will not license the JUCE
+ framework to you, and you must discontinue the installation or download
+ process and cease use of the JUCE framework.
+
+ JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
+ JUCE Privacy Policy: https://juce.com/juce-privacy-policy
+ JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
+
+ Or:
+
+ You may also use this code under the terms of the AGPLv3:
+ https://www.gnu.org/licenses/agpl-3.0.en.html
+
+ THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
+ WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
+
+ ==============================================================================
+*/
+
+namespace juce
+{
+
+struct ScopedBlendCopy
+{
+ explicit ScopedBlendCopy (ComSmartPtr c)
+ : ctx (c)
+ {
+ ctx->SetPrimitiveBlend (D2D1_PRIMITIVE_BLEND_COPY);
+ }
+
+ ~ScopedBlendCopy()
+ {
+ ctx->SetPrimitiveBlend (blend);
+ }
+
+ ComSmartPtr ctx;
+ D2D1_PRIMITIVE_BLEND blend = ctx->GetPrimitiveBlend();
+};
+
+class PushedLayers
+{
+public:
+ PushedLayers() { pushedLayers.reserve (32); }
+ PushedLayers (const PushedLayers&) { pushedLayers.reserve (32); }
+
+ #if JUCE_DEBUG
+ ~PushedLayers()
+ {
+ jassert (pushedLayers.empty());
+ }
+ #endif
+
+ void push (ComSmartPtr context, const D2D1_LAYER_PARAMETERS1& layerParameters)
+ {
+ pushedLayers.emplace_back (OwningLayer { layerParameters });
+ pushedLayers.back().push (context);
+ }
+
+ void push (ComSmartPtr context, const Rectangle& r)
+ {
+ pushedLayers.emplace_back (r);
+ pushedLayers.back().push (context);
+ }
+
+ void popOne (ComSmartPtr context)
+ {
+ if (pushedLayers.empty())
+ return;
+
+ pushedLayers.back().pop (context);
+ pushedLayers.pop_back();
+ }
+
+ bool isEmpty() const
+ {
+ return pushedLayers.empty();
+ }
+
+ void fillGeometryWithNoLayersActive (ComSmartPtr ctx,
+ ComSmartPtr geo,
+ ComSmartPtr brush)
+ {
+ ComSmartPtr factory;
+ ctx->GetFactory (factory.resetAndGetPointerAddress());
+
+ const auto hasGeoLayer = std::any_of (pushedLayers.begin(),
+ pushedLayers.end(),
+ [] (const auto& x) { return std::holds_alternative (x.var); });
+
+ const auto intersection = [&]() -> ComSmartPtr
+ {
+ if (! hasGeoLayer)
+ return {};
+
+ const auto contextSize = ctx->GetPixelSize();
+
+ ComSmartPtr rect;
+ factory->CreateRectangleGeometry (D2D1::RectF (0.0f,
+ 0.0f,
+ (float) contextSize.width,
+ (float) contextSize.height),
+ rect.resetAndGetPointerAddress());
+
+ ComSmartPtr clip = rect;
+
+ for (const auto& layer : pushedLayers)
+ {
+ ScopedGeometryWithSink scope { factory, D2D1_FILL_MODE_WINDING };
+
+ if (auto* l = std::get_if (&layer.var))
+ {
+ clip->CombineWithGeometry (l->geometry,
+ D2D1_COMBINE_MODE_INTERSECT,
+ l->params.maskTransform,
+ scope.sink);
+ }
+ else if (auto* r = std::get_if> (&layer.var))
+ {
+ ComSmartPtr temporaryRect;
+ factory->CreateRectangleGeometry (D2DUtilities::toRECT_F (*r),
+ temporaryRect.resetAndGetPointerAddress());
+ clip->CombineWithGeometry (temporaryRect,
+ D2D1_COMBINE_MODE_INTERSECT,
+ D2D1::Matrix3x2F::Identity(),
+ scope.sink);
+ }
+
+ clip = scope.geometry;
+ }
+
+ return clip;
+ }();
+
+ const auto clipWithGeo = [&]() -> ComSmartPtr
+ {
+ if (intersection == nullptr)
+ return geo;
+
+ ScopedGeometryWithSink scope { factory, D2D1_FILL_MODE_WINDING };
+ intersection->CombineWithGeometry (geo,
+ D2D1_COMBINE_MODE_INTERSECT,
+ D2D1::Matrix3x2F::Identity(),
+ scope.sink);
+ return scope.geometry;
+ }();
+
+ if (intersection != nullptr)
+ {
+ std::for_each (pushedLayers.rbegin(),
+ pushedLayers.rend(),
+ [&] (const auto& layer) { layer.pop (ctx); });
+ }
+
+ {
+ const ScopedBlendCopy scope { ctx };
+ ctx->FillGeometry (clipWithGeo, brush);
+ }
+
+ if (intersection != nullptr)
+ {
+ pushedLayers.clear();
+
+ auto newLayer = D2D1::LayerParameters1();
+ newLayer.geometricMask = intersection;
+ push (ctx, newLayer);
+ }
+ }
+
+private:
+ struct OwningLayer
+ {
+ explicit OwningLayer (const D2D1_LAYER_PARAMETERS1& p) : params (p) {}
+
+ D2D1_LAYER_PARAMETERS1 params;
+ ComSmartPtr geometry = params.geometricMask != nullptr ? addComSmartPtrOwner (params.geometricMask) : nullptr;
+ ComSmartPtr brush = params.opacityBrush != nullptr ? addComSmartPtrOwner (params.opacityBrush) : nullptr;
+ };
+
+ struct Layer
+ {
+ explicit Layer (std::variant> v) : var (std::move (v)) {}
+
+ void push (ComSmartPtr context) const
+ {
+ if (auto* layer = std::get_if (&var))
+ context->PushLayer (layer->params, nullptr);
+ else if (auto* rect = std::get_if> (&var))
+ context->PushAxisAlignedClip (D2DUtilities::toRECT_F (*rect), D2D1_ANTIALIAS_MODE_ALIASED);
+ }
+
+ void pop (ComSmartPtr context) const
+ {
+ if (std::holds_alternative (var))
+ context->PopLayer();
+ else if (std::holds_alternative> (var))
+ context->PopAxisAlignedClip();
+ }
+
+ std::variant> var;
+ };
+
+ std::vector pushedLayers;
+
+ //==============================================================================
+ // PushedLayer represents a Direct2D clipping or transparency layer
+ //
+ // D2D layers have to be pushed into the device context. Every push has to be
+ // matched with a pop.
+ //
+ // D2D has special layers called "axis aligned clip layers" which clip to an
+ // axis-aligned rectangle. Pushing an axis-aligned clip layer must be matched
+ // with a call to deviceContext->PopAxisAlignedClip() in the reverse order
+ // in which the layers were pushed.
+ //
+ // So if the pushed layer stack is built like this:
+ //
+ // PushLayer()
+ // PushLayer()
+ // PushAxisAlignedClip()
+ // PushLayer()
+ //
+ // the layer stack must be popped like this:
+ //
+ // PopLayer()
+ // PopAxisAlignedClip()
+ // PopLayer()
+ // PopLayer()
+ //
+ // PushedLayer, PushedAxisAlignedClipLayer, and LayerPopper all exist just to unwind the
+ // layer stack accordingly.
+};
+
+struct PagesAndArea
+{
+ Image imageHandle;
+ Span pages;
+ Rectangle area;
+
+ static PagesAndArea make (const Image& image, ComSmartPtr device)
+ {
+ using GetImage = Image (*) (const Image&);
+ constexpr GetImage converters[] { [] (const Image& i) { return i; },
+ [] (const Image& i) { return NativeImageType{}.convert (i); } };
+
+ for (auto* getImage : converters)
+ {
+ const auto converted = getImage (image);
+ const auto native = converted.getPixelData()->getNativeExtensions();
+
+ if (auto pages = native.getPages (device); ! pages.empty())
+ return PagesAndArea { converted, std::move (pages), converted.getBounds().withPosition (native.getTopLeft()) };
+ }
+
+ // Not sure how this could happen unless the NativeImageType no longer provides Windows native details...
+ jassertfalse;
+ return {};
+ }
+};
+
+struct Direct2DGraphicsContext::SavedState
+{
+public:
+ // Constructor for first stack entry
+ SavedState (Direct2DGraphicsContext& ownerIn,
+ Rectangle frameSizeIn,
+ ComSmartPtr deviceContext,
+ ComSmartPtr& colourBrushIn,
+ Direct2DDeviceResources& deviceResourcesIn)
+ : owner (ownerIn),
+ context (deviceContext),
+ currentBrush (colourBrushIn),
+ colourBrush (colourBrushIn),
+ deviceResources (deviceResourcesIn),
+ deviceSpaceClipList (frameSizeIn.toFloat())
+ {
+ }
+
+ void pushLayer (const D2D1_LAYER_PARAMETERS1& layerParameters)
+ {
+ layers.push (context, layerParameters);
+ }
+
+ void pushGeometryClipLayer (ComSmartPtr geometry)
+ {
+ if (geometry != nullptr)
+ pushLayer (D2D1::LayerParameters1 (D2D1::InfiniteRect(), geometry));
+ }
+
+ void pushTransformedRectangleGeometryClipLayer (ComSmartPtr geometry, const AffineTransform& transform)
+ {
+ JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (owner.metrics, pushGeometryLayerTime)
+
+ jassert (geometry != nullptr);
+ auto layerParameters = D2D1::LayerParameters1 (D2D1::InfiniteRect(), geometry);
+ layerParameters.maskTransform = D2DUtilities::transformToMatrix (transform);
+ pushLayer (layerParameters);
+ }
+
+ void pushAliasedAxisAlignedClipLayer (const Rectangle& r)
+ {
+ JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (owner.metrics, pushAliasedAxisAlignedLayerTime)
+
+ layers.push (context, r);
+ }
+
+ void pushTransparencyLayer (float opacity)
+ {
+ pushLayer ({ D2D1::InfiniteRect(), nullptr, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, D2D1::IdentityMatrix(), opacity, {}, {} });
+ }
+
+ void popLayers()
+ {
+ while (! layers.isEmpty())
+ layers.popOne (context);
+ }
+
+ void popTopLayer()
+ {
+ layers.popOne (context);
+ }
+
+ void setFont (const Font& newFont)
+ {
+ font = newFont;
+ }
+
+ void setOpacity (float newOpacity)
+ {
+ fillType.setOpacity (newOpacity);
+ }
+
+ void clearFill()
+ {
+ linearGradient = nullptr;
+ radialGradient = nullptr;
+ bitmapBrush = nullptr;
+ currentBrush = nullptr;
+ }
+
+ /** Translate a JUCE FillType to a Direct2D brush */
+ void updateCurrentBrush()
+ {
+ if (fillType.isColour())
+ {
+ // Reuse the same colour brush
+ currentBrush = colourBrush;
+ }
+ else if (fillType.isTiledImage())
+ {
+ if (fillType.image.isNull())
+ return;
+
+ const auto device = D2DUtilities::getDeviceForContext (context);
+ const auto imageFormat = fillType.image.getFormat();
+ const auto targetFormat = imageFormat == Image::SingleChannel ? Image::ARGB : imageFormat;
+ const auto pagesAndArea = PagesAndArea::make (fillType.image.convertedToFormat (targetFormat), device);
+
+ if (pagesAndArea.pages.empty())
+ return;
+
+ const auto bitmap = pagesAndArea.pages.front().bitmap;
+
+ if (bitmap == nullptr)
+ return;
+
+ D2D1_BRUSH_PROPERTIES brushProps { fillType.getOpacity(), D2DUtilities::transformToMatrix (fillType.transform) };
+ auto bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP);
+ const auto hr = context->CreateBitmapBrush (bitmap,
+ bmProps,
+ brushProps,
+ bitmapBrush.resetAndGetPointerAddress());
+
+ if (FAILED (hr))
+ return;
+
+ currentBrush = bitmapBrush;
+ }
+ else if (fillType.isGradient())
+ {
+ if (fillType.gradient->isRadial)
+ {
+ radialGradient = deviceResources.radialGradientCache.get (*fillType.gradient, context, owner.metrics.get());
+ currentBrush = radialGradient;
+ }
+ else
+ {
+ linearGradient = deviceResources.linearGradientCache.get (*fillType.gradient, context, owner.metrics.get());
+ currentBrush = linearGradient;
+ }
+ }
+
+ updateColourBrush();
+ }
+
+ void updateColourBrush()
+ {
+ if (colourBrush && fillType.isColour())
+ {
+ auto colour = D2DUtilities::toCOLOR_F (fillType.colour);
+ colourBrush->SetColor (colour);
+ }
+ }
+
+ enum BrushTransformFlags
+ {
+ noTransforms = 0,
+ applyWorldTransform = 1,
+ applyInverseWorldTransform = 2,
+ applyFillTypeTransform = 4,
+ applyWorldAndFillTypeTransforms = applyFillTypeTransform | applyWorldTransform
+ };
+
+ ComSmartPtr getBrush (int flags = applyWorldAndFillTypeTransforms)
+ {
+ if (fillType.isInvisible())
+ return nullptr;
+
+ if (! fillType.isGradient() && ! fillType.isTiledImage())
+ return currentBrush;
+
+ Point translation{};
+ AffineTransform transform{};
+
+ if (fillType.isGradient())
+ {
+ if ((flags & BrushTransformFlags::applyWorldTransform) != 0)
+ {
+ if (currentTransform.isOnlyTranslated)
+ translation = currentTransform.offset.toFloat();
+ else
+ transform = currentTransform.getTransform();
+ }
+
+ if ((flags & BrushTransformFlags::applyFillTypeTransform) != 0)
+ {
+ if (fillType.transform.isOnlyTranslation())
+ translation += Point (fillType.transform.getTranslationX(), fillType.transform.getTranslationY());
+ else
+ transform = transform.followedBy (fillType.transform);
+ }
+
+ if ((flags & BrushTransformFlags::applyInverseWorldTransform) != 0)
+ {
+ if (currentTransform.isOnlyTranslated)
+ translation -= currentTransform.offset.toFloat();
+ else
+ transform = transform.followedBy (currentTransform.getTransform().inverted());
+ }
+
+ const auto p1 = fillType.gradient->point1 + translation;
+ const auto p2 = fillType.gradient->point2 + translation;
+
+ if (fillType.gradient->isRadial)
+ {
+ const auto radius = p2.getDistanceFrom (p1);
+ radialGradient->SetRadiusX (radius);
+ radialGradient->SetRadiusY (radius);
+ radialGradient->SetCenter ({ p1.x, p1.y });
+ }
+ else
+ {
+ linearGradient->SetStartPoint ({ p1.x, p1.y });
+ linearGradient->SetEndPoint ({ p2.x, p2.y });
+ }
+ }
+ else if (fillType.isTiledImage())
+ {
+ if ((flags & BrushTransformFlags::applyWorldTransform) != 0)
+ transform = currentTransform.getTransform();
+
+ if ((flags & BrushTransformFlags::applyFillTypeTransform) != 0)
+ transform = transform.followedBy (fillType.transform);
+
+ if ((flags & BrushTransformFlags::applyInverseWorldTransform) != 0)
+ transform = transform.followedBy (currentTransform.getTransform().inverted());
+ }
+
+ currentBrush->SetTransform (D2DUtilities::transformToMatrix (transform));
+ currentBrush->SetOpacity (fillType.getOpacity());
+
+ return currentBrush;
+ }
+
+ bool doesIntersectClipList (Rectangle r) const noexcept
+ {
+ return deviceSpaceClipList.intersects (r.toFloat());
+ }
+
+ bool doesIntersectClipList (Rectangle r) const noexcept
+ {
+ return deviceSpaceClipList.intersects (r);
+ }
+
+ bool doesIntersectClipList (Line r) const noexcept
+ {
+ return doesIntersectClipList (Rectangle { r.getStart(), r.getEnd() }.expanded (1.0f));
+ }
+
+ bool doesIntersectClipList (const RectangleList& other) const noexcept
+ {
+ return deviceSpaceClipList.intersects (other);
+ }
+
+ bool isCurrentTransformAxisAligned() const noexcept
+ {
+ return currentTransform.isOnlyTranslated || (currentTransform.complexTransform.mat01 == 0.0f && currentTransform.complexTransform.mat10 == 0.0f);
+ }
+
+ static String toString (const RenderingHelpers::TranslationOrTransform& t)
+ {
+ String s;
+ s << "Offset " << t.offset.toString() << newLine;
+ s << "Transform " << t.complexTransform.mat00 << " " << t.complexTransform.mat01 << " " << t.complexTransform.mat02 << " / ";
+ s << " " << t.complexTransform.mat10 << " " << t.complexTransform.mat11 << " " << t.complexTransform.mat12 << newLine;
+ return s;
+ }
+
+ PushedLayers layers;
+
+ Direct2DGraphicsContext& owner;
+
+ ComSmartPtr context;
+ ComSmartPtr currentBrush;
+ ComSmartPtr& colourBrush; // reference to shared colour brush
+ ComSmartPtr bitmapBrush;
+ ComSmartPtr linearGradient;
+ ComSmartPtr radialGradient;
+
+ RenderingHelpers::TranslationOrTransform currentTransform;
+
+ Direct2DDeviceResources& deviceResources;
+ RectangleList deviceSpaceClipList;
+
+ Font font { FontOptions{} };
+
+ FillType fillType;
+
+ D2D1_INTERPOLATION_MODE interpolationMode = D2D1_INTERPOLATION_MODE_LINEAR;
+
+ JUCE_LEAK_DETECTOR (SavedState)
+};
+
+struct Direct2DGraphicsContext::Pimpl : private DxgiAdapterListener
+{
+protected:
+ Direct2DGraphicsContext& owner;
+ SharedResourcePointer directX;
+ SharedResourcePointer directWrite;
+
+ std::optional deviceResources;
+
+ std::vector> savedClientStates;
+
+ virtual bool prepare()
+ {
+ if (! deviceResources.has_value())
+ deviceResources = Direct2DDeviceResources::create (getDeviceContext());
+
+ return deviceResources.has_value();
+ }
+
+ virtual void teardown()
+ {
+ deviceResources.reset();
+ }
+
+ virtual bool checkPaintReady()
+ {
+ return deviceResources.has_value();
+ }
+
+public:
+ explicit Pimpl (Direct2DGraphicsContext& ownerIn)
+ : owner (ownerIn)
+ {
+ directX->adapters.addListener (*this);
+ }
+
+ ~Pimpl() override
+ {
+ directX->adapters.removeListener (*this);
+
+ popAllSavedStates();
+ }
+
+ virtual SavedState* startFrame()
+ {
+ prepare();
+
+ // Anything to paint?
+ const auto paintAreas = getPaintAreas();
+ const auto paintBounds = paintAreas.getBounds();
+
+ if (! getFrameSize().intersects (paintBounds) || paintBounds.isEmpty() || paintAreas.isEmpty())
+ return nullptr;
+
+ // Is Direct2D ready to paint?
+ if (! checkPaintReady())
+ return nullptr;
+
+ #if JUCE_DIRECT2D_METRICS
+ owner.metrics->startFrame();
+ #endif
+
+ JUCE_TRACE_EVENT_INT_RECT_LIST (etw::startD2DFrame, etw::direct2dKeyword, owner.getFrameId(), paintAreas);
+
+ const auto deviceContext = getDeviceContext();
+
+ // Init device context transform
+ resetTransform (deviceContext);
+
+ // Start drawing
+ deviceContext->SetTarget (getDeviceContextTarget());
+ deviceContext->BeginDraw();
+
+ // Init the save state stack and return the first saved state
+ return pushFirstSavedState (paintBounds);
+ }
+
+ virtual HRESULT finishFrame()
+ {
+ // Fully pop the state stack
+ popAllSavedStates();
+
+ // Finish drawing
+ // SetTarget(nullptr) so the device context doesn't hold a reference to the swap chain buffer
+ HRESULT hr = S_OK;
+ {
+ JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (owner.metrics, endDrawDuration)
+ JUCE_SCOPED_TRACE_EVENT_FRAME (etw::endDraw, etw::direct2dKeyword, owner.getFrameId());
+
+ const auto deviceContext = getDeviceContext();
+ hr = deviceContext->EndDraw();
+ deviceContext->SetTarget (nullptr);
+ }
+
+ jassert (SUCCEEDED (hr));
+
+ if (FAILED (hr))
+ teardown();
+
+ #if JUCE_DIRECT2D_METRICS
+ owner.metrics->finishFrame();
+ #endif
+
+ return hr;
+ }
+
+ SavedState* getCurrentSavedState() const
+ {
+ return ! savedClientStates.empty() ? savedClientStates.back().get() : nullptr;
+ }
+
+ SavedState* pushFirstSavedState (Rectangle initialClipRegion)
+ {
+ jassert (savedClientStates.empty());
+
+ savedClientStates.push_back (std::make_unique (owner,
+ initialClipRegion,
+ getDeviceContext(),
+ deviceResources->colourBrush,
+ *deviceResources));
+
+ return getCurrentSavedState();
+ }
+
+ SavedState* pushSavedState()
+ {
+ jassert (! savedClientStates.empty());
+
+ savedClientStates.push_back (std::make_unique (*savedClientStates.back()));
+
+ return getCurrentSavedState();
+ }
+
+ SavedState* popSavedState()
+ {
+ savedClientStates.back()->popLayers();
+ savedClientStates.pop_back();
+
+ return getCurrentSavedState();
+ }
+
+ void popAllSavedStates()
+ {
+ while (! savedClientStates.empty())
+ popSavedState();
+ }
+
+ virtual RectangleList getPaintAreas() const = 0;
+ virtual Rectangle getFrameSize() const = 0;
+ virtual ComSmartPtr getDeviceContext() const = 0;
+ virtual ComSmartPtr getDeviceContextTarget() const = 0;
+
+ void setDeviceContextTransform (AffineTransform transform)
+ {
+ setTransform (getDeviceContext(), transform);
+ }
+
+ void resetDeviceContextTransform()
+ {
+ resetTransform (getDeviceContext());
+ }
+
+ auto getDirect2DFactory()
+ {
+ return directX->getD2DFactory();
+ }
+
+ auto getDirectWriteFactory()
+ {
+ return directWrite->getDWriteFactory();
+ }
+
+ auto getDirectWriteFactory4()
+ {
+ return directWrite->getDWriteFactory4();
+ }
+
+ auto& getFontCollection()
+ {
+ return directWrite->getFonts();
+ }
+
+ bool fillSpriteBatch (const RectangleList& list)
+ {
+ if (! owner.currentState->fillType.isColour())
+ return false;
+
+ auto* rectangleListSpriteBatch = deviceResources->rectangleListSpriteBatch.get();
+
+ if (rectangleListSpriteBatch == nullptr)
+ return false;
+
+ const auto deviceContext = getDeviceContext();
+
+ if (deviceContext == nullptr)
+ return false;
+
+ owner.applyPendingClipList();
+
+ const auto& transform = owner.currentState->currentTransform;
+
+ if (transform.isOnlyTranslated)
+ {
+ auto translateRectangle = [&] (const Rectangle& r) -> Rectangle
+ {
+ return transform.translated (r);
+ };
+
+ rectangleListSpriteBatch->fillRectangles (deviceContext,
+ list,
+ owner.currentState->fillType.colour,
+ translateRectangle,
+ owner.metrics.get());
+ return true;
+ }
+
+ if (owner.currentState->isCurrentTransformAxisAligned())
+ {
+ auto transformRectangle = [&] (const Rectangle& r) -> Rectangle
+ {
+ return transform.boundsAfterTransform (r);
+ };
+
+ rectangleListSpriteBatch->fillRectangles (deviceContext,
+ list,
+ owner.currentState->fillType.colour,
+ transformRectangle,
+ owner.metrics.get());
+ return true;
+ }
+
+ auto checkRectangleWithoutTransforming = [&] (const Rectangle& r) -> Rectangle
+ {
+ return r;
+ };
+
+ ScopedTransform scopedTransform { *this, owner.currentState };
+ rectangleListSpriteBatch->fillRectangles (deviceContext,
+ list,
+ owner.currentState->fillType.colour,
+ checkRectangleWithoutTransforming,
+ owner.metrics.get());
+
+ return true;
+ }
+
+ static Line offsetShape (Line a, Point b)
+ {
+ return Line { a.getStart() + b, a.getEnd() + b };
+ }
+
+ static Rectangle offsetShape (Rectangle a, Point b)
+ {
+ return a + b;
+ }
+
+ static RectangleList offsetShape (RectangleList a, Point b)
+ {
+ a.offsetAll (b);
+ return a;
+ }
+
+ template
+ void paintPrimitive (const Shape& shape, Fn&& primitiveOp)
+ {
+ const auto& transform = owner.currentState->currentTransform;
+
+ owner.applyPendingClipList();
+
+ auto deviceContext = getDeviceContext();
+
+ if (deviceContext == nullptr)
+ return;
+
+ const auto fillTransform = transform.isOnlyTranslated
+ ? SavedState::BrushTransformFlags::applyWorldAndFillTypeTransforms
+ : SavedState::BrushTransformFlags::applyFillTypeTransform;
+
+ const auto brush = owner.currentState->getBrush (fillTransform);
+
+ if (transform.isOnlyTranslated)
+ {
+ const auto translated = offsetShape (shape, transform.offset.toFloat());
+
+ if (owner.currentState->doesIntersectClipList (translated))
+ primitiveOp (translated, deviceContext, brush);
+ }
+ else if (owner.currentState->doesIntersectClipList (transform.boundsAfterTransform (shape)))
+ {
+ ScopedTransform scopedTransform { *this, owner.currentState };
+ primitiveOp (shape, deviceContext, brush);
+ }
+ }
+
+ DirectWriteGlyphRun glyphRun;
+
+private:
+ static void resetTransform (ID2D1DeviceContext1* context)
+ {
+ context->SetTransform (D2D1::IdentityMatrix());
+ }
+
+ static void setTransform (ID2D1DeviceContext1* context, AffineTransform newTransform)
+ {
+ context->SetTransform (D2DUtilities::transformToMatrix (newTransform));
+ }
+
+ DxgiAdapter::Ptr findAdapter() const
+ {
+ return Direct2DDeviceResources::findAdapter (directX->adapters, getDeviceContext());
+ }
+
+ void adapterCreated (DxgiAdapter::Ptr newAdapter) override
+ {
+ const auto adapter = findAdapter();
+
+ if (adapter == nullptr || ! adapter->uniqueIDMatches (newAdapter))
+ teardown();
+ }
+
+ void adapterRemoved (DxgiAdapter::Ptr expiringAdapter) override
+ {
+ const auto adapter = findAdapter();
+
+ if (adapter != nullptr && adapter->uniqueIDMatches (expiringAdapter))
+ teardown();
+ }
+
+ HWND hwnd = nullptr;
+
+ #if JUCE_DIRECT2D_METRICS
+ int64 paintStartTicks = 0;
+ #endif
+
+ JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
+};
+
+} // namespace juce
diff --git a/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp b/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp
index 5e68256454..897151063d 100644
--- a/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp
+++ b/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp
@@ -35,859 +35,6 @@
namespace juce
{
-struct ScopedBlendCopy
-{
- explicit ScopedBlendCopy (ComSmartPtr c)
- : ctx (c)
- {
- ctx->SetPrimitiveBlend (D2D1_PRIMITIVE_BLEND_COPY);
- }
-
- ~ScopedBlendCopy()
- {
- ctx->SetPrimitiveBlend (blend);
- }
-
- ComSmartPtr ctx;
- D2D1_PRIMITIVE_BLEND blend = ctx->GetPrimitiveBlend();
-};
-
-class PushedLayers
-{
-public:
- PushedLayers() { pushedLayers.reserve (32); }
- PushedLayers (const PushedLayers&) { pushedLayers.reserve (32); }
-
- #if JUCE_DEBUG
- ~PushedLayers()
- {
- jassert (pushedLayers.empty());
- }
- #endif
-
- void push (ComSmartPtr context, const D2D1_LAYER_PARAMETERS1& layerParameters)
- {
- pushedLayers.emplace_back (OwningLayer { layerParameters });
- pushedLayers.back().push (context);
- }
-
- void push (ComSmartPtr context, const Rectangle& r)
- {
- pushedLayers.emplace_back (r);
- pushedLayers.back().push (context);
- }
-
- void popOne (ComSmartPtr context)
- {
- if (pushedLayers.empty())
- return;
-
- pushedLayers.back().pop (context);
- pushedLayers.pop_back();
- }
-
- bool isEmpty() const
- {
- return pushedLayers.empty();
- }
-
- void fillGeometryWithNoLayersActive (ComSmartPtr ctx,
- ComSmartPtr geo,
- ComSmartPtr brush)
- {
- ComSmartPtr factory;
- ctx->GetFactory (factory.resetAndGetPointerAddress());
-
- const auto hasGeoLayer = std::any_of (pushedLayers.begin(),
- pushedLayers.end(),
- [] (const auto& x) { return std::holds_alternative (x.var); });
-
- const auto intersection = [&]() -> ComSmartPtr
- {
- if (! hasGeoLayer)
- return {};
-
- const auto contextSize = ctx->GetPixelSize();
-
- ComSmartPtr rect;
- factory->CreateRectangleGeometry (D2D1::RectF (0.0f,
- 0.0f,
- (float) contextSize.width,
- (float) contextSize.height),
- rect.resetAndGetPointerAddress());
-
- ComSmartPtr clip = rect;
-
- for (const auto& layer : pushedLayers)
- {
- ScopedGeometryWithSink scope { factory, D2D1_FILL_MODE_WINDING };
-
- if (auto* l = std::get_if (&layer.var))
- {
- clip->CombineWithGeometry (l->geometry,
- D2D1_COMBINE_MODE_INTERSECT,
- l->params.maskTransform,
- scope.sink);
- }
- else if (auto* r = std::get_if> (&layer.var))
- {
- ComSmartPtr temporaryRect;
- factory->CreateRectangleGeometry (D2DUtilities::toRECT_F (*r),
- temporaryRect.resetAndGetPointerAddress());
- clip->CombineWithGeometry (temporaryRect,
- D2D1_COMBINE_MODE_INTERSECT,
- D2D1::Matrix3x2F::Identity(),
- scope.sink);
- }
-
- clip = scope.geometry;
- }
-
- return clip;
- }();
-
- const auto clipWithGeo = [&]() -> ComSmartPtr
- {
- if (intersection == nullptr)
- return geo;
-
- ScopedGeometryWithSink scope { factory, D2D1_FILL_MODE_WINDING };
- intersection->CombineWithGeometry (geo,
- D2D1_COMBINE_MODE_INTERSECT,
- D2D1::Matrix3x2F::Identity(),
- scope.sink);
- return scope.geometry;
- }();
-
- if (intersection != nullptr)
- {
- std::for_each (pushedLayers.rbegin(),
- pushedLayers.rend(),
- [&] (const auto& layer) { layer.pop (ctx); });
- }
-
- {
- const ScopedBlendCopy scope { ctx };
- ctx->FillGeometry (clipWithGeo, brush);
- }
-
- if (intersection != nullptr)
- {
- pushedLayers.clear();
-
- auto newLayer = D2D1::LayerParameters1();
- newLayer.geometricMask = intersection;
- push (ctx, newLayer);
- }
- }
-
-private:
- struct OwningLayer
- {
- explicit OwningLayer (const D2D1_LAYER_PARAMETERS1& p) : params (p) {}
-
- D2D1_LAYER_PARAMETERS1 params;
- ComSmartPtr geometry = params.geometricMask != nullptr ? addComSmartPtrOwner (params.geometricMask) : nullptr;
- ComSmartPtr brush = params.opacityBrush != nullptr ? addComSmartPtrOwner (params.opacityBrush) : nullptr;
- };
-
- struct Layer
- {
- explicit Layer (std::variant> v) : var (std::move (v)) {}
-
- void push (ComSmartPtr context) const
- {
- if (auto* layer = std::get_if (&var))
- context->PushLayer (layer->params, nullptr);
- else if (auto* rect = std::get_if> (&var))
- context->PushAxisAlignedClip (D2DUtilities::toRECT_F (*rect), D2D1_ANTIALIAS_MODE_ALIASED);
- }
-
- void pop (ComSmartPtr context) const
- {
- if (std::holds_alternative (var))
- context->PopLayer();
- else if (std::holds_alternative> (var))
- context->PopAxisAlignedClip();
- }
-
- std::variant> var;
- };
-
- std::vector pushedLayers;
-
- //==============================================================================
- // PushedLayer represents a Direct2D clipping or transparency layer
- //
- // D2D layers have to be pushed into the device context. Every push has to be
- // matched with a pop.
- //
- // D2D has special layers called "axis aligned clip layers" which clip to an
- // axis-aligned rectangle. Pushing an axis-aligned clip layer must be matched
- // with a call to deviceContext->PopAxisAlignedClip() in the reverse order
- // in which the layers were pushed.
- //
- // So if the pushed layer stack is built like this:
- //
- // PushLayer()
- // PushLayer()
- // PushAxisAlignedClip()
- // PushLayer()
- //
- // the layer stack must be popped like this:
- //
- // PopLayer()
- // PopAxisAlignedClip()
- // PopLayer()
- // PopLayer()
- //
- // PushedLayer, PushedAxisAlignedClipLayer, and LayerPopper all exist just to unwind the
- // layer stack accordingly.
-};
-
-struct PagesAndArea
-{
- Image imageHandle;
- Span pages;
- Rectangle area;
-
- static PagesAndArea make (const Image& image, ComSmartPtr device)
- {
- using GetImage = Image (*) (const Image&);
- constexpr GetImage converters[] { [] (const Image& i) { return i; },
- [] (const Image& i) { return NativeImageType{}.convert (i); } };
-
- for (auto* getImage : converters)
- {
- const auto converted = getImage (image);
- const auto native = converted.getPixelData()->getNativeExtensions();
-
- if (auto pages = native.getPages (device); ! pages.empty())
- return PagesAndArea { converted, std::move (pages), converted.getBounds().withPosition (native.getTopLeft()) };
- }
-
- // Not sure how this could happen unless the NativeImageType no longer provides Windows native details...
- jassertfalse;
- return {};
- }
-};
-
-struct Direct2DGraphicsContext::SavedState
-{
-public:
- // Constructor for first stack entry
- SavedState (Direct2DGraphicsContext& ownerIn,
- Rectangle frameSizeIn,
- ComSmartPtr deviceContext,
- ComSmartPtr& colourBrushIn,
- Direct2DDeviceResources& deviceResourcesIn)
- : owner (ownerIn),
- context (deviceContext),
- currentBrush (colourBrushIn),
- colourBrush (colourBrushIn),
- deviceResources (deviceResourcesIn),
- deviceSpaceClipList (frameSizeIn.toFloat())
- {
- }
-
- void pushLayer (const D2D1_LAYER_PARAMETERS1& layerParameters)
- {
- layers.push (context, layerParameters);
- }
-
- void pushGeometryClipLayer (ComSmartPtr geometry)
- {
- if (geometry != nullptr)
- pushLayer (D2D1::LayerParameters1 (D2D1::InfiniteRect(), geometry));
- }
-
- void pushTransformedRectangleGeometryClipLayer (ComSmartPtr geometry, const AffineTransform& transform)
- {
- JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (owner.metrics, pushGeometryLayerTime)
-
- jassert (geometry != nullptr);
- auto layerParameters = D2D1::LayerParameters1 (D2D1::InfiniteRect(), geometry);
- layerParameters.maskTransform = D2DUtilities::transformToMatrix (transform);
- pushLayer (layerParameters);
- }
-
- void pushAliasedAxisAlignedClipLayer (const Rectangle& r)
- {
- JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (owner.metrics, pushAliasedAxisAlignedLayerTime)
-
- layers.push (context, r);
- }
-
- void pushTransparencyLayer (float opacity)
- {
- pushLayer ({ D2D1::InfiniteRect(), nullptr, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, D2D1::IdentityMatrix(), opacity, {}, {} });
- }
-
- void popLayers()
- {
- while (! layers.isEmpty())
- layers.popOne (context);
- }
-
- void popTopLayer()
- {
- layers.popOne (context);
- }
-
- void setFont (const Font& newFont)
- {
- font = newFont;
- }
-
- void setOpacity (float newOpacity)
- {
- fillType.setOpacity (newOpacity);
- }
-
- void clearFill()
- {
- linearGradient = nullptr;
- radialGradient = nullptr;
- bitmapBrush = nullptr;
- currentBrush = nullptr;
- }
-
- /** Translate a JUCE FillType to a Direct2D brush */
- void updateCurrentBrush()
- {
- if (fillType.isColour())
- {
- // Reuse the same colour brush
- currentBrush = colourBrush;
- }
- else if (fillType.isTiledImage())
- {
- if (fillType.image.isNull())
- return;
-
- const auto device = D2DUtilities::getDeviceForContext (context);
- const auto imageFormat = fillType.image.getFormat();
- const auto targetFormat = imageFormat == Image::SingleChannel ? Image::ARGB : imageFormat;
- const auto pagesAndArea = PagesAndArea::make (fillType.image.convertedToFormat (targetFormat), device);
-
- if (pagesAndArea.pages.empty())
- return;
-
- const auto bitmap = pagesAndArea.pages.front().bitmap;
-
- if (bitmap == nullptr)
- return;
-
- D2D1_BRUSH_PROPERTIES brushProps { fillType.getOpacity(), D2DUtilities::transformToMatrix (fillType.transform) };
- auto bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP);
- const auto hr = context->CreateBitmapBrush (bitmap,
- bmProps,
- brushProps,
- bitmapBrush.resetAndGetPointerAddress());
-
- if (FAILED (hr))
- return;
-
- currentBrush = bitmapBrush;
- }
- else if (fillType.isGradient())
- {
- if (fillType.gradient->isRadial)
- {
- radialGradient = deviceResources.radialGradientCache.get (*fillType.gradient, context, owner.metrics.get());
- currentBrush = radialGradient;
- }
- else
- {
- linearGradient = deviceResources.linearGradientCache.get (*fillType.gradient, context, owner.metrics.get());
- currentBrush = linearGradient;
- }
- }
-
- updateColourBrush();
- }
-
- void updateColourBrush()
- {
- if (colourBrush && fillType.isColour())
- {
- auto colour = D2DUtilities::toCOLOR_F (fillType.colour);
- colourBrush->SetColor (colour);
- }
- }
-
- enum BrushTransformFlags
- {
- noTransforms = 0,
- applyWorldTransform = 1,
- applyInverseWorldTransform = 2,
- applyFillTypeTransform = 4,
- applyWorldAndFillTypeTransforms = applyFillTypeTransform | applyWorldTransform
- };
-
- ComSmartPtr getBrush (int flags = applyWorldAndFillTypeTransforms)
- {
- if (fillType.isInvisible())
- return nullptr;
-
- if (! fillType.isGradient() && ! fillType.isTiledImage())
- return currentBrush;
-
- Point translation{};
- AffineTransform transform{};
-
- if (fillType.isGradient())
- {
- if ((flags & BrushTransformFlags::applyWorldTransform) != 0)
- {
- if (currentTransform.isOnlyTranslated)
- translation = currentTransform.offset.toFloat();
- else
- transform = currentTransform.getTransform();
- }
-
- if ((flags & BrushTransformFlags::applyFillTypeTransform) != 0)
- {
- if (fillType.transform.isOnlyTranslation())
- translation += Point (fillType.transform.getTranslationX(), fillType.transform.getTranslationY());
- else
- transform = transform.followedBy (fillType.transform);
- }
-
- if ((flags & BrushTransformFlags::applyInverseWorldTransform) != 0)
- {
- if (currentTransform.isOnlyTranslated)
- translation -= currentTransform.offset.toFloat();
- else
- transform = transform.followedBy (currentTransform.getTransform().inverted());
- }
-
- const auto p1 = fillType.gradient->point1 + translation;
- const auto p2 = fillType.gradient->point2 + translation;
-
- if (fillType.gradient->isRadial)
- {
- const auto radius = p2.getDistanceFrom (p1);
- radialGradient->SetRadiusX (radius);
- radialGradient->SetRadiusY (radius);
- radialGradient->SetCenter ({ p1.x, p1.y });
- }
- else
- {
- linearGradient->SetStartPoint ({ p1.x, p1.y });
- linearGradient->SetEndPoint ({ p2.x, p2.y });
- }
- }
- else if (fillType.isTiledImage())
- {
- if ((flags & BrushTransformFlags::applyWorldTransform) != 0)
- transform = currentTransform.getTransform();
-
- if ((flags & BrushTransformFlags::applyFillTypeTransform) != 0)
- transform = transform.followedBy (fillType.transform);
-
- if ((flags & BrushTransformFlags::applyInverseWorldTransform) != 0)
- transform = transform.followedBy (currentTransform.getTransform().inverted());
- }
-
- currentBrush->SetTransform (D2DUtilities::transformToMatrix (transform));
- currentBrush->SetOpacity (fillType.getOpacity());
-
- return currentBrush;
- }
-
- bool doesIntersectClipList (Rectangle r) const noexcept
- {
- return deviceSpaceClipList.intersects (r.toFloat());
- }
-
- bool doesIntersectClipList (Rectangle r) const noexcept
- {
- return deviceSpaceClipList.intersects (r);
- }
-
- bool doesIntersectClipList (Line r) const noexcept
- {
- return doesIntersectClipList (Rectangle { r.getStart(), r.getEnd() }.expanded (1.0f));
- }
-
- bool doesIntersectClipList (const RectangleList& other) const noexcept
- {
- return deviceSpaceClipList.intersects (other);
- }
-
- bool isCurrentTransformAxisAligned() const noexcept
- {
- return currentTransform.isOnlyTranslated || (currentTransform.complexTransform.mat01 == 0.0f && currentTransform.complexTransform.mat10 == 0.0f);
- }
-
- static String toString (const RenderingHelpers::TranslationOrTransform& t)
- {
- String s;
- s << "Offset " << t.offset.toString() << newLine;
- s << "Transform " << t.complexTransform.mat00 << " " << t.complexTransform.mat01 << " " << t.complexTransform.mat02 << " / ";
- s << " " << t.complexTransform.mat10 << " " << t.complexTransform.mat11 << " " << t.complexTransform.mat12 << newLine;
- return s;
- }
-
- PushedLayers layers;
-
- Direct2DGraphicsContext& owner;
-
- ComSmartPtr context;
- ComSmartPtr currentBrush;
- ComSmartPtr& colourBrush; // reference to shared colour brush
- ComSmartPtr bitmapBrush;
- ComSmartPtr linearGradient;
- ComSmartPtr radialGradient;
-
- RenderingHelpers::TranslationOrTransform currentTransform;
-
- Direct2DDeviceResources& deviceResources;
- RectangleList deviceSpaceClipList;
-
- Font font { FontOptions{} };
-
- FillType fillType;
-
- D2D1_INTERPOLATION_MODE interpolationMode = D2D1_INTERPOLATION_MODE_LINEAR;
-
- JUCE_LEAK_DETECTOR (SavedState)
-};
-
-static Line operator+ (Line a, Point b)
-{
- return { a.getStart() + b, a.getEnd() + b };
-}
-
-static RectangleList operator+ (RectangleList a, Point b)
-{
- a.offsetAll (b);
- return a;
-}
-
-//==============================================================================
-struct Direct2DGraphicsContext::Pimpl : private DxgiAdapterListener
-{
-protected:
- Direct2DGraphicsContext& owner;
- SharedResourcePointer directX;
- SharedResourcePointer directWrite;
-
- std::optional deviceResources;
-
- std::vector> savedClientStates;
-
- virtual bool prepare()
- {
- if (! deviceResources.has_value())
- deviceResources = Direct2DDeviceResources::create (getDeviceContext());
-
- return deviceResources.has_value();
- }
-
- virtual void teardown()
- {
- deviceResources.reset();
- }
-
- virtual bool checkPaintReady()
- {
- return deviceResources.has_value();
- }
-
-public:
- explicit Pimpl (Direct2DGraphicsContext& ownerIn)
- : owner (ownerIn)
- {
- directX->adapters.addListener (*this);
- }
-
- ~Pimpl() override
- {
- directX->adapters.removeListener (*this);
-
- popAllSavedStates();
- }
-
- virtual SavedState* startFrame()
- {
- prepare();
-
- // Anything to paint?
- const auto paintAreas = getPaintAreas();
- const auto paintBounds = paintAreas.getBounds();
-
- if (! getFrameSize().intersects (paintBounds) || paintBounds.isEmpty() || paintAreas.isEmpty())
- return nullptr;
-
- // Is Direct2D ready to paint?
- if (! checkPaintReady())
- return nullptr;
-
- #if JUCE_DIRECT2D_METRICS
- owner.metrics->startFrame();
- #endif
-
- JUCE_TRACE_EVENT_INT_RECT_LIST (etw::startD2DFrame, etw::direct2dKeyword, owner.getFrameId(), paintAreas);
-
- const auto deviceContext = getDeviceContext();
-
- // Init device context transform
- resetTransform (deviceContext);
-
- // Start drawing
- deviceContext->SetTarget (getDeviceContextTarget());
- deviceContext->BeginDraw();
-
- // Init the save state stack and return the first saved state
- return pushFirstSavedState (paintBounds);
- }
-
- virtual HRESULT finishFrame()
- {
- // Fully pop the state stack
- popAllSavedStates();
-
- // Finish drawing
- // SetTarget(nullptr) so the device context doesn't hold a reference to the swap chain buffer
- HRESULT hr = S_OK;
- {
- JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (owner.metrics, endDrawDuration)
- JUCE_SCOPED_TRACE_EVENT_FRAME (etw::endDraw, etw::direct2dKeyword, owner.getFrameId());
-
- const auto deviceContext = getDeviceContext();
- hr = deviceContext->EndDraw();
- deviceContext->SetTarget (nullptr);
- }
-
- jassert (SUCCEEDED (hr));
-
- if (FAILED (hr))
- teardown();
-
- #if JUCE_DIRECT2D_METRICS
- owner.metrics->finishFrame();
- #endif
-
- return hr;
- }
-
- SavedState* getCurrentSavedState() const
- {
- return ! savedClientStates.empty() ? savedClientStates.back().get() : nullptr;
- }
-
- SavedState* pushFirstSavedState (Rectangle initialClipRegion)
- {
- jassert (savedClientStates.empty());
-
- savedClientStates.push_back (std::make_unique (owner,
- initialClipRegion,
- getDeviceContext(),
- deviceResources->colourBrush,
- *deviceResources));
-
- return getCurrentSavedState();
- }
-
- SavedState* pushSavedState()
- {
- jassert (! savedClientStates.empty());
-
- savedClientStates.push_back (std::make_unique (*savedClientStates.back()));
-
- return getCurrentSavedState();
- }
-
- SavedState* popSavedState()
- {
- savedClientStates.back()->popLayers();
- savedClientStates.pop_back();
-
- return getCurrentSavedState();
- }
-
- void popAllSavedStates()
- {
- while (! savedClientStates.empty())
- popSavedState();
- }
-
- virtual RectangleList getPaintAreas() const = 0;
- virtual Rectangle getFrameSize() const = 0;
- virtual ComSmartPtr getDeviceContext() const = 0;
- virtual ComSmartPtr getDeviceContextTarget() const = 0;
-
- void setDeviceContextTransform (AffineTransform transform)
- {
- setTransform (getDeviceContext(), transform);
- }
-
- void resetDeviceContextTransform()
- {
- resetTransform (getDeviceContext());
- }
-
- auto getDirect2DFactory()
- {
- return directX->getD2DFactory();
- }
-
- auto getDirectWriteFactory()
- {
- return directWrite->getDWriteFactory();
- }
-
- auto getDirectWriteFactory4()
- {
- return directWrite->getDWriteFactory4();
- }
-
- auto& getFontCollection()
- {
- return directWrite->getFonts();
- }
-
- bool fillSpriteBatch (const RectangleList& list)
- {
- if (! owner.currentState->fillType.isColour())
- return false;
-
- auto* rectangleListSpriteBatch = deviceResources->rectangleListSpriteBatch.get();
-
- if (rectangleListSpriteBatch == nullptr)
- return false;
-
- const auto deviceContext = getDeviceContext();
-
- if (deviceContext == nullptr)
- return false;
-
- owner.applyPendingClipList();
-
- const auto& transform = owner.currentState->currentTransform;
-
- if (transform.isOnlyTranslated)
- {
- auto translateRectangle = [&] (const Rectangle& r) -> Rectangle
- {
- return transform.translated (r);
- };
-
- rectangleListSpriteBatch->fillRectangles (deviceContext,
- list,
- owner.currentState->fillType.colour,
- translateRectangle,
- owner.metrics.get());
- return true;
- }
-
- if (owner.currentState->isCurrentTransformAxisAligned())
- {
- auto transformRectangle = [&] (const Rectangle& r) -> Rectangle
- {
- return transform.boundsAfterTransform (r);
- };
-
- rectangleListSpriteBatch->fillRectangles (deviceContext,
- list,
- owner.currentState->fillType.colour,
- transformRectangle,
- owner.metrics.get());
- return true;
- }
-
- auto checkRectangleWithoutTransforming = [&] (const Rectangle& r) -> Rectangle
- {
- return r;
- };
-
- ScopedTransform scopedTransform { *this, owner.currentState };
- rectangleListSpriteBatch->fillRectangles (deviceContext,
- list,
- owner.currentState->fillType.colour,
- checkRectangleWithoutTransforming,
- owner.metrics.get());
-
- return true;
- }
-
- template
- void paintPrimitive (const Shape& shape, Fn&& primitiveOp)
- {
- const auto& transform = owner.currentState->currentTransform;
-
- owner.applyPendingClipList();
-
- auto deviceContext = getDeviceContext();
-
- if (deviceContext == nullptr)
- return;
-
- const auto fillTransform = transform.isOnlyTranslated
- ? SavedState::BrushTransformFlags::applyWorldAndFillTypeTransforms
- : SavedState::BrushTransformFlags::applyFillTypeTransform;
-
- const auto brush = owner.currentState->getBrush (fillTransform);
-
- if (transform.isOnlyTranslated)
- {
- const auto translated = shape + transform.offset.toFloat();
-
- if (owner.currentState->doesIntersectClipList (translated))
- primitiveOp (translated, deviceContext, brush);
- }
- else if (owner.currentState->doesIntersectClipList (transform.boundsAfterTransform (shape)))
- {
- ScopedTransform scopedTransform { *this, owner.currentState };
- primitiveOp (shape, deviceContext, brush);
- }
- }
-
- DirectWriteGlyphRun glyphRun;
-
-private:
- static void resetTransform (ID2D1DeviceContext1* context)
- {
- context->SetTransform (D2D1::IdentityMatrix());
- }
-
- static void setTransform (ID2D1DeviceContext1* context, AffineTransform newTransform)
- {
- context->SetTransform (D2DUtilities::transformToMatrix (newTransform));
- }
-
- DxgiAdapter::Ptr findAdapter() const
- {
- return Direct2DDeviceResources::findAdapter (directX->adapters, getDeviceContext());
- }
-
- void adapterCreated (DxgiAdapter::Ptr newAdapter) override
- {
- const auto adapter = findAdapter();
-
- if (adapter == nullptr || ! adapter->uniqueIDMatches (newAdapter))
- teardown();
- }
-
- void adapterRemoved (DxgiAdapter::Ptr expiringAdapter) override
- {
- const auto adapter = findAdapter();
-
- if (adapter != nullptr && adapter->uniqueIDMatches (expiringAdapter))
- teardown();
- }
-
- HWND hwnd = nullptr;
-
- #if JUCE_DIRECT2D_METRICS
- int64 paintStartTicks = 0;
- #endif
-
- JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
-};
-
//==============================================================================
Direct2DGraphicsContext::Direct2DGraphicsContext() = default;
Direct2DGraphicsContext::~Direct2DGraphicsContext() = default;