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;