From 19061e6d17e21c7bf493644413238df6366521bc Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 15 Apr 2024 19:15:28 +0100 Subject: [PATCH] Direct2D: Add initial support --- .../Builds/Android/app/CMakeLists.txt | 26 + .../VisualStudio2017/DemoRunner_App.vcxproj | 25 + .../DemoRunner_App.vcxproj.filters | 39 + .../VisualStudio2019/DemoRunner_App.vcxproj | 25 + .../DemoRunner_App.vcxproj.filters | 39 + .../VisualStudio2022/DemoRunner_App.vcxproj | 25 + .../DemoRunner_App.vcxproj.filters | 39 + .../DemoRunner/Source/UI/MainComponent.cpp | 2 +- .../Builds/Android/app/CMakeLists.txt | 26 + .../AudioPerformanceTest_App.vcxproj | 25 + .../AudioPerformanceTest_App.vcxproj.filters | 39 + .../Builds/Android/app/CMakeLists.txt | 26 + .../AudioPluginHost_App.vcxproj | 25 + .../AudioPluginHost_App.vcxproj.filters | 39 + .../AudioPluginHost_App.vcxproj | 25 + .../AudioPluginHost_App.vcxproj.filters | 39 + .../AudioPluginHost_App.vcxproj | 25 + .../AudioPluginHost_App.vcxproj.filters | 39 + .../Builds/Android/app/CMakeLists.txt | 26 + .../NetworkGraphicsDemo_App.vcxproj | 25 + .../NetworkGraphicsDemo_App.vcxproj.filters | 39 + .../NetworkGraphicsDemo/Source/SharedCanvas.h | 7 +- .../VisualStudio2017/Projucer_App.vcxproj | 25 + .../Projucer_App.vcxproj.filters | 39 + .../VisualStudio2019/Projucer_App.vcxproj | 25 + .../Projucer_App.vcxproj.filters | 39 + .../VisualStudio2022/Projucer_App.vcxproj | 25 + .../Projucer_App.vcxproj.filters | 39 + .../UnitTestRunner_ConsoleApp.vcxproj | 25 + .../UnitTestRunner_ConsoleApp.vcxproj.filters | 39 + .../UnitTestRunner_ConsoleApp.vcxproj | 25 + .../UnitTestRunner_ConsoleApp.vcxproj.filters | 39 + .../UnitTestRunner_ConsoleApp.vcxproj | 25 + .../UnitTestRunner_ConsoleApp.vcxproj.filters | 39 + .../WindowsDLL_StaticLibrary.vcxproj | 25 + .../WindowsDLL_StaticLibrary.vcxproj.filters | 39 + .../native/juce_BasicNativeHeaders.h | 9 +- .../colour/juce_ColourGradient.cpp | 57 +- .../colour/juce_ColourGradient.h | 21 +- .../contexts/juce_GraphicsContext.cpp | 102 +- .../contexts/juce_LowLevelGraphicsContext.h | 73 +- ...uce_LowLevelGraphicsPostScriptRenderer.cpp | 2 +- .../juce_LowLevelGraphicsPostScriptRenderer.h | 2 +- .../juce_LowLevelGraphicsSoftwareRenderer.cpp | 8 +- .../effects/juce_DropShadowEffect.cpp | 73 +- .../juce_graphics/effects/juce_GlowEffect.cpp | 15 +- .../juce_graphics/effects/juce_GlowEffect.h | 1 + modules/juce_graphics/fonts/juce_Font.cpp | 4 + .../fonts/juce_SimpleShapedText.cpp | 18 +- modules/juce_graphics/geometry/juce_Path.cpp | 62 +- modules/juce_graphics/geometry/juce_Path.h | 2 - modules/juce_graphics/images/juce_Image.cpp | 227 +- modules/juce_graphics/images/juce_Image.h | 23 +- modules/juce_graphics/juce_graphics.cpp | 54 +- modules/juce_graphics/juce_graphics.h | 15 +- .../native/juce_CoreGraphicsContext_mac.h | 4 +- .../native/juce_CoreGraphicsContext_mac.mm | 2 +- .../juce_Direct2DGraphicsContext_windows.cpp | 2081 ++++++++++++----- .../juce_Direct2DGraphicsContext_windows.h | 113 +- .../native/juce_Direct2DHelpers_windows.cpp | 443 ++++ .../juce_Direct2DHwndContext_windows.cpp | 720 ++++++ .../native/juce_Direct2DHwndContext_windows.h | 73 + .../juce_Direct2DImageContext_windows.cpp | 124 + .../juce_Direct2DImageContext_windows.h | 55 + .../native/juce_Direct2DImage_windows.cpp | 654 ++++++ .../native/juce_Direct2DImage_windows.h | 93 + .../native/juce_Direct2DMetrics_windows.cpp | 132 ++ .../native/juce_Direct2DMetrics_windows.h | 320 +++ .../native/juce_Direct2DResources_windows.cpp | 670 ++++++ .../juce_DirectWriteTypeface_windows.cpp | 143 +- .../native/juce_DirectX_windows.h | 327 +++ .../juce_graphics/native/juce_EventTracing.h | 310 +++ .../native/juce_RenderingHelpers.h | 68 +- .../components/juce_Component.cpp | 96 +- .../juce_StandardCachedComponentImage.h | 129 + modules/juce_gui_basics/juce_gui_basics.cpp | 28 +- .../mouse/juce_DragAndDropContainer.cpp | 30 +- .../native/juce_VBlank_windows.cpp | 24 +- .../native/juce_Windowing_windows.cpp | 1222 ++++++---- .../windows/juce_ComponentPeer.cpp | 2 + .../windows/juce_ComponentPeer.h | 13 +- .../native/juce_HWNDComponent_windows.cpp | 19 +- .../juce_opengl/native/juce_OpenGL_windows.h | 4 +- 83 files changed, 8182 insertions(+), 1558 deletions(-) create mode 100644 modules/juce_graphics/native/juce_Direct2DHelpers_windows.cpp create mode 100644 modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp create mode 100644 modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h create mode 100644 modules/juce_graphics/native/juce_Direct2DImageContext_windows.cpp create mode 100644 modules/juce_graphics/native/juce_Direct2DImageContext_windows.h create mode 100644 modules/juce_graphics/native/juce_Direct2DImage_windows.cpp create mode 100644 modules/juce_graphics/native/juce_Direct2DImage_windows.h create mode 100644 modules/juce_graphics/native/juce_Direct2DMetrics_windows.cpp create mode 100644 modules/juce_graphics/native/juce_Direct2DMetrics_windows.h create mode 100644 modules/juce_graphics/native/juce_Direct2DResources_windows.cpp create mode 100644 modules/juce_graphics/native/juce_DirectX_windows.h create mode 100644 modules/juce_graphics/native/juce_EventTracing.h create mode 100644 modules/juce_gui_basics/detail/juce_StandardCachedComponentImage.h diff --git a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt index d3ab2b3b2f..48d60a1cf5 100644 --- a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt +++ b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt @@ -1949,8 +1949,20 @@ 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_Direct2DHelpers_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DImageContext_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DImageContext_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DMetrics_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DMetrics_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DResources_windows.cpp" "../../../../../modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp" "../../../../../modules/juce_graphics/native/juce_DirectWriteTypeLayout_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_DirectX_windows.h" + "../../../../../modules/juce_graphics/native/juce_EventTracing.h" "../../../../../modules/juce_graphics/native/juce_Fonts_android.cpp" "../../../../../modules/juce_graphics/native/juce_Fonts_freetype.cpp" "../../../../../modules/juce_graphics/native/juce_Fonts_linux.cpp" @@ -2050,6 +2062,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_gui_basics/detail/juce_ScopedContentSharerInterface.h" "../../../../../modules/juce_gui_basics/detail/juce_ScopedMessageBoxImpl.h" "../../../../../modules/juce_gui_basics/detail/juce_ScopedMessageBoxInterface.h" + "../../../../../modules/juce_gui_basics/detail/juce_StandardCachedComponentImage.h" "../../../../../modules/juce_gui_basics/detail/juce_ToolbarItemDragAndDropOverlayComponent.h" "../../../../../modules/juce_gui_basics/detail/juce_TopLevelWindowManager.h" "../../../../../modules/juce_gui_basics/detail/juce_ViewportHelpers.h" @@ -4432,8 +4445,20 @@ 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_Direct2DHelpers_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DImageContext_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DImageContext_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DMetrics_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DMetrics_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DResources_windows.cpp" "../../../../../modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp" "../../../../../modules/juce_graphics/native/juce_DirectWriteTypeLayout_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_DirectX_windows.h" + "../../../../../modules/juce_graphics/native/juce_EventTracing.h" "../../../../../modules/juce_graphics/native/juce_Fonts_android.cpp" "../../../../../modules/juce_graphics/native/juce_Fonts_freetype.cpp" "../../../../../modules/juce_graphics/native/juce_Fonts_linux.cpp" @@ -4533,6 +4558,7 @@ set_source_files_properties( "../../../../../modules/juce_gui_basics/detail/juce_ScopedContentSharerInterface.h" "../../../../../modules/juce_gui_basics/detail/juce_ScopedMessageBoxImpl.h" "../../../../../modules/juce_gui_basics/detail/juce_ScopedMessageBoxInterface.h" + "../../../../../modules/juce_gui_basics/detail/juce_StandardCachedComponentImage.h" "../../../../../modules/juce_gui_basics/detail/juce_ToolbarItemDragAndDropOverlayComponent.h" "../../../../../modules/juce_gui_basics/detail/juce_TopLevelWindowManager.h" "../../../../../modules/juce_gui_basics/detail/juce_ViewportHelpers.h" diff --git a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj index 4472af4312..470450b0ce 100644 --- a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj +++ b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj @@ -2351,6 +2351,24 @@ true + + true + + + true + + + true + + + true + + + true + + + true + true @@ -4220,6 +4238,12 @@ + + + + + + @@ -4275,6 +4299,7 @@ + diff --git a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters index 927e467c9d..5ec3094dc5 100644 --- a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters +++ b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters @@ -3103,6 +3103,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -7314,6 +7332,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -7479,6 +7515,9 @@ JUCE Modules\juce_gui_basics\detail + + JUCE Modules\juce_gui_basics\detail + JUCE Modules\juce_gui_basics\detail diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj index 386672621c..73f73f0de7 100644 --- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj +++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj @@ -2351,6 +2351,24 @@ true + + true + + + true + + + true + + + true + + + true + + + true + true @@ -4220,6 +4238,12 @@ + + + + + + @@ -4275,6 +4299,7 @@ + diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters index 719d35ca9c..a406b70e15 100644 --- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters +++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters @@ -3103,6 +3103,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -7314,6 +7332,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -7479,6 +7515,9 @@ JUCE Modules\juce_gui_basics\detail + + JUCE Modules\juce_gui_basics\detail + JUCE Modules\juce_gui_basics\detail diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj index bcc8892132..bb22be11a5 100644 --- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj +++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj @@ -2351,6 +2351,24 @@ true + + true + + + true + + + true + + + true + + + true + + + true + true @@ -4220,6 +4238,12 @@ + + + + + + @@ -4275,6 +4299,7 @@ + diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters index f2bd15238a..3dacfcc93f 100644 --- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters +++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters @@ -3103,6 +3103,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -7314,6 +7332,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -7479,6 +7515,9 @@ JUCE Modules\juce_gui_basics\detail + + JUCE Modules\juce_gui_basics\detail + JUCE Modules\juce_gui_basics\detail diff --git a/examples/DemoRunner/Source/UI/MainComponent.cpp b/examples/DemoRunner/Source/UI/MainComponent.cpp index ecd11ef99a..975c32a754 100644 --- a/examples/DemoRunner/Source/UI/MainComponent.cpp +++ b/examples/DemoRunner/Source/UI/MainComponent.cpp @@ -288,7 +288,7 @@ MainComponent::MainComponent() { #if JUCE_MAC && USE_COREGRAPHICS_RENDERING setRenderingEngine (1); - #else + #elif ! JUCE_WINDOWS setRenderingEngine (0); #endif } diff --git a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt index 258cb171a3..dfab116b91 100644 --- a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt +++ b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt @@ -1711,8 +1711,20 @@ 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_Direct2DHelpers_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DImageContext_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DImageContext_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DMetrics_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DMetrics_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DResources_windows.cpp" "../../../../../modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp" "../../../../../modules/juce_graphics/native/juce_DirectWriteTypeLayout_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_DirectX_windows.h" + "../../../../../modules/juce_graphics/native/juce_EventTracing.h" "../../../../../modules/juce_graphics/native/juce_Fonts_android.cpp" "../../../../../modules/juce_graphics/native/juce_Fonts_freetype.cpp" "../../../../../modules/juce_graphics/native/juce_Fonts_linux.cpp" @@ -1812,6 +1824,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_gui_basics/detail/juce_ScopedContentSharerInterface.h" "../../../../../modules/juce_gui_basics/detail/juce_ScopedMessageBoxImpl.h" "../../../../../modules/juce_gui_basics/detail/juce_ScopedMessageBoxInterface.h" + "../../../../../modules/juce_gui_basics/detail/juce_StandardCachedComponentImage.h" "../../../../../modules/juce_gui_basics/detail/juce_ToolbarItemDragAndDropOverlayComponent.h" "../../../../../modules/juce_gui_basics/detail/juce_TopLevelWindowManager.h" "../../../../../modules/juce_gui_basics/detail/juce_ViewportHelpers.h" @@ -3876,8 +3889,20 @@ 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_Direct2DHelpers_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DImageContext_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DImageContext_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DMetrics_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DMetrics_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DResources_windows.cpp" "../../../../../modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp" "../../../../../modules/juce_graphics/native/juce_DirectWriteTypeLayout_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_DirectX_windows.h" + "../../../../../modules/juce_graphics/native/juce_EventTracing.h" "../../../../../modules/juce_graphics/native/juce_Fonts_android.cpp" "../../../../../modules/juce_graphics/native/juce_Fonts_freetype.cpp" "../../../../../modules/juce_graphics/native/juce_Fonts_linux.cpp" @@ -3977,6 +4002,7 @@ set_source_files_properties( "../../../../../modules/juce_gui_basics/detail/juce_ScopedContentSharerInterface.h" "../../../../../modules/juce_gui_basics/detail/juce_ScopedMessageBoxImpl.h" "../../../../../modules/juce_gui_basics/detail/juce_ScopedMessageBoxInterface.h" + "../../../../../modules/juce_gui_basics/detail/juce_StandardCachedComponentImage.h" "../../../../../modules/juce_gui_basics/detail/juce_ToolbarItemDragAndDropOverlayComponent.h" "../../../../../modules/juce_gui_basics/detail/juce_TopLevelWindowManager.h" "../../../../../modules/juce_gui_basics/detail/juce_ViewportHelpers.h" diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj index b3f26783c9..13eaac485d 100644 --- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj +++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj @@ -2044,6 +2044,24 @@ true + + true + + + true + + + true + + + true + + + true + + + true + true @@ -3691,6 +3709,12 @@ + + + + + + @@ -3746,6 +3770,7 @@ + diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters index a17006751e..c21a5fb549 100644 --- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters +++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters @@ -2647,6 +2647,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -6369,6 +6387,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -6534,6 +6570,9 @@ JUCE Modules\juce_gui_basics\detail + + JUCE Modules\juce_gui_basics\detail + JUCE Modules\juce_gui_basics\detail diff --git a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt index 876c932b7e..3480c79f14 100644 --- a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt +++ b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt @@ -1841,8 +1841,20 @@ 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_Direct2DHelpers_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DImageContext_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DImageContext_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DMetrics_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DMetrics_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DResources_windows.cpp" "../../../../../modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp" "../../../../../modules/juce_graphics/native/juce_DirectWriteTypeLayout_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_DirectX_windows.h" + "../../../../../modules/juce_graphics/native/juce_EventTracing.h" "../../../../../modules/juce_graphics/native/juce_Fonts_android.cpp" "../../../../../modules/juce_graphics/native/juce_Fonts_freetype.cpp" "../../../../../modules/juce_graphics/native/juce_Fonts_linux.cpp" @@ -1942,6 +1954,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_gui_basics/detail/juce_ScopedContentSharerInterface.h" "../../../../../modules/juce_gui_basics/detail/juce_ScopedMessageBoxImpl.h" "../../../../../modules/juce_gui_basics/detail/juce_ScopedMessageBoxInterface.h" + "../../../../../modules/juce_gui_basics/detail/juce_StandardCachedComponentImage.h" "../../../../../modules/juce_gui_basics/detail/juce_ToolbarItemDragAndDropOverlayComponent.h" "../../../../../modules/juce_gui_basics/detail/juce_TopLevelWindowManager.h" "../../../../../modules/juce_gui_basics/detail/juce_ViewportHelpers.h" @@ -4159,8 +4172,20 @@ 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_Direct2DHelpers_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DImageContext_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DImageContext_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DMetrics_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DMetrics_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DResources_windows.cpp" "../../../../../modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp" "../../../../../modules/juce_graphics/native/juce_DirectWriteTypeLayout_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_DirectX_windows.h" + "../../../../../modules/juce_graphics/native/juce_EventTracing.h" "../../../../../modules/juce_graphics/native/juce_Fonts_android.cpp" "../../../../../modules/juce_graphics/native/juce_Fonts_freetype.cpp" "../../../../../modules/juce_graphics/native/juce_Fonts_linux.cpp" @@ -4260,6 +4285,7 @@ set_source_files_properties( "../../../../../modules/juce_gui_basics/detail/juce_ScopedContentSharerInterface.h" "../../../../../modules/juce_gui_basics/detail/juce_ScopedMessageBoxImpl.h" "../../../../../modules/juce_gui_basics/detail/juce_ScopedMessageBoxInterface.h" + "../../../../../modules/juce_gui_basics/detail/juce_StandardCachedComponentImage.h" "../../../../../modules/juce_gui_basics/detail/juce_ToolbarItemDragAndDropOverlayComponent.h" "../../../../../modules/juce_gui_basics/detail/juce_TopLevelWindowManager.h" "../../../../../modules/juce_gui_basics/detail/juce_ViewportHelpers.h" diff --git a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj index efe24d5370..b671307ed1 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj +++ b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj @@ -2178,6 +2178,24 @@ true + + true + + + true + + + true + + + true + + + true + + + true + true @@ -3924,6 +3942,12 @@ + + + + + + @@ -3979,6 +4003,7 @@ + diff --git a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters index 2c14ce6efc..8c6f069a3a 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters +++ b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters @@ -2854,6 +2854,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -6804,6 +6822,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -6969,6 +7005,9 @@ JUCE Modules\juce_gui_basics\detail + + JUCE Modules\juce_gui_basics\detail + JUCE Modules\juce_gui_basics\detail diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj index 01f090e1f6..81ca1234d4 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj +++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj @@ -2178,6 +2178,24 @@ true + + true + + + true + + + true + + + true + + + true + + + true + true @@ -3924,6 +3942,12 @@ + + + + + + @@ -3979,6 +4003,7 @@ + diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters index a9ec14bd1f..a292c2ed5e 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters +++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters @@ -2854,6 +2854,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -6804,6 +6822,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -6969,6 +7005,9 @@ JUCE Modules\juce_gui_basics\detail + + JUCE Modules\juce_gui_basics\detail + JUCE Modules\juce_gui_basics\detail diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj index 531ae00fa6..6ba5be41be 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj +++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj @@ -2178,6 +2178,24 @@ true + + true + + + true + + + true + + + true + + + true + + + true + true @@ -3924,6 +3942,12 @@ + + + + + + @@ -3979,6 +4003,7 @@ + diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters index 2bfc7ee7fd..164d80dfdf 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters +++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters @@ -2854,6 +2854,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -6804,6 +6822,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -6969,6 +7005,9 @@ JUCE Modules\juce_gui_basics\detail + + JUCE Modules\juce_gui_basics\detail + JUCE Modules\juce_gui_basics\detail diff --git a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt index 5466961251..e06bb633a4 100644 --- a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt +++ b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt @@ -1730,8 +1730,20 @@ 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_Direct2DHelpers_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DImageContext_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DImageContext_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DMetrics_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DMetrics_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DResources_windows.cpp" "../../../../../modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp" "../../../../../modules/juce_graphics/native/juce_DirectWriteTypeLayout_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_DirectX_windows.h" + "../../../../../modules/juce_graphics/native/juce_EventTracing.h" "../../../../../modules/juce_graphics/native/juce_Fonts_android.cpp" "../../../../../modules/juce_graphics/native/juce_Fonts_freetype.cpp" "../../../../../modules/juce_graphics/native/juce_Fonts_linux.cpp" @@ -1831,6 +1843,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_gui_basics/detail/juce_ScopedContentSharerInterface.h" "../../../../../modules/juce_gui_basics/detail/juce_ScopedMessageBoxImpl.h" "../../../../../modules/juce_gui_basics/detail/juce_ScopedMessageBoxInterface.h" + "../../../../../modules/juce_gui_basics/detail/juce_StandardCachedComponentImage.h" "../../../../../modules/juce_gui_basics/detail/juce_ToolbarItemDragAndDropOverlayComponent.h" "../../../../../modules/juce_gui_basics/detail/juce_TopLevelWindowManager.h" "../../../../../modules/juce_gui_basics/detail/juce_ViewportHelpers.h" @@ -3975,8 +3988,20 @@ 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_Direct2DHelpers_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DImage_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DImageContext_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DImageContext_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DMetrics_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_Direct2DMetrics_windows.h" + "../../../../../modules/juce_graphics/native/juce_Direct2DResources_windows.cpp" "../../../../../modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp" "../../../../../modules/juce_graphics/native/juce_DirectWriteTypeLayout_windows.cpp" + "../../../../../modules/juce_graphics/native/juce_DirectX_windows.h" + "../../../../../modules/juce_graphics/native/juce_EventTracing.h" "../../../../../modules/juce_graphics/native/juce_Fonts_android.cpp" "../../../../../modules/juce_graphics/native/juce_Fonts_freetype.cpp" "../../../../../modules/juce_graphics/native/juce_Fonts_linux.cpp" @@ -4076,6 +4101,7 @@ set_source_files_properties( "../../../../../modules/juce_gui_basics/detail/juce_ScopedContentSharerInterface.h" "../../../../../modules/juce_gui_basics/detail/juce_ScopedMessageBoxImpl.h" "../../../../../modules/juce_gui_basics/detail/juce_ScopedMessageBoxInterface.h" + "../../../../../modules/juce_gui_basics/detail/juce_StandardCachedComponentImage.h" "../../../../../modules/juce_gui_basics/detail/juce_ToolbarItemDragAndDropOverlayComponent.h" "../../../../../modules/juce_gui_basics/detail/juce_TopLevelWindowManager.h" "../../../../../modules/juce_gui_basics/detail/juce_ViewportHelpers.h" diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj index 6dd4b3b37b..782e2ff0bd 100644 --- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj +++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj @@ -2065,6 +2065,24 @@ true + + true + + + true + + + true + + + true + + + true + + + true + true @@ -3789,6 +3807,12 @@ + + + + + + @@ -3844,6 +3868,7 @@ + diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters index 813d5906a9..8c75d8c006 100644 --- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters +++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters @@ -2701,6 +2701,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -6531,6 +6549,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -6696,6 +6732,9 @@ JUCE Modules\juce_gui_basics\detail + + JUCE Modules\juce_gui_basics\detail + JUCE Modules\juce_gui_basics\detail diff --git a/extras/NetworkGraphicsDemo/Source/SharedCanvas.h b/extras/NetworkGraphicsDemo/Source/SharedCanvas.h index c98e3eaf27..1bc2b5a395 100644 --- a/extras/NetworkGraphicsDemo/Source/SharedCanvas.h +++ b/extras/NetworkGraphicsDemo/Source/SharedCanvas.h @@ -307,9 +307,10 @@ public: } //============================================================================== - bool isVectorDevice() const override { return true; } - float getPhysicalPixelScaleFactor() override { return 1.0f; } - void setOrigin (Point o) override { addTransform (AffineTransform::translation ((float) o.x, (float) o.y)); } + bool isVectorDevice() const override { return true; } + float getPhysicalPixelScaleFactor() const override { return 1.0f; } + uint64_t getFrameId() const override { return 0; } + void setOrigin (Point o) override { addTransform (AffineTransform::translation ((float) o.x, (float) o.y)); } void addTransform (const AffineTransform& t) override { diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj index 9805bc0059..8a0dcd6fd3 100644 --- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj @@ -1247,6 +1247,24 @@ true + + true + + + true + + + true + + + true + + + true + + + true + true @@ -2606,6 +2624,12 @@ + + + + + + @@ -2661,6 +2685,7 @@ + diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters index 501a5f7948..4afdbb74b7 100644 --- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters @@ -1645,6 +1645,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -4509,6 +4527,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -4674,6 +4710,9 @@ JUCE Modules\juce_gui_basics\detail + + JUCE Modules\juce_gui_basics\detail + JUCE Modules\juce_gui_basics\detail diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj index cf7dd55d5b..5a95bf893b 100644 --- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj @@ -1247,6 +1247,24 @@ true + + true + + + true + + + true + + + true + + + true + + + true + true @@ -2606,6 +2624,12 @@ + + + + + + @@ -2661,6 +2685,7 @@ + diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters index 6c47970b38..6b17fd3496 100644 --- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters @@ -1645,6 +1645,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -4509,6 +4527,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -4674,6 +4710,9 @@ JUCE Modules\juce_gui_basics\detail + + JUCE Modules\juce_gui_basics\detail + JUCE Modules\juce_gui_basics\detail diff --git a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj index ba9ca29d0d..79dcdea15c 100644 --- a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj @@ -1247,6 +1247,24 @@ true + + true + + + true + + + true + + + true + + + true + + + true + true @@ -2606,6 +2624,12 @@ + + + + + + @@ -2661,6 +2685,7 @@ + diff --git a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters index 6a0a19264c..31635c9d15 100644 --- a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters @@ -1645,6 +1645,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -4509,6 +4527,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -4674,6 +4710,9 @@ JUCE Modules\juce_gui_basics\detail + + JUCE Modules\juce_gui_basics\detail + JUCE Modules\juce_gui_basics\detail diff --git a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj index 90eb689173..9d30fa240e 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj +++ b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj @@ -2186,6 +2186,24 @@ true + + true + + + true + + + true + + + true + + + true + + + true + true @@ -4020,6 +4038,12 @@ + + + + + + @@ -4075,6 +4099,7 @@ + diff --git a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters index eec19e5c28..d7f24e70d1 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters +++ b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters @@ -2875,6 +2875,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -6918,6 +6936,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -7083,6 +7119,9 @@ JUCE Modules\juce_gui_basics\detail + + JUCE Modules\juce_gui_basics\detail + JUCE Modules\juce_gui_basics\detail diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj index 95766d8b0b..7d083e9e14 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj +++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj @@ -2186,6 +2186,24 @@ true + + true + + + true + + + true + + + true + + + true + + + true + true @@ -4020,6 +4038,12 @@ + + + + + + @@ -4075,6 +4099,7 @@ + diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters index f0d8c5d661..e30b11dead 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters +++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters @@ -2875,6 +2875,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -6918,6 +6936,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -7083,6 +7119,9 @@ JUCE Modules\juce_gui_basics\detail + + JUCE Modules\juce_gui_basics\detail + JUCE Modules\juce_gui_basics\detail diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj index 0bc775c155..9440bacc4d 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj +++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj @@ -2186,6 +2186,24 @@ true + + true + + + true + + + true + + + true + + + true + + + true + true @@ -4020,6 +4038,12 @@ + + + + + + @@ -4075,6 +4099,7 @@ + diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters index 03dd29757d..b0818c6f51 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters +++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters @@ -2875,6 +2875,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -6918,6 +6936,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -7083,6 +7119,9 @@ JUCE Modules\juce_gui_basics\detail + + JUCE Modules\juce_gui_basics\detail + JUCE Modules\juce_gui_basics\detail diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj index 3487f33952..31f1069025 100644 --- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj +++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj @@ -2064,6 +2064,24 @@ true + + true + + + true + + + true + + + true + + + true + + + true + true @@ -3765,6 +3783,12 @@ + + + + + + @@ -3820,6 +3844,7 @@ + diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters index 37f34aedc0..0b69cd7a7b 100644 --- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters +++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters @@ -2698,6 +2698,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -6498,6 +6516,24 @@ JUCE Modules\juce_graphics\native + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + + + JUCE Modules\juce_graphics\native + JUCE Modules\juce_graphics\native @@ -6663,6 +6699,9 @@ JUCE Modules\juce_gui_basics\detail + + JUCE Modules\juce_gui_basics\detail + JUCE Modules\juce_gui_basics\detail diff --git a/modules/juce_core/native/juce_BasicNativeHeaders.h b/modules/juce_core/native/juce_BasicNativeHeaders.h index cca7a67f38..32c7126d05 100644 --- a/modules/juce_core/native/juce_BasicNativeHeaders.h +++ b/modules/juce_core/native/juce_BasicNativeHeaders.h @@ -100,13 +100,8 @@ #define _WINSOCK_DEPRECATED_NO_WARNINGS 1 #define STRICT 1 #define WIN32_LEAN_AND_MEAN 1 - #if JUCE_MINGW - #if ! defined (_WIN32_WINNT) - #define _WIN32_WINNT 0x0600 - #endif - #else - #define _WIN32_WINNT 0x0602 - #endif + #define WINVER _WIN32_WINNT_WIN10 + #define _WIN32_WINNT _WIN32_WINNT_WIN10 #define _UNICODE 1 #define UNICODE 1 #ifndef _WIN32_IE diff --git a/modules/juce_graphics/colour/juce_ColourGradient.cpp b/modules/juce_graphics/colour/juce_ColourGradient.cpp index cb13bd07f0..11317ef36c 100644 --- a/modules/juce_graphics/colour/juce_ColourGradient.cpp +++ b/modules/juce_graphics/colour/juce_ColourGradient.cpp @@ -89,8 +89,6 @@ ColourGradient::ColourGradient (Colour colour1, Point p1, ColourPoint { 1.0, colour2 }); } -ColourGradient::~ColourGradient() {} - ColourGradient ColourGradient::vertical (Colour c1, float y1, Colour c2, float y2) { return { c1, 0, y1, c2, 0, y2, false }; @@ -101,17 +99,45 @@ ColourGradient ColourGradient::horizontal (Colour c1, float x1, Colour c2, float return { c1, x1, 0, c2, x2, 0, false }; } -bool ColourGradient::operator== (const ColourGradient& other) const noexcept +struct PointComparisons { - return point1 == other.point1 && point2 == other.point2 - && isRadial == other.isRadial - && colours == other.colours; + auto tie() const { return std::tie (point->x, point->y); } + + bool operator== (const PointComparisons& other) const { return tie() == other.tie(); } + bool operator!= (const PointComparisons& other) const { return tie() != other.tie(); } + bool operator< (const PointComparisons& other) const { return tie() < other.tie(); } + + const Point* point = nullptr; +}; + +struct ColourGradient::ColourPointArrayComparisons +{ + bool operator== (const ColourPointArrayComparisons& other) const { return *array == *other.array; } + bool operator!= (const ColourPointArrayComparisons& other) const { return *array != *other.array; } + + bool operator< (const ColourPointArrayComparisons& other) const + { + return std::lexicographical_compare (array->begin(), array->end(), other.array->begin(), other.array->end()); + } + + const Array* array = nullptr; +}; + +auto ColourGradient::tie() const +{ + return std::tuple (PointComparisons { &point1 }, + PointComparisons { &point2 }, + isRadial, + ColourPointArrayComparisons { &colours }); } -bool ColourGradient::operator!= (const ColourGradient& other) const noexcept -{ - return ! operator== (other); -} +bool ColourGradient::operator== (const ColourGradient& other) const noexcept { return tie() == other.tie(); } +bool ColourGradient::operator!= (const ColourGradient& other) const noexcept { return tie() != other.tie(); } + +bool ColourGradient::operator< (const ColourGradient& other) const noexcept { return tie() < other.tie(); } +bool ColourGradient::operator<= (const ColourGradient& other) const noexcept { return tie() <= other.tie(); } +bool ColourGradient::operator> (const ColourGradient& other) const noexcept { return tie() > other.tie(); } +bool ColourGradient::operator>= (const ColourGradient& other) const noexcept { return tie() >= other.tie(); } //============================================================================== void ColourGradient::clearColours() @@ -265,15 +291,4 @@ bool ColourGradient::isInvisible() const noexcept return true; } -bool ColourGradient::ColourPoint::operator== (ColourPoint other) const noexcept -{ - const auto tie = [] (const ColourPoint& p) { return std::tie (p.position, p.colour); }; - return tie (*this) == tie (other); -} - -bool ColourGradient::ColourPoint::operator!= (ColourPoint other) const noexcept -{ - return ! operator== (other); -} - } // namespace juce diff --git a/modules/juce_graphics/colour/juce_ColourGradient.h b/modules/juce_graphics/colour/juce_ColourGradient.h index b427c68d10..6439c2377a 100644 --- a/modules/juce_graphics/colour/juce_ColourGradient.h +++ b/modules/juce_graphics/colour/juce_ColourGradient.h @@ -124,9 +124,6 @@ public: return horizontal (colourLeft, (float) area.getX(), colourRight, (float) area.getRight()); } - /** Destructor */ - ~ColourGradient(); - //============================================================================== /** Removes any colours that have been added. @@ -224,18 +221,32 @@ public: bool operator== (const ColourGradient&) const noexcept; bool operator!= (const ColourGradient&) const noexcept; + /** This comparison, and the other ordered comparisons are provided only for compatibility with + ordered container types like std::set and std::map. + */ + bool operator< (const ColourGradient&) const noexcept; + bool operator<= (const ColourGradient&) const noexcept; + bool operator> (const ColourGradient&) const noexcept; + bool operator>= (const ColourGradient&) const noexcept; private: //============================================================================== struct ColourPoint { - bool operator== (ColourPoint) const noexcept; - bool operator!= (ColourPoint) const noexcept; + auto tie() const { return std::tuple (position, colour.getPixelARGB().getNativeARGB()); } + + bool operator== (ColourPoint other) const noexcept { return tie() == other.tie(); } + bool operator!= (ColourPoint other) const noexcept { return tie() != other.tie(); } + bool operator< (ColourPoint other) const noexcept { return tie() < other.tie(); } double position; Colour colour; }; + struct ColourPointArrayComparisons; + + auto tie() const; + Array colours; JUCE_LEAK_DETECTOR (ColourGradient) diff --git a/modules/juce_graphics/contexts/juce_GraphicsContext.cpp b/modules/juce_graphics/contexts/juce_GraphicsContext.cpp index 4631b3cb59..68c49ab8e6 100644 --- a/modules/juce_graphics/contexts/juce_GraphicsContext.cpp +++ b/modules/juce_graphics/contexts/juce_GraphicsContext.cpp @@ -128,6 +128,8 @@ Graphics::Graphics (LowLevelGraphicsContext& internalContext) noexcept //============================================================================== void Graphics::resetToDefaultState() { + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::resetToDefaultState, etw::graphicsKeyword, context.getFrameId()); + saveStateIfPending(); context.setFill (FillType()); context.setFont (FontOptions{}.withMetricsKind (TypefaceMetricsKind::legacy)); @@ -141,6 +143,8 @@ bool Graphics::isVectorDevice() const bool Graphics::reduceClipRegion (Rectangle area) { + JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::reduceClipRegionRectangle, etw::graphicsKeyword, context.getFrameId(), area) + saveStateIfPending(); return context.clipToRectangle (area); } @@ -152,12 +156,16 @@ bool Graphics::reduceClipRegion (int x, int y, int w, int h) bool Graphics::reduceClipRegion (const RectangleList& clipRegion) { + JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::reduceClipRegionRectangleList, etw::graphicsKeyword, context.getFrameId(), clipRegion) + saveStateIfPending(); return context.clipToRectangleList (clipRegion); } bool Graphics::reduceClipRegion (const Path& path, const AffineTransform& transform) { + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::reduceClipRegionPath, etw::graphicsKeyword, context.getFrameId()); + saveStateIfPending(); context.clipToPath (path, transform); return ! context.isClipEmpty(); @@ -165,6 +173,8 @@ bool Graphics::reduceClipRegion (const Path& path, const AffineTransform& transf bool Graphics::reduceClipRegion (const Image& image, const AffineTransform& transform) { + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::reduceClipRegionImage, etw::graphicsKeyword, context.getFrameId()); + saveStateIfPending(); context.clipToImageAlpha (image, transform); return ! context.isClipEmpty(); @@ -172,6 +182,8 @@ bool Graphics::reduceClipRegion (const Image& image, const AffineTransform& tran void Graphics::excludeClipRegion (Rectangle rectangleToExclude) { + JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::excludeClipRegion, etw::graphicsKeyword, context.getFrameId(), rectangleToExclude); + saveStateIfPending(); context.excludeClipRectangle (rectangleToExclude); } @@ -188,12 +200,16 @@ Rectangle Graphics::getClipBounds() const void Graphics::saveState() { + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::saveState, etw::graphicsKeyword, context.getFrameId()); + saveStateIfPending(); saveStatePending = true; } void Graphics::restoreState() { + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::restoreState, etw::graphicsKeyword, context.getFrameId()); + if (saveStatePending) saveStatePending = false; else @@ -204,6 +220,8 @@ void Graphics::saveStateIfPending() { if (saveStatePending) { + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::saveState, etw::graphicsKeyword, context.getFrameId()); + saveStatePending = false; context.saveState(); } @@ -222,6 +240,8 @@ void Graphics::setOrigin (int x, int y) void Graphics::addTransform (const AffineTransform& transform) { + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::addTransform, etw::graphicsKeyword, context.getFrameId()); + saveStateIfPending(); context.addTransform (transform); } @@ -233,12 +253,16 @@ bool Graphics::clipRegionIntersects (Rectangle area) const void Graphics::beginTransparencyLayer (float layerOpacity) { + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::beginTransparencyLayer, etw::graphicsKeyword, context.getFrameId()); + saveStateIfPending(); context.beginTransparencyLayer (layerOpacity); } void Graphics::endTransparencyLayer() { + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::endTransparencyLayer, etw::graphicsKeyword, context.getFrameId()); + context.endTransparencyLayer(); } @@ -477,42 +501,62 @@ void Graphics::drawFittedText (const String& text, int x, int y, int width, int //============================================================================== void Graphics::fillRect (Rectangle r) const { + JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::fillRect, etw::graphicsKeyword, context.getFrameId(), r) + context.fillRect (r, false); } void Graphics::fillRect (Rectangle r) const { + JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_F32 (etw::fillRect, etw::graphicsKeyword, context.getFrameId(), r) + context.fillRect (r); } void Graphics::fillRect (int x, int y, int width, int height) const { + JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::fillRect, etw::graphicsKeyword, context.getFrameId(), (Rectangle { x, y, width, height })) + context.fillRect (coordsToRectangle (x, y, width, height), false); } void Graphics::fillRect (float x, float y, float width, float height) const { + JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_F32 (etw::fillRect, etw::graphicsKeyword, context.getFrameId(), (Rectangle { x, y, width, height })) + fillRect (coordsToRectangle (x, y, width, height)); } void Graphics::fillRectList (const RectangleList& rectangles) const { + JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_F32 (etw::fillRectList, etw::graphicsKeyword, context.getFrameId(), rectangles) + context.fillRectList (rectangles); } void Graphics::fillRectList (const RectangleList& rects) const { - for (auto& r : rects) - context.fillRect (r, false); + JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::fillRectList, etw::graphicsKeyword, context.getFrameId(), rects) + + RectangleList converted; + + for (const auto& r : rects) + converted.add (r.toFloat()); + + context.fillRectList (converted); } void Graphics::fillAll() const { + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::fillAll, etw::graphicsKeyword, context.getFrameId()) + context.fillAll(); } void Graphics::fillAll (Colour colourToUse) const { + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::fillAll, etw::graphicsKeyword, context.getFrameId()) + if (! colourToUse.isTransparent()) { context.saveState(); @@ -522,16 +566,19 @@ void Graphics::fillAll (Colour colourToUse) const } } - //============================================================================== void Graphics::fillPath (const Path& path) const { + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::fillPath, etw::graphicsKeyword, context.getFrameId()); + if (! (context.isClipEmpty() || path.isEmpty())) context.fillPath (path, AffineTransform()); } void Graphics::fillPath (const Path& path, const AffineTransform& transform) const { + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::fillPath, etw::graphicsKeyword, context.getFrameId()) + if (! (context.isClipEmpty() || path.isEmpty())) context.fillPath (path, transform); } @@ -540,9 +587,10 @@ void Graphics::strokePath (const Path& path, const PathStrokeType& strokeType, const AffineTransform& transform) const { - Path stroke; - strokeType.createStrokedPath (stroke, path, transform, context.getPhysicalPixelScaleFactor()); - fillPath (stroke); + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::strokePath, etw::graphicsKeyword, context.getFrameId()) + + if (! (context.isClipEmpty() || path.isEmpty())) + context.strokePath (path, strokeType, transform); } //============================================================================== @@ -563,22 +611,16 @@ void Graphics::drawRect (Rectangle r, int lineThickness) const void Graphics::drawRect (Rectangle r, const float lineThickness) const { - jassert (r.getWidth() >= 0.0f && r.getHeight() >= 0.0f); + JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_F32 (etw::drawRect, etw::graphicsKeyword, context.getFrameId(), r) - RectangleList rects; - rects.addWithoutMerging (r.removeFromTop (lineThickness)); - rects.addWithoutMerging (r.removeFromBottom (lineThickness)); - rects.addWithoutMerging (r.removeFromLeft (lineThickness)); - rects.addWithoutMerging (r.removeFromRight (lineThickness)); - context.fillRectList (rects); + jassert (r.getWidth() >= 0.0f && r.getHeight() >= 0.0f); + context.drawRect (r, lineThickness); } //============================================================================== void Graphics::fillEllipse (Rectangle area) const { - Path p; - p.addEllipse (area); - fillPath (p); + context.fillEllipse (area); } void Graphics::fillEllipse (float x, float y, float w, float h) const @@ -593,21 +635,7 @@ void Graphics::drawEllipse (float x, float y, float width, float height, float l void Graphics::drawEllipse (Rectangle area, float lineThickness) const { - Path p; - - if (approximatelyEqual (area.getWidth(), area.getHeight())) - { - // For a circle, we can avoid having to generate a stroke - p.addEllipse (area.expanded (lineThickness * 0.5f)); - p.addEllipse (area.reduced (lineThickness * 0.5f)); - p.setUsingNonZeroWinding (false); - fillPath (p); - } - else - { - p.addEllipse (area); - strokePath (p, PathStrokeType (lineThickness)); - } + context.drawEllipse (area, lineThickness); } void Graphics::fillRoundedRectangle (float x, float y, float width, float height, float cornerSize) const @@ -617,9 +645,7 @@ void Graphics::fillRoundedRectangle (float x, float y, float width, float height void Graphics::fillRoundedRectangle (Rectangle r, const float cornerSize) const { - Path p; - p.addRoundedRectangle (r, cornerSize); - fillPath (p); + context.fillRoundedRectangle (r, cornerSize); } void Graphics::drawRoundedRectangle (float x, float y, float width, float height, @@ -630,9 +656,7 @@ void Graphics::drawRoundedRectangle (float x, float y, float width, float height void Graphics::drawRoundedRectangle (Rectangle r, float cornerSize, float lineThickness) const { - Path p; - p.addRoundedRectangle (r, cornerSize); - strokePath (p, PathStrokeType (lineThickness)); + context.drawRoundedRectangle (r, cornerSize, lineThickness); } void Graphics::drawArrow (Line line, float lineThickness, float arrowheadWidth, float arrowheadLength) const @@ -719,9 +743,7 @@ void Graphics::drawLine (float x1, float y1, float x2, float y2, float lineThick void Graphics::drawLine (Line line, const float lineThickness) const { - Path p; - p.addLineSegment (line, lineThickness); - fillPath (p); + context.drawLineWithThickness (line, lineThickness); } void Graphics::drawDashedLine (Line line, const float* dashLengths, diff --git a/modules/juce_graphics/contexts/juce_LowLevelGraphicsContext.h b/modules/juce_graphics/contexts/juce_LowLevelGraphicsContext.h index 132eaf7459..c685fcfbc7 100644 --- a/modules/juce_graphics/contexts/juce_LowLevelGraphicsContext.h +++ b/modules/juce_graphics/contexts/juce_LowLevelGraphicsContext.h @@ -71,7 +71,7 @@ public: */ virtual void setOrigin (Point) = 0; virtual void addTransform (const AffineTransform&) = 0; - virtual float getPhysicalPixelScaleFactor() = 0; + virtual float getPhysicalPixelScaleFactor() const = 0; virtual bool clipToRectangle (const Rectangle&) = 0; virtual bool clipToRectangleList (const RectangleList&) = 0; @@ -100,9 +100,35 @@ public: virtual void fillRect (const Rectangle&) = 0; virtual void fillRectList (const RectangleList&) = 0; virtual void fillPath (const Path&, const AffineTransform&) = 0; + + virtual void drawRect (const Rectangle& rect, float lineThickness) + { + auto r = rect; + RectangleList rects; + rects.addWithoutMerging (r.removeFromTop (lineThickness)); + rects.addWithoutMerging (r.removeFromBottom (lineThickness)); + rects.addWithoutMerging (r.removeFromLeft (lineThickness)); + rects.addWithoutMerging (r.removeFromRight (lineThickness)); + fillRectList (rects); + } + + virtual void strokePath (const Path& path, const PathStrokeType& strokeType, const AffineTransform& transform) + { + Path stroke; + strokeType.createStrokedPath (stroke, path, transform, getPhysicalPixelScaleFactor()); + fillPath (stroke, {}); + } + virtual void drawImage (const Image&, const AffineTransform&) = 0; virtual void drawLine (const Line&) = 0; + virtual void drawLineWithThickness (const Line& line, float lineThickness) + { + Path p; + p.addLineSegment (line, lineThickness); + fillPath (p, {}); + } + virtual void setFont (const Font&) = 0; virtual const Font& getFont() = 0; @@ -110,6 +136,51 @@ public: virtual void drawGlyphs (Span, Span>, const AffineTransform&) = 0; + + virtual void drawRoundedRectangle (const Rectangle& r, float cornerSize, float lineThickness) + { + Path p; + p.addRoundedRectangle (r, cornerSize); + strokePath (p, PathStrokeType (lineThickness), {}); + } + + virtual void fillRoundedRectangle (const Rectangle& r, float cornerSize) + { + Path p; + p.addRoundedRectangle (r, cornerSize); + fillPath (p, {}); + } + + virtual void drawEllipse (const Rectangle& area, float lineThickness) + { + Path p; + + if (approximatelyEqual (area.getWidth(), area.getHeight())) + { + // For a circle, we can avoid having to generate a stroke + p.addEllipse (area.expanded (lineThickness * 0.5f)); + p.addEllipse (area.reduced (lineThickness * 0.5f)); + p.setUsingNonZeroWinding (false); + fillPath (p, {}); + } + else + { + p.addEllipse (area); + strokePath (p, PathStrokeType (lineThickness), {}); + } + } + + virtual void fillEllipse (const Rectangle& area) + { + Path p; + p.addEllipse (area); + fillPath (p, {}); + } + + /** Returns an integer that uniquely identifies the current frame. + Useful for debugging/logging. + */ + virtual uint64_t getFrameId() const = 0; }; } // namespace juce diff --git a/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp b/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp index be7e39e215..fac4e0cb56 100644 --- a/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp +++ b/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp @@ -114,7 +114,7 @@ void LowLevelGraphicsPostScriptRenderer::addTransform (const AffineTransform& /* jassertfalse; } -float LowLevelGraphicsPostScriptRenderer::getPhysicalPixelScaleFactor() { return 1.0f; } +float LowLevelGraphicsPostScriptRenderer::getPhysicalPixelScaleFactor() const { return 1.0f; } bool LowLevelGraphicsPostScriptRenderer::clipToRectangle (const Rectangle& r) { diff --git a/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h b/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h index 8782d0e088..755a6538d8 100644 --- a/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h +++ b/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h @@ -55,7 +55,7 @@ public: bool isVectorDevice() const override; void setOrigin (Point) override; void addTransform (const AffineTransform&) override; - float getPhysicalPixelScaleFactor() override; + float getPhysicalPixelScaleFactor() const override; bool clipToRectangle (const Rectangle&) override; bool clipToRectangleList (const RectangleList&) override; diff --git a/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp b/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp index b6250e4f22..9af41bf287 100644 --- a/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp +++ b/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp @@ -39,6 +39,7 @@ LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& : RenderingHelpers::StackBasedLowLevelGraphicsContext (new RenderingHelpers::SoftwareRendererSavedState (image, image.getBounds())) { + JUCE_TRACE_LOG_PAINT_CALL (etw::startGDIImage, getFrameId()); } LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& image, Point origin, @@ -46,8 +47,13 @@ LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& : RenderingHelpers::StackBasedLowLevelGraphicsContext (new RenderingHelpers::SoftwareRendererSavedState (image, initialClip, origin)) { + + JUCE_TRACE_EVENT_INT_RECT_LIST (etw::startGDIFrame, etw::softwareRendererKeyword, getFrameId(), initialClip); } -LowLevelGraphicsSoftwareRenderer::~LowLevelGraphicsSoftwareRenderer() {} +LowLevelGraphicsSoftwareRenderer::~LowLevelGraphicsSoftwareRenderer() +{ + JUCE_TRACE_LOG_PAINT_CALL (etw::endGDIFrame, getFrameId()); +} } // namespace juce diff --git a/modules/juce_graphics/effects/juce_DropShadowEffect.cpp b/modules/juce_graphics/effects/juce_DropShadowEffect.cpp index e865c8b380..91377f554a 100644 --- a/modules/juce_graphics/effects/juce_DropShadowEffect.cpp +++ b/modules/juce_graphics/effects/juce_DropShadowEffect.cpp @@ -35,47 +35,6 @@ namespace juce { -static void blurDataTriplets (uint8* d, int num, const int delta) noexcept -{ - uint32 last = d[0]; - d[0] = (uint8) ((d[0] + d[delta] + 1) / 3); - d += delta; - - num -= 2; - - do - { - const uint32 newLast = d[0]; - d[0] = (uint8) ((last + d[0] + d[delta] + 1) / 3); - d += delta; - last = newLast; - } - while (--num > 0); - - d[0] = (uint8) ((last + d[0] + 1) / 3); -} - -static void blurSingleChannelImage (uint8* const data, const int width, const int height, - const int lineStride, const int repetitions) noexcept -{ - jassert (width > 2 && height > 2); - - for (int y = 0; y < height; ++y) - for (int i = repetitions; --i >= 0;) - blurDataTriplets (data + lineStride * y, width, 1); - - for (int x = 0; x < width; ++x) - for (int i = repetitions; --i >= 0;) - blurDataTriplets (data + x, height, lineStride); -} - -static void blurSingleChannelImage (Image& image, int radius) -{ - const Image::BitmapData bm (image, Image::BitmapData::readWrite); - blurSingleChannelImage (bm.data, bm.width, bm.height, bm.lineStride, 2 * radius); -} - -//============================================================================== DropShadow::DropShadow (Colour shadowColour, const int r, Point o) noexcept : colour (shadowColour), radius (r), offset (o) { @@ -86,16 +45,15 @@ void DropShadow::drawForImage (Graphics& g, const Image& srcImage) const { jassert (radius > 0); - if (srcImage.isValid()) - { - Image shadowImage (srcImage.convertedToFormat (Image::SingleChannel)); - shadowImage.duplicateIfShared(); + if (! srcImage.isValid()) + return; - blurSingleChannelImage (shadowImage, radius); + Image blurred; + srcImage.convertedToFormat (Image::SingleChannel) + .applyGaussianBlurEffect ((float) radius, blurred); - g.setColour (colour); - g.drawImageAt (shadowImage, offset.x, offset.y, true); - } + g.setColour (colour); + g.drawImageAt (blurred, offset.x, offset.y, true); } void DropShadow::drawForPath (Graphics& g, const Path& path) const @@ -103,24 +61,25 @@ void DropShadow::drawForPath (Graphics& g, const Path& path) const jassert (radius > 0); auto area = (path.getBounds().getSmallestIntegerContainer() + offset) - .expanded (radius + 1) - .getIntersection (g.getClipBounds().expanded (radius + 1)); + .expanded (radius + 1) + .getIntersection (g.getClipBounds().expanded (radius + 1)); if (area.getWidth() > 2 && area.getHeight() > 2) { - Image renderedPath (Image::SingleChannel, area.getWidth(), area.getHeight(), true); + Image pathImage { Image::SingleChannel, area.getWidth(), area.getHeight(), true }; { - Graphics g2 (renderedPath); + Graphics g2 (pathImage); g2.setColour (Colours::white); g2.fillPath (path, AffineTransform::translation ((float) (offset.x - area.getX()), (float) (offset.y - area.getY()))); } - blurSingleChannelImage (renderedPath, radius); + Image blurred; + pathImage.applyGaussianBlurEffect ((float) radius, blurred); g.setColour (colour); - g.drawImageAt (renderedPath, area.getX(), area.getY(), true); + g.drawImageAt (blurred, area.getX(), area.getY(), true); } } @@ -167,8 +126,8 @@ void DropShadow::drawForRectangle (Graphics& g, const Rectangle& targetArea } //============================================================================== -DropShadowEffect::DropShadowEffect() {} -DropShadowEffect::~DropShadowEffect() {} +DropShadowEffect::DropShadowEffect() = default; +DropShadowEffect::~DropShadowEffect() = default; void DropShadowEffect::setShadowProperties (const DropShadow& newShadow) { diff --git a/modules/juce_graphics/effects/juce_GlowEffect.cpp b/modules/juce_graphics/effects/juce_GlowEffect.cpp index 7a7e271150..9c4ca4cc2d 100644 --- a/modules/juce_graphics/effects/juce_GlowEffect.cpp +++ b/modules/juce_graphics/effects/juce_GlowEffect.cpp @@ -35,8 +35,8 @@ namespace juce { -GlowEffect::GlowEffect() {} -GlowEffect::~GlowEffect() {} +GlowEffect::GlowEffect() = default; +GlowEffect::~GlowEffect() = default; void GlowEffect::setGlowProperties (float newRadius, Colour newColour, Point pos) { @@ -47,17 +47,10 @@ void GlowEffect::setGlowProperties (float newRadius, Colour newColour, Point offset; + Image cachedImage; JUCE_LEAK_DETECTOR (GlowEffect) }; diff --git a/modules/juce_graphics/fonts/juce_Font.cpp b/modules/juce_graphics/fonts/juce_Font.cpp index 25607cf23e..2f92f6c3df 100644 --- a/modules/juce_graphics/fonts/juce_Font.cpp +++ b/modules/juce_graphics/fonts/juce_Font.cpp @@ -774,6 +774,10 @@ static bool characterNotRendered (uint32_t c) static bool isFontSuitableForCodepoint (const Font& font, juce_wchar c) { const auto& hbFont = font.getNativeDetails().font; + + if (hbFont == nullptr) + return false; + hb_codepoint_t glyph{}; return characterNotRendered ((uint32_t) c) diff --git a/modules/juce_graphics/fonts/juce_SimpleShapedText.cpp b/modules/juce_graphics/fonts/juce_SimpleShapedText.cpp index ebf85d0be2..c76421321f 100644 --- a/modules/juce_graphics/fonts/juce_SimpleShapedText.cpp +++ b/modules/juce_graphics/fonts/juce_SimpleShapedText.cpp @@ -322,12 +322,12 @@ private: }; /* Returns glyphs in logical order as that favours wrapping. */ -static auto lowLevelShape (const String& string, - Range range, - const Font& font, - TextScript script, - const String& language, - TextDirection direction) +static std::vector lowLevelShape (const String& string, + Range range, + const Font& font, + TextScript script, + const String& language, + TextDirection direction) { HbBuffer buffer { hb_buffer_create() }; hb_buffer_clear_contents (buffer.get()); @@ -431,6 +431,12 @@ static auto lowLevelShape (const String& string, auto nativeFont = font.getNativeDetails().font; + if (nativeFont == nullptr) + { + jassertfalse; + return {}; + } + hb_shape (nativeFont.get(), buffer.get(), features.data(), (unsigned int) features.size()); const auto [infos, positions] = [&buffer] diff --git a/modules/juce_graphics/geometry/juce_Path.cpp b/modules/juce_graphics/geometry/juce_Path.cpp index 8ba3d61c37..5efd49da68 100644 --- a/modules/juce_graphics/geometry/juce_Path.cpp +++ b/modules/juce_graphics/geometry/juce_Path.cpp @@ -77,9 +77,7 @@ static bool isMarker (float value, float marker) noexcept } //============================================================================== -Path::PathBounds::PathBounds() noexcept -{ -} +Path::PathBounds::PathBounds() noexcept = default; Rectangle Path::PathBounds::getRectangle() const noexcept { @@ -88,7 +86,7 @@ Rectangle Path::PathBounds::getRectangle() const noexcept void Path::PathBounds::reset() noexcept { - pathXMin = pathYMin = pathYMax = pathXMax = 0; + *this = {}; } void Path::PathBounds::reset (float x, float y) noexcept @@ -107,13 +105,9 @@ void Path::PathBounds::extend (float x, float y) noexcept } //============================================================================== -Path::Path() -{ -} +Path::Path() = default; -Path::~Path() -{ -} +Path::~Path() = default; Path::Path (const Path& other) : data (other.data), @@ -124,32 +118,31 @@ Path::Path (const Path& other) Path& Path::operator= (const Path& other) { - if (this != &other) - { - data = other.data; - bounds = other.bounds; - useNonZeroWinding = other.useNonZeroWinding; - } - + auto copy = other; + *this = std::move (copy); return *this; } Path::Path (Path&& other) noexcept - : data (std::move (other.data)), - bounds (other.bounds), - useNonZeroWinding (other.useNonZeroWinding) + : data (std::exchange (other.data, {})), + bounds (std::exchange (other.bounds, {})), + useNonZeroWinding (std::exchange (other.useNonZeroWinding, {})) { } Path& Path::operator= (Path&& other) noexcept { - data = std::move (other.data); - bounds = other.bounds; - useNonZeroWinding = other.useNonZeroWinding; + auto copy = std::move (other); + swapWithPath (copy); return *this; } -bool Path::operator== (const Path& other) const noexcept { return useNonZeroWinding == other.useNonZeroWinding && data == other.data; } +bool Path::operator== (const Path& other) const noexcept +{ + const auto tie = [] (const auto& x) { return std::tie (x.useNonZeroWinding, x.data); }; + return tie (*this) == tie (other); +} + bool Path::operator!= (const Path& other) const noexcept { return ! operator== (other); } void Path::clear() noexcept @@ -161,10 +154,7 @@ void Path::clear() noexcept void Path::swapWithPath (Path& other) noexcept { data.swapWith (other.data); - std::swap (bounds.pathXMin, other.bounds.pathXMin); - std::swap (bounds.pathXMax, other.bounds.pathXMax); - std::swap (bounds.pathYMin, other.bounds.pathYMin); - std::swap (bounds.pathYMax, other.bounds.pathYMax); + std::swap (bounds, other.bounds); std::swap (useNonZeroWinding, other.useNonZeroWinding); } @@ -967,8 +957,8 @@ bool Path::contains (float x, float y, float tolerance) const } } - return useNonZeroWinding ? (negativeCrossings != positiveCrossings) - : ((negativeCrossings + positiveCrossings) & 1) != 0; + return isUsingNonZeroWinding() ? (negativeCrossings != positiveCrossings) + : ((negativeCrossings + positiveCrossings) & 1) != 0; } bool Path::contains (Point point, float tolerance) const @@ -1279,11 +1269,11 @@ void Path::loadPathFromStream (InputStream& source) break; case 'n': - useNonZeroWinding = true; + setUsingNonZeroWinding (true); break; case 'z': - useNonZeroWinding = false; + setUsingNonZeroWinding (false); break; case 'e': @@ -1304,7 +1294,7 @@ void Path::loadPathFromData (const void* const pathData, const size_t numberOfBy void Path::writePathToStream (OutputStream& dest) const { - dest.writeByte (useNonZeroWinding ? 'n' : 'z'); + dest.writeByte (isUsingNonZeroWinding() ? 'n' : 'z'); for (auto* i = data.begin(); i != data.end();) { @@ -1352,7 +1342,7 @@ void Path::writePathToStream (OutputStream& dest) const String Path::toString() const { MemoryOutputStream s (2048); - if (! useNonZeroWinding) + if (! isUsingNonZeroWinding()) s << 'a'; float lastMarker = 0.0f; @@ -1489,10 +1479,6 @@ Path::Iterator::Iterator (const Path& p) noexcept { } -Path::Iterator::~Iterator() noexcept -{ -} - bool Path::Iterator::next() noexcept { if (index != path.data.end()) diff --git a/modules/juce_graphics/geometry/juce_Path.h b/modules/juce_graphics/geometry/juce_Path.h index 1999bf6c77..3bcbe48958 100644 --- a/modules/juce_graphics/geometry/juce_Path.h +++ b/modules/juce_graphics/geometry/juce_Path.h @@ -724,7 +724,6 @@ public: */ bool isUsingNonZeroWinding() const { return useNonZeroWinding; } - //============================================================================== /** Iterates the lines and curves that a path contains. @@ -735,7 +734,6 @@ public: public: //============================================================================== Iterator (const Path& path) noexcept; - ~Iterator() noexcept; //============================================================================== /** Moves onto the next element in the path. diff --git a/modules/juce_graphics/images/juce_Image.cpp b/modules/juce_graphics/images/juce_Image.cpp index 770ee158c0..e593d95392 100644 --- a/modules/juce_graphics/images/juce_Image.cpp +++ b/modules/juce_graphics/images/juce_Image.cpp @@ -35,6 +35,96 @@ namespace juce { +struct BitmapDataDetail +{ + BitmapDataDetail() = delete; + + static void convert (const Image::BitmapData& src, Image::BitmapData& dest) + { + jassert (src.width == dest.width); + jassert (src.height == dest.height); + + if (src.pixelStride == dest.pixelStride && src.pixelFormat == dest.pixelFormat) + { + for (int y = 0; y < dest.height; ++y) + memcpy (dest.getLinePointer (y), src.getLinePointer (y), (size_t) dest.pixelStride * (size_t) dest.width); + } + else + { + for (int y = 0; y < dest.height; ++y) + for (int x = 0; x < dest.width; ++x) + dest.setPixelColour (x, y, src.getPixelColour (x, y)); + } + } + + static Image convert (const Image::BitmapData& src, const ImageType& type) + { + Image result (type.create (src.pixelFormat, src.width, src.height, false)); + + { + Image::BitmapData dest (result, Image::BitmapData::writeOnly); + BitmapDataDetail::convert (src, dest); + } + + return result; + } +}; + +class SubsectionPixelData : public ImagePixelData +{ +public: + SubsectionPixelData (ImagePixelData::Ptr source, Rectangle r) + : ImagePixelData (source->pixelFormat, r.getWidth(), r.getHeight()), + sourceImage (std::move (source)), + area (r) + { + } + + std::unique_ptr createLowLevelContext() override + { + auto g = sourceImage->createLowLevelContext(); + g->clipToRectangle (area); + g->setOrigin (area.getPosition()); + return g; + } + + void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override + { + sourceImage->initialiseBitmapData (bitmap, x + area.getX(), y + area.getY(), mode); + + if (mode != Image::BitmapData::readOnly) + sendDataChangeMessage(); + } + + ImagePixelData::Ptr clone() override + { + jassert (getReferenceCount() > 0); // (This method can't be used on an unowned pointer, as it will end up self-deleting) + auto type = createType(); + + Image newImage (type->create (pixelFormat, area.getWidth(), area.getHeight(), pixelFormat != Image::RGB)); + + { + Graphics g (newImage); + g.drawImageAt (Image (*this), 0, 0); + } + + return *newImage.getPixelData(); + } + + std::unique_ptr createType() const override { return sourceImage->createType(); } + + /* as we always hold a reference to image, don't double count */ + int getSharedCount() const noexcept override { return getReferenceCount() + sourceImage->getSharedCount() - 1; } + +private: + friend class Image; + const ImagePixelData::Ptr sourceImage; + const Rectangle area; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SubsectionPixelData) +}; + +//============================================================================== ImagePixelData::ImagePixelData (Image::PixelFormat format, int w, int h) : pixelFormat (format), width (w), height (h) { @@ -57,9 +147,14 @@ int ImagePixelData::getSharedCount() const noexcept return getReferenceCount(); } +void ImagePixelData::applyGaussianBlurEffect ([[maybe_unused]] float radius, Image& result) +{ + result = {}; +} + //============================================================================== -ImageType::ImageType() {} -ImageType::~ImageType() {} +ImageType::ImageType() = default; +ImageType::~ImageType() = default; Image ImageType::convert (const Image& source) const { @@ -68,26 +163,14 @@ Image ImageType::convert (const Image& source) const const Image::BitmapData src (source, Image::BitmapData::readOnly); - Image newImage (create (src.pixelFormat, src.width, src.height, false)); - Image::BitmapData dest (newImage, Image::BitmapData::writeOnly); + if (src.data == nullptr) + return {}; - if (src.pixelStride == dest.pixelStride && src.pixelFormat == dest.pixelFormat) - { - for (int y = 0; y < dest.height; ++y) - memcpy (dest.getLinePointer (y), src.getLinePointer (y), (size_t) dest.lineStride); - } - else - { - for (int y = 0; y < dest.height; ++y) - for (int x = 0; x < dest.width; ++x) - dest.setPixelColour (x, y, src.getPixelColour (x, y)); - } - - return newImage; + return BitmapDataDetail::convert (src, *this); } //============================================================================== -class SoftwarePixelData final : public ImagePixelData +class SoftwarePixelData : public ImagePixelData { public: SoftwarePixelData (Image::PixelFormat formatToUse, int w, int h, bool clearImage) @@ -133,8 +216,8 @@ private: JUCE_LEAK_DETECTOR (SoftwarePixelData) }; -SoftwareImageType::SoftwareImageType() {} -SoftwareImageType::~SoftwareImageType() {} +SoftwareImageType::SoftwareImageType() = default; +SoftwareImageType::~SoftwareImageType() = default; ImagePixelData::Ptr SoftwareImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const { @@ -147,15 +230,15 @@ int SoftwareImageType::getTypeID() const } //============================================================================== -NativeImageType::NativeImageType() {} -NativeImageType::~NativeImageType() {} +NativeImageType::NativeImageType() = default; +NativeImageType::~NativeImageType() = default; int NativeImageType::getTypeID() const { return 1; } -#if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD +#if JUCE_LINUX || JUCE_BSD ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const { return new SoftwarePixelData (format, width, height, clearImage); @@ -163,58 +246,6 @@ ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int widt #endif //============================================================================== -class SubsectionPixelData final : public ImagePixelData -{ -public: - SubsectionPixelData (ImagePixelData::Ptr source, Rectangle r) - : ImagePixelData (source->pixelFormat, r.getWidth(), r.getHeight()), - sourceImage (std::move (source)), area (r) - { - } - - std::unique_ptr createLowLevelContext() override - { - auto g = sourceImage->createLowLevelContext(); - g->clipToRectangle (area); - g->setOrigin (area.getPosition()); - return g; - } - - void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override - { - sourceImage->initialiseBitmapData (bitmap, x + area.getX(), y + area.getY(), mode); - - if (mode != Image::BitmapData::readOnly) - sendDataChangeMessage(); - } - - ImagePixelData::Ptr clone() override - { - jassert (getReferenceCount() > 0); // (This method can't be used on an unowned pointer, as it will end up self-deleting) - auto type = createType(); - - Image newImage (type->create (pixelFormat, area.getWidth(), area.getHeight(), pixelFormat != Image::RGB)); - - { - Graphics g (newImage); - g.drawImageAt (Image (*this), 0, 0); - } - - return *newImage.getPixelData(); - } - - std::unique_ptr createType() const override { return sourceImage->createType(); } - - /* as we always hold a reference to image, don't double count */ - int getSharedCount() const noexcept override { return getReferenceCount() + sourceImage->getSharedCount() - 1; } - -private: - friend class Image; - const ImagePixelData::Ptr sourceImage; - const Rectangle area; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SubsectionPixelData) -}; Image Image::getClippedImage (const Rectangle& area) const { @@ -226,14 +257,11 @@ Image Image::getClippedImage (const Rectangle& area) const if (validArea.isEmpty()) return {}; - return Image (*new SubsectionPixelData (image, validArea)); + return Image { ImagePixelData::Ptr { new SubsectionPixelData { image, validArea } } }; } - //============================================================================== -Image::Image() noexcept -{ -} +Image::Image() noexcept = default; Image::Image (ReferenceCountedObjectPtr instance) noexcept : image (std::move (instance)) @@ -272,11 +300,44 @@ Image& Image::operator= (Image&& other) noexcept return *this; } -Image::~Image() -{ -} +Image::~Image() = default; int Image::getReferenceCount() const noexcept { return image == nullptr ? 0 : image->getSharedCount(); } + +void Image::applyGaussianBlurEffect (float radius, Image& result) const +{ + if (image == nullptr) + { + result = {}; + return; + } + + auto copy = result; + image->applyGaussianBlurEffect (radius, copy); + + if (copy.isValid()) + { + result = std::move (copy); + return; + } + + const auto tie = [] (const auto& x) { return std::tuple (x.getFormat(), x.getWidth(), x.getHeight()); }; + + if (tie (*this) != tie (result)) + result = Image { getFormat(), getWidth(), getHeight(), false }; + + ImageConvolutionKernel blurKernel (roundToInt (radius * 2.0f)); + + blurKernel.createGaussianBlur (radius); + + blurKernel.applyToImage (result, *this, result.getBounds()); +} + +bool Image::isValid() const noexcept +{ + return image != nullptr; +} + int Image::getWidth() const noexcept { return image == nullptr ? 0 : image->width; } int Image::getHeight() const noexcept { return image == nullptr ? 0 : image->height; } Rectangle Image::getBounds() const noexcept { return image == nullptr ? Rectangle() : Rectangle (image->width, image->height); } diff --git a/modules/juce_graphics/images/juce_Image.h b/modules/juce_graphics/images/juce_Image.h index d419f08993..8d1fb59ad3 100644 --- a/modules/juce_graphics/images/juce_Image.h +++ b/modules/juce_graphics/images/juce_Image.h @@ -153,7 +153,7 @@ public: The isNull() method is the opposite of isValid(). @see isNull */ - inline bool isValid() const noexcept { return image != nullptr; } + bool isValid() const noexcept; /** Returns true if this image is not valid. If you create an Image with the default constructor, it has no size or content, and is null @@ -161,7 +161,7 @@ public: The isNull() method is the opposite of isValid(). @see isValid */ - inline bool isNull() const noexcept { return image == nullptr; } + bool isNull() const noexcept { return ! isValid(); } //============================================================================== /** Returns the image's width (in pixels). */ @@ -416,6 +416,13 @@ public: */ int getReferenceCount() const noexcept; + /** Applies a blur to this image, placing the blurred image in the result out-parameter. + + If result is already the correct size, then its storage will be reused directly. + Otherwise, new storage may be allocated for the blurred image. + */ + void applyGaussianBlurEffect (float radius, Image& result) const; + //============================================================================== /** @internal */ ImagePixelData* getPixelData() const noexcept { return image.get(); } @@ -469,9 +476,19 @@ public: virtual void initialiseBitmapData (Image::BitmapData&, int x, int y, Image::BitmapData::ReadWriteMode) = 0; /** Returns the number of Image objects which are currently referring to the same internal shared image data. This is different to the reference count as an instance of ImagePixelData - can internally depend on another ImagePixelData via it's member variables. */ + can internally depend on another ImagePixelData via it's member variables. + */ virtual int getSharedCount() const noexcept; + /** Applies a native blur effect to this image, if available. + + Implementations should attempt to re-use the storage provided in the result out-parameter + when possible. + + If native blurs are unsupported, or if creating a blur fails for any other reason, + the result out-parameter will be reset to an invalid image. + */ + virtual void applyGaussianBlurEffect (float radius, Image& result); /** The pixel format of the image data. */ const Image::PixelFormat pixelFormat; diff --git a/modules/juce_graphics/juce_graphics.cpp b/modules/juce_graphics/juce_graphics.cpp index a5e90a29cb..3c30c91eeb 100644 --- a/modules/juce_graphics/juce_graphics.cpp +++ b/modules/juce_graphics/juce_graphics.cpp @@ -55,19 +55,45 @@ #include #elif JUCE_WINDOWS - // get rid of some warnings in Window's own headers + // get rid of some warnings in Window's own headers JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4458) - #include - #include + /* If you hit a compile error trying to include these files, you may need to update + your version of the Windows SDK to the latest one. The DirectWrite and Direct2D + headers are in the version 8 SDKs. - #if JUCE_MINGW - #include - #include - #endif + Need Direct2D 1.3 for sprite batching + */ + + #include + #include + #include + #include + #include + #include + + #if JUCE_ETW_TRACELOGGING + #include + #include + + TRACELOGGING_DEFINE_PROVIDER (JUCETraceLogProvider, + "JUCETraceLogProvider", + // {6A612E78-284D-4DDB-877A-5F521EB33132} + (0x6a612e78, 0x284d, 0x4ddb, 0x87, 0x7a, 0x5f, 0x52, 0x1e, 0xb3, 0x31, 0x32)); + +#endif JUCE_END_IGNORE_WARNINGS_MSVC + #if ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES + #pragma comment(lib, "Dwrite.lib") + #pragma comment(lib, "D2d1.lib") + #pragma comment(lib, "DXGI.lib") + #pragma comment(lib, "D3D11.lib") + #pragma comment(lib, "DComp.lib") + #pragma comment(lib, "dxguid.lib") + #endif + #elif JUCE_IOS #import #import @@ -120,6 +146,7 @@ //============================================================================== #include "fonts/juce_FunctionPointerDestructor.h" +#include "native/juce_EventTracing.h" #include "unicode/juce_UnicodeScript.h" #include "unicode/juce_Unicode.h" @@ -179,11 +206,18 @@ #include "native/juce_IconHelpers_mac.cpp" #elif JUCE_WINDOWS + #include "native/juce_DirectX_windows.h" #include "native/juce_DirectWriteTypeface_windows.cpp" #include "native/juce_IconHelpers_windows.cpp" - #if JUCE_DIRECT2D - #include "native/juce_Direct2DGraphicsContext_windows.cpp" - #endif + #include "native/juce_Direct2DHelpers_windows.cpp" + #include "native/juce_Direct2DResources_windows.cpp" + #include "native/juce_Direct2DImage_windows.h" + #include "native/juce_Direct2DGraphicsContext_windows.cpp" + #include "native/juce_Direct2DHwndContext_windows.cpp" + #include "native/juce_Direct2DImageContext_windows.h" + #include "native/juce_Direct2DImageContext_windows.cpp" + #include "native/juce_Direct2DImage_windows.cpp" + #include "native/juce_Direct2DMetrics_windows.cpp" #elif JUCE_LINUX || JUCE_BSD #include "native/juce_Fonts_linux.cpp" diff --git a/modules/juce_graphics/juce_graphics.h b/modules/juce_graphics/juce_graphics.h index 2bc4ff8652..571de617c2 100644 --- a/modules/juce_graphics/juce_graphics.h +++ b/modules/juce_graphics/juce_graphics.h @@ -78,15 +78,6 @@ #define JUCE_USE_COREIMAGE_LOADER 1 #endif -/** Config: JUCE_USE_DIRECTWRITE - - Enabling this flag means that DirectWrite will be used when available for font - management and layout. -*/ -#ifndef JUCE_USE_DIRECTWRITE - #define JUCE_USE_DIRECTWRITE 1 -#endif - /** Config: JUCE_DISABLE_COREGRAPHICS_FONT_SMOOTHING Setting this flag will turn off CoreGraphics font smoothing on macOS, which some people @@ -165,6 +156,8 @@ namespace juce #include "native/juce_CoreGraphicsContext_mac.h" #endif -#if JUCE_DIRECT2D && JUCE_WINDOWS -#include "native/juce_Direct2DGraphicsContext_windows.h" +#if JUCE_WINDOWS + #include "native/juce_Direct2DMetrics_windows.h" + #include "native/juce_Direct2DGraphicsContext_windows.h" + #include "native/juce_Direct2DHwndContext_windows.h" #endif diff --git a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h index 7113ceac24..31007602af 100644 --- a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h +++ b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h @@ -88,7 +88,7 @@ public: void setOrigin (Point) override; void addTransform (const AffineTransform&) override; - float getPhysicalPixelScaleFactor() override; + float getPhysicalPixelScaleFactor() const override; bool clipToRectangle (const Rectangle&) override; bool clipToRectangleList (const RectangleList&) override; void excludeClipRectangle (const Rectangle&) override; @@ -125,6 +125,8 @@ public: Span>, const AffineTransform&) override; + uint64_t getFrameId() const override { return 0; } + private: //============================================================================== detail::ContextPtr context; diff --git a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm index aede46f7f6..6a6ea8d0a8 100644 --- a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm +++ b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm @@ -272,7 +272,7 @@ void CoreGraphicsContext::addTransform (const AffineTransform& transform) jassert (getPhysicalPixelScaleFactor() > 0.0f); } -float CoreGraphicsContext::getPhysicalPixelScaleFactor() +float CoreGraphicsContext::getPhysicalPixelScaleFactor() const { auto t = CGContextGetUserSpaceToDeviceSpaceTransform (context.get()); auto determinant = (t.a * t.d) - (t.c * t.b); diff --git a/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp b/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp index cb2a1ca51b..5bc70185bb 100644 --- a/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp +++ b/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp @@ -35,742 +35,1599 @@ namespace juce { -template -D2D1_RECT_F rectangleToRectF (const Rectangle& r) -{ - return { (float) r.getX(), (float) r.getY(), (float) r.getRight(), (float) r.getBottom() }; -} +#if JUCE_DIRECT2D_METRICS +JUCE_IMPLEMENT_SINGLETON (Direct2DMetricsHub) +#endif -static D2D1_COLOR_F colourToD2D (Colour c) -{ - return { c.getFloatRed(), c.getFloatGreen(), c.getFloatBlue(), c.getFloatAlpha() }; -} - -static void pathToGeometrySink (const Path& path, ID2D1GeometrySink* sink, const AffineTransform& transform) -{ - Path::Iterator it (path); - - while (it.next()) - { - switch (it.elementType) - { - case Path::Iterator::cubicTo: - { - transform.transformPoint (it.x1, it.y1); - transform.transformPoint (it.x2, it.y2); - transform.transformPoint (it.x3, it.y3); - - sink->AddBezier ({ { it.x1, it.y1 }, { it.x2, it.y2 }, { it.x3, it.y3 } }); - break; - } - - case Path::Iterator::lineTo: - { - transform.transformPoint (it.x1, it.y1); - sink->AddLine ({ it.x1, it.y1 }); - break; - } - - case Path::Iterator::quadraticTo: - { - transform.transformPoint (it.x1, it.y1); - transform.transformPoint (it.x2, it.y2); - sink->AddQuadraticBezier ({ { it.x1, it.y1 }, { it.x2, it.y2 } }); - break; - } - - case Path::Iterator::closePath: - { - sink->EndFigure (D2D1_FIGURE_END_CLOSED); - break; - } - - case Path::Iterator::startNewSubPath: - { - transform.transformPoint (it.x1, it.y1); - sink->BeginFigure ({ it.x1, it.y1 }, D2D1_FIGURE_BEGIN_FILLED); - break; - } - } - } -} - -static D2D1::Matrix3x2F transformToMatrix (const AffineTransform& transform) -{ - return { transform.mat00, transform.mat10, transform.mat01, transform.mat11, transform.mat02, transform.mat12 }; -} - -static D2D1_POINT_2F pointTransformed (int x, int y, const AffineTransform& transform) -{ - transform.transformPoint (x, y); - return { (FLOAT) x, (FLOAT) y }; -} - -static void rectToGeometrySink (const Rectangle& rect, ID2D1GeometrySink* sink, const AffineTransform& transform) -{ - sink->BeginFigure (pointTransformed (rect.getX(), rect.getY(), transform), D2D1_FIGURE_BEGIN_FILLED); - sink->AddLine (pointTransformed (rect.getRight(), rect.getY(), transform)); - sink->AddLine (pointTransformed (rect.getRight(), rect.getBottom(), transform)); - sink->AddLine (pointTransformed (rect.getX(), rect.getBottom(), transform)); - sink->EndFigure (D2D1_FIGURE_END_CLOSED); -} - -//============================================================================== -struct Direct2DLowLevelGraphicsContext::Pimpl -{ - ID2D1PathGeometry* rectListToPathGeometry (const RectangleList& clipRegion) - { - ID2D1PathGeometry* p = nullptr; - factories->d2dFactory->CreatePathGeometry (&p); - - ComSmartPtr sink; - auto hr = p->Open (sink.resetAndGetPointerAddress()); // xxx handle error - sink->SetFillMode (D2D1_FILL_MODE_WINDING); - - for (int i = clipRegion.getNumRectangles(); --i >= 0;) - rectToGeometrySink (clipRegion.getRectangle (i), sink, AffineTransform()); - - hr = sink->Close(); - return p; - } - - ID2D1PathGeometry* pathToPathGeometry (const Path& path, const AffineTransform& transform) - { - ID2D1PathGeometry* p = nullptr; - factories->d2dFactory->CreatePathGeometry (&p); - - ComSmartPtr sink; - auto hr = p->Open (sink.resetAndGetPointerAddress()); - sink->SetFillMode (D2D1_FILL_MODE_WINDING); // xxx need to check Path::isUsingNonZeroWinding() - - pathToGeometrySink (path, sink, transform); - - hr = sink->Close(); - return p; - } - - SharedResourcePointer factories; - - ComSmartPtr renderingTarget; - ComSmartPtr colourBrush; -}; - -//============================================================================== -struct Direct2DLowLevelGraphicsContext::SavedState +class PushedLayers { public: - SavedState (Direct2DLowLevelGraphicsContext& owner_) - : owner (owner_) - { - if (owner.currentState != nullptr) - { - // xxx seems like a very slow way to create one of these, and this is a performance - // bottleneck.. Can the same internal objects be shared by multiple state objects, maybe using copy-on-write? - setFill (owner.currentState->fillType); - currentBrush = owner.currentState->currentBrush; - clipRect = owner.currentState->clipRect; - transform = owner.currentState->transform; + PushedLayers() { pushedLayers.reserve (32); } + PushedLayers (const PushedLayers&) { pushedLayers.reserve (32); } - font = owner.currentState->font; - currentFontFace = owner.currentState->currentFontFace; - } + #if JUCE_DEBUG + ~PushedLayers() + { + jassert (pushedLayers.empty()); + } + #endif + + void push (ComSmartPtr context, const D2D1_LAYER_PARAMETERS& layerParameters) + { + // Clipping and transparency are all handled by pushing Direct2D layers. The SavedState creates an internal stack + // of Layer objects to keep track of how many layers need to be popped. + // Pass nullptr for the PushLayer layer parameter to allow Direct2D to manage the layers (Windows 8 or later) + context->PushLayer (layerParameters, nullptr); + pushedLayers.emplace_back (popLayerFlag); + } + + void push (ComSmartPtr context, const Rectangle& r) + { + context->PushAxisAlignedClip (D2DUtilities::toRECT_F (r), D2D1_ANTIALIAS_MODE_ALIASED); + pushedLayers.emplace_back (popAxisAlignedLayerFlag); + } + + void popOne (ComSmartPtr context) + { + if (pushedLayers.empty()) + return; + + if (pushedLayers.back() == popLayerFlag) + context->PopLayer(); else - { - const auto size = owner.pimpl->renderingTarget->GetPixelSize(); - clipRect.setSize (size.width, size.height); - setFill (FillType (Colours::black)); - } + context->PopAxisAlignedClip(); + + pushedLayers.pop_back(); } - ~SavedState() + bool isEmpty() const { - clearClip(); - clearFont(); - clearFill(); - clearPathClip(); - clearImageClip(); - complexClipLayer = nullptr; - bitmapMaskLayer = nullptr; + return pushedLayers.empty(); } - void clearClip() +private: + //============================================================================== + // 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. + enum + { + popLayerFlag, + popAxisAlignedLayerFlag + }; + + std::vector pushedLayers; +}; + +struct Direct2DGraphicsContext::SavedState +{ +public: + // Constructor for first stack entry + SavedState (Direct2DGraphicsContext& ownerIn, + Rectangle frameSizeIn, + ComSmartPtr& colourBrushIn, + DxgiAdapter::Ptr& adapterIn, + Direct2DDeviceResources& deviceResourcesIn) + : owner (ownerIn), + currentBrush (colourBrushIn), + colourBrush (colourBrushIn), + adapter (adapterIn), + deviceResources (deviceResourcesIn), + deviceSpaceClipList (frameSizeIn.toFloat()) { - popClips(); - shouldClipRect = false; } - void clipToRectangle (const Rectangle& r) + void pushLayer (const D2D1_LAYER_PARAMETERS& layerParameters) { - clearClip(); - clipRect = r.toFloat().transformedBy (transform).getSmallestIntegerContainer(); - shouldClipRect = true; - pushClips(); + layers.push (deviceResources.deviceContext.context, layerParameters); } - void clearPathClip() + void pushGeometryClipLayer (ComSmartPtr geometry) { - popClips(); - - if (shouldClipComplex) - { - complexClipGeometry = nullptr; - shouldClipComplex = false; - } + if (geometry != nullptr) + pushLayer (D2D1::LayerParameters (D2D1::InfiniteRect(), geometry)); } - void Direct2DLowLevelGraphicsContext::SavedState::clipToPath (ID2D1Geometry* geometry) + void pushTransformedRectangleGeometryClipLayer (ComSmartPtr geometry, const AffineTransform& transform) { - clearPathClip(); + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (owner.metrics, pushGeometryLayerTime) - if (complexClipLayer == nullptr) - owner.pimpl->renderingTarget->CreateLayer (complexClipLayer.resetAndGetPointerAddress()); - - complexClipGeometry = geometry; - shouldClipComplex = true; - pushClips(); + jassert (geometry != nullptr); + auto layerParameters = D2D1::LayerParameters (D2D1::InfiniteRect(), geometry); + layerParameters.maskTransform = D2DUtilities::transformToMatrix (transform); + pushLayer (layerParameters); } - void clearRectListClip() + void pushAliasedAxisAlignedClipLayer (const Rectangle& r) { - popClips(); + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (owner.metrics, pushAliasedAxisAlignedLayerTime) - if (shouldClipRectList) - { - rectListGeometry = nullptr; - shouldClipRectList = false; - } + layers.push (deviceResources.deviceContext.context, r); } - void clipToRectList (ID2D1Geometry* geometry) + void pushTransparencyLayer (float opacity) { - clearRectListClip(); - - if (rectListLayer == nullptr) - owner.pimpl->renderingTarget->CreateLayer (rectListLayer.resetAndGetPointerAddress()); - - rectListGeometry = geometry; - shouldClipRectList = true; - pushClips(); + pushLayer ({ D2D1::InfiniteRect(), nullptr, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, D2D1::IdentityMatrix(), opacity, {}, {} }); } - void clearImageClip() + void popLayers() { - popClips(); - - if (shouldClipBitmap) - { - maskBitmap = nullptr; - bitmapMaskBrush = nullptr; - shouldClipBitmap = false; - } + while (! layers.isEmpty()) + layers.popOne (deviceResources.deviceContext.context); } - void clipToImage (const Image& clipImage, const AffineTransform& clipTransform) + void popTopLayer() { - clearImageClip(); - - if (bitmapMaskLayer == nullptr) - owner.pimpl->renderingTarget->CreateLayer (bitmapMaskLayer.resetAndGetPointerAddress()); - - D2D1_BRUSH_PROPERTIES brushProps = { 1, transformToMatrix (clipTransform) }; - auto bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP); - D2D1_SIZE_U size = { (UINT32) clipImage.getWidth(), (UINT32) clipImage.getHeight() }; - auto bp = D2D1::BitmapProperties(); - - maskImage = clipImage.convertedToFormat (Image::ARGB); - Image::BitmapData bd (maskImage, Image::BitmapData::readOnly); // xxx should be maskImage? - bp.pixelFormat = owner.pimpl->renderingTarget->GetPixelFormat(); - bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; - - auto hr = owner.pimpl->renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, maskBitmap.resetAndGetPointerAddress()); - hr = owner.pimpl->renderingTarget->CreateBitmapBrush (maskBitmap, bmProps, brushProps, bitmapMaskBrush.resetAndGetPointerAddress()); - - imageMaskLayerParams = D2D1::LayerParameters(); - imageMaskLayerParams.opacityBrush = bitmapMaskBrush; - - shouldClipBitmap = true; - pushClips(); - } - - void popClips() - { - if (clipsBitmap) - { - owner.pimpl->renderingTarget->PopLayer(); - clipsBitmap = false; - } - - if (clipsComplex) - { - owner.pimpl->renderingTarget->PopLayer(); - clipsComplex = false; - } - - if (clipsRectList) - { - owner.pimpl->renderingTarget->PopLayer(); - clipsRectList = false; - } - - if (clipsRect) - { - owner.pimpl->renderingTarget->PopAxisAlignedClip(); - clipsRect = false; - } - } - - void pushClips() - { - if (shouldClipRect && !clipsRect) - { - owner.pimpl->renderingTarget->PushAxisAlignedClip (rectangleToRectF (clipRect), D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); - clipsRect = true; - } - - if (shouldClipRectList && !clipsRectList) - { - auto layerParams = D2D1::LayerParameters(); - rectListGeometry->GetBounds (D2D1::IdentityMatrix(), &layerParams.contentBounds); - layerParams.geometricMask = rectListGeometry; - owner.pimpl->renderingTarget->PushLayer (layerParams, rectListLayer); - clipsRectList = true; - } - - if (shouldClipComplex && !clipsComplex) - { - auto layerParams = D2D1::LayerParameters(); - complexClipGeometry->GetBounds (D2D1::IdentityMatrix(), &layerParams.contentBounds); - layerParams.geometricMask = complexClipGeometry; - owner.pimpl->renderingTarget->PushLayer (layerParams, complexClipLayer); - clipsComplex = true; - } - - if (shouldClipBitmap && !clipsBitmap) - { - owner.pimpl->renderingTarget->PushLayer (imageMaskLayerParams, bitmapMaskLayer); - clipsBitmap = true; - } - } - - void setFill (const FillType& newFillType) - { - if (fillType != newFillType) - { - fillType = newFillType; - clearFill(); - } - } - - void clearFont() - { - currentFontFace = localFontFace = nullptr; + layers.popOne (deviceResources.deviceContext.context); } void setFont (const Font& newFont) { - if (font != newFont) - { - font = newFont; - clearFont(); - } - } - - void createFont() - { - if (currentFontFace == nullptr) - { - auto typefacePtr = font.getTypefacePtr(); - auto* typeface = dynamic_cast (typefacePtr.get()); - currentFontFace = typeface->getIDWriteFontFace(); - fontHeightToEmSizeFactor = typeface->getUnitsToHeightScaleFactor(); - } + font = newFont; } void setOpacity (float newOpacity) { fillType.setOpacity (newOpacity); - - if (currentBrush != nullptr) - currentBrush->SetOpacity (newOpacity); } void clearFill() { - gradientStops = nullptr; linearGradient = nullptr; radialGradient = nullptr; - bitmap = nullptr; bitmapBrush = nullptr; currentBrush = nullptr; } - void createBrush() + /** Translate a JUCE FillType to a Direct2D brush */ + void updateCurrentBrush() { - if (currentBrush == nullptr) + if (fillType.isColour()) { - if (fillType.isColour()) + // Reuse the same colour brush + currentBrush = colourBrush; + } + else if (fillType.isTiledImage()) + { + if (fillType.image.isNull()) + return; + + const auto d2d1Bitmap = [&] { - auto colour = colourToD2D (fillType.colour); - owner.pimpl->colourBrush->SetColor (colour); - currentBrush = owner.pimpl->colourBrush; - } - else if (fillType.isTiledImage()) + if (auto direct2DPixelData = dynamic_cast (fillType.image.getPixelData())) + if (auto bitmap = direct2DPixelData->getAdapterD2D1Bitmap()) + if (bitmap->GetPixelFormat().format == DXGI_FORMAT_B8G8R8A8_UNORM) + return bitmap; + + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (Direct2DMetricsHub::getInstance()->imageContextMetrics, createBitmapTime); + + return Direct2DBitmap::fromImage (fillType.image, deviceResources.deviceContext.context, Image::ARGB); + }(); + + if (d2d1Bitmap != nullptr) { - D2D1_BRUSH_PROPERTIES brushProps = { fillType.getOpacity(), transformToMatrix (fillType.transform) }; + D2D1_BRUSH_PROPERTIES brushProps { fillType.getOpacity(), D2DUtilities::transformToMatrix (fillType.transform) }; auto bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP); - - image = fillType.image; - - D2D1_SIZE_U size = { (UINT32) image.getWidth(), (UINT32) image.getHeight() }; - auto bp = D2D1::BitmapProperties(); - - this->image = image.convertedToFormat (Image::ARGB); - Image::BitmapData bd (this->image, Image::BitmapData::readOnly); - bp.pixelFormat = owner.pimpl->renderingTarget->GetPixelFormat(); - bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; - - auto hr = owner.pimpl->renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, bitmap.resetAndGetPointerAddress()); - hr = owner.pimpl->renderingTarget->CreateBitmapBrush (bitmap, bmProps, brushProps, bitmapBrush.resetAndGetPointerAddress()); - - currentBrush = bitmapBrush; - } - else if (fillType.isGradient()) - { - gradientStops = nullptr; - - D2D1_BRUSH_PROPERTIES brushProps = { fillType.getOpacity(), transformToMatrix (fillType.transform.followedBy (transform)) }; - - const int numColors = fillType.gradient->getNumColours(); - - HeapBlock stops (numColors); - - for (int i = fillType.gradient->getNumColours(); --i >= 0;) + if (const auto hr = deviceResources.deviceContext.context->CreateBitmapBrush (d2d1Bitmap, + bmProps, + brushProps, + bitmapBrush.resetAndGetPointerAddress()); SUCCEEDED (hr)) { - stops[i].color = colourToD2D (fillType.gradient->getColour (i)); - stops[i].position = (FLOAT) fillType.gradient->getColourPosition (i); - } - - owner.pimpl->renderingTarget->CreateGradientStopCollection (stops.getData(), numColors, gradientStops.resetAndGetPointerAddress()); - - if (fillType.gradient->isRadial) - { - radialGradient = nullptr; - - const auto p1 = fillType.gradient->point1; - const auto p2 = fillType.gradient->point2; - const auto r = p1.getDistanceFrom (p2); - const auto props = D2D1::RadialGradientBrushProperties ({ p1.x, p1.y }, {}, r, r); - - owner.pimpl->renderingTarget->CreateRadialGradientBrush (props, brushProps, gradientStops, radialGradient.resetAndGetPointerAddress()); - currentBrush = radialGradient; - } - else - { - linearGradient = 0; - - const auto p1 = fillType.gradient->point1; - const auto p2 = fillType.gradient->point2; - const auto props = D2D1::LinearGradientBrushProperties ({ p1.x, p1.y }, { p2.x, p2.y }); - - owner.pimpl->renderingTarget->CreateLinearGradientBrush (props, brushProps, gradientStops, linearGradient.resetAndGetPointerAddress()); - - currentBrush = linearGradient; + currentBrush = bitmapBrush; } } } + else if (fillType.isGradient()) + { + if (fillType.gradient->isRadial) + { + radialGradient = deviceResources.radialGradientCache.get (*fillType.gradient, deviceResources.deviceContext.context, owner.metrics.get()); + currentBrush = radialGradient; + } + else + { + linearGradient = deviceResources.linearGradientCache.get (*fillType.gradient, deviceResources.deviceContext.context, owner.metrics.get()); + currentBrush = linearGradient; + } + } + + updateColourBrush(); } - Direct2DLowLevelGraphicsContext& owner; + void updateColourBrush() + { + if (colourBrush && fillType.isColour()) + { + auto colour = D2DUtilities::toCOLOR_F (fillType.colour); + colourBrush->SetColor (colour); + } + } - AffineTransform transform; + enum BrushTransformFlags + { + noTransforms = 0, + applyWorldTransform = 1, + applyInverseWorldTransform = 2, + applyFillTypeTransform = 4, + applyWorldAndFillTypeTransforms = applyFillTypeTransform | applyWorldTransform + }; - Font font; - float fontHeightToEmSizeFactor = 1.0f; + ComSmartPtr getBrush (int flags = applyWorldAndFillTypeTransforms) + { + if (fillType.isInvisible()) + return nullptr; - IDWriteFontFace* currentFontFace = nullptr; - ComSmartPtr localFontFace; + if (! fillType.isGradient() && ! fillType.isTiledImage()) + return currentBrush; - Rectangle clipRect; - bool clipsRect = false, shouldClipRect = false; + Point translation{}; + AffineTransform transform{}; - Image image; - ComSmartPtr bitmap; // xxx needs a better name - what is this for?? - bool clipsBitmap = false, shouldClipBitmap = false; + if ((flags & BrushTransformFlags::applyWorldTransform) != 0) + { + if (currentTransform.isOnlyTranslated) + translation = currentTransform.offset.toFloat(); + else + transform = currentTransform.getTransform(); + } - ComSmartPtr complexClipGeometry; - D2D1_LAYER_PARAMETERS complexClipLayerParams; - ComSmartPtr complexClipLayer; - bool clipsComplex = false, shouldClipComplex = false; + if ((flags & BrushTransformFlags::applyFillTypeTransform) != 0) + { + if (fillType.transform.isOnlyTranslation()) + translation += Point (fillType.transform.getTranslationX(), fillType.transform.getTranslationY()); + else + transform = transform.followedBy (fillType.transform); + } - ComSmartPtr rectListGeometry; - D2D1_LAYER_PARAMETERS rectListLayerParams; - ComSmartPtr rectListLayer; - bool clipsRectList = false, shouldClipRectList = false; + if ((flags & BrushTransformFlags::applyInverseWorldTransform) != 0) + { + if (currentTransform.isOnlyTranslated) + translation -= currentTransform.offset.toFloat(); + else + transform = transform.followedBy (currentTransform.getTransform().inverted()); + } - Image maskImage; - D2D1_LAYER_PARAMETERS imageMaskLayerParams; - ComSmartPtr bitmapMaskLayer; - ComSmartPtr maskBitmap; - ComSmartPtr bitmapMaskBrush; + if (fillType.isGradient()) + { + const auto p1 = fillType.gradient->point1 + translation; + const auto p2 = fillType.gradient->point2 + translation; - ID2D1Brush* currentBrush = nullptr; + 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 }); + } + } + + 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() }); + } + + 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 currentBrush = nullptr; + ComSmartPtr& colourBrush; // reference to shared colour brush ComSmartPtr bitmapBrush; ComSmartPtr linearGradient; ComSmartPtr radialGradient; - ComSmartPtr gradientStops; + + RenderingHelpers::TranslationOrTransform currentTransform; + + DxgiAdapter::Ptr& adapter; + Direct2DDeviceResources& deviceResources; + RectangleList deviceSpaceClipList; + + Font font { FontOptions {} }; FillType fillType; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SavedState) + 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; + RectangleList paintAreas; + + DxgiAdapter::Ptr adapter; + Direct2DDeviceResources deviceResources; + + std::vector> savedClientStates; + + virtual HRESULT prepare() + { + if (! deviceResources.canPaint (adapter)) + { + if (auto hr = deviceResources.create (adapter); FAILED (hr)) + return hr; + } + + return S_OK; + } + + virtual void teardown() + { + deviceResources.release(); + } + + virtual ComSmartPtr getDeviceContextTarget() const = 0; + + virtual void updatePaintAreas() = 0; + + virtual bool checkPaintReady() + { + return deviceResources.canPaint (adapter); + } + +public: + Pimpl (Direct2DGraphicsContext& ownerIn, bool opaqueIn) + : owner (ownerIn), opaque (opaqueIn) + { + setTargetAlpha (1.0f); + + directX->adapters.addListener (*this); + } + + ~Pimpl() override + { + directX->adapters.removeListener (*this); + + popAllSavedStates(); + } + + void setTargetAlpha (float alpha) + { + backgroundColor = D2DUtilities::toCOLOR_F (Colours::black.withAlpha (opaque ? targetAlpha : 0.0f)); + targetAlpha = alpha; + } + + virtual void clearBackground() + { + deviceResources.deviceContext.context->Clear (backgroundColor); + } + + virtual SavedState* startFrame() + { + prepare(); + + // Anything to paint? + updatePaintAreas(); + auto paintBounds = paintAreas.getBounds(); + + if (! getFrameSize().intersects (paintBounds) || paintBounds.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); + + // Init device context transform + deviceResources.deviceContext.resetTransform(); + + const auto effectiveDpi = USER_DEFAULT_SCREEN_DPI * owner.getPhysicalPixelScaleFactor(); + deviceResources.deviceContext.context->SetDpi (effectiveDpi, effectiveDpi); + + // Start drawing + deviceResources.deviceContext.context->SetTarget (getDeviceContextTarget()); + deviceResources.deviceContext.context->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()); + + hr = deviceResources.deviceContext.context->EndDraw(); + deviceResources.deviceContext.context->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, + deviceResources.colourBrush, + adapter, + 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(); + } + + DxgiAdapter& getAdapter() const noexcept + { + return *adapter; + } + + ComSmartPtr getDeviceContext() const noexcept + { + return deviceResources.deviceContext.context; + } + + const auto& getPaintAreas() const noexcept + { + return paintAreas; + } + + virtual Rectangle getFrameSize() = 0; + + void setDeviceContextTransform (AffineTransform transform) + { + deviceResources.deviceContext.setTransform (transform); + } + + void resetDeviceContextTransform() + { + deviceResources.deviceContext.setTransform ({}); + } + + 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; + + 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 = deviceResources.deviceContext.context; + + if (deviceContext == nullptr) + return; + + const auto fillTransform = transform.isOnlyTranslated + ? SavedState::BrushTransformFlags::applyWorldAndFillTypeTransforms + : SavedState::BrushTransformFlags::applyFillTypeTransform; + + const auto brush = owner.currentState->getBrush (fillTransform); + + if (brush == nullptr) + return; + + 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; + bool opaque = true; + float targetAlpha = 1.0f; + D2D1_COLOR_F backgroundColor{}; + +private: + void adapterCreated (DxgiAdapter::Ptr newAdapter) override + { + if (! adapter || adapter->uniqueIDMatches (newAdapter)) + { + teardown(); + + adapter = newAdapter; + } + } + + void adapterRemoved (DxgiAdapter::Ptr expiringAdapter) override + { + if (adapter && adapter->uniqueIDMatches (expiringAdapter)) + { + teardown(); + + adapter = nullptr; + } + } + + HWND hwnd = nullptr; + + #if JUCE_DIRECT2D_METRICS + int64 paintStartTicks = 0; + #endif + + JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl) }; //============================================================================== -Direct2DLowLevelGraphicsContext::Direct2DLowLevelGraphicsContext (HWND hwnd_) - : hwnd (hwnd_), - currentState (nullptr), - pimpl (new Pimpl()) -{ - RECT windowRect; - GetClientRect (hwnd, &windowRect); - D2D1_SIZE_U size = { (UINT32) (windowRect.right - windowRect.left), (UINT32) (windowRect.bottom - windowRect.top) }; - bounds.setSize (size.width, size.height); +Direct2DGraphicsContext::Direct2DGraphicsContext() = default; +Direct2DGraphicsContext::~Direct2DGraphicsContext() = default; - if (pimpl->factories->d2dFactory != nullptr) +bool Direct2DGraphicsContext::startFrame() +{ + auto pimpl = getPimpl(); + currentState = pimpl->startFrame(); + + if (currentState == nullptr) + return false; + + if (auto deviceContext = pimpl->getDeviceContext()) { - [[maybe_unused]] auto hr = pimpl->factories->d2dFactory->CreateHwndRenderTarget ({}, { hwnd, size }, pimpl->renderingTarget.resetAndGetPointerAddress()); - jassert (SUCCEEDED (hr)); - hr = pimpl->renderingTarget->CreateSolidColorBrush (D2D1::ColorF::ColorF (0.0f, 0.0f, 0.0f, 1.0f), pimpl->colourBrush.resetAndGetPointerAddress()); + resetPendingClipList(); + + clipToRectangleList (pimpl->getPaintAreas()); + + // Clear the buffer *after* setting the clip region + clearTargetBuffer(); + + // Init font & brush + setFont (currentState->font); + currentState->updateCurrentBrush(); } + + return true; } -Direct2DLowLevelGraphicsContext::~Direct2DLowLevelGraphicsContext() +void Direct2DGraphicsContext::endFrame() { - states.clear(); -} + getPimpl()->finishFrame(); -void Direct2DLowLevelGraphicsContext::resized() -{ - RECT windowRect; - GetClientRect (hwnd, &windowRect); - D2D1_SIZE_U size = { (UINT32) (windowRect.right - windowRect.left), (UINT32) (windowRect.bottom - windowRect.top) }; - - pimpl->renderingTarget->Resize (size); - bounds.setSize (size.width, size.height); -} - -void Direct2DLowLevelGraphicsContext::clear() -{ - pimpl->renderingTarget->Clear (D2D1::ColorF (D2D1::ColorF::White, 0.0f)); // xxx why white and not black? -} - -void Direct2DLowLevelGraphicsContext::start() -{ - pimpl->renderingTarget->BeginDraw(); - saveState(); -} - -void Direct2DLowLevelGraphicsContext::end() -{ - states.clear(); currentState = nullptr; - pimpl->renderingTarget->EndDraw(); - pimpl->renderingTarget->CheckWindowState(); + ++frame; } -void Direct2DLowLevelGraphicsContext::setOrigin (Point o) +void Direct2DGraphicsContext::setOrigin (Point o) { - addTransform (AffineTransform::translation ((float) o.x, (float) o.y)); + applyPendingClipList(); + + currentState->currentTransform.setOrigin (o); } -void Direct2DLowLevelGraphicsContext::addTransform (const AffineTransform& transform) +void Direct2DGraphicsContext::addTransform (const AffineTransform& transform) { - currentState->transform = transform.followedBy (currentState->transform); + // The pending clip list is based on the transform stored in currentState, so apply the pending clip list before adding the transform + applyPendingClipList(); + + currentState->currentTransform.addTransform (transform); + + resetPendingClipList(); } -float Direct2DLowLevelGraphicsContext::getPhysicalPixelScaleFactor() +bool Direct2DGraphicsContext::clipToRectangle (const Rectangle& r) { - return std::sqrt (std::abs (currentState->transform.getDeterminant())); -} + const auto& transform = currentState->currentTransform; + auto& deviceSpaceClipList = currentState->deviceSpaceClipList; -bool Direct2DLowLevelGraphicsContext::clipToRectangle (const Rectangle& r) -{ - currentState->clipToRectangle (r); - return ! isClipEmpty(); -} - -bool Direct2DLowLevelGraphicsContext::clipToRectangleList (const RectangleList& clipRegion) -{ - currentState->clipToRectList (pimpl->rectListToPathGeometry (clipRegion)); - return ! isClipEmpty(); -} - -void Direct2DLowLevelGraphicsContext::excludeClipRectangle (const Rectangle&) -{ - //xxx -} - -void Direct2DLowLevelGraphicsContext::clipToPath (const Path& path, const AffineTransform& transform) -{ - currentState->clipToPath (pimpl->pathToPathGeometry (path, transform)); -} - -void Direct2DLowLevelGraphicsContext::clipToImageAlpha (const Image& sourceImage, const AffineTransform& transform) -{ - currentState->clipToImage (sourceImage, transform); -} - -bool Direct2DLowLevelGraphicsContext::clipRegionIntersects (const Rectangle& r) -{ - return currentState->clipRect.intersects (r.toFloat().transformedBy (currentState->transform).getSmallestIntegerContainer()); -} - -Rectangle Direct2DLowLevelGraphicsContext::getClipBounds() const -{ - // xxx could this take into account complex clip regions? - return currentState->clipRect.toFloat().transformedBy (currentState->transform.inverted()).getSmallestIntegerContainer(); -} - -bool Direct2DLowLevelGraphicsContext::isClipEmpty() const -{ - return currentState->clipRect.isEmpty(); -} - -void Direct2DLowLevelGraphicsContext::saveState() -{ - states.add (new SavedState (*this)); - currentState = states.getLast(); -} - -void Direct2DLowLevelGraphicsContext::restoreState() -{ - jassert (states.size() > 1); //you should never pop the last state! - states.removeLast (1); - currentState = states.getLast(); -} - -void Direct2DLowLevelGraphicsContext::beginTransparencyLayer (float /*opacity*/) -{ - jassertfalse; //xxx todo -} - -void Direct2DLowLevelGraphicsContext::endTransparencyLayer() -{ - jassertfalse; //xxx todo -} - -void Direct2DLowLevelGraphicsContext::setFill (const FillType& fillType) -{ - currentState->setFill (fillType); -} - -void Direct2DLowLevelGraphicsContext::setOpacity (float newOpacity) -{ - currentState->setOpacity (newOpacity); -} - -void Direct2DLowLevelGraphicsContext::setInterpolationQuality (Graphics::ResamplingQuality /*quality*/) -{ -} - -void Direct2DLowLevelGraphicsContext::fillRect (const Rectangle& r, bool /*replaceExistingContents*/) -{ - fillRect (r.toFloat()); -} - -void Direct2DLowLevelGraphicsContext::fillRect (const Rectangle& r) -{ - pimpl->renderingTarget->SetTransform (transformToMatrix (currentState->transform)); - currentState->createBrush(); - pimpl->renderingTarget->FillRectangle (rectangleToRectF (r), currentState->currentBrush); - pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix()); -} - -void Direct2DLowLevelGraphicsContext::fillRectList (const RectangleList& list) -{ - for (auto& r : list) - fillRect (r); -} - -void Direct2DLowLevelGraphicsContext::fillPath (const Path& p, const AffineTransform& transform) -{ - currentState->createBrush(); - ComSmartPtr geometry (pimpl->pathToPathGeometry (p, transform.followedBy (currentState->transform))); - - if (pimpl->renderingTarget != nullptr) - pimpl->renderingTarget->FillGeometry (geometry, currentState->currentBrush); -} - -void Direct2DLowLevelGraphicsContext::drawImage (const Image& image, const AffineTransform& transform) -{ - pimpl->renderingTarget->SetTransform (transformToMatrix (transform.followedBy (currentState->transform))); - - D2D1_SIZE_U size = { (UINT32) image.getWidth(), (UINT32) image.getHeight() }; - auto bp = D2D1::BitmapProperties(); - - Image img (image.convertedToFormat (Image::ARGB)); - Image::BitmapData bd (img, Image::BitmapData::readOnly); - bp.pixelFormat = pimpl->renderingTarget->GetPixelFormat(); - bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; + JUCE_TRACE_EVENT_INT_RECT_LIST (etw::clipToRectangle, etw::direct2dKeyword, getFrameId(), r); + // The renderer needs to keep track of the aggregate clip rectangles in order to correctly report the + // clip region to the caller. The renderer also needs to push Direct2D clip layers to the device context + // to perform the actual clipping. The reported clip region will not necessarily match the Direct2D clip region + // if the clip region is transformed, or the clip region is an image or a path. + // + // Pushing Direct2D clip layers is expensive and there's no need to clip until something is actually drawn. + // So - pendingClipList is a list of the areas that need to actually be clipped. Each fill or + // draw method then applies any pending clip areas before drawing. + // + // Also - calling ID2D1DeviceContext::SetTransform is expensive, so check the current transform to see + // if the renderer can pre-transform the clip rectangle instead. + if (transform.isOnlyTranslated) { - ComSmartPtr tempBitmap; - pimpl->renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, tempBitmap.resetAndGetPointerAddress()); - if (tempBitmap != nullptr) - pimpl->renderingTarget->DrawBitmap (tempBitmap); + // The current transform is only a translation, so save a few cycles by just adding the + // offset instead of transforming the rectangle; the software renderer does something similar. + auto translatedR = r.toFloat() + transform.offset.toFloat(); + deviceSpaceClipList.clipTo (translatedR); + + pendingClipList.clipTo (translatedR); + } + else if (currentState->isCurrentTransformAxisAligned()) + { + // The current transform is a simple scale + translation, so pre-transform the rectangle + auto transformedR = transform.boundsAfterTransform (r.toFloat()); + deviceSpaceClipList.clipTo (transformedR); + + pendingClipList.clipTo (transformedR); + } + else + { + deviceSpaceClipList = getPimpl()->getFrameSize().toFloat(); + + // The current transform is too complex to pre-transform the rectangle, so just add the + // rectangle to the clip list. The renderer will need to call ID2D1DeviceContext::SetTransform + // before applying the clip layer. + pendingClipList.clipTo (r.toFloat()); } - pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix()); + return ! isClipEmpty(); } -void Direct2DLowLevelGraphicsContext::drawLine (const Line& line) +bool Direct2DGraphicsContext::clipToRectangleList (const RectangleList& newClipList) { - // xxx doesn't seem to be correctly aligned, may need nudging by 0.5 to match the software renderer's behaviour - pimpl->renderingTarget->SetTransform (transformToMatrix (currentState->transform)); - currentState->createBrush(); + JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::clipToRectangleList, etw::direct2dKeyword, getFrameId(), newClipList) - pimpl->renderingTarget->DrawLine (D2D1::Point2F (line.getStartX(), line.getStartY()), - D2D1::Point2F (line.getEndX(), line.getEndY()), - currentState->currentBrush); - pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix()); + const auto& transform = currentState->currentTransform; + auto& deviceSpaceClipList = currentState->deviceSpaceClipList; + + // This works a lot like clipToRect + + // Just one rectangle? + if (newClipList.getNumRectangles() == 1) + return clipToRectangle (newClipList.getRectangle (0)); + + if (transform.isIdentity()) + { + deviceSpaceClipList.clipTo (newClipList); + + pendingClipList.clipTo (newClipList); + } + else if (currentState->currentTransform.isOnlyTranslated) + { + RectangleList offsetList (newClipList); + offsetList.offsetAll (transform.offset); + deviceSpaceClipList.clipTo (offsetList); + + pendingClipList.clipTo (offsetList); + } + else if (currentState->isCurrentTransformAxisAligned()) + { + RectangleList scaledList; + + for (auto& i : newClipList) + scaledList.add (transform.boundsAfterTransform (i.toFloat())); + + deviceSpaceClipList.clipTo (scaledList); + pendingClipList.clipTo (scaledList); + } + else + { + deviceSpaceClipList = getPimpl()->getFrameSize().toFloat(); + + pendingClipList.clipTo (newClipList); + } + + return ! isClipEmpty(); } -void Direct2DLowLevelGraphicsContext::setFont (const Font& newFont) +void Direct2DGraphicsContext::excludeClipRectangle (const Rectangle& userSpaceExcludedRectangle) { + JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::excludeClipRectangle, etw::direct2dKeyword, getFrameId(), userSpaceExcludedRectangle) + + applyPendingClipList(); + + auto& transform = currentState->currentTransform; + auto& deviceSpaceClipList = currentState->deviceSpaceClipList; + const auto frameSize = getPimpl()->getFrameSize().toFloat(); + + if (transform.isOnlyTranslated) + { + // Just a translation; pre-translate the exclusion area + auto translatedR = transform.translated (userSpaceExcludedRectangle.toFloat()); + + if (! translatedR.contains (frameSize)) + { + deviceSpaceClipList.subtract (translatedR); + pendingClipList.subtract (translatedR); + } + } + else if (currentState->isCurrentTransformAxisAligned()) + { + // Just a scale + translation; pre-transform the exclusion area + auto transformedR = transform.boundsAfterTransform (userSpaceExcludedRectangle.toFloat()); + + if (! transformedR.contains (frameSize)) + { + deviceSpaceClipList.subtract (transformedR); + pendingClipList.subtract (transformedR); + } + } + else + { + deviceSpaceClipList = frameSize; + pendingClipList.subtract (userSpaceExcludedRectangle.toFloat()); + } +} + +void Direct2DGraphicsContext::setPhysicalPixelScaleFactor (float f) +{ + scale = f; +} + +void Direct2DGraphicsContext::resetPendingClipList() +{ + auto& transform = currentState->currentTransform; + + const auto frameSize = transform.isOnlyTranslated || currentState->isCurrentTransformAxisAligned() + ? getPimpl()->getFrameSize() + : getPimpl()->getFrameSize().transformedBy (transform.getTransform().inverted()); + + pendingClipList.reset (frameSize.toFloat()); +} + +void Direct2DGraphicsContext::applyPendingClipList() +{ + auto& transform = currentState->currentTransform; + bool const axisAligned = currentState->isCurrentTransformAxisAligned(); + const auto list = pendingClipList.getList(); + + // Clip if the pending clip list is not empty and smaller than the frame size + if (! list.containsRectangle (getPimpl()->getFrameSize().toFloat()) && ! list.isEmpty()) + { + if (list.getNumRectangles() == 1 && (transform.isOnlyTranslated || axisAligned)) + { + auto r = list.getRectangle (0); + currentState->pushAliasedAxisAlignedClipLayer (r); + } + else + { + auto clipTransform = transform.isOnlyTranslated || axisAligned ? AffineTransform{} : transform.getTransform(); + if (auto clipGeometry = D2DHelpers::rectListToPathGeometry (getPimpl()->getDirect2DFactory(), + list, + clipTransform, + D2D1_FILL_MODE_WINDING, + D2D1_FIGURE_BEGIN_FILLED, + metrics.get())) + { + currentState->pushGeometryClipLayer (clipGeometry); + } + } + + resetPendingClipList(); + } +} + +void Direct2DGraphicsContext::clipToPath (const Path& path, const AffineTransform& transform) +{ + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::clipToPath, etw::direct2dKeyword, getFrameId()); + + applyPendingClipList(); + + // Set the clip list to the full size of the frame to match + // the software renderer + auto pathTransform = currentState->currentTransform.getTransformWith (transform); + auto transformedBounds = path.getBounds().transformedBy (pathTransform); + currentState->deviceSpaceClipList.clipTo (transformedBounds); + + if (auto deviceContext = getPimpl()->getDeviceContext()) + { + currentState->pushGeometryClipLayer (D2DHelpers::pathToPathGeometry (getPimpl()->getDirect2DFactory(), + path, + pathTransform, + D2D1_FIGURE_BEGIN_FILLED, + metrics.get())); + } +} + +void Direct2DGraphicsContext::clipToImageAlpha (const Image& sourceImage, const AffineTransform& transform) +{ + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::clipToImageAlpha, etw::direct2dKeyword, getFrameId()); + + if (sourceImage.isNull()) + return; + + applyPendingClipList(); + + // Put a rectangle clip layer under the image clip layer + // The D2D bitmap brush will extend past the boundaries of sourceImage, so clip + // to the sourceImage bounds + auto brushTransform = currentState->currentTransform.getTransformWith (transform); + { + D2D1_RECT_F sourceImageRectF = D2DUtilities::toRECT_F (sourceImage.getBounds()); + ComSmartPtr geometry; + getPimpl()->getDirect2DFactory()->CreateRectangleGeometry (sourceImageRectF, geometry.resetAndGetPointerAddress()); + + if (geometry) + currentState->pushTransformedRectangleGeometryClipLayer (geometry, brushTransform); + } + + // Set the clip list to the full size of the frame to match + // the software renderer + currentState->deviceSpaceClipList = getPimpl()->getFrameSize().toFloat(); + + if (auto deviceContext = getPimpl()->getDeviceContext()) + { + // Is this a Direct2D image already? + ComSmartPtr d2d1Bitmap; + + if (auto direct2DPixelData = dynamic_cast (sourceImage.getPixelData())) + d2d1Bitmap = direct2DPixelData->getAdapterD2D1Bitmap(); + + if (! d2d1Bitmap) + { + // Convert sourceImage to single-channel alpha-only maskImage + d2d1Bitmap = Direct2DBitmap::fromImage (sourceImage, deviceContext, Image::SingleChannel); + } + + if (d2d1Bitmap) + { + // Make a transformed bitmap brush using the bitmap + // As usual, apply the current transform first *then* the transform parameter + ComSmartPtr brush; + auto matrix = D2DUtilities::transformToMatrix (brushTransform); + D2D1_BRUSH_PROPERTIES brushProps = { 1.0f, matrix }; + + auto bitmapBrushProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP); + auto hr = deviceContext->CreateBitmapBrush (d2d1Bitmap, bitmapBrushProps, brushProps, brush.resetAndGetPointerAddress()); + + if (SUCCEEDED (hr)) + { + // Push the clipping layer onto the layer stack + // Don't set maskTransform in the LayerParameters struct; that only applies to geometry clipping + // Do set the contentBounds member, transformed appropriately + auto layerParams = D2D1::LayerParameters(); + auto transformedBounds = sourceImage.getBounds().toFloat().transformedBy (brushTransform); + layerParams.contentBounds = D2DUtilities::toRECT_F (transformedBounds); + layerParams.opacityBrush = brush; + + currentState->pushLayer (layerParams); + } + } + } +} + +bool Direct2DGraphicsContext::clipRegionIntersects (const Rectangle& r) +{ + const auto rect = currentState->currentTransform.isOnlyTranslated ? currentState->currentTransform.translated (r.toFloat()) + : currentState->currentTransform.boundsAfterTransform (r.toFloat()); + return currentState->deviceSpaceClipList.intersectsRectangle (rect); +} + +Rectangle Direct2DGraphicsContext::getClipBounds() const +{ + return currentState->currentTransform.deviceSpaceToUserSpace (currentState->deviceSpaceClipList.getBounds()).getSmallestIntegerContainer(); +} + +bool Direct2DGraphicsContext::isClipEmpty() const +{ + return getClipBounds().isEmpty(); +} + +void Direct2DGraphicsContext::saveState() +{ + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::saveState, etw::direct2dKeyword, getFrameId()); + + applyPendingClipList(); + + currentState = getPimpl()->pushSavedState(); +} + +void Direct2DGraphicsContext::restoreState() +{ + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::restoreState, etw::direct2dKeyword, getFrameId()); + + currentState = getPimpl()->popSavedState(); + + currentState->updateColourBrush(); + jassert (currentState); + + resetPendingClipList(); +} + +void Direct2DGraphicsContext::beginTransparencyLayer (float opacity) +{ + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::beginTransparencyLayer, etw::direct2dKeyword, getFrameId()); + + applyPendingClipList(); + + if (auto deviceContext = getPimpl()->getDeviceContext()) + currentState->pushTransparencyLayer (opacity); +} + +void Direct2DGraphicsContext::endTransparencyLayer() +{ + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::endTransparencyLayer, etw::direct2dKeyword, getFrameId()); + + if (auto deviceContext = getPimpl()->getDeviceContext()) + currentState->popTopLayer(); +} + +void Direct2DGraphicsContext::setFill (const FillType& fillType) +{ + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::setFill, etw::direct2dKeyword, getFrameId()); + + if (auto deviceContext = getPimpl()->getDeviceContext()) + { + currentState->fillType = fillType; + currentState->updateCurrentBrush(); + } +} + +void Direct2DGraphicsContext::setOpacity (float newOpacity) +{ + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::setOpacity, etw::direct2dKeyword, getFrameId()); + + currentState->setOpacity (newOpacity); + + if (auto deviceContext = getPimpl()->getDeviceContext()) + currentState->updateCurrentBrush(); +} + +void Direct2DGraphicsContext::setInterpolationQuality (Graphics::ResamplingQuality quality) +{ + switch (quality) + { + case Graphics::ResamplingQuality::lowResamplingQuality: + currentState->interpolationMode = D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR; + break; + + case Graphics::ResamplingQuality::mediumResamplingQuality: + currentState->interpolationMode = D2D1_INTERPOLATION_MODE_LINEAR; + break; + + case Graphics::ResamplingQuality::highResamplingQuality: + currentState->interpolationMode = D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC; + break; + } +} + +void Direct2DGraphicsContext::fillRect (const Rectangle& r, bool replaceExistingContents) +{ + if (replaceExistingContents) + { + JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::fillRectReplace, etw::direct2dKeyword, getFrameId(), r); + + applyPendingClipList(); + clipToRectangle (r); + getPimpl()->clearBackground(); + currentState->popTopLayer(); + } + + auto fill = [] (Rectangle rect, ComSmartPtr deviceContext, ComSmartPtr brush) + { + deviceContext->FillRectangle (D2DUtilities::toRECT_F (rect), brush); + }; + + getPimpl()->paintPrimitive (r.toFloat(), fill); +} + +void Direct2DGraphicsContext::fillRect (const Rectangle& r) +{ + auto fill = [] (Rectangle rect, ComSmartPtr deviceContext, ComSmartPtr brush) + { + deviceContext->FillRectangle (D2DUtilities::toRECT_F (rect), brush); + }; + + getPimpl()->paintPrimitive (r, fill); +} + +void Direct2DGraphicsContext::fillRectList (const RectangleList& list) +{ + if (getPimpl()->fillSpriteBatch (list)) + return; + + auto fill = [] (const RectangleList& l, ComSmartPtr deviceContext, ComSmartPtr brush) + { + for (const auto& r : l) + deviceContext->FillRectangle (D2DUtilities::toRECT_F (r), brush); + }; + + getPimpl()->paintPrimitive (list, fill); +} + +void Direct2DGraphicsContext::drawRect (const Rectangle& r, float lineThickness) +{ + // ID2D1DeviceContext::DrawRectangle centers the stroke around the edges of the specified rectangle, but + // the software renderer contains the stroke within the rectangle + // To match the software renderer, reduce the rectangle by half the stroke width + if (r.getWidth() * 0.5f < lineThickness || r.getHeight() * 0.5f < lineThickness) + return; + + auto draw = [&] (Rectangle rect, ComSmartPtr deviceContext, ComSmartPtr brush) + { + deviceContext->DrawRectangle (D2DUtilities::toRECT_F (rect), brush, lineThickness); + }; + + auto reducedR = r.reduced (lineThickness * 0.5f); + + getPimpl()->paintPrimitive (reducedR, draw); +} + +void Direct2DGraphicsContext::fillPath (const Path& p, const AffineTransform& transform) +{ + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::fillPath, etw::direct2dKeyword, getFrameId()); + + if (p.isEmpty()) + return; + + applyPendingClipList(); + + const auto deviceContext = getPimpl()->getDeviceContext(); + const auto brush = currentState->getBrush (SavedState::applyFillTypeTransform); + const auto factory = getPimpl()->getDirect2DFactory(); + const auto geometry = D2DHelpers::pathToPathGeometry (factory, + p, + transform, + D2D1_FIGURE_BEGIN_FILLED, + metrics.get()); + + if (deviceContext == nullptr || brush == nullptr || geometry == nullptr) + return; + + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, fillGeometryTime) + + ScopedTransform scopedTransform { *getPimpl(), currentState }; + deviceContext->FillGeometry (geometry, brush); +} + +void Direct2DGraphicsContext::strokePath (const Path& p, const PathStrokeType& strokeType, const AffineTransform& transform) +{ + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::drawPath, etw::direct2dKeyword, getFrameId()); + + if (p.isEmpty()) + return; + + applyPendingClipList(); + + const auto deviceContext = getPimpl()->getDeviceContext(); + const auto brush = currentState->getBrush (SavedState::applyFillTypeTransform); + const auto factory = getPimpl()->getDirect2DFactory(); + const auto strokeStyle = D2DHelpers::pathStrokeTypeToStrokeStyle (factory, strokeType); + const auto geometry = D2DHelpers::pathToPathGeometry (factory, + p, + transform, + D2D1_FIGURE_BEGIN_HOLLOW, + metrics.get()); + + if (deviceContext == nullptr || brush == nullptr || geometry == nullptr || strokeStyle == nullptr) + return; + + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, drawGeometryTime) + + ScopedTransform scopedTransform { *getPimpl(), currentState }; + deviceContext->DrawGeometry (geometry, brush, strokeType.getStrokeThickness(), strokeStyle); +} + +void Direct2DGraphicsContext::drawImage (const Image& image, const AffineTransform& transform) +{ + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, drawImageTime) + + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::drawImage, etw::direct2dKeyword, getFrameId()); + + if (image.isNull()) + return; + + applyPendingClipList(); + + if (auto deviceContext = getPimpl()->getDeviceContext()) + { + // Is this a Direct2D image already with the correct format? + ComSmartPtr d2d1Bitmap; + Rectangle imageClipArea; + + if (auto direct2DPixelData = dynamic_cast (image.getPixelData())) + { + d2d1Bitmap = direct2DPixelData->getAdapterD2D1Bitmap(); + imageClipArea = { direct2DPixelData->width, direct2DPixelData->height }; + } + + if (! d2d1Bitmap || d2d1Bitmap->GetPixelFormat().format != DXGI_FORMAT_B8G8R8A8_UNORM) + { + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (Direct2DMetricsHub::getInstance()->imageContextMetrics, createBitmapTime); + + d2d1Bitmap = Direct2DBitmap::fromImage (image, deviceContext, Image::ARGB); + imageClipArea = image.getBounds(); + } + + if (d2d1Bitmap) + { + auto sourceRectF = D2DUtilities::toRECT_F (imageClipArea); + + auto imageTransform = currentState->currentTransform.getTransformWith (transform); + + if (imageTransform.isOnlyTranslation()) + { + auto destinationRect = D2DUtilities::toRECT_F (imageClipArea.toFloat() + Point { imageTransform.getTranslationX(), imageTransform.getTranslationY() }); + + deviceContext->DrawBitmap (d2d1Bitmap, + &destinationRect, + currentState->fillType.getOpacity(), + currentState->interpolationMode, + &sourceRectF, + {}); + + return; + } + + if (D2DHelpers::isTransformAxisAligned (imageTransform)) + { + auto destinationRect = D2DUtilities::toRECT_F (imageClipArea.toFloat().transformedBy (imageTransform)); + + deviceContext->DrawBitmap (d2d1Bitmap, + &destinationRect, + currentState->fillType.getOpacity(), + currentState->interpolationMode, + &sourceRectF, + {}); + return; + } + + ScopedTransform scopedTransform { *getPimpl(), currentState, transform }; + deviceContext->DrawBitmap (d2d1Bitmap, + nullptr, + currentState->fillType.getOpacity(), + currentState->interpolationMode, + &sourceRectF, + {}); + } + } +} + +void Direct2DGraphicsContext::drawLine (const Line& line) +{ + drawLineWithThickness (line, 1.0f); +} + +void Direct2DGraphicsContext::drawLineWithThickness (const Line& line, float lineThickness) +{ + auto draw = [&] (Line l, ComSmartPtr deviceContext, ComSmartPtr brush) + { + const auto makePoint = [] (const auto& x) { return D2D1::Point2F (x.getX(), x.getY()); }; + deviceContext->DrawLine (makePoint (l.getStart()), + makePoint (l.getEnd()), + brush, + lineThickness); + }; + + getPimpl()->paintPrimitive (line, draw); +} + +void Direct2DGraphicsContext::setFont (const Font& newFont) +{ + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::setFont, etw::direct2dKeyword, getFrameId()); + currentState->setFont (newFont); } -const Font& Direct2DLowLevelGraphicsContext::getFont() +const Font& Direct2DGraphicsContext::getFont() { return currentState->font; } -void Direct2DLowLevelGraphicsContext::drawGlyph (int glyphNumber, const AffineTransform& transform) +float Direct2DGraphicsContext::getPhysicalPixelScaleFactor() const { - currentState->createBrush(); - currentState->createFont(); + return scale; +} - auto hScale = currentState->font.getHorizontalScale(); +void Direct2DGraphicsContext::drawRoundedRectangle (const Rectangle& area, float cornerSize, float lineThickness) +{ + auto draw = [&] (Rectangle rect, ComSmartPtr deviceContext, ComSmartPtr brush) + { + D2D1_ROUNDED_RECT roundedRect { D2DUtilities::toRECT_F (rect), cornerSize, cornerSize }; + deviceContext->DrawRoundedRectangle (roundedRect, brush, lineThickness); + }; - pimpl->renderingTarget->SetTransform (transformToMatrix (AffineTransform::scale (hScale, 1.0f) - .followedBy (transform) - .followedBy (currentState->transform))); + getPimpl()->paintPrimitive (area, draw); +} - const auto glyphIndices = (UINT16) glyphNumber; - const auto glyphAdvances = 0.0f; - DWRITE_GLYPH_OFFSET offset = { 0.0f, 0.0f }; +void Direct2DGraphicsContext::fillRoundedRectangle (const Rectangle& area, float cornerSize) +{ + auto fill = [&] (Rectangle rect, ComSmartPtr deviceContext, ComSmartPtr brush) + { + D2D1_ROUNDED_RECT roundedRect { D2DUtilities::toRECT_F (rect), cornerSize, cornerSize }; + deviceContext->FillRoundedRectangle (roundedRect, brush); + }; - DWRITE_GLYPH_RUN glyphRun; - glyphRun.fontFace = currentState->currentFontFace; - glyphRun.fontEmSize = (FLOAT) (currentState->font.getHeight() * currentState->fontHeightToEmSizeFactor); - glyphRun.glyphCount = 1; - glyphRun.glyphIndices = &glyphIndices; - glyphRun.glyphAdvances = &glyphAdvances; - glyphRun.glyphOffsets = &offset; - glyphRun.isSideways = FALSE; - glyphRun.bidiLevel = 0; + getPimpl()->paintPrimitive (area, fill); +} - pimpl->renderingTarget->DrawGlyphRun ({}, &glyphRun, currentState->currentBrush); - pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix()); +void Direct2DGraphicsContext::drawEllipse (const Rectangle& area, float lineThickness) +{ + auto draw = [&] (Rectangle rect, ComSmartPtr deviceContext, ComSmartPtr brush) + { + auto centre = rect.getCentre(); + D2D1_ELLIPSE ellipse { { centre.x, centre.y }, rect.proportionOfWidth (0.5f), rect.proportionOfHeight (0.5f) }; + deviceContext->DrawEllipse (ellipse, brush, lineThickness); + }; + + getPimpl()->paintPrimitive (area, draw); +} + +void Direct2DGraphicsContext::fillEllipse (const Rectangle& area) +{ + auto fill = [&] (Rectangle rect, ComSmartPtr deviceContext, ComSmartPtr brush) + { + auto centre = rect.getCentre(); + D2D1_ELLIPSE ellipse { { centre.x, centre.y }, rect.proportionOfWidth (0.5f), rect.proportionOfHeight (0.5f) }; + deviceContext->FillEllipse (ellipse, brush); + }; + + getPimpl()->paintPrimitive (area, fill); +} + +void Direct2DGraphicsContext::drawGlyphs (Span glyphNumbers, + Span> positions, + const AffineTransform& transform) +{ + jassert (glyphNumbers.size() == positions.size()); + + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, drawGlyphRunTime) + + JUCE_SCOPED_TRACE_EVENT_FRAME (etw::drawGlyphRun, etw::direct2dKeyword, getFrameId()); + + if (currentState->fillType.isInvisible() || glyphNumbers.empty() || positions.empty()) + return; + + const auto& font = currentState->font; + + const auto deviceContext = getPimpl()->getDeviceContext(); + + if (! deviceContext) + return; + + const auto typeface = font.getTypefacePtr(); + const auto fontFace = [&]() -> ComSmartPtr + { + if (auto* x = dynamic_cast (typeface.get())) + return x->getIDWriteFontFace(); + + return {}; + }(); + + if (fontFace == nullptr) + return; + + const auto brush = currentState->getBrush (SavedState::BrushTransformFlags::applyFillTypeTransform); + + if (! brush) + return; + + applyPendingClipList(); + + D2D1_POINT_2F baselineOrigin { 0.0f, 0.0f }; + + const auto fontScale = font.getHorizontalScale(); + const auto scaledTransform = AffineTransform::scale (fontScale, 1.0f).followedBy (transform); + const auto glyphRunTransform = scaledTransform.followedBy (currentState->currentTransform.getTransform()); + const auto onlyTranslated = glyphRunTransform.isOnlyTranslation(); + + if (onlyTranslated) + baselineOrigin = { glyphRunTransform.getTranslationX(), glyphRunTransform.getTranslationY() }; + else + getPimpl()->setDeviceContextTransform (glyphRunTransform); + + auto& run = getPimpl()->glyphRun; + run.replace (positions, fontScale); + + DWRITE_GLYPH_RUN directWriteGlyphRun; + directWriteGlyphRun.fontFace = fontFace; + directWriteGlyphRun.fontEmSize = font.getHeightInPoints(); + directWriteGlyphRun.glyphCount = (UINT32) glyphNumbers.size(); + directWriteGlyphRun.glyphIndices = glyphNumbers.data(); + directWriteGlyphRun.glyphAdvances = run.getAdvances(); + directWriteGlyphRun.glyphOffsets = run.getOffsets(); + directWriteGlyphRun.isSideways = FALSE; + directWriteGlyphRun.bidiLevel = 0; + + const auto tryDrawColourGlyphs = [&] + { + // There's a helpful colour glyph rendering sample at + // https://github.com/microsoft/Windows-universal-samples/blob/main/Samples/DWriteColorGlyph/cpp/CustomTextRenderer.cpp + const auto factory = getPimpl()->getDirectWriteFactory4(); + + if (factory == nullptr) + return false; + + const auto ctx = deviceContext.getInterface(); + + if (ctx == nullptr) + return false; + + ComSmartPtr enumerator; + + constexpr auto formats = DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE + | DWRITE_GLYPH_IMAGE_FORMATS_CFF + | DWRITE_GLYPH_IMAGE_FORMATS_COLR + | DWRITE_GLYPH_IMAGE_FORMATS_PNG + | DWRITE_GLYPH_IMAGE_FORMATS_JPEG + | DWRITE_GLYPH_IMAGE_FORMATS_TIFF + | DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8; + + if (const auto hr = factory->TranslateColorGlyphRun (baselineOrigin, + &directWriteGlyphRun, + nullptr, + formats, + DWRITE_MEASURING_MODE_NATURAL, + nullptr, + 0, + enumerator.resetAndGetPointerAddress()); + FAILED (hr) || enumerator == nullptr) + { + // NOCOLOR is expected if the font has no colour glyphs. Other errors are not expected. + jassert (hr == DWRITE_E_NOCOLOR && enumerator == nullptr); + return false; + } + + for (BOOL gotRun = false; SUCCEEDED (enumerator->MoveNext (&gotRun)) && gotRun;) + { + const DWRITE_COLOR_GLYPH_RUN1* colourRun = nullptr; + + if (FAILED (enumerator->GetCurrentRun (&colourRun)) || colourRun == nullptr) + break; + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wswitch-enum") + switch (colourRun->glyphImageFormat) + { + case DWRITE_GLYPH_IMAGE_FORMATS_PNG: + case DWRITE_GLYPH_IMAGE_FORMATS_JPEG: + case DWRITE_GLYPH_IMAGE_FORMATS_TIFF: + case DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8: + ctx->DrawColorBitmapGlyphRun (colourRun->glyphImageFormat, + { colourRun->baselineOriginX, colourRun->baselineOriginY }, + &colourRun->glyphRun, + colourRun->measuringMode); + break; + + case DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE: + case DWRITE_GLYPH_IMAGE_FORMATS_CFF: + case DWRITE_GLYPH_IMAGE_FORMATS_COLR: + default: + { + const auto useForeground = colourRun->paletteIndex == 0xffff; + const auto lastColour = currentState->colourBrush->GetColor(); + const auto colourBrush = currentState->colourBrush; + + if (! useForeground) + colourBrush->SetColor (colourRun->runColor); + + const auto brushToUse = useForeground ? ComSmartPtr (brush) + : ComSmartPtr (colourBrush); + + ctx->DrawGlyphRun ({ colourRun->baselineOriginX, colourRun->baselineOriginY }, + &colourRun->glyphRun, + colourRun->glyphRunDescription, + brushToUse, + colourRun->measuringMode); + + if (! useForeground) + colourBrush->SetColor (lastColour); + + break; + } + } + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + } + + return true; + }; + + if (! tryDrawColourGlyphs()) + deviceContext->DrawGlyphRun (baselineOrigin, &directWriteGlyphRun, brush); + + if (! onlyTranslated) + getPimpl()->resetDeviceContextTransform(); +} + +Direct2DGraphicsContext::ScopedTransform::ScopedTransform (Pimpl& pimplIn, SavedState* stateIn) + : pimpl (pimplIn), state (stateIn) +{ + pimpl.setDeviceContextTransform (stateIn->currentTransform.getTransform()); +} + +Direct2DGraphicsContext::ScopedTransform::ScopedTransform (Pimpl& pimplIn, SavedState* stateIn, const AffineTransform& transform) + : pimpl (pimplIn), state (stateIn) +{ + pimpl.setDeviceContextTransform (stateIn->currentTransform.getTransformWith (transform)); +} + +Direct2DGraphicsContext::ScopedTransform::~ScopedTransform() +{ + pimpl.resetDeviceContextTransform(); } } // namespace juce diff --git a/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.h b/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.h index 52b0b1e2fc..5576700c3b 100644 --- a/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.h +++ b/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.h @@ -35,23 +35,18 @@ namespace juce { -#ifndef _WINDEF_ -class HWND__; // Forward or never -typedef HWND__* HWND; -#endif - -class Direct2DLowLevelGraphicsContext : public LowLevelGraphicsContext +class Direct2DGraphicsContext : public LowLevelGraphicsContext { public: - Direct2DLowLevelGraphicsContext (HWND); - ~Direct2DLowLevelGraphicsContext(); + Direct2DGraphicsContext(); + ~Direct2DGraphicsContext() override; //============================================================================== bool isVectorDevice() const override { return false; } void setOrigin (Point) override; void addTransform (const AffineTransform&) override; - float getPhysicalPixelScaleFactor() override; + float getPhysicalPixelScaleFactor() const override; bool clipToRectangle (const Rectangle&) override; bool clipToRectangleList (const RectangleList&) override; void excludeClipRectangle (const Rectangle&) override; @@ -83,29 +78,101 @@ public: void drawLine (const Line&) override; void setFont (const Font&) override; const Font& getFont() override; - void drawGlyph (int glyphNumber, const AffineTransform&) override; - - void resized(); - void clear(); - - void start(); - void end(); + void drawGlyphs (Span, + Span>, + const AffineTransform&) override; //============================================================================== -private: + // These methods were not originally part of the LowLevelGraphicsContext; they + // were added because Direct2D supports these drawing primitives directly. + // The specialised functions are more efficient than emulating the same behaviour, e.g. + // by drawing paths. + void drawLineWithThickness (const Line&, float) override; + + void drawEllipse (const Rectangle& area, float lineThickness) override; + void fillEllipse (const Rectangle& area) override; + + void drawRect (const Rectangle&, float) override; + void strokePath (const Path&, const PathStrokeType& strokeType, const AffineTransform&) override; + + void drawRoundedRectangle (const Rectangle& area, float cornerSize, float lineThickness) override; + void fillRoundedRectangle (const Rectangle& area, float cornerSize) override; + + //============================================================================== + bool startFrame(); + void endFrame(); + + virtual Image createSnapshot() const { return {}; } + + uint64_t getFrameId() const override { return frame; } + + Direct2DMetrics::Ptr metrics; + + //============================================================================== + // Min & max frame sizes; same as Direct3D texture size limits + static int constexpr minFrameSize = 1; + static int constexpr maxFrameSize = 16384; + + //============================================================================== + void setPhysicalPixelScaleFactor (float); + +protected: struct SavedState; + SavedState* currentState = nullptr; - HWND hwnd; + class PendingClipList + { + public: + void clipTo (Rectangle i) + { + list.clipTo (i); + } - SavedState* currentState; - OwnedArray states; + template + void clipTo (const RectangleList& other) + { + list.clipTo (other); + } - Rectangle bounds; + void subtract (Rectangle i) + { + list.subtract (i); + } + + RectangleList getList() const { return list; } + + void reset (Rectangle maxBounds) + { + list = maxBounds; + } + + private: + RectangleList list; + }; + + PendingClipList pendingClipList; struct Pimpl; - std::unique_ptr pimpl; + virtual Pimpl* getPimpl() const noexcept = 0; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DLowLevelGraphicsContext) + void resetPendingClipList(); + void applyPendingClipList(); + virtual void clearTargetBuffer() = 0; + + struct ScopedTransform + { + ScopedTransform (Pimpl&, SavedState*); + ScopedTransform (Pimpl&, SavedState*, const AffineTransform& transform); + ~ScopedTransform(); + + Pimpl& pimpl; + SavedState* state = nullptr; + }; + + uint64_t frame = 0; + float scale = 1.0f; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DGraphicsContext) }; } // namespace juce diff --git a/modules/juce_graphics/native/juce_Direct2DHelpers_windows.cpp b/modules/juce_graphics/native/juce_Direct2DHelpers_windows.cpp new file mode 100644 index 0000000000..91a570e4ef --- /dev/null +++ b/modules/juce_graphics/native/juce_Direct2DHelpers_windows.cpp @@ -0,0 +1,443 @@ +/* + ============================================================================== + + 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 +{ + +class ScopedMultithread +{ +public: + explicit ScopedMultithread (ID2D1Multithread* multithreadIn) + : multithread (addComSmartPtrOwner (multithreadIn)) + { + multithreadIn->Enter(); + } + + ~ScopedMultithread() + { + multithread->Leave(); + } + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScopedMultithread) + + ComSmartPtr multithread; +}; + +/* ScopedGeometryWithSink creates an ID2D1PathGeometry object with an open sink. */ +struct ScopedGeometryWithSink +{ + ScopedGeometryWithSink (ID2D1Factory* factory, D2D1_FILL_MODE fillMode) + { + if (const auto hr = factory->CreatePathGeometry (geometry.resetAndGetPointerAddress()); FAILED (hr)) + return; + + if (const auto hr = geometry->Open (sink.resetAndGetPointerAddress()); FAILED (hr)) + return; + + sink->SetFillMode (fillMode); + } + + ~ScopedGeometryWithSink() + { + if (sink == nullptr) + return; + + const auto hr = sink->Close(); + jassertquiet (SUCCEEDED (hr)); + } + + ComSmartPtr geometry; + ComSmartPtr sink; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScopedGeometryWithSink) +}; + +class WindowsScopedEvent +{ +public: + explicit WindowsScopedEvent (HANDLE handleIn) + : handle (handleIn) + { + } + + WindowsScopedEvent() + : WindowsScopedEvent (CreateEvent (nullptr, FALSE, FALSE, nullptr)) + { + } + + HANDLE getHandle() const noexcept + { + return handle.get(); + } + +private: + std::unique_ptr, FunctionPointerDestructor> handle; +}; + +//============================================================================== +struct D2DHelpers +{ + static bool isTransformAxisAligned (const AffineTransform& transform) + { + return transform.mat01 == 0.0f && transform.mat10 == 0.0f; + } + + static void pathToGeometrySink (const Path& path, ID2D1GeometrySink* sink, const AffineTransform& transform, D2D1_FIGURE_BEGIN figureMode) + { + // Every call to BeginFigure must have a matching call to EndFigure. But - the Path does not necessarily + // have matching startNewSubPath and closePath markers. The figureStarted flag indicates if an extra call + // to BeginFigure or EndFigure is needed during the iteration loop or when exiting this function. + Path::Iterator it (path); + bool figureStarted = false; + + while (it.next()) + { + switch (it.elementType) + { + case Path::Iterator::cubicTo: + { + jassert (figureStarted); + + transform.transformPoint (it.x1, it.y1); + transform.transformPoint (it.x2, it.y2); + transform.transformPoint (it.x3, it.y3); + + sink->AddBezier ({ { it.x1, it.y1 }, { it.x2, it.y2 }, { it.x3, it.y3 } }); + break; + } + + case Path::Iterator::lineTo: + { + jassert (figureStarted); + + transform.transformPoint (it.x1, it.y1); + sink->AddLine ({ it.x1, it.y1 }); + break; + } + + case Path::Iterator::quadraticTo: + { + jassert (figureStarted); + + transform.transformPoint (it.x1, it.y1); + transform.transformPoint (it.x2, it.y2); + sink->AddQuadraticBezier ({ { it.x1, it.y1 }, { it.x2, it.y2 } }); + break; + } + + case Path::Iterator::closePath: + { + if (figureStarted) + { + sink->EndFigure (D2D1_FIGURE_END_CLOSED); + figureStarted = false; + } + break; + } + + case Path::Iterator::startNewSubPath: + { + if (figureStarted) + { + sink->EndFigure (D2D1_FIGURE_END_OPEN); + } + + transform.transformPoint (it.x1, it.y1); + sink->BeginFigure ({ it.x1, it.y1 }, figureMode); + + figureStarted = true; + break; + } + } + } + + if (figureStarted) + { + sink->EndFigure (D2D1_FIGURE_END_OPEN); + } + } + + static D2D1_POINT_2F pointTransformed (Point pt, const AffineTransform& transform) + { + transform.transformPoint (pt.x, pt.y); + return { (FLOAT) pt.x, (FLOAT) pt.y }; + } + + static void rectToGeometrySink (const Rectangle& rect, + ID2D1GeometrySink* sink, + const AffineTransform& transform, + D2D1_FIGURE_BEGIN figureMode) + { + const auto a = pointTransformed (rect.getTopLeft(), transform); + const auto b = pointTransformed (rect.getTopRight(), transform); + const auto c = pointTransformed (rect.getBottomRight(), transform); + const auto d = pointTransformed (rect.getBottomLeft(), transform); + + sink->BeginFigure (a, figureMode); + sink->AddLine (b); + sink->AddLine (c); + sink->AddLine (d); + sink->EndFigure (D2D1_FIGURE_END_CLOSED); + } + + static ComSmartPtr rectListToPathGeometry (ID2D1Factory* factory, + const RectangleList& clipRegion, + const AffineTransform& transform, + D2D1_FILL_MODE fillMode, + D2D1_FIGURE_BEGIN figureMode, + [[maybe_unused]] Direct2DMetrics* metrics) + { + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, createGeometryTime) + ScopedGeometryWithSink objects { factory, fillMode }; + + if (objects.sink == nullptr) + return {}; + + for (int i = clipRegion.getNumRectangles(); --i >= 0;) + rectToGeometrySink (clipRegion.getRectangle (i), objects.sink, transform, figureMode); + + return objects.geometry; + } + + static ComSmartPtr pathToPathGeometry (ID2D1Factory* factory, + const Path& path, + const AffineTransform& transform, + D2D1_FIGURE_BEGIN figureMode, + [[maybe_unused]] Direct2DMetrics* metrics) + { + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, createGeometryTime) + ScopedGeometryWithSink objects { factory, path.isUsingNonZeroWinding() ? D2D1_FILL_MODE_WINDING : D2D1_FILL_MODE_ALTERNATE }; + + if (objects.sink == nullptr) + return {}; + + pathToGeometrySink (path, objects.sink, transform, figureMode); + + return objects.geometry; + } + + static ComSmartPtr pathStrokeTypeToStrokeStyle (ID2D1Factory1* factory, const PathStrokeType& strokeType) + { + // JUCE JointStyle ID2D1StrokeStyle + // --------------- ---------------- + // mitered D2D1_LINE_JOIN_MITER + // curved D2D1_LINE_JOIN_ROUND + // beveled D2D1_LINE_JOIN_BEVEL + // + // JUCE EndCapStyle ID2D1StrokeStyle + // ---------------- ---------------- + // butt D2D1_CAP_STYLE_FLAT + // square D2D1_CAP_STYLE_SQUARE + // rounded D2D1_CAP_STYLE_ROUND + auto lineJoin = D2D1_LINE_JOIN_MITER; + switch (strokeType.getJointStyle()) + { + case PathStrokeType::JointStyle::mitered: + // already set + break; + + case PathStrokeType::JointStyle::curved: + lineJoin = D2D1_LINE_JOIN_ROUND; + break; + + case PathStrokeType::JointStyle::beveled: + lineJoin = D2D1_LINE_JOIN_BEVEL; + break; + + default: + // invalid EndCapStyle + jassertfalse; + break; + } + + auto capStyle = D2D1_CAP_STYLE_FLAT; + switch (strokeType.getEndStyle()) + { + case PathStrokeType::EndCapStyle::butt: + // already set + break; + + case PathStrokeType::EndCapStyle::square: + capStyle = D2D1_CAP_STYLE_SQUARE; + break; + + case PathStrokeType::EndCapStyle::rounded: + capStyle = D2D1_CAP_STYLE_ROUND; + break; + + default: + // invalid EndCapStyle + jassertfalse; + break; + } + + D2D1_STROKE_STYLE_PROPERTIES1 strokeStyleProperties + { + capStyle, + capStyle, + capStyle, + lineJoin, + strokeType.getStrokeThickness(), + D2D1_DASH_STYLE_SOLID, + 0.0f, + D2D1_STROKE_TRANSFORM_TYPE_NORMAL + }; + ComSmartPtr strokeStyle; + factory->CreateStrokeStyle (strokeStyleProperties, + nullptr, + 0, + strokeStyle.resetAndGetPointerAddress()); + + return strokeStyle; + } + +}; + +//============================================================================== +/* UpdateRegion extracts the invalid region for a window + UpdateRegion is used to service WM_PAINT to add the invalid region of a window to + deferredRepaints. UpdateRegion marks the region as valid, and the region should be painted on the + next vblank. + This is similar to the invalid region update in HWNDComponentPeer::handlePaintMessage() +*/ +class UpdateRegion +{ +public: + ~UpdateRegion() + { + clear(); + } + + void findRECTAndValidate (HWND windowHandle) + { + numRect = 0; + + auto regionHandle = CreateRectRgn (0, 0, 0, 0); + + if (regionHandle) + { + auto regionType = GetUpdateRgn (windowHandle, regionHandle, false); + + if (regionType == SIMPLEREGION || regionType == COMPLEXREGION) + { + auto regionDataBytes = GetRegionData (regionHandle, (DWORD) block.getSize(), (RGNDATA*) block.getData()); + + if (regionDataBytes > block.getSize()) + { + block.ensureSize (regionDataBytes); + regionDataBytes = GetRegionData (regionHandle, (DWORD) block.getSize(), (RGNDATA*) block.getData()); + } + + if (regionDataBytes > 0) + { + auto header = (RGNDATAHEADER const* const) block.getData(); + + if (header->iType == RDH_RECTANGLES) + numRect = header->nCount; + } + } + + if (numRect > 0) + ValidateRgn (windowHandle, regionHandle); + else + ValidateRect (windowHandle, nullptr); + + DeleteObject (regionHandle); + regionHandle = nullptr; + + return; + } + + ValidateRect (windowHandle, nullptr); + } + + void clear() + { + numRect = 0; + } + + uint32 getNumRECT() const + { + return numRect; + } + + RECT* getRECTArray() + { + auto header = (RGNDATAHEADER const* const) block.getData(); + return (RECT*) (header + 1); + } + + static void forwardInvalidRegionToParent (HWND childHwnd) + { + auto regionHandle = CreateRectRgn (0, 0, 0, 0); + + if (regionHandle) + { + GetUpdateRgn (childHwnd, regionHandle, false); + ValidateRgn (childHwnd, regionHandle); + InvalidateRgn (GetParent (childHwnd), regionHandle, FALSE); + DeleteObject (regionHandle); + } + } + +private: + MemoryBlock block { 1024 }; + uint32 numRect = 0; +}; + +//============================================================================== +/** Heap storage for a DirectWrite glyph run */ +class DirectWriteGlyphRun +{ +public: + void replace (Span> positions, float scale) + { + advances.resize (positions.size(), 0.0f); + offsets.resize (positions.size()); + std::transform (positions.begin(), positions.end(), offsets.begin(), [&] (auto& g) + { + return DWRITE_GLYPH_OFFSET { g.x / scale, -g.y }; + }); + } + + auto* getAdvances() const { return advances.data(); } + auto* getOffsets() const { return offsets .data(); } + +private: + std::vector advances; + std::vector offsets; +}; + +} // namespace juce diff --git a/modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp b/modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp new file mode 100644 index 0000000000..30804a10c6 --- /dev/null +++ b/modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp @@ -0,0 +1,720 @@ +/* + ============================================================================== + + 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 +{ + +//============================================================================== +class Presentation +{ +public: + SLIST_ENTRY& getListEntry() { return listEntry; } + + auto getPresentationBitmap() const + { + jassert (presentationBitmap != nullptr); + return presentationBitmap; + } + + auto getPresentationBitmap (const Rectangle& swapSize, ComSmartPtr context) + { + if (presentationBitmap != nullptr) + { + const auto size = presentationBitmap->GetPixelSize(); + + if (size.width != (uint32) swapSize.getWidth() || size.height != (uint32) swapSize.getHeight()) + presentationBitmap = nullptr; + } + + if (presentationBitmap == nullptr) + { + presentationBitmap = Direct2DBitmap::createBitmap (context, + Image::ARGB, + { (uint32) swapSize.getWidth(), (uint32) swapSize.getHeight() }, + swapSize.getWidth() * 4, + D2D1_BITMAP_OPTIONS_TARGET); + } + + return presentationBitmap; + } + + void setPaintAreas (RectangleList areas) + { + paintAreas = std::move (areas); + } + + auto getPaintAreas() const + { + return paintAreas; + } + + void setResult (HRESULT x) + { + hr = x; + } + + auto getResult() const + { + return hr; + } + +private: + JUCE_ALIGN (MEMORY_ALLOCATION_ALIGNMENT) + SLIST_ENTRY listEntry; + ComSmartPtr presentationBitmap; + RectangleList paintAreas; + HRESULT hr = S_OK; +}; + +struct Direct2DHwndContext::HwndPimpl : public Direct2DGraphicsContext::Pimpl +{ +private: + struct SwapChainThread + { + SwapChainThread (Direct2DHwndContext::HwndPimpl& ownerIn, + ComSmartPtr multithreadIn) + : owner (ownerIn), + multithread (multithreadIn), + swapChainEventHandle (ownerIn.swap.swapChainEvent->getHandle()) + { + InitializeSListHead (&paintedPresentations); + InitializeSListHead (&retiredPresentations); + + for (auto& p : presentations) + InterlockedPushEntrySList (&retiredPresentations, &p.getListEntry()); + } + + ~SwapChainThread() + { + SetEvent (quitEvent.getHandle()); + thread.join(); + + InterlockedFlushSList (&paintedPresentations); + InterlockedFlushSList (&retiredPresentations); + } + + Presentation* getFreshPresentation() + { + if (auto listEntry = InterlockedPopEntrySList (&retiredPresentations)) + return reinterpret_cast (listEntry); + + return nullptr; + } + + void pushPaintedPresentation (Presentation* presentationIn) + { + InterlockedPushEntrySList (&paintedPresentations, &presentationIn->getListEntry()); + SetEvent (wakeEvent.getHandle()); + } + + void retirePresentation (Presentation* presentationIn) + { + InterlockedPushEntrySList (&retiredPresentations, &presentationIn->getListEntry()); + } + + void notify() + { + SetEvent (wakeEvent.getHandle()); + } + + private: + void serviceSwapChain() + { + if (swapChainReady) + { + if (auto listEntry = InterlockedPopEntrySList (&paintedPresentations)) + { + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (owner.owner.metrics, swapChainThreadTime); + + auto filledPresentation = reinterpret_cast (listEntry); + + { + ScopedMultithread scopedMultithread { multithread }; + owner.present (filledPresentation, 0); + } + + swapChainReady = false; + InterlockedPushEntrySList (&retiredPresentations, listEntry); + } + } + } + + JUCE_ALIGN (MEMORY_ALLOCATION_ALIGNMENT) + SLIST_HEADER paintedPresentations; + JUCE_ALIGN (MEMORY_ALLOCATION_ALIGNMENT) + SLIST_HEADER retiredPresentations; + Direct2DHwndContext::HwndPimpl& owner; + ComSmartPtr multithread; + HANDLE swapChainEventHandle = nullptr; + bool swapChainReady = false; + std::vector presentations = std::vector (2); + + WindowsScopedEvent wakeEvent; + WindowsScopedEvent quitEvent; + std::thread thread { [&] { threadLoop(); } }; + + void threadLoop() + { + Thread::setCurrentThreadName ("swapChainThread"); + + for (;;) + { + const HANDLE handles[] { swapChainEventHandle, quitEvent.getHandle(), wakeEvent.getHandle() }; + + const auto waitResult = WaitForMultipleObjects ((DWORD) std::size (handles), handles, FALSE, INFINITE); + + switch (waitResult) + { + case WAIT_OBJECT_0: + { + swapChainReady = true; + serviceSwapChain(); + break; + } + + case WAIT_OBJECT_0 + 1: + return; + + case WAIT_OBJECT_0 + 2: + { + serviceSwapChain(); + break; + } + + case WAIT_FAILED: + default: + jassertfalse; + break; + } + } + } + }; + + SwapChain swap; + std::unique_ptr swapChainThread; + Presentation* presentation = nullptr; + CompositionTree compositionTree; + UpdateRegion updateRegion; + RectangleList deferredRepaints; + Rectangle frameSize; + std::vector dirtyRectangles; + bool resizing = false; + int64 lastFinishFrameTicks = 0; + + HWND hwnd = nullptr; + + HRESULT prepare() override + { + if (! adapter || ! adapter->direct2DDevice) + { + adapter = directX->adapters.getAdapterForHwnd (hwnd); + + if (! adapter) + return E_FAIL; + } + + if (! deviceResources.canPaint (adapter)) + { + if (auto hr = deviceResources.create (adapter); FAILED (hr)) + return hr; + } + + if (! hwnd || frameSize.isEmpty()) + return E_FAIL; + + if (! swap.canPaint()) + { + if (auto hr = swap.create (hwnd, frameSize, adapter); FAILED (hr)) + return hr; + + if (auto hr = swap.createBuffer (deviceResources.deviceContext.context); FAILED (hr)) + return hr; + } + + if (! swapChainThread) + { + if (swap.swapChainEvent.has_value()) + swapChainThread = std::make_unique (*this, directX->getD2DMultithread()); + } + + if (! compositionTree.canPaint()) + { + if (auto hr = compositionTree.create (adapter->dxgiDevice, hwnd, swap.chain); FAILED (hr)) + return hr; + } + + return S_OK; + } + + void teardown() override + { + compositionTree.release(); + swapChainThread = nullptr; + swap.release(); + + Pimpl::teardown(); + } + + void updatePaintAreas() override + { + // Does the entire buffer need to be filled? + if (swap.state == SwapChain::State::bufferAllocated || resizing) + deferredRepaints = swap.getSize(); + + // If the window alpha is less than 1.0, clip to the union of the + // deferred repaints so the device context Clear() works correctly + if (targetAlpha < 1.0f || ! opaque) + paintAreas = deferredRepaints.getBounds(); + else + paintAreas = deferredRepaints; + } + + bool checkPaintReady() override + { + // Try not to saturate the message thread; this is a little crude. Perhaps some kind of credit system... + if (auto now = Time::getHighResolutionTicks(); Time::highResolutionTicksToSeconds (now - lastFinishFrameTicks) < 0.001) + return false; + + if (! presentation) + { + presentation = swapChainThread->getFreshPresentation(); + + if (presentation && FAILED (presentation->getResult())) + teardown(); + } + + // Paint if: + // resources are allocated + // deferredRepaints has areas to be painted + // the swap chain thread is ready + bool ready = Pimpl::checkPaintReady(); + ready &= swap.canPaint(); + ready &= compositionTree.canPaint(); + ready &= deferredRepaints.getNumRectangles() > 0 || resizing; + ready &= presentation != nullptr; + return ready; + } + + JUCE_DECLARE_WEAK_REFERENCEABLE (HwndPimpl) + +public: + HwndPimpl (Direct2DHwndContext& ownerIn, HWND hwndIn, bool opaqueIn) + : Pimpl (ownerIn, opaqueIn), + hwnd (hwndIn) + { + adapter = directX->adapters.getAdapterForHwnd (hwndIn); + } + + ~HwndPimpl() override = default; + + HWND getHwnd() const { return hwnd; } + + void handleShowWindow() + { + // One of the trickier problems was determining when Direct2D & DXGI resources can be safely created; + // that's not really spelled out in the documentation. + // This method is called when the component peer receives WM_SHOWWINDOW + prepare(); + + frameSize = getClientRect(); + deferredRepaints = frameSize; + } + + Rectangle getClientRect() const + { + RECT clientRect; + GetClientRect (hwnd, &clientRect); + + return Rectangle::leftTopRightBottom (clientRect.left, clientRect.top, clientRect.right, clientRect.bottom); + } + + Rectangle getFrameSize() override + { + return getClientRect(); + } + + ComSmartPtr getDeviceContextTarget() const override + { + if (presentation != nullptr) + return presentation->getPresentationBitmap (swap.getSize(), deviceResources.deviceContext.context); + + return {}; + } + + void startResizing() + { + resizing = true; + } + + void finishResizing() + { + resizing = false; + } + + void setSize (Rectangle size) + { + if (size == frameSize) + return; + + resizeSwapChain (size); + } + + void resizeSwapChain (Rectangle size) + { + if (size.isEmpty()) + return; + + // Require the entire window to be repainted + frameSize = size; + deferredRepaints = size; + InvalidateRect (hwnd, nullptr, TRUE); + + // Resize/scale the swap chain + prepare(); + + if (auto deviceContext = deviceResources.deviceContext.context) + { + ScopedMultithread scopedMultithread { directX->getD2DMultithread() }; + + auto hr = swap.resize (size, (float) owner.getPhysicalPixelScaleFactor(), deviceContext); + jassert (SUCCEEDED (hr)); + if (FAILED (hr)) + teardown(); + + if (swapChainThread) + swapChainThread->notify(); + } + + clearWindowRedirectionBitmap(); + } + + void addDeferredRepaint (Rectangle deferredRepaint) + { + deferredRepaints.add (deferredRepaint); + + JUCE_TRACE_EVENT_INT_RECT (etw::repaint, etw::paintKeyword, snappedRectangle); + } + + void addInvalidWindowRegionToDeferredRepaints() + { + updateRegion.findRECTAndValidate (hwnd); + + // Call addDeferredRepaint for each RECT in the update region to make + // sure they are snapped properly for DPI scaling + auto rectArray = updateRegion.getRECTArray(); + + for (uint32 i = 0; i < updateRegion.getNumRECT(); ++i) + addDeferredRepaint (D2DUtilities::toRectangle (rectArray[i])); + + updateRegion.clear(); + } + + void clearWindowRedirectionBitmap() + { + if (! opaque && swap.state == SwapChain::State::bufferAllocated) + { + deviceResources.deviceContext.createHwndRenderTarget (hwnd); + + // Clear the GDI redirection bitmap using a Direct2D 1.0 render target + auto& hwndRenderTarget = deviceResources.deviceContext.hwndRenderTarget; + + if (hwndRenderTarget) + { + const auto colorF = D2DUtilities::toCOLOR_F (getBackgroundTransparencyKeyColour()); + + RECT clientRect; + GetClientRect (hwnd, &clientRect); + + D2D1_SIZE_U size { (uint32) (clientRect.right - clientRect.left), (uint32) (clientRect.bottom - clientRect.top) }; + hwndRenderTarget->Resize (size); + hwndRenderTarget->BeginDraw(); + hwndRenderTarget->Clear (colorF); + hwndRenderTarget->EndDraw(); + } + } + } + + SavedState* startFrame() override + { + if (resizing) + { + deferredRepaints = frameSize; + setSize (getClientRect()); + } + + auto savedState = Pimpl::startFrame(); + + // If a new frame is starting, clear deferredAreas in case repaint is called + // while the frame is being painted to ensure the new areas are painted on the + // next frame + if (savedState) + { + JUCE_TRACE_LOG_D2D_PAINT_CALL (etw::direct2dHwndPaintStart, owner.getFrameId()); + + presentation->setPaintAreas (paintAreas); + + deferredRepaints.clear(); + } + + return savedState; + } + + HRESULT finishFrame() override + { + const ScopeGuard scope { [this] + { + presentation = nullptr; + lastFinishFrameTicks = Time::getHighResolutionTicks(); + } }; + + if (auto hr = Pimpl::finishFrame(); FAILED (hr)) + return hr; + + if (resizing) + { + present (presentation, 0); + swapChainThread->retirePresentation (presentation); + } + else + { + swapChainThread->pushPaintedPresentation (presentation); + } + + return S_OK; + } + + void present (Presentation* paintedPresentation, uint32 flags) + { + if (paintedPresentation == nullptr) + return; + + // Fill out the array of dirty rectangles + // Compare paintAreas to the swap chain buffer area. If the rectangles in paintAreas are contained + // by the swap chain buffer area, then mark those rectangles as dirty. DXGI will only keep the dirty rectangles from the + // current buffer and copy the clean area from the previous buffer. + // The buffer needs to be completely filled before using dirty rectangles. The dirty rectangles need to be contained + // within the swap chain buffer. + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (owner.metrics, present1Duration); + + // Allocate enough memory for the array of dirty rectangles + const auto areas = paintedPresentation->getPaintAreas(); + paintedPresentation->setPaintAreas ({}); + + dirtyRectangles.resize ((size_t) areas.getNumRectangles()); + + // Fill the array of dirty rectangles, intersecting each paint area with the swap chain buffer + DXGI_PRESENT_PARAMETERS presentParameters{}; + + if (swap.state == SwapChain::State::bufferFilled) + { + auto* dirtyRectangle = dirtyRectangles.data(); + auto const swapChainSize = swap.getSize(); + + for (const auto& area : areas) + { + // If this paint area contains the entire swap chain, then + // no need for dirty rectangles + if (area.contains (swapChainSize)) + { + presentParameters.DirtyRectsCount = 0; + break; + } + + // Intersect this paint area with the swap chain buffer + auto intersection = area.getIntersection (swapChainSize); + + if (intersection.isEmpty()) + { + // Can't clip to an empty rectangle + continue; + } + + D2D1_POINT_2U destPoint { (uint32) intersection.getX(), (uint32) intersection.getY() }; + D2D1_RECT_U sourceRect { (uint32) intersection.getX(), + (uint32) intersection.getY(), + (uint32) intersection.getRight(), + (uint32) intersection.getBottom() }; + swap.buffer->CopyFromBitmap (&destPoint, paintedPresentation->getPresentationBitmap(), &sourceRect); + + // Add this intersected paint area to the dirty rectangle array (scaled for DPI) + *dirtyRectangle = D2DUtilities::toRECT (intersection); + + dirtyRectangle++; + presentParameters.DirtyRectsCount++; + } + + presentParameters.pDirtyRects = dirtyRectangles.data(); + } + + if (presentParameters.DirtyRectsCount == 0) + { + D2D1_POINT_2U destPoint { 0, 0 }; + swap.buffer->CopyFromBitmap (&destPoint, paintedPresentation->getPresentationBitmap(), nullptr); + } + + // Present the freshly painted buffer + const auto hr = swap.chain->Present1 (swap.presentSyncInterval, swap.presentFlags | flags, &presentParameters); + jassert (SUCCEEDED (hr)); + paintedPresentation->setResult (hr); + + // The buffer is now completely filled and ready for dirty rectangles for the next frame + swap.state = SwapChain::State::bufferFilled; + + JUCE_TRACE_LOG_D2D_PAINT_CALL (etw::direct2dHwndPaintEnd, owner.getFrameId()); + } + + Image createSnapshot() const + { + if (frameSize.isEmpty() || deviceResources.deviceContext.context == nullptr || swap.buffer == nullptr) + return {}; + + // Create the bitmap to receive the snapshot + D2D1_BITMAP_PROPERTIES1 bitmapProperties{}; + bitmapProperties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET; + bitmapProperties.dpiX = bitmapProperties.dpiY = USER_DEFAULT_SCREEN_DPI * owner.getPhysicalPixelScaleFactor(); + bitmapProperties.pixelFormat = swap.buffer->GetPixelFormat(); + + const D2D_SIZE_U size { (UINT32) frameSize.getWidth(), (UINT32) frameSize.getHeight() }; + + ComSmartPtr snapshot; + + if (const auto hr = deviceResources.deviceContext.context->CreateBitmap (size, nullptr, 0, bitmapProperties, snapshot.resetAndGetPointerAddress()); FAILED (hr)) + return {}; + + swap.chain->Present (0, DXGI_PRESENT_DO_NOT_WAIT); + + // Copy the swap chain buffer to the bitmap snapshot + D2D_POINT_2U p { 0, 0 }; + const auto sourceRect = D2DUtilities::toRECT_U (frameSize); + + if (const auto hr = snapshot->CopyFromBitmap (&p, swap.buffer, &sourceRect); FAILED (hr)) + return {}; + + auto pixelData = Direct2DPixelData::fromDirect2DBitmap (snapshot); + Image result { pixelData }; + + swap.chain->Present (0, DXGI_PRESENT_DO_NOT_WAIT); + + return result; + } +}; + +//============================================================================== +Direct2DHwndContext::Direct2DHwndContext (void* windowHandle, bool opaque) +{ + #if JUCE_DIRECT2D_METRICS + metrics = new Direct2DMetrics { Direct2DMetricsHub::getInstance()->lock, + "HWND " + String::toHexString ((pointer_sized_int) windowHandle), + windowHandle }; + Direct2DMetricsHub::getInstance()->add (metrics); + #endif + + pimpl = std::make_unique (*this, reinterpret_cast (windowHandle), opaque); + updateSize(); +} + +Direct2DHwndContext::~Direct2DHwndContext() +{ + #if JUCE_DIRECT2D_METRICS + Direct2DMetricsHub::getInstance()->remove (metrics); + #endif +} + +void* Direct2DHwndContext::getHwnd() const noexcept +{ + return pimpl->getHwnd(); +} + +Direct2DGraphicsContext::Pimpl* Direct2DHwndContext::getPimpl() const noexcept +{ + return pimpl.get(); +} + +void Direct2DHwndContext::handleShowWindow() +{ + pimpl->handleShowWindow(); +} + +void Direct2DHwndContext::setWindowAlpha (float alpha) +{ + pimpl->setTargetAlpha (alpha); +} + +void Direct2DHwndContext::startResizing() +{ + pimpl->startResizing(); +} + +void Direct2DHwndContext::finishResizing() +{ + pimpl->startResizing(); +} + +void Direct2DHwndContext::setSize (int width, int height) +{ + pimpl->setSize ({ width, height }); +} + +void Direct2DHwndContext::updateSize() +{ + pimpl->setSize (pimpl->getClientRect()); +} + +void Direct2DHwndContext::addDeferredRepaint (Rectangle deferredRepaint) +{ + pimpl->addDeferredRepaint (deferredRepaint); +} + +void Direct2DHwndContext::addInvalidWindowRegionToDeferredRepaints() +{ + pimpl->addInvalidWindowRegionToDeferredRepaints(); +} + +Image Direct2DHwndContext::createSnapshot() const +{ + return pimpl->createSnapshot(); +} + +void Direct2DHwndContext::clearTargetBuffer() +{ + // For opaque windows, clear the background to black with the window alpha + // For non-opaque windows, clear the background to transparent black + // In either case, add a transparency layer if the window alpha is less than 1.0 + pimpl->getDeviceContext()->Clear (pimpl->backgroundColor); + + if (pimpl->targetAlpha < 1.0f) + beginTransparencyLayer (pimpl->targetAlpha); +} + +} // namespace juce diff --git a/modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h b/modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h new file mode 100644 index 0000000000..5461236bf6 --- /dev/null +++ b/modules/juce_graphics/native/juce_Direct2DHwndContext_windows.h @@ -0,0 +1,73 @@ +/* + ============================================================================== + + 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 +{ + +class Direct2DHwndContext : public Direct2DGraphicsContext +{ +public: + Direct2DHwndContext (void* windowHandle, bool opaque); + ~Direct2DHwndContext() override; + + void* getHwnd() const noexcept; + void handleShowWindow(); + void setWindowAlpha (float alpha); + + void startResizing(); + void finishResizing(); + void setSize (int width, int height); + void updateSize(); + + void addDeferredRepaint (Rectangle deferredRepaint); + void addInvalidWindowRegionToDeferredRepaints(); + + Image createSnapshot() const override; + + static Colour getBackgroundTransparencyKeyColour() noexcept + { + return Colour { 0xff000001 }; + } + +private: + struct HwndPimpl; + std::unique_ptr pimpl; + + Pimpl* getPimpl() const noexcept override; + void clearTargetBuffer() override; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DHwndContext) +}; + +} // namespace juce diff --git a/modules/juce_graphics/native/juce_Direct2DImageContext_windows.cpp b/modules/juce_graphics/native/juce_Direct2DImageContext_windows.cpp new file mode 100644 index 0000000000..a23f2f73a1 --- /dev/null +++ b/modules/juce_graphics/native/juce_Direct2DImageContext_windows.cpp @@ -0,0 +1,124 @@ +/* + ============================================================================== + + 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 Direct2DImageContext::ImagePimpl : public Direct2DGraphicsContext::Pimpl +{ +public: + static constexpr auto opaque = false; + + ImagePimpl (Direct2DImageContext& ownerIn, Direct2DPixelData::Ptr targetIn) + : Pimpl (ownerIn, opaque), + target (targetIn) + { + if (target != nullptr) + adapter = target->getAdapter(); + else + jassertfalse; + } + + Rectangle getFrameSize() override + { + const auto targetBitmap = getBitmap(); + + if (targetBitmap == nullptr) + return {}; + + auto size = targetBitmap->GetSize(); + return Rectangle { size.width, size.height }.getSmallestIntegerContainer(); + } + + ComSmartPtr getDeviceContextTarget() const override + { + return getBitmap(); + } + + HRESULT finishFrame() override + { + const auto result = Pimpl::finishFrame(); + + if (target != nullptr) + target->flushToSoftwareBackup(); + + return result; + } + +private: + ComSmartPtr getBitmap() const + { + if (target == nullptr) + return {}; + + return target->getAdapterD2D1Bitmap(); + } + + void updatePaintAreas() override + { + paintAreas = getFrameSize(); + } + + Direct2DPixelData::Ptr target; + + JUCE_DECLARE_WEAK_REFERENCEABLE (ImagePimpl) +}; + +//============================================================================== +Direct2DImageContext::Direct2DImageContext (Direct2DPixelData::Ptr targetIn) + : pimpl (new ImagePimpl { *this, targetIn }) +{ + #if JUCE_DIRECT2D_METRICS + metrics = Direct2DMetricsHub::getInstance()->imageContextMetrics; + #endif + + startFrame(); +} + +Direct2DImageContext::~Direct2DImageContext() +{ + endFrame(); +} + +Direct2DGraphicsContext::Pimpl* Direct2DImageContext::getPimpl() const noexcept +{ + return pimpl.get(); +} + +void Direct2DImageContext::clearTargetBuffer() +{ + // The bitmap was already cleared when it was created; do nothing here +} + +} // namespace juce diff --git a/modules/juce_graphics/native/juce_Direct2DImageContext_windows.h b/modules/juce_graphics/native/juce_Direct2DImageContext_windows.h new file mode 100644 index 0000000000..abe5027ea4 --- /dev/null +++ b/modules/juce_graphics/native/juce_Direct2DImageContext_windows.h @@ -0,0 +1,55 @@ +/* + ============================================================================== + + 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 +{ + +class Direct2DImageContext : public Direct2DGraphicsContext +{ +public: + explicit Direct2DImageContext (Direct2DPixelData::Ptr); + + ~Direct2DImageContext() override; + +private: + struct ImagePimpl; + std::unique_ptr pimpl; + + Pimpl* getPimpl() const noexcept override; + void clearTargetBuffer() override; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DImageContext) +}; + +} // namespace juce diff --git a/modules/juce_graphics/native/juce_Direct2DImage_windows.cpp b/modules/juce_graphics/native/juce_Direct2DImage_windows.cpp new file mode 100644 index 0000000000..c4cb8438e9 --- /dev/null +++ b/modules/juce_graphics/native/juce_Direct2DImage_windows.cpp @@ -0,0 +1,654 @@ +/* + ============================================================================== + + 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 +{ + +class NativeReadOnlyDataReleaser : public Image::BitmapData::BitmapDataReleaser +{ +public: + NativeReadOnlyDataReleaser (Image::PixelFormat pixelFormat, + int lineStride, + int w, + int h, + ComSmartPtr deviceContextIn, + ComSmartPtr sourceBitmap, + Point offset) + : bitmap (Direct2DBitmap::createBitmap (deviceContextIn, + pixelFormat, + { (UINT32) w, (UINT32) h }, + lineStride, + D2D1_BITMAP_OPTIONS_CPU_READ | D2D1_BITMAP_OPTIONS_CANNOT_DRAW)) + { + const D2D1_POINT_2U destPoint { 0, 0 }; + const Rectangle fullRect { w, h }; + const auto sourceRect = D2DUtilities::toRECT_U (fullRect.getIntersection (fullRect.withPosition (offset))); + + if (auto hr = bitmap->CopyFromBitmap (&destPoint, sourceBitmap, &sourceRect); FAILED (hr)) + return; + + D2D1_MAPPED_RECT mappedRect{}; + bitmap->Map (D2D1_MAP_OPTIONS_READ, &mappedRect); + data = mappedRect.bits; + pitch = mappedRect.pitch; + } + + ~NativeReadOnlyDataReleaser() override + { + bitmap->Unmap(); + } + + auto getData() const + { + return data; + } + + auto getPitch() const + { + return pitch; + } + +private: + ComSmartPtr bitmap; + BYTE* data = nullptr; + UINT32 pitch = 0; +}; + +class SoftwareDataReleaser : public Image::BitmapData::BitmapDataReleaser +{ +public: + SoftwareDataReleaser (std::unique_ptr r, + Image backupIn, + ComSmartPtr nativeBitmapIn, + Image::BitmapData::ReadWriteMode modeIn, + D2D1_RECT_U targetRectIn) + : oldReleaser (std::move (r)), + backup (std::move (backupIn)), + nativeBitmap (nativeBitmapIn), + targetRect (targetRectIn), + mode (modeIn) + { + } + + static void flushImage (Image softwareImage, ComSmartPtr native, D2D1_RECT_U target) + { + if (softwareImage.getFormat() == Image::PixelFormat::RGB) + softwareImage = softwareImage.convertedToFormat (Image::PixelFormat::ARGB); + + const Image::BitmapData bitmapData { softwareImage, + (int) target.left, + (int) target.top, + (int) (target.right - target.left), + (int) (target.bottom - target.top), + Image::BitmapData::readOnly }; + const auto hr = native->CopyFromMemory (&target, bitmapData.data, (UINT32) bitmapData.lineStride); + jassertquiet (SUCCEEDED (hr)); + } + + ~SoftwareDataReleaser() override + { + // Ensure that writes to the backup bitmap have been flushed before reading from it + oldReleaser = nullptr; + + if (mode != Image::BitmapData::ReadWriteMode::readOnly) + flushImage (backup, nativeBitmap, targetRect); + } + +private: + std::unique_ptr oldReleaser; + Image backup; + ComSmartPtr nativeBitmap; + D2D1_RECT_U targetRect{}; + Image::BitmapData::ReadWriteMode mode{}; +}; + +ComSmartPtr Direct2DPixelData::createAdapterBitmap() const +{ + auto bitmap = Direct2DBitmap::createBitmap (context, + pixelFormat, + { (UINT32) width, (UINT32) height }, + getLineStride(), + D2D1_BITMAP_OPTIONS_TARGET); + + // The bitmap may be slightly too large due + // to DPI scaling, so fill it with transparent black + if (bitmap == nullptr || ! clearImage) + return bitmap; + + context->SetTarget (bitmap); + context->BeginDraw(); + context->Clear(); + context->EndDraw(); + context->SetTarget (nullptr); + + return bitmap; +} + +void Direct2DPixelData::createDeviceResources() +{ + if (adapter == nullptr) + adapter = directX->adapters.getDefaultAdapter(); + + if (context == nullptr) + context = Direct2DDeviceContext::createContext (adapter); + + if (nativeBitmap == nullptr) + { + nativeBitmap = createAdapterBitmap(); + + if (backup.isValid()) + SoftwareDataReleaser::flushImage (backup, nativeBitmap, { 0, 0, (UINT32) width, (UINT32) height }); + } +} + +void Direct2DPixelData::initBitmapDataReadOnly (Image::BitmapData& bitmap, int x, int y) +{ + const auto pixelStride = getPixelStride(); + const auto lineStride = getLineStride(); + + const auto offset = (size_t) x * (size_t) pixelStride + (size_t) y * (size_t) lineStride; + bitmap.pixelFormat = pixelFormat; + bitmap.pixelStride = pixelStride; + bitmap.lineStride = lineStride; + bitmap.size = (size_t) (height * lineStride) - offset; + + JUCE_TRACE_LOG_D2D_IMAGE_MAP_DATA; + + auto releaser = std::make_unique (pixelFormat, + lineStride, + width, + height, + context, + getAdapterD2D1Bitmap(), + Point { x, y }); + bitmap.data = releaser->getData(); + bitmap.lineStride = (int) releaser->getPitch(); + bitmap.dataReleaser = std::move (releaser); +} + +auto Direct2DPixelData::make (Image::PixelFormat formatToUse, + int widthIn, + int heightIn, + bool clearImageIn, + DxgiAdapter::Ptr adapterIn) -> Ptr +{ + return new Direct2DPixelData (formatToUse, widthIn, heightIn, clearImageIn, adapterIn); +} + +auto Direct2DPixelData::fromDirect2DBitmap (ComSmartPtr bitmap) -> Ptr +{ + const auto size = bitmap->GetPixelSize(); + return new Direct2DPixelData { Image::ARGB, (int) size.width, (int) size.height, false, nullptr }; +} + +Direct2DPixelData::Direct2DPixelData (Image::PixelFormat f, int widthIn, int heightIn, bool clear, DxgiAdapter::Ptr adapterIn) + : ImagePixelData (f, widthIn, heightIn), + clearImage (clear), + adapter (adapterIn != nullptr ? adapterIn : directX->adapters.getDefaultAdapter()) +{ + directX->adapters.addListener (*this); +} + +Direct2DPixelData::~Direct2DPixelData() +{ + directX->adapters.removeListener (*this); +} + +std::unique_ptr Direct2DPixelData::createLowLevelContext() +{ + sendDataChangeMessage(); + + return std::make_unique (this); +} + +void Direct2DPixelData::initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) +{ + JUCE_TRACE_LOG_D2D_IMAGE_MAP_DATA; + + // The native format matches the JUCE format, and there's no need to write from CPU->GPU, so + // map the GPU memory as read-only and return that. + if (mode == Image::BitmapData::ReadWriteMode::readOnly && pixelFormat != Image::PixelFormat::RGB) + { + initBitmapDataReadOnly (bitmap, x, y); + return; + } + + // The native format does not match the JUCE format, or the user wants to read the current state of the image. + // If the user wants to read the image, then we'll need to copy it to CPU memory. + if (mode != Image::BitmapData::ReadWriteMode::writeOnly) + { + // Store the previous width and height, and set up the BitmapData to cover the entire image area + const auto oldW = std::exchange (bitmap.width, width); + const auto oldH = std::exchange (bitmap.height, height); + + // Map the image as read-only. + initBitmapDataReadOnly (bitmap, 0, 0); + // Copy the mapped image to CPU memory in the correct format. + backup = BitmapDataDetail::convert (bitmap, SoftwareImageType{}); + // Unmap the image (important, the BitmapData is reused later on). + bitmap.dataReleaser = {}; + + // Reset the initial width and height + bitmap.width = oldW; + bitmap.height = oldH; + } + + // If the user doesn't want to read from the image, then we may need to create a blank image that they can write to. + if (! backup.isValid()) + backup = Image { SoftwareImageType{}.create (pixelFormat, width, height, false) }; + + // Redirect the BitmapData to our backup software image. + backup.getPixelData()->initialiseBitmapData (bitmap, x, y, mode); + + // When this dataReleaser is destroyed, then if the mode is not read-only, image data will be copied + // from the software image to GPU memory. + bitmap.dataReleaser = std::make_unique (std::move (bitmap.dataReleaser), + backup, + getAdapterD2D1Bitmap(), + mode, + D2D1_RECT_U { (UINT32) x, (UINT32) y, (UINT32) width, (UINT32) height }); +} + +void Direct2DPixelData::flushToSoftwareBackup() +{ + backup = SoftwareImageType{}.convert (Image { this }); +} + +ImagePixelData::Ptr Direct2DPixelData::clone() +{ + auto cloned = make (pixelFormat, width, height, false, nullptr); + + if (cloned == nullptr) + return {}; + + cloned->backup = backup.createCopy(); + + const D2D1_POINT_2U destinationPoint { 0, 0 }; + const auto sourceRectU = D2DUtilities::toRECT_U (Rectangle { width, height }); + const auto sourceD2D1Bitmap = getAdapterD2D1Bitmap(); + const auto destinationD2D1Bitmap = cloned->getAdapterD2D1Bitmap(); + + if (sourceD2D1Bitmap == nullptr || destinationD2D1Bitmap == nullptr) + return {}; + + if (const auto hr = destinationD2D1Bitmap->CopyFromBitmap (&destinationPoint, sourceD2D1Bitmap, &sourceRectU); FAILED (hr)) + { + jassertfalse; + return {}; + } + + return cloned; +} + +void Direct2DPixelData::applyGaussianBlurEffect (float radius, Image& result) +{ + // The result must be a separate image! + jassert (result.getPixelData() != this); + + if (context == nullptr) + { + result = {}; + return; + } + + ComSmartPtr effect; + if (const auto hr = context->CreateEffect (CLSID_D2D1GaussianBlur, effect.resetAndGetPointerAddress()); + FAILED (hr) || effect == nullptr) + { + result = {}; + return; + } + + effect->SetInput (0, getAdapterD2D1Bitmap()); + effect->SetValue (D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, radius / 3.0f); + + const auto tie = [] (const auto& x) { return std::tuple (x.pixelFormat, x.width, x.height); }; + const auto originalPixelData = dynamic_cast (result.getPixelData()); + + if (originalPixelData == nullptr || tie (*this) != tie (*originalPixelData)) + result = Image { make (pixelFormat, width, height, false, adapter) }; + + const auto outputPixelData = dynamic_cast (result.getPixelData()); + + if (outputPixelData == nullptr) + { + result = {}; + return; + } + + outputPixelData->createDeviceResources(); + auto outputDataContext = outputPixelData->context; + + if (outputDataContext == nullptr) + { + result = {}; + return; + } + + outputDataContext->SetTarget (outputPixelData->getAdapterD2D1Bitmap()); + outputDataContext->BeginDraw(); + outputDataContext->Clear(); + outputDataContext->DrawImage (effect); + outputDataContext->EndDraw(); + outputDataContext->SetTarget (nullptr); +} + +std::unique_ptr Direct2DPixelData::createType() const +{ + return std::make_unique(); +} + +void Direct2DPixelData::adapterCreated (DxgiAdapter::Ptr) +{ +} + +void Direct2DPixelData::adapterRemoved (DxgiAdapter::Ptr) +{ + adapter = nullptr; + context = nullptr; + nativeBitmap = nullptr; +} + +ComSmartPtr Direct2DPixelData::getAdapterD2D1Bitmap() +{ + createDeviceResources(); + return nativeBitmap; +} + +//============================================================================== +ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const +{ + SharedResourcePointer directX; + + if (directX->adapters.getFactory() == nullptr) + { + // Make sure the DXGI factory exists + // + // The caller may be trying to create an Image from a static variable; if this is a DLL, then this is + // probably called from DllMain. You can't create a DXGI factory from DllMain, so fall back to a + // software image. + return new SoftwarePixelData { format, width, height, clearImage }; + } + + return Direct2DPixelData::make (format, width, height, clearImage, nullptr); +} + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class Direct2DImageUnitTest final : public UnitTest +{ +public: + Direct2DImageUnitTest() + : UnitTest ("Direct2DImageUnitTest", UnitTestCategories::graphics) + { + compareFunctions[{ Image::RGB, Image::RGB }] = [] (uint8* rgb1, uint8* rgb2) + { + return rgb1[0] == rgb2[0] && rgb1[1] == rgb2[1] && rgb1[2] == rgb2[2]; + }; + + compareFunctions[{ Image::RGB, Image::ARGB }] = [] (uint8* rgb, uint8* argb) + { + // Compare bytes directly to avoid alpha premultiply issues + return rgb[0] == argb[0] && // blue + rgb[1] == argb[1] && // green + rgb[2] == argb[2]; // red + }; + + compareFunctions[{ Image::RGB, Image::SingleChannel }] = [] (uint8*, uint8* singleChannel) + { + return *singleChannel == 0xff; + }; + + compareFunctions[{ Image::ARGB, Image::RGB }] = [] (uint8* argb, uint8* rgb) + { + // Compare bytes directly to avoid alpha premultiply issues + return argb[0] == rgb[0] && argb[1] == rgb[1] && argb[2] == rgb[2]; + }; + + compareFunctions[{ Image::ARGB, Image::ARGB }] = [] (uint8* argb1, uint8* argb2) + { + return *reinterpret_cast (argb1) == *reinterpret_cast (argb2); + }; + + compareFunctions[{ Image::ARGB, Image::SingleChannel }] = [] (uint8* argb, uint8* singleChannel) + { + return argb[3] = *singleChannel; + }; + + compareFunctions[{ Image::SingleChannel, Image::RGB }] = [] (uint8* singleChannel, uint8* rgb) + { + auto alpha = *singleChannel; + return rgb[0] == alpha && rgb[1] == alpha && rgb[2] == alpha; + }; + + compareFunctions[{ Image::SingleChannel, Image::ARGB }] = [] (uint8* singleChannel, uint8* argb) + { + return *singleChannel = argb[3]; + }; + + compareFunctions[{ Image::SingleChannel, Image::SingleChannel }] = [] (uint8* singleChannel1, uint8* singleChannel2) + { + return *singleChannel1 == *singleChannel2; + }; + } + + void runTest() override + { + beginTest ("Direct2DImageUnitTest"); + + random = getRandom(); + + for (auto format : formats) + compareSameFormat (format); + + testFormatConversion(); + } + + Rectangle randomRectangleWithin (Rectangle container) noexcept + { + auto x = random.nextInt (container.getWidth() - 2); + auto y = random.nextInt (container.getHeight() - 2); + auto w = random.nextInt (container.getHeight() - x); + auto h = random.nextInt (container.getWidth() - y); + h = jmax (h, 1); + w = jmax (w, 1); + return Rectangle { x, y, w, h }; + } + + void compareSameFormat (Image::PixelFormat format) + { + auto softwareImage = Image { SoftwareImageType{}.create (format, 100, 100, true) }; + { + Graphics g { softwareImage }; + g.fillCheckerBoard (softwareImage.getBounds().toFloat(), 21.0f, 21.0f, makeRandomColor(), makeRandomColor()); + } + + auto direct2DImage = NativeImageType{}.convert (softwareImage); + + compareImages (softwareImage, direct2DImage, compareFunctions[{ softwareImage.getFormat(), direct2DImage.getFormat() }]); + checkReadWriteModes (softwareImage); + checkReadWriteModes (direct2DImage); + } + + void compareImages (Image& image1, Image& image2, std::function compareBytes) + { + { + // BitmapData width & height should match + Rectangle area = randomRectangleWithin (image1.getBounds()); + Image::BitmapData data1 { image1, area.getX(), area.getY(), area.getWidth(), area.getHeight(), Image::BitmapData::ReadWriteMode::readOnly }; + Image::BitmapData data2 { image2, area.getX(), area.getY(), area.getWidth(), area.getHeight(), Image::BitmapData::ReadWriteMode::readOnly }; + + expect (data1.width == data2.width); + expect (data1.height == data2.height); + } + + { + // Bitmap data should match after ImageType::convert + Image::BitmapData data1 { image1, Image::BitmapData::ReadWriteMode::readOnly }; + Image::BitmapData data2 { image2, Image::BitmapData::ReadWriteMode::readOnly }; + + for (int y = 0; y < data1.height; ++y) + { + auto line1 = data1.getLinePointer (y); + auto line2 = data2.getLinePointer (y); + + for (int x = 0; x < data1.width; ++x) + { + expect (compareBytes (line1, line2), "Failed comparing format " + String { image1.getFormat() } + " to " + String { image2.getFormat() }); + + line1 += data1.pixelStride; + line2 += data2.pixelStride; + } + } + } + + { + // Subsection data should match + // Should be able to have two different BitmapData objects simultaneously for the same source image + Rectangle area1 = randomRectangleWithin (image1.getBounds()); + Rectangle area2 = randomRectangleWithin (image1.getBounds()); + Image::BitmapData data1 { image1, Image::BitmapData::ReadWriteMode::readOnly }; + Image::BitmapData data2a { image2, area1.getX(), area1.getY(), area1.getWidth(), area1.getHeight(), Image::BitmapData::ReadWriteMode::readOnly }; + Image::BitmapData data2b { image2, area2.getX(), area2.getY(), area2.getWidth(), area2.getHeight(), Image::BitmapData::ReadWriteMode::readOnly }; + + auto compareSubsection = [&] (Image::BitmapData& subsection1, Image::BitmapData& subsection2, Rectangle area) + { + for (int y = 0; y < area.getHeight(); ++y) + { + auto line1 = subsection1.getLinePointer (y + area.getY()); + auto line2 = subsection2.getLinePointer (y); + + for (int x = 0; x < area.getWidth(); ++x) + { + expect (compareBytes (line1 + (x + area.getX()) * subsection1.pixelStride, line2 + x * subsection2.pixelStride)); + } + } + }; + + compareSubsection (data1, data2a, area1); + compareSubsection (data1, data2b, area2); + } + } + + void checkReadWriteModes (Image& image) + { + // Check read and write modes + int x = random.nextInt (image.getWidth()); + auto writeColor = makeRandomColor().withAlpha (1.0f); + auto expectedColor = writeColor; + switch (image.getFormat()) + { + case Image::SingleChannel: + { + auto alpha = writeColor.getAlpha(); + expectedColor = Colour { alpha, alpha, alpha, alpha }; + break; + } + + case Image::RGB: + case Image::ARGB: + break; + + case Image::UnknownFormat: + default: + jassertfalse; + break; + } + + { + Image::BitmapData data { image, Image::BitmapData::ReadWriteMode::writeOnly }; + + for (int y = 0; y < data.height; ++y) + data.setPixelColour (x, y, writeColor); + } + + { + Image::BitmapData data { image, Image::BitmapData::ReadWriteMode::readOnly }; + + for (int y = 0; y < data.height; ++y) + { + auto color = data.getPixelColour (x, y); + expect (color == expectedColor); + } + } + } + + void testFormatConversion() + { + for (auto sourceFormat : formats) + { + for (auto destFormat : formats) + { + auto softwareStartImage = Image { SoftwareImageType {}.create (sourceFormat, 100, 100, true) }; + { + Graphics g { softwareStartImage }; + g.fillCheckerBoard (softwareStartImage.getBounds().toFloat(), 21.0f, 21.0f, makeRandomColor(), makeRandomColor()); + } + auto convertedSoftwareImage = softwareStartImage.convertedToFormat (destFormat); + + compareImages (softwareStartImage, convertedSoftwareImage, compareFunctions[{ sourceFormat, destFormat }]); + + auto direct2DImage = NativeImageType {}.convert (softwareStartImage); + + compareImages (softwareStartImage, direct2DImage, compareFunctions[{ sourceFormat, sourceFormat }]); + + auto convertedDirect2DImage = direct2DImage.convertedToFormat (destFormat); + + compareImages (softwareStartImage, convertedDirect2DImage, compareFunctions[{ sourceFormat, destFormat }]); + } + } + } + + Colour makeRandomColor() + { + uint8 red = (uint8) random.nextInt (255); + uint8 green = (uint8) random.nextInt (255); + uint8 blue = (uint8) random.nextInt (255); + uint8 alpha = (uint8) random.nextInt (255); + return Colour { red, green, blue, alpha }; + } + + + Random random; + std::array const formats { Image::RGB, Image::ARGB, Image::SingleChannel }; + std::map, std::function> compareFunctions; +}; + +static Direct2DImageUnitTest direct2DImageUnitTest; + +#endif + +} // namespace juce diff --git a/modules/juce_graphics/native/juce_Direct2DImage_windows.h b/modules/juce_graphics/native/juce_Direct2DImage_windows.h new file mode 100644 index 0000000000..cad495de55 --- /dev/null +++ b/modules/juce_graphics/native/juce_Direct2DImage_windows.h @@ -0,0 +1,93 @@ +/* + ============================================================================== + + 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 +{ + +class Direct2DPixelData : public ImagePixelData, + private DxgiAdapterListener +{ +public: + using Ptr = ReferenceCountedObjectPtr; + + static Ptr make (Image::PixelFormat formatToUse, + int w, + int h, + bool clearImageIn, + DxgiAdapter::Ptr adapterIn); + + static Ptr fromDirect2DBitmap (ComSmartPtr bitmap); + + ~Direct2DPixelData() override; + + std::unique_ptr createLowLevelContext() override; + + void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override; + + ImagePixelData::Ptr clone() override; + + void applyGaussianBlurEffect (float radius, Image& result) override; + + std::unique_ptr createType() const override; + + DxgiAdapter::Ptr getAdapter() const { return adapter; } + ComSmartPtr getAdapterD2D1Bitmap(); + + void flushToSoftwareBackup(); + +private: + Direct2DPixelData (Image::PixelFormat, int, int, bool, DxgiAdapter::Ptr); + + int getPixelStride() const { return pixelFormat == Image::SingleChannel ? 1 : 4; } + int getLineStride() const { return (getPixelStride() * jmax (1, width) + 3) & ~3; } + + void adapterCreated (DxgiAdapter::Ptr) override; + void adapterRemoved (DxgiAdapter::Ptr) override; + + void initBitmapDataReadOnly (Image::BitmapData&, int, int); + + ComSmartPtr createAdapterBitmap() const; + void createDeviceResources(); + + SharedResourcePointer directX; + const bool clearImage; + Image backup; + DxgiAdapter::Ptr adapter; + ComSmartPtr context; + ComSmartPtr nativeBitmap; + + JUCE_LEAK_DETECTOR (Direct2DPixelData) +}; + +} // namespace juce diff --git a/modules/juce_graphics/native/juce_Direct2DMetrics_windows.cpp b/modules/juce_graphics/native/juce_Direct2DMetrics_windows.cpp new file mode 100644 index 0000000000..d0ae2b9d49 --- /dev/null +++ b/modules/juce_graphics/native/juce_Direct2DMetrics_windows.cpp @@ -0,0 +1,132 @@ +/* + ============================================================================== + + 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. + + ============================================================================== +*/ + +#if JUCE_DIRECT2D_METRICS + +namespace juce +{ + +String Direct2DMetricsHub::getProcessString() noexcept +{ + auto processID = GetCurrentProcessId(); + return String::toHexString ((pointer_sized_int) processID); +} + +void Direct2DMetricsHub::HubPipeServer::messageReceived (const MemoryBlock& message) +{ + int requestType = *(int*) message.getData(); + switch (requestType) + { + case getValuesRequest: + { + ScopedLock locker { owner.lock }; + + auto foregroundWindow = GetForegroundWindow(); + Direct2DMetrics::Ptr metrics = nullptr; + for (int i = 0; i < owner.metricsArray.size(); ++i) + { + auto arrayEntry = owner.metricsArray[i]; + if (arrayEntry->windowHandle && arrayEntry->windowHandle == foregroundWindow) + { + metrics = arrayEntry; + break; + } + } + + if (! metrics) + { + if (owner.lastMetrics && owner.metricsArray.contains (owner.lastMetrics)) + metrics = owner.lastMetrics; + } + + if (metrics) + { + MemoryBlock block { sizeof (GetValuesResponse), true }; + + auto* response = (GetValuesResponse*) block.getData(); + response->responseType = getValuesRequest; + response->windowHandle = metrics->windowHandle; + + for (size_t i = 0; i <= Direct2DMetrics::drawGlyphRunTime; ++i) + { + auto& accumulator = metrics->getAccumulator (i); + response->values[i].count = accumulator.getCount(); + response->values[i].total = metrics->getSum (i); + response->values[i].average = accumulator.getAverage(); + response->values[i].minimum = accumulator.getMinValue(); + response->values[i].maximum = accumulator.getMaxValue(); + response->values[i].stdDev = accumulator.getStandardDeviation(); + } + + // Track bitmap operations common to all device contexts + for (size_t i = Direct2DMetrics::createBitmapTime; i <= Direct2DMetrics::unmapBitmapTime; ++i) + { + auto& accumulator = owner.imageContextMetrics->getAccumulator (i); + response->values[i].count = accumulator.getCount(); + response->values[i].total = metrics->getSum (i); + response->values[i].average = accumulator.getAverage(); + response->values[i].minimum = accumulator.getMinValue(); + response->values[i].maximum = accumulator.getMaxValue(); + response->values[i].stdDev = accumulator.getStandardDeviation(); + } + + sendMessage (block); + + owner.lastMetrics = metrics.get(); + } + break; + } + + case resetValuesRequest: + { + owner.resetAll(); + break; + } + } +} + +void Direct2DMetricsHub::resetAll() +{ + ScopedLock locker { lock }; + + imageContextMetrics->reset(); + for (auto metrics : metricsArray) + { + metrics->reset(); + } +} + +} // namespace juce + +#endif diff --git a/modules/juce_graphics/native/juce_Direct2DMetrics_windows.h b/modules/juce_graphics/native/juce_Direct2DMetrics_windows.h new file mode 100644 index 0000000000..db4d4011ba --- /dev/null +++ b/modules/juce_graphics/native/juce_Direct2DMetrics_windows.h @@ -0,0 +1,320 @@ +/* + ============================================================================== + + 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. + + ============================================================================== +*/ + +#pragma once + +#if JUCE_DIRECT2D_METRICS + +namespace juce +{ + +struct Direct2DMetrics : public ReferenceCountedObject +{ + using Ptr = ReferenceCountedObjectPtr; + +#define DIRECT2D_PAINT_STAT_LIST \ + DIRECT2D_PAINT_STAT (messageThreadPaintDuration) \ + DIRECT2D_PAINT_STAT (swapChainThreadTime) \ + DIRECT2D_PAINT_STAT (frameInterval) \ + DIRECT2D_PAINT_STAT (endDrawDuration) \ + DIRECT2D_PAINT_STAT (present1Duration) \ + DIRECT2D_PAINT_STAT (createGeometryTime) \ + DIRECT2D_PAINT_STAT (drawGeometryTime) \ + DIRECT2D_PAINT_STAT (fillGeometryTime) \ + DIRECT2D_PAINT_STAT (createFilledGRTime) \ + DIRECT2D_PAINT_STAT (createStrokedGRTime) \ + DIRECT2D_PAINT_STAT (drawGRTime) \ + DIRECT2D_PAINT_STAT (createGradientTime) \ + DIRECT2D_PAINT_STAT (pushAliasedAxisAlignedLayerTime) \ + DIRECT2D_PAINT_STAT (pushGeometryLayerTime) \ + DIRECT2D_PAINT_STAT (fillTranslatedRectTime) \ + DIRECT2D_PAINT_STAT (fillAxisAlignedRectTime) \ + DIRECT2D_PAINT_STAT (fillTransformedRectTime) \ + DIRECT2D_PAINT_STAT (fillRectListTime) \ + DIRECT2D_PAINT_STAT (drawImageTime) \ + DIRECT2D_PAINT_STAT (spriteBatchTime) \ + DIRECT2D_PAINT_STAT (spriteBatchSetupTime) \ + DIRECT2D_PAINT_STAT (createSpriteSourceTime) \ + DIRECT2D_PAINT_STAT (setSpritesTime) \ + DIRECT2D_PAINT_STAT (addSpritesTime) \ + DIRECT2D_PAINT_STAT (clearSpritesTime) \ + DIRECT2D_PAINT_STAT (drawSpritesTime) \ + DIRECT2D_PAINT_STAT (drawGlyphRunTime) \ + DIRECT2D_PAINT_STAT (createBitmapTime) \ + DIRECT2D_PAINT_STAT (mapBitmapTime) \ + DIRECT2D_LAST_PAINT_STAT (unmapBitmapTime) + +#define DIRECT2D_PAINT_STAT(name) name, +#define DIRECT2D_LAST_PAINT_STAT(name) name + enum + { + DIRECT2D_PAINT_STAT_LIST, + numStats + }; +#undef DIRECT2D_PAINT_STAT +#undef DIRECT2D_LAST_PAINT_STAT + +#define DIRECT2D_PAINT_STAT(name) #name, +#define DIRECT2D_LAST_PAINT_STAT(name) #name + StringArray const accumulatorNames { DIRECT2D_PAINT_STAT_LIST }; +#undef DIRECT2D_PAINT_STAT +#undef DIRECT2D_LAST_PAINT_STAT + + CriticalSection& lock; + String const name; + void* const windowHandle; + int64 const creationTime = Time::getMillisecondCounter(); + double const millisecondsPerTick = 1000.0 / (double) Time::getHighResolutionTicksPerSecond(); + int paintCount = 0; + int presentCount = 0; + int present1Count = 0; + int64 lastPaintStartTicks = 0; + uint64 lockAcquireMaxTicks = 0; + + Direct2DMetrics (CriticalSection& lockIn, String nameIn, void* windowHandleIn) + : lock (lockIn), + name (nameIn), + windowHandle (windowHandleIn) + { + } + + ~Direct2DMetrics() = default; + + void startFrame() + { + ScopedLock locker { lock }; + zerostruct (sums); + } + + void finishFrame() + { + } + + void reset() + { + ScopedLock locker { lock }; + + for (auto& accumulator : runningAccumulators) + accumulator.reset(); + + lastPaintStartTicks = 0; + paintCount = 0; + present1Count = 0; + lockAcquireMaxTicks = 0; + } + + auto& getAccumulator (size_t index) noexcept + { + return runningAccumulators[index]; + } + + auto getSum (size_t index) const noexcept + { + return sums[index]; + } + + void addValueTicks (size_t index, int64 ticks) + { + addValueMsec (index, Time::highResolutionTicksToSeconds (ticks) * 1000.0); + } + + void addValueMsec (size_t index, double value) + { + ScopedLock locker { lock }; + + auto& accumulator = runningAccumulators[index]; + + switch (index) + { + case frameInterval: + if (accumulator.getCount() > 100) + { + accumulator.reset(); + } + break; + } + accumulator.addValue (value); + + sums[index] += value; + } + +private: + std::array, numStats> runningAccumulators; + std::array sums; +}; + +struct Direct2DScopedElapsedTime +{ + Direct2DScopedElapsedTime (Direct2DMetrics::Ptr& metricsIn, size_t accumulatorIndexIn) + : metrics (metricsIn.get()), + accumulatorIndex (accumulatorIndexIn) + { + } + + Direct2DScopedElapsedTime (Direct2DMetrics* metricsIn, size_t accumulatorIndexIn) + : metrics (metricsIn), + accumulatorIndex (accumulatorIndexIn) + { + } + + ~Direct2DScopedElapsedTime() + { + auto finishTicks = Time::getHighResolutionTicks(); + metrics->addValueTicks (accumulatorIndex, finishTicks - startTicks); + } + + int64 startTicks = Time::getHighResolutionTicks(); + Direct2DMetrics* metrics; + size_t accumulatorIndex; +}; + +class Direct2DMetricsHub : public DeletedAtShutdown +{ +public: + Direct2DMetricsHub() + { + imageContextMetrics = new Direct2DMetrics { lock, "Image " + getProcessString(), nullptr }; + add (imageContextMetrics); + } + + ~Direct2DMetricsHub() override + { + clearSingletonInstance(); + } + + void add (Direct2DMetrics::Ptr metrics) + { + metricsArray.insert (0, metrics); + } + + void remove (Direct2DMetrics::Ptr metrics) + { + metricsArray.removeObject (metrics); + } + + Direct2DMetrics::Ptr getMetricsForWindowHandle (void* windowHandle) noexcept + { + for (auto& metrics : metricsArray) + if (metrics->windowHandle == windowHandle) + return metrics; + + return nullptr; + } + + enum + { + getValuesRequest, + resetValuesRequest + }; + + struct MetricValues + { + size_t count; + double total; + double average; + double minimum; + double maximum; + double stdDev; + }; + + struct GetValuesResponse + { + int responseType; + void* windowHandle; + MetricValues values[Direct2DMetrics::numStats]; + }; + + CriticalSection lock; + Direct2DMetrics::Ptr imageContextMetrics; + + static constexpr int magicNumber = 0xd2d1; + + JUCE_DECLARE_SINGLETON (Direct2DMetricsHub, false) + +private: + static String getProcessString() noexcept; + + void resetAll(); + + struct HubPipeServer : public InterprocessConnection + { + explicit HubPipeServer (Direct2DMetricsHub& ownerIn) + : InterprocessConnection (false, magicNumber), + owner (ownerIn) + { + createPipe ("JUCEDirect2DMetricsHub:" + owner.getProcessString(), -1, true); + } + + ~HubPipeServer() override + { + disconnect(); + } + + void connectionMade() override + { + } + + void connectionLost() override + { + } + + void messageReceived (const MemoryBlock& message) override; + + Direct2DMetricsHub& owner; + }; + + HubPipeServer hubPipeServer { *this }; + ReferenceCountedArray metricsArray; + Direct2DMetrics* lastMetrics = nullptr; +}; + +} // namespace juce + +#define JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME(metrics, name) juce::Direct2DScopedElapsedTime scopedElapsedTime_##name { metrics, juce::Direct2DMetrics::name }; + +#else + +namespace juce +{ + +struct Direct2DMetrics : public ReferenceCountedObject +{ + using Ptr = ReferenceCountedObjectPtr; +}; + +} // namespace juce + +#define JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME(metrics, name) + +#endif diff --git a/modules/juce_graphics/native/juce_Direct2DResources_windows.cpp b/modules/juce_graphics/native/juce_Direct2DResources_windows.cpp new file mode 100644 index 0000000000..8616e910f7 --- /dev/null +++ b/modules/juce_graphics/native/juce_Direct2DResources_windows.cpp @@ -0,0 +1,670 @@ +/* + ============================================================================== + + 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 Direct2DDeviceContext +{ + HRESULT createHwndRenderTarget (HWND hwnd) + { + if (hwndRenderTarget != nullptr) + return S_OK; + + SharedResourcePointer directX; + + D2D1_SIZE_U size { 1, 1 }; + + D2D1_RENDER_TARGET_PROPERTIES renderTargetProps{}; + renderTargetProps.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; + renderTargetProps.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM; + + D2D1_HWND_RENDER_TARGET_PROPERTIES hwndRenderTargetProps{}; + hwndRenderTargetProps.hwnd = hwnd; + hwndRenderTargetProps.pixelSize = size; + hwndRenderTargetProps.presentOptions = D2D1_PRESENT_OPTIONS_IMMEDIATELY | D2D1_PRESENT_OPTIONS_RETAIN_CONTENTS; + return directX->getD2DFactory()->CreateHwndRenderTarget (&renderTargetProps, + &hwndRenderTargetProps, + hwndRenderTarget.resetAndGetPointerAddress()); + } + + void resetTransform() + { + context->SetTransform (D2D1::IdentityMatrix()); + } + + void setTransform (AffineTransform newTransform) + { + context->SetTransform (D2DUtilities::transformToMatrix (newTransform)); + } + + void release() + { + hwndRenderTarget = nullptr; + context = nullptr; + } + + static ComSmartPtr createContext (DxgiAdapter::Ptr adapter) + { + ComSmartPtr result; + + if (const auto hr = adapter->direct2DDevice->CreateDeviceContext (D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS, + result.resetAndGetPointerAddress()); + FAILED (hr)) + { + jassertfalse; + return {}; + } + + result->SetTextAntialiasMode (D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); + result->SetAntialiasMode (D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + result->SetUnitMode (D2D1_UNIT_MODE_PIXELS); + + return result; + } + + ComSmartPtr context; + ComSmartPtr hwndRenderTarget; +}; + +class Direct2DBitmap final +{ +public: + static ComSmartPtr fromImage (const Image& image, + ComSmartPtr deviceContext, + Image::PixelFormat outputFormat) + { + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (Direct2DMetricsHub::getInstance()->imageContextMetrics, createBitmapTime); + + jassert (outputFormat == Image::ARGB || outputFormat == Image::SingleChannel); + + JUCE_TRACE_LOG_D2D_PAINT_CALL (etw::createDirect2DBitmapFromImage, etw::graphicsKeyword); + + // Calling Image::convertedToFormat could cause unchecked recursion since convertedToFormat + // calls Graphics::drawImageAt which calls Direct2DGraphicsContext::drawImage which calls this function... + // + // Use a software image for the conversion instead so the Graphics::drawImageAt call doesn't go + // through the Direct2D renderer + // + // Be sure to explicitly set the DPI to 96.0 for the image; otherwise it will default to the screen DPI + // and may be scaled incorrectly + const auto convertedImage = SoftwareImageType{}.convert (image).convertedToFormat (outputFormat); + + if (! convertedImage.isValid()) + return {}; + + Image::BitmapData bitmapData { convertedImage, Image::BitmapData::readWrite }; + + D2D1_BITMAP_PROPERTIES1 bitmapProperties{}; + bitmapProperties.pixelFormat.format = outputFormat == Image::SingleChannel + ? DXGI_FORMAT_A8_UNORM + : DXGI_FORMAT_B8G8R8A8_UNORM; + bitmapProperties.pixelFormat.alphaMode = outputFormat == Image::RGB + ? D2D1_ALPHA_MODE_IGNORE + : D2D1_ALPHA_MODE_PREMULTIPLIED; + bitmapProperties.dpiX = USER_DEFAULT_SCREEN_DPI; + bitmapProperties.dpiY = USER_DEFAULT_SCREEN_DPI; + + const D2D1_SIZE_U size { (UINT32) image.getWidth(), (UINT32) image.getHeight() }; + + ComSmartPtr bitmap; + deviceContext->CreateBitmap (size, + bitmapData.data, + (UINT32) bitmapData.lineStride, + bitmapProperties, + bitmap.resetAndGetPointerAddress()); + return bitmap; + } + + static ComSmartPtr createBitmap (ComSmartPtr deviceContext, + Image::PixelFormat format, + D2D_SIZE_U size, + int lineStride, + D2D1_BITMAP_OPTIONS options) + { + JUCE_TRACE_LOG_D2D_PAINT_CALL (etw::createDirect2DBitmap, etw::graphicsKeyword); + + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (Direct2DMetricsHub::getInstance()->imageContextMetrics, createBitmapTime); + + #if JUCE_DEBUG + // Verify that the GPU can handle a bitmap of this size + // + // If you need a bitmap larger than this, you'll need to either split it up into multiple bitmaps + // or use a software image (see SoftwareImageType). + auto maxBitmapSize = deviceContext->GetMaximumBitmapSize(); + jassert (size.width <= maxBitmapSize && size.height <= maxBitmapSize); + #endif + + D2D1_BITMAP_PROPERTIES1 bitmapProperties{}; + bitmapProperties.dpiX = bitmapProperties.dpiY = USER_DEFAULT_SCREEN_DPI; + bitmapProperties.pixelFormat.format = format == Image::SingleChannel + ? DXGI_FORMAT_A8_UNORM + : DXGI_FORMAT_B8G8R8A8_UNORM; + bitmapProperties.pixelFormat.alphaMode = format == Image::RGB + ? D2D1_ALPHA_MODE_IGNORE + : D2D1_ALPHA_MODE_PREMULTIPLIED; + bitmapProperties.bitmapOptions = options; + + ComSmartPtr bitmap; + deviceContext->CreateBitmap (size, + nullptr, + (UINT32) lineStride, + bitmapProperties, + bitmap.resetAndGetPointerAddress()); + return bitmap; + } + + Direct2DBitmap() = delete; +}; + +static ComSmartPtr makeGradientStopCollection (const ColourGradient& gradient, + ComSmartPtr deviceContext, + [[maybe_unused]] Direct2DMetrics* metrics) noexcept +{ + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, createGradientTime); + + const int numColors = gradient.getNumColours(); + + std::vector stops ((size_t) numColors); + + for (auto [index, stop] : enumerate (stops, int{})) + { + stop.color = D2DUtilities::toCOLOR_F (gradient.getColour (index)); + stop.position = (FLOAT) gradient.getColourPosition (index); + } + + ComSmartPtr result; + deviceContext->CreateGradientStopCollection (stops.data(), (UINT32) stops.size(), result.resetAndGetPointerAddress()); + return result; +} + +class LinearGradientCache +{ +public: + ComSmartPtr get (const ColourGradient& gradient, + ComSmartPtr deviceContext, + Direct2DMetrics* metrics) + { + jassert (! gradient.isRadial); + + return cache.get (gradient, [&deviceContext, &metrics] (const auto& key) + { + const auto gradientStops = makeGradientStopCollection (key, deviceContext, metrics); + const auto p1 = key.point1; + const auto p2 = key.point2; + const auto linearGradientBrushProperties = D2D1::LinearGradientBrushProperties ({ p1.x, p1.y }, { p2.x, p2.y }); + const D2D1_BRUSH_PROPERTIES brushProps { 1.0f, D2D1::IdentityMatrix() }; + + ComSmartPtr result; + deviceContext->CreateLinearGradientBrush (linearGradientBrushProperties, + brushProps, + gradientStops, + result.resetAndGetPointerAddress()); + return result; + }); + } + +private: + LruCache> cache; +}; + +class RadialGradientCache +{ +public: + ComSmartPtr get (const ColourGradient& gradient, + ComSmartPtr deviceContext, + Direct2DMetrics* metrics) + { + jassert (gradient.isRadial); + + return cache.get (gradient, [&deviceContext, &metrics] (const auto& key) + { + const auto gradientStops = makeGradientStopCollection (key, deviceContext, metrics); + + const auto p1 = key.point1; + const auto p2 = key.point2; + const auto r = p1.getDistanceFrom (p2); + const auto radialGradientBrushProperties = D2D1::RadialGradientBrushProperties ({ p1.x, p1.y }, {}, r, r); + const D2D1_BRUSH_PROPERTIES brushProps { 1.0F, D2D1::IdentityMatrix() }; + + ComSmartPtr result; + deviceContext->CreateRadialGradientBrush (radialGradientBrushProperties, + brushProps, + gradientStops, + result.resetAndGetPointerAddress()); + return result; + }); + } + +private: + LruCache> cache; +}; + +class RectangleListSpriteBatch +{ +public: + RectangleListSpriteBatch() = default; + + ~RectangleListSpriteBatch() + { + release(); + } + + void release() + { + whiteRectangle = nullptr; + spriteBatches = {}; + destinations.free(); + destinationsCapacity = 0; + } + + template + void fillRectangles (ComSmartPtr deviceContext, + const RectangleList& rectangles, + const Colour colour, + TransformRectangle&& transformRectangle, + [[maybe_unused]] Direct2DMetrics* metrics) + { + if (rectangles.isEmpty()) + return; + + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, spriteBatchTime) + + auto numRectanglesPainted = 0; + while (numRectanglesPainted < rectangles.getNumRectangles()) + { + auto numRectanglesRemaining = rectangles.getNumRectangles() - numRectanglesPainted; + auto spriteBatchSize = isPowerOfTwo (numRectanglesRemaining) ? numRectanglesRemaining : (nextPowerOfTwo (numRectanglesRemaining) >> 1); + + { + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, spriteBatchSetupTime); + + if (destinationsCapacity < (size_t) spriteBatchSize) + { + destinations.calloc (spriteBatchSize); + destinationsCapacity = (size_t) spriteBatchSize; + } + + auto destination = destinations.getData(); + + for (int i = numRectanglesPainted; i < numRectanglesPainted + spriteBatchSize; ++i) + { + auto r = rectangles.getRectangle (i); + r = transformRectangle (r); + *destination = D2DUtilities::toRECT_F (r); + ++destination; + } + } + + if (! whiteRectangle) + { + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, createSpriteSourceTime); + + auto hr = deviceContext->CreateCompatibleRenderTarget (D2D1_SIZE_F { (float) rectangleSize, (float) rectangleSize }, + D2D1_SIZE_U { rectangleSize, rectangleSize }, + D2D1_PIXEL_FORMAT { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED }, + whiteRectangle.resetAndGetPointerAddress()); + if (FAILED (hr)) + return; + + whiteRectangle->BeginDraw(); + whiteRectangle->Clear (D2D1_COLOR_F { 1.0f, 1.0f, 1.0f, 1.0f }); + whiteRectangle->EndDraw(); + } + + ComSmartPtr bitmap; + if (auto hr = whiteRectangle->GetBitmap (bitmap.resetAndGetPointerAddress()); SUCCEEDED (hr)) + { + ComSmartPtr deviceContext3; + if (hr = deviceContext->QueryInterface (deviceContext3.resetAndGetPointerAddress()); SUCCEEDED (hr)) + { + auto d2dColour = D2DUtilities::toCOLOR_F (colour); + auto spriteBatch = getSpriteBatch (*deviceContext3, (uint32) spriteBatchSize); + + if (spriteBatch == nullptr) + return; + + auto setCount = jmin ((uint32) spriteBatchSize, spriteBatch->GetSpriteCount()); + auto addCount = (uint32) spriteBatchSize > setCount ? (uint32) spriteBatchSize - setCount : 0; + + if (setCount != 0) + { + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, setSpritesTime); + + spriteBatch->SetSprites (0, setCount, destinations.getData(), nullptr, &d2dColour, nullptr, sizeof (D2D1_RECT_F), 0, 0, 0); + } + + if (addCount != 0) + { + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, addSpritesTime); + + spriteBatch->AddSprites (addCount, destinations.getData() + setCount, nullptr, &d2dColour, nullptr, sizeof (D2D1_RECT_F), 0, 0, 0); + } + + JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (metrics, drawSpritesTime); + + deviceContext3->SetAntialiasMode (D2D1_ANTIALIAS_MODE_ALIASED); + deviceContext3->DrawSpriteBatch (spriteBatch, bitmap); + deviceContext3->SetAntialiasMode (D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + } + } + + numRectanglesPainted += spriteBatchSize; + } + } + +private: + ComSmartPtr getSpriteBatch (ID2D1DeviceContext3& dc, uint32 key) + { + return spriteBatches.get ((uint32) key, [&dc] (auto) -> ComSmartPtr + { + ComSmartPtr result; + if (const auto hr = dc.CreateSpriteBatch (result.resetAndGetPointerAddress()); SUCCEEDED (hr)) + return result; + + return nullptr; + }); + } + + static constexpr uint32 rectangleSize = 32; + ComSmartPtr whiteRectangle; + HeapBlock destinations; + size_t destinationsCapacity = 0; + LruCache, 8> spriteBatches; +}; + +class Direct2DDeviceResources +{ +public: + Direct2DDeviceResources() = default; + + // Create a Direct2D device context for a DXGI adapter + HRESULT create (DxgiAdapter::Ptr adapter) + { + jassert (adapter); + + if (deviceContext.context == nullptr) + deviceContext.context = Direct2DDeviceContext::createContext (adapter); + + if (colourBrush == nullptr) + { + if (const auto hr = deviceContext.context->CreateSolidColorBrush (D2D1::ColorF (0.0f, 0.0f, 0.0f, 1.0f), + colourBrush.resetAndGetPointerAddress()); + FAILED (hr)) + { + jassertfalse; + return hr; + } + } + + if (rectangleListSpriteBatch == nullptr) + rectangleListSpriteBatch = std::make_unique(); + + return S_OK; + } + + void release() + { + rectangleListSpriteBatch = nullptr; + linearGradientCache = {}; + radialGradientCache = {}; + colourBrush = nullptr; + deviceContext.release(); + } + + bool canPaint (DxgiAdapter::Ptr adapter) const + { + return adapter->direct2DDevice != nullptr && deviceContext.context != nullptr && colourBrush != nullptr; + } + + Direct2DDeviceContext deviceContext; + ComSmartPtr colourBrush; + LinearGradientCache linearGradientCache; + RadialGradientCache radialGradientCache; + std::unique_ptr rectangleListSpriteBatch; +}; + +class SwapChain +{ +public: + SwapChain() = default; + + HRESULT create (HWND hwnd, Rectangle size, DxgiAdapter::Ptr adapter) + { + if (chain != nullptr || hwnd == nullptr) + return S_OK; + + SharedResourcePointer directX; + auto dxgiFactory = directX->adapters.getFactory(); + + if (dxgiFactory == nullptr || adapter->direct3DDevice == nullptr) + return E_FAIL; + + buffer = nullptr; + chain = nullptr; + + // Make the waitable swap chain + // Create the swap chain with premultiplied alpha support for transparent windows + DXGI_SWAP_CHAIN_DESC1 swapChainDescription = {}; + swapChainDescription.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + swapChainDescription.Width = (UINT) size.getWidth(); + swapChainDescription.Height = (UINT) size.getHeight(); + swapChainDescription.SampleDesc.Count = 1; + swapChainDescription.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapChainDescription.BufferCount = bufferCount; + swapChainDescription.SwapEffect = swapEffect; + swapChainDescription.Flags = swapChainFlags; + + swapChainDescription.Scaling = DXGI_SCALING_STRETCH; + swapChainDescription.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED; + + if (const auto hr = dxgiFactory->CreateSwapChainForComposition (adapter->direct3DDevice, + &swapChainDescription, + nullptr, + chain.resetAndGetPointerAddress()); + FAILED (hr)) + { + return hr; + } + + // Get the waitable swap chain presentation event and set the maximum frame latency + ComSmartPtr chain2; + if (const auto hr = chain.QueryInterface (chain2); FAILED (hr)) + return hr; + + if (chain2 == nullptr) + return E_FAIL; + + swapChainEvent.emplace (chain2->GetFrameLatencyWaitableObject()); + if (swapChainEvent->getHandle() == INVALID_HANDLE_VALUE) + return E_NOINTERFACE; + + if (const auto hr = chain2->SetMaximumFrameLatency (1); SUCCEEDED (hr)) + state = State::chainAllocated; + + return S_OK; + } + + HRESULT createBuffer (ComSmartPtr deviceContext) + { + if (deviceContext == nullptr || chain == nullptr || buffer != nullptr) + return S_OK; + + ComSmartPtr surface; + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") + if (const auto hr = chain->GetBuffer (0, __uuidof (surface), reinterpret_cast (surface.resetAndGetPointerAddress())); FAILED (hr)) + return hr; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + D2D1_BITMAP_PROPERTIES1 bitmapProperties{}; + bitmapProperties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW; + bitmapProperties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM; + bitmapProperties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; + + if (const auto hr = deviceContext->CreateBitmapFromDxgiSurface (surface, bitmapProperties, buffer.resetAndGetPointerAddress()); FAILED (hr)) + return hr; + + state = State::bufferAllocated; + return S_OK; + } + + void release() + { + buffer = nullptr; + chain = nullptr; + state = State::idle; + } + + bool canPaint() const + { + return chain != nullptr && buffer != nullptr && state >= State::bufferAllocated; + } + + HRESULT resize (Rectangle newSize, float dpiScalingFactor, ComSmartPtr deviceContext) + { + if (chain == nullptr) + return E_FAIL; + + auto scaledSize = newSize * dpiScalingFactor; + scaledSize = scaledSize.getUnion ({ Direct2DGraphicsContext::minFrameSize, Direct2DGraphicsContext::minFrameSize }) + .getIntersection ({ Direct2DGraphicsContext::maxFrameSize, Direct2DGraphicsContext::maxFrameSize }); + + buffer = nullptr; + state = State::chainAllocated; + + auto dpi = USER_DEFAULT_SCREEN_DPI * dpiScalingFactor; + deviceContext->SetDpi (dpi, dpi); + + if (const auto hr = chain->ResizeBuffers (0, (UINT) scaledSize.getWidth(), (UINT) scaledSize.getHeight(), DXGI_FORMAT_B8G8R8A8_UNORM, swapChainFlags); FAILED (hr)) + return hr; + + if (const auto hr = createBuffer (deviceContext); FAILED (hr)) + { + release(); + return hr; + } + + return S_OK; + } + + Rectangle getSize() const + { + if (buffer == nullptr) + return {}; + + auto size = buffer->GetPixelSize(); + return { (int) size.width, (int) size.height }; + } + + DXGI_SWAP_EFFECT const swapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + UINT const bufferCount = 2; + uint32 const swapChainFlags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; + uint32 const presentSyncInterval = 1; + uint32 const presentFlags = 0; + ComSmartPtr chain; + ComSmartPtr buffer; + std::optional swapChainEvent; + + enum class State + { + idle, + chainAllocated, + bufferAllocated, + bufferFilled + }; + State state = State::idle; +}; + +//============================================================================== +/* DirectComposition + Using DirectComposition enables transparent windows and smoother window + resizing + + This class builds a simple DirectComposition tree that ultimately contains + the swap chain +*/ +class CompositionTree +{ +public: + HRESULT create (IDXGIDevice* const dxgiDevice, HWND hwnd, IDXGISwapChain1* const swapChain) + { + if (compositionDevice != nullptr) + return S_OK; + + if (dxgiDevice == nullptr) + return E_FAIL; + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") + if (const auto hr = DCompositionCreateDevice (dxgiDevice, + __uuidof (IDCompositionDevice), + reinterpret_cast (compositionDevice.resetAndGetPointerAddress())); + FAILED (hr)) + { + return hr; + } + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + if (const auto hr = compositionDevice->CreateTargetForHwnd (hwnd, FALSE, compositionTarget.resetAndGetPointerAddress()); FAILED (hr)) + return hr; + if (const auto hr = compositionDevice->CreateVisual (compositionVisual.resetAndGetPointerAddress()); FAILED (hr)) + return hr; + if (const auto hr = compositionTarget->SetRoot (compositionVisual); FAILED (hr)) + return hr; + if (const auto hr = compositionVisual->SetContent (swapChain); FAILED (hr)) + return hr; + if (const auto hr = compositionDevice->Commit(); FAILED (hr)) + return hr; + + return S_OK; + } + + void release() + { + compositionVisual = nullptr; + compositionTarget = nullptr; + compositionDevice = nullptr; + } + + bool canPaint() const + { + return compositionVisual != nullptr; + } + +private: + ComSmartPtr compositionDevice; + ComSmartPtr compositionTarget; + ComSmartPtr compositionVisual; +}; + +} // namespace juce diff --git a/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp b/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp index 337bee2d11..8546c65331 100644 --- a/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp +++ b/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp @@ -360,96 +360,93 @@ private: IDWriteFontFileLoader& loader; }; +//============================================================================== class Direct2DFactories { public: Direct2DFactories() { - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") + ComSmartPtr collection; - if (direct2dDll.open ("d2d1.dll")) - { - JUCE_LOAD_WINAPI_FUNCTION (direct2dDll, D2D1CreateFactory, d2d1CreateFactory, - HRESULT, (D2D1_FACTORY_TYPE, REFIID, D2D1_FACTORY_OPTIONS*, void**)) - - if (d2d1CreateFactory != nullptr) - { - D2D1_FACTORY_OPTIONS options; - options.debugLevel = D2D1_DEBUG_LEVEL_NONE; - - d2d1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof (ID2D1Factory), &options, - (void**) d2dFactory.resetAndGetPointerAddress()); - } - } - - if (directWriteDll.open ("DWrite.dll")) - { - JUCE_LOAD_WINAPI_FUNCTION (directWriteDll, DWriteCreateFactory, dWriteCreateFactory, - HRESULT, (DWRITE_FACTORY_TYPE, REFIID, IUnknown**)) - - if (dWriteCreateFactory == nullptr) - return; - - for (const auto uuid : { __uuidof (IDWriteFactory3), __uuidof (IDWriteFactory2), __uuidof (IDWriteFactory) }) - { - dWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED, uuid, - (IUnknown**) directWriteFactory.resetAndGetPointerAddress()); - - if (directWriteFactory != nullptr) - break; - } - - if (directWriteFactory != nullptr) - { - directWriteFactory->RegisterFontFileLoader (fileLoader); - directWriteFactory->RegisterFontCollectionLoader (collectionLoader); - - ComSmartPtr collection; - - if (SUCCEEDED (directWriteFactory->GetSystemFontCollection (collection.resetAndGetPointerAddress(), FALSE)) && collection != nullptr) - fonts.emplace (collection); - else - jassertfalse; - } - - if (d2dFactory != nullptr) - { - auto d2dRTProp = D2D1::RenderTargetProperties (D2D1_RENDER_TARGET_TYPE_SOFTWARE, - D2D1::PixelFormat (DXGI_FORMAT_B8G8R8A8_UNORM, - D2D1_ALPHA_MODE_IGNORE), - 0, 0, - D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE, - D2D1_FEATURE_LEVEL_DEFAULT); - - d2dFactory->CreateDCRenderTarget (&d2dRTProp, directWriteRenderTarget.resetAndGetPointerAddress()); - } - } - - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + if (SUCCEEDED (directWriteFactory->GetSystemFontCollection (collection.resetAndGetPointerAddress(), FALSE)) && collection != nullptr) + fonts.emplace (collection); + else + jassertfalse; } ~Direct2DFactories() { - if (directWriteFactory != nullptr) - { - directWriteFactory->UnregisterFontCollectionLoader (collectionLoader); - directWriteFactory->UnregisterFontFileLoader (fileLoader); - } + if (directWriteFactory == nullptr) + return; + + directWriteFactory->UnregisterFontCollectionLoader (collectionLoader); + directWriteFactory->UnregisterFontFileLoader (fileLoader); } [[nodiscard]] ComSmartPtr getDWriteFactory() const { return directWriteFactory; } + [[nodiscard]] ComSmartPtr getDWriteFactory4() const { return directWriteFactory4; } [[nodiscard]] AggregateFontCollection& getFonts() { jassert (fonts.has_value()); return *fonts; } [[nodiscard]] ComSmartPtr getCollectionLoader() const { return collectionLoader; } private: - DynamicLibrary direct2dDll, directWriteDll; - ComSmartPtr d2dFactory; - ComSmartPtr directWriteFactory; - ComSmartPtr directWriteRenderTarget; - std::optional fonts; + DynamicLibrary direct2dDll { "d2d1.dll" }, directWriteDll { "DWrite.dll" }; - ComSmartPtr fileLoader = becomeComSmartPtrOwner (new MemoryFontFileLoader); - ComSmartPtr collectionLoader = becomeComSmartPtrOwner (new DirectWriteCustomFontCollectionLoader (*fileLoader)); + const ComSmartPtr d2dFactory = [&]() -> ComSmartPtr + { + JUCE_LOAD_WINAPI_FUNCTION (direct2dDll, + D2D1CreateFactory, + d2d1CreateFactory, + HRESULT, + (D2D1_FACTORY_TYPE, REFIID, D2D1_FACTORY_OPTIONS*, void**)) + + if (d2d1CreateFactory == nullptr) + return {}; + + D2D1_FACTORY_OPTIONS options; + options.debugLevel = D2D1_DEBUG_LEVEL_NONE; + + ComSmartPtr result; + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") + d2d1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED, + __uuidof (ID2D1Factory), + &options, + (void**) result.resetAndGetPointerAddress()); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + return result; + }(); + + const ComSmartPtr fileLoader = becomeComSmartPtrOwner (new MemoryFontFileLoader); + const ComSmartPtr collectionLoader = becomeComSmartPtrOwner (new DirectWriteCustomFontCollectionLoader (*fileLoader)); + const ComSmartPtr directWriteFactory = [&]() -> ComSmartPtr + { + JUCE_LOAD_WINAPI_FUNCTION (directWriteDll, + DWriteCreateFactory, + dWriteCreateFactory, + HRESULT, + (DWRITE_FACTORY_TYPE, REFIID, IUnknown**)) + + if (dWriteCreateFactory == nullptr) + return {}; + + ComSmartPtr result; + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") + dWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED, + __uuidof (IDWriteFactory), + (IUnknown**) result.resetAndGetPointerAddress()); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + result->RegisterFontFileLoader (fileLoader); + result->RegisterFontCollectionLoader (collectionLoader); + + return result; + }(); + + const ComSmartPtr directWriteFactory4 = directWriteFactory.getInterface(); + + std::optional fonts; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DFactories) }; @@ -567,7 +564,7 @@ public: return new WindowsDirectWriteTypeface (mappedName, mappedStyle, mapped.font, mappedFace, HbFont { hb_font_create (hbFace.get()) }, {}); } - IDWriteFontFace* getIDWriteFontFace() const { return dwFontFace; } + ComSmartPtr getIDWriteFontFace() const { return dwFontFace; } private: float getKerning (int glyph1, int glyph2) const diff --git a/modules/juce_graphics/native/juce_DirectX_windows.h b/modules/juce_graphics/native/juce_DirectX_windows.h new file mode 100644 index 0000000000..d6546d5796 --- /dev/null +++ b/modules/juce_graphics/native/juce_DirectX_windows.h @@ -0,0 +1,327 @@ +/* + ============================================================================== + + 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 DxgiAdapter : public ReferenceCountedObject +{ + using Ptr = ReferenceCountedObjectPtr; + + DxgiAdapter (ComSmartPtr d2dFactory, ComSmartPtr dxgiAdapterIn) + : dxgiAdapter (dxgiAdapterIn) + { + for (UINT i = 0;; ++i) + { + ComSmartPtr output; + const auto hr = dxgiAdapter->EnumOutputs (i, output.resetAndGetPointerAddress()); + + if (hr == DXGI_ERROR_NOT_FOUND || hr == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE) + break; + + dxgiOutputs.push_back (output); + } + + // This flag adds support for surfaces with a different color channel ordering + // than the API default. It is required for compatibility with Direct2D. + const auto creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + jassert (dxgiAdapter); + + if (const auto hr = D3D11CreateDevice (dxgiAdapter, + D3D_DRIVER_TYPE_UNKNOWN, + nullptr, + creationFlags, + nullptr, + 0, + D3D11_SDK_VERSION, + direct3DDevice.resetAndGetPointerAddress(), + nullptr, + nullptr); FAILED (hr)) + { + return; + } + + if (const auto hr = direct3DDevice->QueryInterface (dxgiDevice.resetAndGetPointerAddress()); FAILED (hr)) + return; + + if (const auto hr = d2dFactory->CreateDevice (dxgiDevice, direct2DDevice.resetAndGetPointerAddress()); FAILED (hr)) + return; + } + + void release() + { + direct2DDevice = nullptr; + dxgiDevice = nullptr; + dxgiOutputs.clear(); + dxgiAdapter = nullptr; + direct3DDevice = nullptr; // release the Direct3D device after the adapter to avoid an exception with AMD + } + + bool uniqueIDMatches (ReferenceCountedObjectPtr other) const + { + auto luid = getAdapterUniqueID(); + auto otherLuid = other->getAdapterUniqueID(); + return (luid.HighPart == otherLuid.HighPart) && (luid.LowPart == otherLuid.LowPart); + } + + LUID getAdapterUniqueID() const + { + DXGI_ADAPTER_DESC1 desc; + + if (auto hr = dxgiAdapter->GetDesc1 (&desc); SUCCEEDED (hr)) + return desc.AdapterLuid; + + return LUID { 0, 0 }; + } + + ComSmartPtr direct3DDevice; + ComSmartPtr dxgiDevice; + ComSmartPtr direct2DDevice; + ComSmartPtr dxgiAdapter; + std::vector> dxgiOutputs; +}; + +struct DxgiAdapterListener +{ + virtual ~DxgiAdapterListener() = default; + virtual void adapterCreated (DxgiAdapter::Ptr adapter) = 0; + virtual void adapterRemoved (DxgiAdapter::Ptr adapter) = 0; +}; + +class DxgiAdapters +{ +public: + explicit DxgiAdapters (ComSmartPtr d2dFactoryIn) + : d2dFactory (d2dFactoryIn) + { + updateAdapters(); + } + + ~DxgiAdapters() + { + releaseAdapters(); + } + + void updateAdapters() + { + if (factory != nullptr && factory->IsCurrent() && ! adapterArray.isEmpty()) + return; + + releaseAdapters(); + + if (factory == nullptr || ! factory->IsCurrent()) + factory = makeDxgiFactory(); + + if (factory == nullptr) + { + // If you hit this, we were unable to create a DXGI Factory, so we won't be able to + // render anything using Direct2D. + // Maybe this version of Windows doesn't have Direct2D support. + jassertfalse; + return; + } + + for (UINT i = 0;; ++i) + { + ComSmartPtr dxgiAdapter; + + if (factory->EnumAdapters1 (i, dxgiAdapter.resetAndGetPointerAddress()) == DXGI_ERROR_NOT_FOUND) + break; + + const auto adapter = adapterArray.add (new DxgiAdapter { d2dFactory, dxgiAdapter }); + listeners.call ([adapter] (DxgiAdapterListener& l) { l.adapterCreated (adapter); }); + } + } + + void releaseAdapters() + { + for (const auto& adapter : adapterArray) + listeners.call ([adapter] (DxgiAdapterListener& l) { l.adapterRemoved (adapter); }); + + adapterArray.clear(); + } + + const auto& getAdapterArray() const + { + return adapterArray; + } + + auto getFactory() const + { + return factory; + } + + DxgiAdapter::Ptr getAdapterForHwnd (HWND hwnd) const + { + const auto monitor = MonitorFromWindow (hwnd, MONITOR_DEFAULTTONULL); + + if (monitor == nullptr) + return getDefaultAdapter(); + + for (auto& adapter : adapterArray) + { + for (const auto& dxgiOutput : adapter->dxgiOutputs) + { + DXGI_OUTPUT_DESC desc{}; + + if (FAILED (dxgiOutput->GetDesc (&desc))) + continue; + + if (desc.Monitor == monitor) + return adapter; + } + } + + return getDefaultAdapter(); + } + + DxgiAdapter::Ptr getDefaultAdapter() const + { + return adapterArray.getFirst(); + } + + void addListener (DxgiAdapterListener& l) + { + listeners.add (&l); + } + + void removeListener (DxgiAdapterListener& l) + { + listeners.remove (&l); + } + +private: + static ComSmartPtr makeDxgiFactory() + { + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") + ComSmartPtr result; + if (const auto hr = CreateDXGIFactory2 (0, __uuidof (IDXGIFactory2), (void**) result.resetAndGetPointerAddress()); SUCCEEDED (hr)) + return result; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + // If CreateDXGIFactory fails, check to see if this is being called in the context of DllMain. + // CreateDXGIFactory will always fail if called from the context of DllMain. In this case, the renderer + // will create a software image instead as a fallback, but that won't perform as well. + // + // You may be creating an Image as a static object, which will likely be created in the context of DllMain. + // Consider deferring your Image creation until later. + jassertfalse; + return {}; + } + + ComSmartPtr d2dFactory; + ListenerList listeners; + ComSmartPtr factory = makeDxgiFactory(); + ReferenceCountedArray adapterArray; +}; + +class DirectX +{ +public: + DirectX() = default; + + auto getD2DFactory() const { return d2dSharedFactory; } + auto getD2DMultithread() const { return multithread; } + +private: + ComSmartPtr d2dSharedFactory = [&] + { + D2D1_FACTORY_OPTIONS options; + options.debugLevel = D2D1_DEBUG_LEVEL_NONE; + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") + ComSmartPtr result; + auto hr = D2D1CreateFactory (D2D1_FACTORY_TYPE_MULTI_THREADED, + __uuidof (ID2D1Factory2), + &options, + (void**) result.resetAndGetPointerAddress()); + jassertquiet (SUCCEEDED (hr)); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + return result; + }(); + + ComSmartPtr multithread = [&] + { + ComSmartPtr result; + d2dSharedFactory->QueryInterface (result.resetAndGetPointerAddress()); + return result; + }(); + +public: + DxgiAdapters adapters { d2dSharedFactory }; + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectX) +}; + +struct D2DUtilities +{ + template + static D2D1_RECT_F toRECT_F (const Rectangle& r) + { + return { (float) r.getX(), (float) r.getY(), (float) r.getRight(), (float) r.getBottom() }; + } + + template + static D2D1_RECT_U toRECT_U (const Rectangle& r) + { + return { (UINT32) r.getX(), (UINT32) r.getY(), (UINT32) r.getRight(), (UINT32) r.getBottom() }; + } + + template + static RECT toRECT (const Rectangle& r) + { + return { r.getX(), r.getY(), r.getRight(), r.getBottom() }; + } + + static Rectangle toRectangle (const RECT& r) + { + return Rectangle::leftTopRightBottom (r.left, r.top, r.right, r.bottom); + } + + static Point toPoint (POINT p) noexcept { return { p.x, p.y }; } + static POINT toPOINT (Point p) noexcept { return { p.x, p.y }; } + + static D2D1_COLOR_F toCOLOR_F (Colour c) + { + return { c.getFloatRed(), c.getFloatGreen(), c.getFloatBlue(), c.getFloatAlpha() }; + } + + static D2D1::Matrix3x2F transformToMatrix (const AffineTransform& transform) + { + return { transform.mat00, transform.mat10, transform.mat01, transform.mat11, transform.mat02, transform.mat12 }; + } +}; + +} // namespace juce diff --git a/modules/juce_graphics/native/juce_EventTracing.h b/modules/juce_graphics/native/juce_EventTracing.h new file mode 100644 index 0000000000..46be41b45d --- /dev/null +++ b/modules/juce_graphics/native/juce_EventTracing.h @@ -0,0 +1,310 @@ +/* + ============================================================================== + + 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. + + ============================================================================== +*/ + +#pragma once + +namespace juce::etw +{ + +//============================================================================== +/* + The following XML can be passed to Windows Performance Recorder (WPR) to enable tracing from + JUCE projects. + - Save the XML into a file with the name JUCE.wprp + - Run the following command from an admin command prompt to start capture. If you use a Developer + Command Prompt, wpr.exe should be found on the PATH already. + - \wpr.exe -start JUCE.wprp + - Start your JUCE project + - Run the following command from an admin command prompt to stop capture + - \wpr.exe -stop TraceCaptureFile.etl description + + + + + + + + + + + + + + + + + + + + + + + + + + + + +*/ + +//============================================================================== +enum +{ + paintKeyword = 1 << 0, + sizeKeyword = 1 << 1, + graphicsKeyword = 1 << 2, + crucialKeyword = 1 << 3, + threadPaintKeyword = 1 << 4, + messageKeyword = 1 << 5, + direct2dKeyword = 1 << 6, + softwareRendererKeyword = 1 << 7, + resourcesKeyword = 1 << 8, + componentKeyword = 1 << 9, + spriteKeyword = 1 << 10 +}; + +enum : uint64_t +{ + direct2dHwndPaintStart, + direct2dHwndPaintEnd, + endDraw, + present1SwapChainStart, + present1SwapChainEnd, + swapChainThreadEvent, + waitForVBlankDone, + callVBlankListeners, + resize, + createResource, + presentIdleFrame, + direct2dImagePaintStart, + direct2dImagePaintEnd, + startD2DFrame, + flush, + startGDIFrame, + startGDIImage, + endGDIFrame, + createLowLevelGraphicsContext, + createDeviceResources, + createSwapChain, + createSwapChainBuffer, + createPeer, + mapBitmap, + unmapBitmap, + createDirect2DBitmapFromImage, + createDirect2DBitmap, + setOrigin, + addTransform, + clipToRectangle, + clipToRectangleList, + excludeClipRectangle, + clipToPath, + clipToImageAlpha, + saveState, + restoreState, + beginTransparencyLayer, + endTransparencyLayer, + setFill, + setOpacity, + setInterpolationQuality, + fillRect, + fillRectReplace, + fillRectList, + drawRectTranslated, + drawRectTransformed, + drawRect, + fillPath, + strokePath, + drawPath, + drawImage, + drawLine, + setFont, + drawGlyph, + drawGlyphRun, + drawTextLayout, + drawRoundedRectangle, + fillRoundedRectangle, + drawEllipse, + fillEllipse, + filledGeometryRealizationCacheHit, + filledGeometryRealizationCreated, + strokedGeometryRealizationCacheHit, + strokedGeometryRealizationCreated, + releaseGeometryRealization, + gradientCacheHit, + gradientCreated, + releaseGradient, + nativeDropShadow, + nativeGlowEffect, + resetToDefaultState, + reduceClipRegionRectangle, + reduceClipRegionRectangleList, + reduceClipRegionImage, + reduceClipRegionPath, + excludeClipRegion, + fillAll, + repaint, + paintComponentAndChildren, + paintWithinParentContext, + createSpriteBatch, + setSprites, + addSprites, + drawSprites, +}; + +#if JUCE_WINDOWS && JUCE_ETW_TRACELOGGING + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wc++98-compat-extra-semi", "-Wmissing-prototypes", "-Wgnu-zero-variadic-macro-arguments") + TRACELOGGING_DECLARE_PROVIDER (JUCETraceLogProvider); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + #define JUCE_WRITE_TRACE_LOG_VA(code, keyword, ...) \ + TraceLoggingWrite (::juce::etw::JUCETraceLogProvider, \ + #code, \ + TraceLoggingLevel (TRACE_LEVEL_INFORMATION), \ + TraceLoggingKeyword ((UINT64) keyword), \ + __VA_ARGS__, \ + TraceLoggingValue ((unsigned short) code, "code")) + #define JUCE_WRITE_TRACE_LOG(code, keyword) \ + TraceLoggingWrite (::juce::etw::JUCETraceLogProvider, \ + #code, \ + TraceLoggingLevel (TRACE_LEVEL_INFORMATION), \ + TraceLoggingKeyword ((UINT64) keyword), \ + TraceLoggingValue ((unsigned short) code, "code")) +#else + #define JUCE_WRITE_TRACE_LOG_VA(code, keyword, ...) + #define JUCE_WRITE_TRACE_LOG(code, keyword) +#endif + +template +auto toVector (const Rectangle& r) +{ + return std::vector { r.getX(), r.getY(), r.getWidth(), r.getHeight() }; +} + +template +auto toVector (const RectangleList& list) +{ + std::vector result; + + for (const auto& r : list) + result.insert (result.end(), { r.getX(), r.getY(), r.getWidth(), r.getHeight() }); + + return result; +} + +//============================================================================== +#if JUCE_WINDOWS && JUCE_ETW_TRACELOGGING + #define JUCE_SCOPED_TRACE_EVENT_FRAME(code, keyword, frameNumber) \ + const ScopeGuard scopedEvent_ ## __LINE__ { [start = ::juce::Time::getHighResolutionTicks(), frame = (frameNumber)] \ + { \ + const auto ticks = ::juce::Time::getHighResolutionTicks() - start; \ + JUCE_WRITE_TRACE_LOG_VA (code, keyword, TraceLoggingValue (ticks), TraceLoggingValue (frame)); \ + } }; + + #define JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_F32(code, keyword, frameNumber, rectIn) \ + const ScopeGuard scopedEvent_ ## __LINE__ { [start = ::juce::Time::getHighResolutionTicks(), frame = (frameNumber), rect = (rectIn)] \ + { \ + const auto ticks = ::juce::Time::getHighResolutionTicks() - start; \ + const std::vector vec = ::juce::etw::toVector (rect); \ + JUCE_WRITE_TRACE_LOG_VA (code, \ + keyword, \ + TraceLoggingValue (ticks), \ + TraceLoggingValue (frame), \ + TraceLoggingFloat32Array (vec.data(), (UINT16) vec.size(), "rect")); \ + } }; + + #define JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32(code, keyword, frameNumber, rectIn) \ + const ScopeGuard scopedEvent_ ## __LINE__ { [start = ::juce::Time::getHighResolutionTicks(), frame = (frameNumber), rect = (rectIn)] \ + { \ + const auto ticks = ::juce::Time::getHighResolutionTicks() - start; \ + const std::vector vec = ::juce::etw::toVector (rect); \ + JUCE_WRITE_TRACE_LOG_VA (code, \ + keyword, \ + TraceLoggingValue (ticks), \ + TraceLoggingValue (frame), \ + TraceLoggingInt32Array (vec.data(), (UINT16) vec.size(), "rect")); \ + } }; +#else + #define JUCE_SCOPED_TRACE_EVENT_FRAME(code, keyword, frameNumber) + #define JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_F32(code, keyword, frameNumber, rectIn) + #define JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32(code, keyword, frameNumber, rectIn) +#endif + +//============================================================================== +#define JUCE_TRACE_LOG_D2D_PAINT_CALL(code, frameNumber) \ + JUCE_WRITE_TRACE_LOG_VA (code, etw::paintKeyword | etw::direct2dKeyword, TraceLoggingValue ((UINT64) frameNumber, "frame")) + +#define JUCE_TRACE_LOG_JUCE_VBLANK_THREAD_EVENT \ + JUCE_WRITE_TRACE_LOG (etw::waitForVBlankDone, etw::softwareRendererKeyword) + +#define JUCE_TRACE_LOG_JUCE_VBLANK_CALL_LISTENERS \ + JUCE_WRITE_TRACE_LOG (etw::callVBlankListeners, etw::softwareRendererKeyword) + +#define JUCE_TRACE_LOG_D2D_RESIZE(message) \ + JUCE_WRITE_TRACE_LOG_VA (etw::resize, etw::paintKeyword | etw::direct2dKeyword, TraceLoggingValue (message, "message")) + +#define JUCE_TRACE_LOG_D2D_IMAGE_MAP_DATA \ + JUCE_WRITE_TRACE_LOG (etw::mapBitmap, etw::direct2dKeyword) + +#define JUCE_TRACE_LOG_D2D_IMAGE_UNMAP_DATA \ + JUCE_WRITE_TRACE_LOG (etw::unmapBitmap, etw::direct2dKeyword) + +#define JUCE_TRACE_LOG_PAINT_COMPONENT_AND_CHILDREN(depth) \ + JUCE_WRITE_TRACE_LOG_VA (etw::paintComponentAndChildren, etw::paintKeyword, TraceLoggingValue (depth, "depth")) + +#define JUCE_TRACE_LOG_PAINT_CALL(code, frameNumber) \ + JUCE_WRITE_TRACE_LOG_VA (code, etw::softwareRendererKeyword, TraceLoggingValue ((UINT64) frameNumber, "frame")) + +#if JUCE_ETW_TRACELOGGING + #define JUCE_TRACE_EVENT_INT_RECT_LIST(code, keyword, frameNumber, rect) \ + { \ + const std::vector vec = ::juce::etw::toVector (rect); \ + JUCE_WRITE_TRACE_LOG (code, \ + etw::softwareRendererKeyword, \ + TraceLoggingValue ((UINT64) frameNumber, "frame"), \ + TraceLoggingInt32Array (vec.data(), (UINT16) vec.size(), "rect")); \ + } + + #define JUCE_TRACE_EVENT_INT_RECT(code, keyword, rect) \ + { \ + const std::vector vec = ::juce::etw::toVector (rect); \ + JUCE_WRITE_TRACE_LOG (code, \ + etw::softwareRendererKeyword, \ + TraceLoggingInt32Array (vec.data(), (UINT16) vec.size(), "rect")); \ + } +#else + #define JUCE_TRACE_EVENT_INT_RECT_LIST(code, keyword, frameNumber, rect) + #define JUCE_TRACE_EVENT_INT_RECT(code, keyword, rect) +#endif + +} // namespace juce::etw diff --git a/modules/juce_graphics/native/juce_RenderingHelpers.h b/modules/juce_graphics/native/juce_RenderingHelpers.h index bf407d51d7..2b5284b415 100644 --- a/modules/juce_graphics/native/juce_RenderingHelpers.h +++ b/modules/juce_graphics/native/juce_RenderingHelpers.h @@ -131,18 +131,35 @@ public: return r + offset.toFloat(); } - template - RectangleOrPoint transformed (RectangleOrPoint r) const noexcept + auto boundsAfterTransform (Rectangle r) const noexcept { jassert (! isOnlyTranslated); return r.transformedBy (complexTransform); } - template - Rectangle deviceSpaceToUserSpace (Rectangle r) const noexcept + auto transformed (Point r) const noexcept { - return isOnlyTranslated ? r - offset - : r.transformedBy (complexTransform.inverted()); + jassert (! isOnlyTranslated); + return r.transformedBy (complexTransform); + } + + auto boundsAfterTransform (const RectangleList& r) const noexcept + { + jassert (! isOnlyTranslated); + return boundsAfterTransform (r.getBounds()); + } + + auto boundsAfterTransform (Line r) const noexcept + { + jassert (! isOnlyTranslated); + return Line { transformed (r.getStart()), transformed (r.getEnd()) }; + } + + template + Rectangle deviceSpaceToUserSpace (Rectangle r) const noexcept + { + return isOnlyTranslated ? r.toFloat() - offset.toFloat() + : r.toFloat().transformedBy (complexTransform.inverted()); } AffineTransform complexTransform; @@ -2007,11 +2024,6 @@ public: cloneClipIfMultiplyReferenced(); clip = clip->clipToRectangle (transform.translated (r)); } - else if (! transform.isRotated) - { - cloneClipIfMultiplyReferenced(); - clip = clip->clipToRectangle (transform.transformed (r)); - } else { Path p; @@ -2042,16 +2054,6 @@ public: clip = clip->clipToRectangleList (offsetList); } } - else if (! transform.isRotated) - { - cloneClipIfMultiplyReferenced(); - RectangleList scaledList; - - for (auto& i : r) - scaledList.add (transform.transformed (i)); - - clip = clip->clipToRectangleList (scaledList); - } else { clipToPath (r.toPath(), {}); @@ -2083,7 +2085,7 @@ public: } else if (! transform.isRotated) { - clip = clip->excludeClipRectangle (getLargestIntegerWithin (transform.transformed (r.toFloat()))); + clip = clip->excludeClipRectangle (getLargestIntegerWithin (transform.boundsAfterTransform (r.toFloat()))); } else { @@ -2141,7 +2143,7 @@ public: Rectangle getClipBounds() const { - return clip != nullptr ? transform.deviceSpaceToUserSpace (clip->getClipBounds()) + return clip != nullptr ? transform.deviceSpaceToUserSpace (clip->getClipBounds()).getSmallestIntegerContainer() : Rectangle(); } @@ -2198,11 +2200,12 @@ public: } else if (! transform.isRotated) { - fillTargetRect (transform.transformed (r), replaceContents); + jassert (! replaceContents); // not implemented + fillTargetRect (transform.boundsAfterTransform (r.toFloat())); } else { - jassert (! replaceContents); // not implemented.. + jassert (! replaceContents); // not implemented fillRectAsPath (r); } } @@ -2215,7 +2218,7 @@ public: if (transform.isOnlyTranslated) fillTargetRect (transform.translated (r)); else if (! transform.isRotated) - fillTargetRect (transform.transformed (r)); + fillTargetRect (transform.boundsAfterTransform (r)); else fillRectAsPath (r); } @@ -2575,13 +2578,18 @@ template class StackBasedLowLevelGraphicsContext : public LowLevelGraphicsContext { public: + explicit StackBasedLowLevelGraphicsContext (uint64_t frameIn) + : frame (frameIn) + { + } + bool isVectorDevice() const override { return false; } Rectangle getClipBounds() const override { return stack->getClipBounds(); } bool isClipEmpty() const override { return stack->clip == nullptr; } void setOrigin (Point o) override { stack->transform.setOrigin (o); } void addTransform (const AffineTransform& t) override { stack->transform.addTransform (t); } - float getPhysicalPixelScaleFactor() override { return stack->transform.getPhysicalPixelScaleFactor(); } + float getPhysicalPixelScaleFactor() const override { return stack->transform.getPhysicalPixelScaleFactor(); } bool clipRegionIntersects (const Rectangle& r) override { return stack->clipRegionIntersects (r); } bool clipToRectangle (const Rectangle& r) override { return stack->clipToRectangle (r); } bool clipToRectangleList (const RectangleList& r) override { return stack->clipToRectangleList (r); } @@ -2603,6 +2611,7 @@ public: void drawLine (const Line& line) override { stack->drawLine (line); } void setFont (const Font& newFont) override { stack->font = newFont; } const Font& getFont() override { return stack->font; } + uint64_t getFrameId() const override { return frame; } void drawGlyphs (Span glyphs, Span> positions, @@ -2627,7 +2636,7 @@ protected: auto& cache = RenderingHelpers::GlyphCache::getInstance(); const Point pos (t.getTranslationX(), t.getTranslationY()); - if (stack->transform.isOnlyTranslated) + if (this->stack->transform.isOnlyTranslated) { const auto drawPos = pos + stack->transform.offset.toFloat(); return std::tuple (cache.get (stack->font, i), drawPos); @@ -2653,7 +2662,7 @@ protected: }(); const auto initialFill = stack->fillType; - const ScopeGuard scope { [&] { stack->setFillType (initialFill); } }; + const ScopeGuard scope { [&] { this->stack->setFillType (initialFill); } }; for (const auto& layer : layers) { @@ -2683,6 +2692,7 @@ protected: StackBasedLowLevelGraphicsContext() = default; RenderingHelpers::SavedStateStack stack; + uint64_t frame = 0; }; JUCE_END_IGNORE_WARNINGS_MSVC diff --git a/modules/juce_gui_basics/components/juce_Component.cpp b/modules/juce_gui_basics/components/juce_Component.cpp index 63350d1844..6b1c181c97 100644 --- a/modules/juce_gui_basics/components/juce_Component.cpp +++ b/modules/juce_gui_basics/components/juce_Component.cpp @@ -505,67 +505,6 @@ bool Component::isOpaque() const noexcept } //============================================================================== -struct StandardCachedComponentImage final : public CachedComponentImage -{ - StandardCachedComponentImage (Component& c) noexcept : owner (c) {} - - void paint (Graphics& g) override - { - scale = g.getInternalContext().getPhysicalPixelScaleFactor(); - auto compBounds = owner.getLocalBounds(); - auto imageBounds = compBounds * scale; - - if (image.isNull() || image.getBounds() != imageBounds) - { - image = Image (owner.isOpaque() ? Image::RGB - : Image::ARGB, - jmax (1, imageBounds.getWidth()), - jmax (1, imageBounds.getHeight()), - ! owner.isOpaque()); - - validArea.clear(); - } - - if (! validArea.containsRectangle (compBounds)) - { - Graphics imG (image); - auto& lg = imG.getInternalContext(); - - lg.addTransform (AffineTransform::scale (scale)); - - for (auto& i : validArea) - lg.excludeClipRectangle (i); - - if (! owner.isOpaque()) - { - lg.setFill (Colours::transparentBlack); - lg.fillRect (compBounds, true); - lg.setFill (Colours::black); - } - - owner.paintEntireComponent (imG, true); - } - - validArea = compBounds; - - g.setColour (Colours::black.withAlpha (owner.getAlpha())); - g.drawImageTransformed (image, AffineTransform::scale ((float) compBounds.getWidth() / (float) imageBounds.getWidth(), - (float) compBounds.getHeight() / (float) imageBounds.getHeight()), false); - } - - bool invalidateAll() override { validArea.clear(); return true; } - bool invalidate (const Rectangle& area) override { validArea.subtract (area); return true; } - void releaseResources() override { image = Image(); } - -private: - Image image; - RectangleList validArea; - Component& owner; - float scale = 1.0f; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StandardCachedComponentImage) -}; - void Component::setCachedComponentImage (CachedComponentImage* newCachedImage) { if (cachedImage.get() != newCachedImage) @@ -581,12 +520,12 @@ void Component::setBufferedToImage (bool shouldBeBuffered) // so by calling setBufferedToImage, you'll be deleting the custom one - this is almost certainly // not what you wanted to happen... If you really do know what you're doing here, and want to // avoid this assertion, just call setCachedComponentImage (nullptr) before setBufferedToImage(). - jassert (cachedImage == nullptr || dynamic_cast (cachedImage.get()) != nullptr); + jassert (cachedImage == nullptr || dynamic_cast (cachedImage.get()) != nullptr); if (shouldBeBuffered) { if (cachedImage == nullptr) - cachedImage.reset (new StandardCachedComponentImage (*this)); + cachedImage = std::make_unique (*this); } else { @@ -1673,6 +1612,20 @@ void Component::paintWithinParentContext (Graphics& g) void Component::paintComponentAndChildren (Graphics& g) { + #if JUCE_ETW_TRACELOGGING + { + int depth = 0; + auto parent = getParentComponent(); + while (parent) + { + parent = parent->getParentComponent(); + depth++; + } + + JUCE_TRACE_LOG_PAINT_COMPONENT_AND_CHILDREN (depth); + } + #endif + auto clipBounds = g.getClipBounds(); if (flags.dontClipGraphicsFlag && getNumChildComponents() == 0) @@ -1756,12 +1709,25 @@ void Component::paintEntireComponent (Graphics& g, bool ignoreAlphaLevel) auto scaledBounds = getLocalBounds() * scale; - Image effectImage (flags.opaqueFlag ? Image::RGB : Image::ARGB, - scaledBounds.getWidth(), scaledBounds.getHeight(), ! flags.opaqueFlag); + // Store the effect image in the image cache to avoid recreating it every time + auto imageFormat = flags.opaqueFlag ? Image::RGB : Image::ARGB; + int64 imageHashCode = scaledBounds.getWidth() | ((int64) scaledBounds.getHeight() << 24) | ((int64) imageFormat << 56); + auto effectImage = ImageCache::getFromHashCode (imageHashCode); + + if (effectImage.isNull()) + { + effectImage = Image { imageFormat, scaledBounds.getWidth(), scaledBounds.getHeight(), ! flags.opaqueFlag }; + ImageCache::addImageToCache (effectImage, imageHashCode); + } + + effectImage.clear (effectImage.getBounds(), (imageFormat == Image::ARGB) ? Colours::transparentBlack : Colours::black); + { Graphics g2 (effectImage); + g2.addTransform (AffineTransform::scale ((float) scaledBounds.getWidth() / (float) getWidth(), (float) scaledBounds.getHeight() / (float) getHeight())); + paintComponentAndChildren (g2); } diff --git a/modules/juce_gui_basics/detail/juce_StandardCachedComponentImage.h b/modules/juce_gui_basics/detail/juce_StandardCachedComponentImage.h new file mode 100644 index 0000000000..2d676b2723 --- /dev/null +++ b/modules/juce_gui_basics/detail/juce_StandardCachedComponentImage.h @@ -0,0 +1,129 @@ +/* + ============================================================================== + + 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::detail +{ + +struct StandardCachedComponentImage : public CachedComponentImage +{ + explicit StandardCachedComponentImage (Component& c) noexcept + : owner (c) + { + } + + void paint (Graphics& g) override + { + scale = g.getInternalContext().getPhysicalPixelScaleFactor(); + auto compBounds = owner.getLocalBounds(); + auto imageBounds = compBounds * scale; + + if (image.isNull() || image.getBounds() != imageBounds) + { + #if JUCE_WINDOWS + auto imageType = SoftwareImageType {}; + #else + auto imageType = NativeImageType {}; + #endif + + image = Image (owner.isOpaque() ? Image::RGB + : Image::ARGB, + jmax (1, imageBounds.getWidth()), + jmax (1, imageBounds.getHeight()), + ! owner.isOpaque(), + imageType); + + validArea.clear(); + } + + paintImage (compBounds, AffineTransform::scale (scale)); + + validArea = compBounds; + + g.setColour (Colours::black.withAlpha (owner.getAlpha())); + g.drawImageTransformed (image, + AffineTransform::scale ((float) compBounds.getWidth() / (float) imageBounds.getWidth(), + (float) compBounds.getHeight() / (float) imageBounds.getHeight()), + false); + } + + void paintImage (Rectangle compBounds, AffineTransform transform) + { + if (! validArea.containsRectangle (compBounds)) + { + Graphics imG (image); + auto& lg = imG.getInternalContext(); + + lg.addTransform (transform); + + for (auto& i : validArea) + lg.excludeClipRectangle (i); + + if (! owner.isOpaque()) + { + lg.setFill (Colours::transparentBlack); + lg.fillRect (compBounds, true); + lg.setFill (Colours::black); + } + + owner.paintEntireComponent (imG, true); + } + } + + bool invalidateAll() override + { + validArea.clear(); + return true; + } + + bool invalidate (const Rectangle& area) override + { + validArea.subtract (area); + return true; + } + + void releaseResources() override + { + image = Image(); + } + +protected: + Image image; + RectangleList validArea; + Component& owner; + float scale = 1.0f; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StandardCachedComponentImage) +}; + +} // namespace juce::detail diff --git a/modules/juce_gui_basics/juce_gui_basics.cpp b/modules/juce_gui_basics/juce_gui_basics.cpp index c60dc69f7e..912320c536 100644 --- a/modules/juce_gui_basics/juce_gui_basics.cpp +++ b/modules/juce_gui_basics/juce_gui_basics.cpp @@ -80,12 +80,19 @@ //============================================================================== #elif JUCE_WINDOWS - #include - #include - #include #include + #include + #include + #include + #include #include - #include + #include + #include + + #if JUCE_ETW_TRACELOGGING + #include + #include + #endif #if JUCE_MINGW // Some MinGW headers use 'new' as a parameter name @@ -109,21 +116,18 @@ #pragma comment(lib, "vfw32.lib") #pragma comment(lib, "imm32.lib") #pragma comment(lib, "comctl32.lib") - #pragma comment(lib, "dxgi.lib") #if JUCE_OPENGL #pragma comment(lib, "OpenGL32.Lib") #pragma comment(lib, "GlU32.Lib") #endif - #if JUCE_DIRECT2D - #pragma comment (lib, "Dwrite.lib") - #pragma comment (lib, "D2d1.lib") - #endif #endif #endif //============================================================================== +#include + #include "detail/juce_AccessibilityHelpers.h" #include "detail/juce_ButtonAccessibilityHandler.h" #include "detail/juce_ScalingHelpers.h" @@ -144,6 +148,7 @@ #include "detail/juce_WindowingHelpers.h" #include "detail/juce_AlertWindowHelpers.h" #include "detail/juce_TopLevelWindowManager.h" +#include "detail/juce_StandardCachedComponentImage.h" //============================================================================== #if JUCE_IOS || JUCE_WINDOWS @@ -185,9 +190,8 @@ #include "native/juce_MouseCursor_mac.mm" #elif JUCE_WINDOWS - #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client - #include - #endif + #include + #include "native/accessibility/juce_ComInterfaces_windows.h" #include "native/accessibility/juce_WindowsUIAWrapper_windows.h" #include "native/accessibility/juce_AccessibilityElement_windows.h" diff --git a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp index c5c5aebe92..7bb0998e7d 100644 --- a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp +++ b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp @@ -454,24 +454,28 @@ void DragAndDropContainer::startDragging (const var& sourceDescription, const auto clipped = (image.getBounds().toDouble() / scaleFactor).getConstrainedPoint (relPos); Image fade (Image::SingleChannel, image.getWidth(), image.getHeight(), true); - Graphics fadeContext (fade); + { + Graphics fadeContext (fade); - ColourGradient gradient; - gradient.isRadial = true; - gradient.point1 = clipped.toFloat() * scaleFactor; - gradient.point2 = gradient.point1 + Point (0.0f, scaleFactor * 400.0f); - gradient.addColour (0.0, Colours::white); - gradient.addColour (0.375, Colours::white); - gradient.addColour (1.0, Colours::transparentWhite); + ColourGradient gradient; + gradient.isRadial = true; + gradient.point1 = clipped.toFloat() * scaleFactor; + gradient.point2 = gradient.point1 + Point (0.0f, scaleFactor * 400.0f); + gradient.addColour (0.0, Colours::white); + gradient.addColour (0.375, Colours::white); + gradient.addColour (1.0, Colours::transparentWhite); - fadeContext.setGradientFill (gradient); - fadeContext.fillAll(); + fadeContext.setGradientFill (gradient); + fadeContext.fillAll(); + } Image composite (Image::ARGB, image.getWidth(), image.getHeight(), true); - Graphics compositeContext (composite); + { + Graphics compositeContext (composite); - compositeContext.reduceClipRegion (fade, {}); - compositeContext.drawImageAt (image, 0, 0); + compositeContext.reduceClipRegion (fade, {}); + compositeContext.drawImageAt (image, 0, 0); + } return { ScaledImage (composite, scaleFactor), clipped }; }(); diff --git a/modules/juce_gui_basics/native/juce_VBlank_windows.cpp b/modules/juce_gui_basics/native/juce_VBlank_windows.cpp index 08be92714f..b5af4c0415 100644 --- a/modules/juce_gui_basics/native/juce_VBlank_windows.cpp +++ b/modules/juce_gui_basics/native/juce_VBlank_windows.cpp @@ -105,7 +105,6 @@ public: [&listener] (const auto& l) { return &(l.get()) == &listener; }); } - //============================================================================== static HMONITOR getMonitorFromOutput (ComSmartPtr output) { DXGI_OUTPUT_DESC desc = {}; @@ -219,12 +218,13 @@ public: if (threadWithListener != threads.end()) removeListener (threadWithListener, listener); - for (auto adapter : adapters) + SharedResourcePointer directX; + for (const auto& adapter : directX->adapters.getAdapterArray()) { UINT i = 0; ComSmartPtr output; - while (adapter->EnumOutputs (i, output.resetAndGetPointerAddress()) != DXGI_ERROR_NOT_FOUND) + while (adapter->dxgiAdapter->EnumOutputs (i, output.resetAndGetPointerAddress()) != DXGI_ERROR_NOT_FOUND) { if (VBlankThread::getMonitorFromOutput (output) == monitor) { @@ -246,21 +246,8 @@ public: void reconfigureDisplays() { - adapters.clear(); - - ComSmartPtr factory; - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") - CreateDXGIFactory (__uuidof (IDXGIFactory), (void**)factory.resetAndGetPointerAddress()); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - UINT i = 0; - ComSmartPtr adapter; - - while (factory->EnumAdapters (i, adapter.resetAndGetPointerAddress()) != DXGI_ERROR_NOT_FOUND) - { - adapters.push_back (adapter); - ++i; - } + SharedResourcePointer directX; + directX->adapters.updateAdapters(); for (auto& thread : threads) thread->updateMonitor(); @@ -304,7 +291,6 @@ private: } //============================================================================== - std::vector> adapters; Threads threads; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VBlankDispatcher) diff --git a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp index c094cd2781..9298a46530 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp @@ -32,6 +32,10 @@ ============================================================================== */ +#if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client + #include +#endif + namespace juce { @@ -667,13 +671,6 @@ ScopedDPIAwarenessDisabler::~ScopedDPIAwarenessDisabler() using SettingChangeCallbackFunc = void (*)(void); extern SettingChangeCallbackFunc settingChangeCallback; -//============================================================================== -static Rectangle rectangleFromRECT (RECT r) noexcept { return { r.left, r.top, r.right - r.left, r.bottom - r.top }; } -static RECT RECTFromRectangle (Rectangle r) noexcept { return { r.getX(), r.getY(), r.getRight(), r.getBottom() }; } - -static Point pointFromPOINT (POINT p) noexcept { return { p.x, p.y }; } -static POINT POINTFromPoint (Point p) noexcept { return { p.x, p.y }; } - //============================================================================== static const Displays::Display* getCurrentDisplayFromScaleFactor (HWND hwnd); @@ -981,6 +978,8 @@ const int KeyPress::rewindKey = 0x30003; class WindowsBitmapImage final : public ImagePixelData { public: + using Ptr = ReferenceCountedObjectPtr; + WindowsBitmapImage (const Image::PixelFormat format, const int w, const int h, const bool clearImage) : ImagePixelData (format, w, h) @@ -1039,7 +1038,11 @@ public: DeleteObject (hBitmap); } - std::unique_ptr createType() const override { return std::make_unique(); } + std::unique_ptr createType() const override + { + // WindowsBitmapImage needs to be a software bitmap, not a D2D bitmap + return std::make_unique(); + } std::unique_ptr createLowLevelContext() override { @@ -1062,15 +1065,17 @@ public: ImagePixelData::Ptr clone() override { - auto im = new WindowsBitmapImage (pixelFormat, width, height, false); + Image newImage { SoftwareImageType{}.create (pixelFormat, width, height, pixelFormat != Image::RGB) }; - for (int i = 0; i < height; ++i) - memcpy (im->imageData + i * lineStride, imageData + i * lineStride, (size_t) lineStride); + { + Graphics g (newImage); + g.drawImageAt (Image { *this }, 0, 0); + } - return im; + return newImage.getPixelData(); } - void blitToWindow (HWND hwnd, HDC dc, bool transparent, int x, int y, uint8 updateLayeredWindowAlpha) noexcept + void blitToWindow (HWND hwnd, HDC dc, bool transparent, int x, int y, uint8 layeredWindowAlpha) noexcept { SetMapMode (dc, MM_TEXT); @@ -1087,9 +1092,10 @@ public: bf.AlphaFormat = 1 /*AC_SRC_ALPHA*/; bf.BlendFlags = 0; bf.BlendOp = AC_SRC_OVER; - bf.SourceConstantAlpha = updateLayeredWindowAlpha; + bf.SourceConstantAlpha = layeredWindowAlpha; - UpdateLayeredWindow (hwnd, nullptr, &pos, &size, hdc, &p, 0, &bf, 2 /*ULW_ALPHA*/); + [[maybe_unused]] auto ok = UpdateLayeredWindow (hwnd, nullptr, &pos, &size, hdc, &p, 0, &bf, 2 /*ULW_ALPHA*/); + jassert (ok); } else { @@ -1120,15 +1126,15 @@ private: }; //============================================================================== -Image createSnapshotOfNativeWindow (void* nativeWindowHandle) +static Image createGDISnapshotOfNativeWindow (void* nativeWindowHandle) { auto hwnd = (HWND) nativeWindowHandle; - auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd); + auto r = convertPhysicalScreenRectangleToLogical (D2DUtilities::toRectangle (getWindowScreenRect (hwnd)), hwnd); const auto w = r.getWidth(); const auto h = r.getHeight(); - auto nativeBitmap = new WindowsBitmapImage (Image::RGB, w, h, true); + WindowsBitmapImage::Ptr nativeBitmap = new WindowsBitmapImage (Image::RGB, w, h, true); Image bitmap (nativeBitmap); ScopedDeviceContext deviceContext { hwnd }; @@ -1423,32 +1429,62 @@ private: }; //============================================================================== -class HWNDComponentPeer final : public ComponentPeer, - private ComponentPeer::VBlankListener, - private Timer - #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client - , public ModifierKeyReceiver - #endif +/* This is an interface to functionality that is implemented differently depending on the rendering + backend, currently either GDI or Direct2D on Windows. + + This isn't public, so feel free to add or remove functions if necessary. + In general, it's best to keep things consistent between renderers, so try to make changes + in the HWNDComponentPeer rather than in implementations of RenderContext wherever possible. + However, any behaviour that is only required in specific renderers should be added to the + RenderContext implementations of those renderers. +*/ +struct RenderContext +{ + virtual ~RenderContext() = default; + + /* The name of the renderer backend. + This must be unique - no two backends may share the same name. + The name may be displayed to the user, so it should be descriptive. + */ + virtual const char* getName() const = 0; + + /* The following functions will all be called by the peer to update the state of the renderer. */ + virtual void updateBorderSize() = 0; + virtual void setAlpha (float) = 0; + virtual void handlePaintMessage() = 0; + virtual void repaint (const Rectangle& area) = 0; + virtual void dispatchDeferredRepaints() = 0; + virtual void performAnyPendingRepaintsNow() = 0; + virtual void onVBlank() = 0; + virtual void beginResize() = 0; + virtual void endResize() = 0; + virtual void handleNcCalcSize (WPARAM wParam, LPARAM lParam) = 0; + virtual void handleShowWindow() = 0; + virtual std::optional getNcHitTestResult() = 0; + + /* Gets a snapshot of whatever the render context is currently showing. */ + virtual Image createSnapshot() = 0; +}; + +//============================================================================== +class HWNDComponentPeer final : public ComponentPeer + , private ComponentPeer::VBlankListener + , private Timer + #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client + , public ModifierKeyReceiver + #endif { public: - enum RenderingEngineType - { - softwareRenderingEngine = 0, - direct2DRenderingEngine - }; - //============================================================================== - HWNDComponentPeer (Component& comp, int windowStyleFlags, HWND parent, bool nonRepainting) + HWNDComponentPeer (Component& comp, + int windowStyleFlags, + HWND parent, + bool nonRepainting, + int engine) : ComponentPeer (comp, windowStyleFlags), dontRepaint (nonRepainting), - parentToAddTo (parent), - currentRenderingEngine (softwareRenderingEngine) + parentToAddTo (parent) { - callFunctionIfNotLocked (&createWindowCallback, this); - - setTitle (component.getName()); - updateShadower(); - getNativeRealtimeModifiers = [] { HWNDComponentPeer::updateKeyModifiers(); @@ -1463,19 +1499,32 @@ public: return ModifierKeys::currentModifiers; }; - updateCurrentMonitorAndRefreshVBlankDispatcher(); + // CreateWindowEx needs to be called from the message thread + callFunctionIfNotLocked (&createWindowCallback, this); + + // Complete the window initialisation on the calling thread + setTitle (component.getName()); + updateShadower(); + + updateCurrentMonitorAndRefreshVBlankDispatcher (ForceRefreshDispatcher::yes); if (parentToAddTo != nullptr) { - monitorUpdateTimer.emplace ([this] { updateCurrentMonitorAndRefreshVBlankDispatcher(); }); - monitorUpdateTimer->startTimer (1000); + monitorUpdateTimer.emplace ([this] + { + updateCurrentMonitorAndRefreshVBlankDispatcher (ForceRefreshDispatcher::yes); + monitorUpdateTimer->startTimer (1000); + }); } suspendResumeRegistration = ScopedSuspendResumeNotificationRegistration { hwnd }; + + setCurrentRenderingEngine (engine); } ~HWNDComponentPeer() override { + // Clean up that needs to happen on the calling thread suspendResumeRegistration = {}; VBlankDispatcher::getInstance()->removeListener (*this); @@ -1489,21 +1538,20 @@ public: shadower = nullptr; currentTouches.deleteAllTouchesForPeer (this); - callFunctionIfNotLocked (&destroyWindowCallback, (void*) hwnd); + // Destroy the window from the message thread + callFunctionIfNotLocked (&destroyWindowCallback, this); + // And one last little bit of cleanup if (dropTarget != nullptr) { dropTarget->peerIsDeleted = true; dropTarget->Release(); dropTarget = nullptr; } - - #if JUCE_DIRECT2D - direct2DContext = nullptr; - #endif } //============================================================================== + auto getHWND() const { return hwnd; } void* getNativeHandle() const override { return hwnd; } void setVisible (bool shouldBeVisible) override @@ -1528,7 +1576,7 @@ public: void repaintNowIfTransparent() { - if (isUsingUpdateLayeredWindow() && lastPaintTime > 0 && Time::getMillisecondCounter() > lastPaintTime + 30) + if (isNotOpaque() && lastPaintTime > 0 && Time::getMillisecondCounter() > lastPaintTime + 30) handlePaintMessage(); } @@ -1543,10 +1591,8 @@ public: roundToInt ((info.rcWindow.bottom - info.rcClient.bottom) / scaleFactor), roundToInt ((info.rcWindow.right - info.rcClient.right) / scaleFactor)); - #if JUCE_DIRECT2D - if (direct2DContext != nullptr) - direct2DContext->resized(); - #endif + if (renderContext != nullptr) + renderContext->updateBorderSize(); } void setBounds (const Rectangle& bounds, bool isNowFullScreen) override @@ -1564,11 +1610,11 @@ public: auto newBounds = windowBorder.addedTo (bounds); - if (isUsingUpdateLayeredWindow()) + if (isNotOpaque()) { if (auto parentHwnd = GetParent (hwnd)) { - auto parentRect = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (parentHwnd)), hwnd); + auto parentRect = convertPhysicalScreenRectangleToLogical (D2DUtilities::toRectangle (getWindowScreenRect (parentHwnd)), hwnd); newBounds.translate (parentRect.getX(), parentRect.getY()); } } @@ -1597,9 +1643,9 @@ public: auto bounds = [this] { if (parentToAddTo == nullptr) - return convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd); + return convertPhysicalScreenRectangleToLogical (D2DUtilities::toRectangle (getWindowScreenRect (hwnd)), hwnd); - auto localBounds = rectangleFromRECT (getWindowClientRect (hwnd)); + auto localBounds = D2DUtilities::toRectangle (getWindowClientRect (hwnd)); if (isPerMonitorDPIAwareWindow (hwnd)) return (localBounds.toDouble() / getPlatformScaleFactor()).toNearestInt(); @@ -1612,7 +1658,7 @@ public: Point getScreenPosition() const { - auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd); + auto r = convertPhysicalScreenRectangleToLogical (D2DUtilities::toRectangle (getWindowScreenRect (hwnd)), hwnd); return { r.getX() + windowBorder.getLeft(), r.getY() + windowBorder.getTop() }; @@ -1624,30 +1670,30 @@ public: using ComponentPeer::localToGlobal; using ComponentPeer::globalToLocal; + bool isLayeredWindowStyle() const noexcept + { + return (GetWindowLong (hwnd, GWL_EXSTYLE) & WS_EX_LAYERED) != 0; + } + + void setLayeredWindowStyle (bool layered) noexcept + { + auto exStyle = GetWindowLong (hwnd, GWL_EXSTYLE); + + if (layered) + exStyle |= WS_EX_LAYERED; + else + exStyle &= ~WS_EX_LAYERED; + + SetWindowLong (hwnd, GWL_EXSTYLE, exStyle); + } + void setAlpha (float newAlpha) override { + if (renderContext == nullptr) + return; + const ScopedValueSetter scope (shouldIgnoreModalDismiss, true); - - auto intAlpha = (uint8) jlimit (0, 255, (int) (newAlpha * 255.0f)); - - if (component.isOpaque()) - { - if (newAlpha < 1.0f) - { - SetWindowLong (hwnd, GWL_EXSTYLE, GetWindowLong (hwnd, GWL_EXSTYLE) | WS_EX_LAYERED); - SetLayeredWindowAttributes (hwnd, RGB (0, 0, 0), intAlpha, LWA_ALPHA); - } - else - { - SetWindowLong (hwnd, GWL_EXSTYLE, GetWindowLong (hwnd, GWL_EXSTYLE) & ~WS_EX_LAYERED); - RedrawWindow (hwnd, nullptr, nullptr, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN); - } - } - else - { - updateLayeredWindowAlpha = intAlpha; - component.repaint(); - } + renderContext->setAlpha (newAlpha); } void setMinimised (bool shouldBeMinimised) override @@ -1705,6 +1751,9 @@ public: if (constrainer != nullptr) constrainer->resizeEnd(); } + + if (renderContext != nullptr) + renderContext->updateBorderSize(); } bool isFullScreen() const override @@ -1721,13 +1770,13 @@ public: bool contains (Point localPos, bool trueIfInAChildWindow) const override { - auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd); + auto r = convertPhysicalScreenRectangleToLogical (D2DUtilities::toRectangle (getWindowScreenRect (hwnd)), hwnd); if (! r.withZeroOrigin().contains (localPos)) return false; - auto w = WindowFromPoint (POINTFromPoint (convertLogicalScreenPointToPhysical (localPos + getScreenPosition(), - hwnd))); + auto w = WindowFromPoint (D2DUtilities::toPOINT (convertLogicalScreenPointToPhysical (localPos + getScreenPosition(), + hwnd))); return w == hwnd || (trueIfInAChildWindow && (IsChild (hwnd, w) != 0)); } @@ -1853,33 +1902,28 @@ public: void repaint (const Rectangle& area) override { - deferredRepaints.add ((area.toDouble() * getPlatformScaleFactor()).getSmallestIntegerContainer()); + if (renderContext != nullptr) + renderContext->repaint ((area.toDouble() * getPlatformScaleFactor()).getSmallestIntegerContainer()); } void dispatchDeferredRepaints() { - for (auto deferredRect : deferredRepaints) - { - auto r = RECTFromRectangle (deferredRect); - InvalidateRect (hwnd, &r, FALSE); - } - - deferredRepaints.clear(); + if (renderContext != nullptr) + renderContext->dispatchDeferredRepaints(); } void performAnyPendingRepaintsNow() override { - if (component.isVisible()) - { - dispatchDeferredRepaints(); + if (renderContext != nullptr) + renderContext->performAnyPendingRepaintsNow(); + } - WeakReference localRef (&component); - MSG m; + Image createSnapshot() + { + if (renderContext != nullptr) + return renderContext->createSnapshot(); - if (isUsingUpdateLayeredWindow() || PeekMessage (&m, hwnd, WM_PAINT, WM_PAINT, PM_REMOVE)) - if (localRef != nullptr) // (the PeekMessage call can dispatch messages, which may delete this comp) - handlePaintMessage(); - } + return {}; } //============================================================================== @@ -1887,6 +1931,9 @@ public: { vBlankListeners.call ([] (auto& l) { l.onVBlank(); }); dispatchDeferredRepaints(); + + if (renderContext != nullptr) + renderContext->onVBlank(); } //============================================================================== @@ -1993,7 +2040,7 @@ public: private: Point getMousePos (POINTL mousePos) const { - const auto originalPos = pointFromPOINT ({ mousePos.x, mousePos.y }); + const auto originalPos = D2DUtilities::toPoint ({ mousePos.x, mousePos.y }); const auto logicalPos = convertPhysicalScreenPointToLogical (originalPos, peer.hwnd); return detail::ScalingHelpers::screenPosToLocalPos (peer.component, logicalPos.toFloat()); } @@ -2161,13 +2208,28 @@ public: #endif } + static void getLastError() + { + TCHAR messageBuffer[256] = {}; + + FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + GetLastError(), + MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), + messageBuffer, + (DWORD) numElementsInArray (messageBuffer) - 1, + nullptr); + + DBG (messageBuffer); + jassertfalse; + } + + bool hasTitleBar() const { return (styleFlags & windowHasTitleBar) != 0; } + double getScaleFactor() const { return scaleFactor; } + private: HWND hwnd, parentToAddTo; std::unique_ptr shadower; - RenderingEngineType currentRenderingEngine; - #if JUCE_DIRECT2D - std::unique_ptr direct2DContext; - #endif uint32 lastPaintTime = 0; ULONGLONG lastMagnifySize = 0; bool fullScreen = false, isDragging = false, isMouseOver = false, @@ -2175,7 +2237,6 @@ private: BorderSize windowBorder; IconConverters::IconPtr currentWindowIcon; FileDropTarget* dropTarget = nullptr; - uint8 updateLayeredWindowAlpha = 255; UWPUIViewSettings uwpViewSettings; #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client ModifierKeyProvider* modProvider = nullptr; @@ -2190,36 +2251,6 @@ private: //============================================================================== static MultiTouchMapper currentTouches; - //============================================================================== - struct TemporaryImage final : private Timer - { - TemporaryImage() {} - - Image& getImage (bool transparent, int w, int h) - { - auto format = transparent ? Image::ARGB : Image::RGB; - - if ((! image.isValid()) || image.getWidth() < w || image.getHeight() < h || image.getFormat() != format) - image = Image (new WindowsBitmapImage (format, (w + 31) & ~31, (h + 31) & ~31, false)); - - startTimer (3000); - return image; - } - - void timerCallback() override - { - stopTimer(); - image = {}; - } - - private: - Image image; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TemporaryImage) - }; - - TemporaryImage offscreenImageGenerator; - //============================================================================== class WindowClassHolder final : private DeletedAtShutdown { @@ -2238,7 +2269,6 @@ private: WNDCLASSEX wcex = {}; wcex.cbSize = sizeof (wcex); - wcex.style = CS_OWNDC; wcex.lpfnWndProc = (WNDPROC) windowProc; wcex.lpszClassName = windowClassName.toWideCharPointer(); wcex.cbWndExtra = 32; @@ -2352,11 +2382,11 @@ private: //============================================================================== static void* createWindowCallback (void* userData) { - static_cast (userData)->createWindow(); + static_cast (userData)->createWindowOnMessageThread(); return nullptr; } - void createWindow() + void createWindowOnMessageThread() { DWORD exstyle = 0; DWORD type = WS_CLIPSIBLINGS | WS_CLIPCHILDREN; @@ -2392,9 +2422,10 @@ private: else exstyle |= WS_EX_APPWINDOW; + // Don't set WS_EX_TRANSPARENT here; setting that flag hides OpenGL child windows + // behind the Direct2D composition tree. if ((styleFlags & windowHasMinimiseButton) != 0) type |= WS_MINIMIZEBOX; if ((styleFlags & windowHasMaximiseButton) != 0) type |= WS_MAXIMIZEBOX; - if ((styleFlags & windowIgnoresMouseClicks) != 0) exstyle |= WS_EX_TRANSPARENT; if ((styleFlags & windowIsSemiTransparent) != 0) exstyle |= WS_EX_LAYERED; hwnd = CreateWindowEx (exstyle, WindowClassHolder::getInstance()->getWindowClassName(), @@ -2451,29 +2482,24 @@ private: // correctly enable the menu items that we specify in the wm_initmenu message. GetSystemMenu (hwnd, false); - auto alpha = component.getAlpha(); - if (alpha < 1.0f) - setAlpha (alpha); + setAlpha (component.getAlpha()); } else { - TCHAR messageBuffer[256] = {}; - - FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, GetLastError(), MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), - messageBuffer, (DWORD) numElementsInArray (messageBuffer) - 1, nullptr); - - DBG (messageBuffer); - jassertfalse; + getLastError(); } } static BOOL CALLBACK revokeChildDragDropCallback (HWND hwnd, LPARAM) { RevokeDragDrop (hwnd); return TRUE; } - static void* destroyWindowCallback (void* handle) + static void* destroyWindowCallback (void* userData) { - auto hwnd = reinterpret_cast (handle); + static_cast (userData)->destroyWindowOnMessageThread(); + return nullptr; + } + void destroyWindowOnMessageThread() noexcept + { if (IsWindow (hwnd)) { RevokeDragDrop (hwnd); @@ -2483,8 +2509,6 @@ private: DestroyWindow (hwnd); } - - return nullptr; } static void* toFrontCallback1 (void* h) @@ -2510,13 +2534,16 @@ private: return GetFocus(); } - bool isUsingUpdateLayeredWindow() const + bool isOpaque() const + { + return component.isOpaque(); + } + + bool isNotOpaque() const { return ! component.isOpaque(); } - bool hasTitleBar() const noexcept { return (styleFlags & windowHasTitleBar) != 0; } - void updateShadower() { if (! component.isCurrentlyModal() && (styleFlags & windowHasDropShadow) != 0 @@ -2553,234 +2580,24 @@ private: } } - struct ChildWindowClippingInfo - { - HDC dc; - HWNDComponentPeer* peer; - RectangleList* clip; - Point origin; - int savedDC; - }; - - static BOOL CALLBACK clipChildWindowCallback (HWND hwnd, LPARAM context) - { - if (IsWindowVisible (hwnd)) - { - auto& info = *(ChildWindowClippingInfo*) context; - - if (GetParent (hwnd) == info.peer->hwnd) - { - auto clip = rectangleFromRECT (getWindowClientRect (hwnd)); - - info.clip->subtract (clip - info.origin); - - if (info.savedDC == 0) - info.savedDC = SaveDC (info.dc); - - ExcludeClipRect (info.dc, clip.getX(), clip.getY(), clip.getRight(), clip.getBottom()); - } - } - - return TRUE; - } - //============================================================================== void handlePaintMessage() { - #if JUCE_DIRECT2D - if (direct2DContext != nullptr) - { - RECT r; - - if (GetUpdateRect (hwnd, &r, false)) - { - direct2DContext->start(); - direct2DContext->clipToRectangle (convertPhysicalScreenRectangleToLogical (rectangleFromRECT (r), hwnd)); - handlePaint (*direct2DContext); - direct2DContext->end(); - ValidateRect (hwnd, &r); - } - } - else - #endif - { - HRGN rgn = CreateRectRgn (0, 0, 0, 0); - const int regionType = GetUpdateRgn (hwnd, rgn, false); - - PAINTSTRUCT paintStruct; - HDC dc = BeginPaint (hwnd, &paintStruct); // Note this can immediately generate a WM_NCPAINT - // message and become re-entrant, but that's OK - - // if something in a paint handler calls, e.g. a message box, this can become reentrant and - // corrupt the image it's using to paint into, so do a check here. - static bool reentrant = false; - - if (! reentrant) - { - const ScopedValueSetter setter (reentrant, true, false); - - if (dontRepaint) - component.handleCommandMessage (0); // (this triggers a repaint in the openGL context) - else - performPaint (dc, rgn, regionType, paintStruct); - } - - DeleteObject (rgn); - EndPaint (hwnd, &paintStruct); - - #if JUCE_MSVC - _fpreset(); // because some graphics cards can unmask FP exceptions - #endif - - } + if (renderContext != nullptr) + renderContext->handlePaintMessage(); lastPaintTime = Time::getMillisecondCounter(); } - void performPaint (HDC dc, HRGN rgn, int regionType, PAINTSTRUCT& paintStruct) - { - int x = paintStruct.rcPaint.left; - int y = paintStruct.rcPaint.top; - int w = paintStruct.rcPaint.right - x; - int h = paintStruct.rcPaint.bottom - y; - - const bool transparent = isUsingUpdateLayeredWindow(); - - if (transparent) - { - // it's not possible to have a transparent window with a title bar at the moment! - jassert (! hasTitleBar()); - - auto r = getWindowScreenRect (hwnd); - x = y = 0; - w = r.right - r.left; - h = r.bottom - r.top; - } - - if (w > 0 && h > 0) - { - Image& offscreenImage = offscreenImageGenerator.getImage (transparent, w, h); - - RectangleList contextClip; - const Rectangle clipBounds (w, h); - - bool needToPaintAll = true; - - if (regionType == COMPLEXREGION && ! transparent) - { - HRGN clipRgn = CreateRectRgnIndirect (&paintStruct.rcPaint); - CombineRgn (rgn, rgn, clipRgn, RGN_AND); - DeleteObject (clipRgn); - - std::aligned_storage_t<8192, alignof (RGNDATA)> rgnData; - const DWORD res = GetRegionData (rgn, sizeof (rgnData), (RGNDATA*) &rgnData); - - if (res > 0 && res <= sizeof (rgnData)) - { - const RGNDATAHEADER* const hdr = &(((const RGNDATA*) &rgnData)->rdh); - - if (hdr->iType == RDH_RECTANGLES - && hdr->rcBound.right - hdr->rcBound.left >= w - && hdr->rcBound.bottom - hdr->rcBound.top >= h) - { - needToPaintAll = false; - - auto rects = unalignedPointerCast ((char*) &rgnData + sizeof (RGNDATAHEADER)); - - for (int i = (int) ((RGNDATA*) &rgnData)->rdh.nCount; --i >= 0;) - { - if (rects->right <= x + w && rects->bottom <= y + h) - { - const int cx = jmax (x, (int) rects->left); - contextClip.addWithoutMerging (Rectangle (cx - x, rects->top - y, - rects->right - cx, rects->bottom - rects->top) - .getIntersection (clipBounds)); - } - else - { - needToPaintAll = true; - break; - } - - ++rects; - } - } - } - } - - if (needToPaintAll) - { - contextClip.clear(); - contextClip.addWithoutMerging (Rectangle (w, h)); - } - - ChildWindowClippingInfo childClipInfo = { dc, this, &contextClip, Point (x, y), 0 }; - EnumChildWindows (hwnd, clipChildWindowCallback, (LPARAM) &childClipInfo); - - if (! contextClip.isEmpty()) - { - if (transparent) - for (auto& i : contextClip) - offscreenImage.clear (i); - - { - auto context = component.getLookAndFeel() - .createGraphicsContext (offscreenImage, { -x, -y }, contextClip); - - context->addTransform (AffineTransform::scale ((float) getPlatformScaleFactor())); - handlePaint (*context); - } - - static_cast (offscreenImage.getPixelData()) - ->blitToWindow (hwnd, dc, transparent, x, y, updateLayeredWindowAlpha); - } - - if (childClipInfo.savedDC != 0) - RestoreDC (dc, childClipInfo.savedDC); - } - } - //============================================================================== void doMouseEvent (Point position, float pressure, float orientation = 0.0f, ModifierKeys mods = ModifierKeys::currentModifiers) { handleMouseEvent (MouseInputSource::InputSourceType::mouse, position, mods, pressure, orientation, getMouseEventTime()); } - StringArray getAvailableRenderingEngines() override - { - StringArray s ("Software Renderer"); - - #if JUCE_DIRECT2D - if (SystemStats::getOperatingSystemType() >= SystemStats::Windows7) - s.add ("Direct2D"); - #endif - - return s; - } - - int getCurrentRenderingEngine() const override { return currentRenderingEngine; } - - #if JUCE_DIRECT2D - void updateDirect2DContext() - { - if (currentRenderingEngine != direct2DRenderingEngine) - direct2DContext = nullptr; - else if (direct2DContext == nullptr) - direct2DContext.reset (new Direct2DLowLevelGraphicsContext (hwnd)); - } - #endif - - void setCurrentRenderingEngine ([[maybe_unused]] int index) override - { - #if JUCE_DIRECT2D - if (getAvailableRenderingEngines().size() > 1) - { - currentRenderingEngine = index == 1 ? direct2DRenderingEngine : softwareRenderingEngine; - updateDirect2DContext(); - repaint (component.getLocalBounds()); - } - #endif - } + StringArray getAvailableRenderingEngines() override; + int getCurrentRenderingEngine() const override; + void setCurrentRenderingEngine (int e) override; static uint32 getMinTimeBetweenMouseMoves() { @@ -2954,7 +2771,7 @@ private: if (peer == nullptr) peer = this; - return std::tuple (peer, peer->globalToLocal (convertPhysicalScreenPointToLogical (pointFromPOINT (currentMousePos), hwnd).toFloat())); + return std::tuple (peer, peer->globalToLocal (convertPhysicalScreenPointToLogical (D2DUtilities::toPoint (currentMousePos), hwnd).toFloat())); } static MouseInputSource::InputSourceType getPointerType (WPARAM wParam) @@ -3060,8 +2877,8 @@ private: const auto touchIndex = currentTouches.getIndexOfTouch (this, touch.dwID); const auto time = getMouseEventTime(); - const auto pos = globalToLocal (convertPhysicalScreenPointToLogical (pointFromPOINT ({ roundToInt (touch.x / 100.0f), - roundToInt (touch.y / 100.0f) }), hwnd).toFloat()); + const auto pos = globalToLocal (convertPhysicalScreenPointToLogical (D2DUtilities::toPoint ({ roundToInt (touch.x / 100.0f), + roundToInt (touch.y / 100.0f) }), hwnd).toFloat()); const auto pressure = touchPressure; auto modsToSend = ModifierKeys::currentModifiers; @@ -3147,7 +2964,7 @@ private: const auto pressure = (penInfo.penMask & PEN_MASK_PRESSURE) ? (float) penInfo.pressure / 1024.0f : MouseInputSource::defaultPressure; - if (! handlePenInput (penInfo, globalToLocal (convertPhysicalScreenPointToLogical (pointFromPOINT (getPOINTFromLParam (lParam)), hwnd).toFloat()), + if (! handlePenInput (penInfo, globalToLocal (convertPhysicalScreenPointToLogical (D2DUtilities::toPoint (getPOINTFromLParam (lParam)), hwnd).toFloat()), pressure, isDown, isUp)) return false; } @@ -3444,7 +3261,7 @@ private: { if (isConstrainedNativeWindow()) { - const auto logicalBounds = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (r).toFloat(), hwnd); + const auto logicalBounds = convertPhysicalScreenRectangleToLogical (D2DUtilities::toRectangle (r).toFloat(), hwnd); auto pos = detail::ScalingHelpers::unscaledScreenPosToScaled (component, logicalBounds).toNearestInt(); const auto original = getCurrentScaledBounds(); @@ -3456,9 +3273,11 @@ private: wParam == WMSZ_BOTTOM || wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_BOTTOMRIGHT, wParam == WMSZ_RIGHT || wParam == WMSZ_TOPRIGHT || wParam == WMSZ_BOTTOMRIGHT); - r = RECTFromRectangle (convertLogicalScreenRectangleToPhysical (detail::ScalingHelpers::scaledScreenPosToUnscaled (component, pos.toFloat()).toNearestInt(), hwnd)); + r = D2DUtilities::toRECT (convertLogicalScreenRectangleToPhysical (detail::ScalingHelpers::scaledScreenPosToUnscaled (component, pos.toFloat()).toNearestInt(), hwnd)); } + updateBorderSize(); + return TRUE; } @@ -3470,7 +3289,7 @@ private: && (wp.x > -32000 && wp.y > -32000) && ! Component::isMouseButtonDownAnywhere()) { - const auto logicalBounds = convertPhysicalScreenRectangleToLogical (rectangleFromRECT ({ wp.x, wp.y, wp.x + wp.cx, wp.y + wp.cy }).toFloat(), hwnd); + const auto logicalBounds = convertPhysicalScreenRectangleToLogical (D2DUtilities::toRectangle ({ wp.x, wp.y, wp.x + wp.cx, wp.y + wp.cy }).toFloat(), hwnd); auto pos = detail::ScalingHelpers::unscaledScreenPosToScaled (component, logicalBounds).toNearestInt(); const auto original = getCurrentScaledBounds(); @@ -3708,7 +3527,6 @@ private: } #endif -public: static LRESULT CALLBACK windowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam) { // Ensure that non-client areas are scaled for per-monitor DPI awareness v1 - can't @@ -3725,7 +3543,6 @@ public: return DefWindowProcW (h, message, wParam, lParam); } -private: static void* callFunctionIfNotLocked (MessageCallbackFunction* callback, void* userData) { auto& mm = *MessageManager::getInstance(); @@ -3743,15 +3560,15 @@ private: Point getPointFromLocalLParam (LPARAM lParam) noexcept { - auto p = pointFromPOINT (getPOINTFromLParam (lParam)); + auto p = D2DUtilities::toPoint (getPOINTFromLParam (lParam)); if (isPerMonitorDPIAwareWindow (hwnd)) { // LPARAM is relative to this window's top-left but may be on a different monitor so we need to calculate the // physical screen position and then convert this to local logical coordinates auto r = getWindowScreenRect (hwnd); - return globalToLocal (Desktop::getInstance().getDisplays().physicalToLogical (pointFromPOINT ({ r.left + p.x + roundToInt (windowBorder.getLeft() * scaleFactor), - r.top + p.y + roundToInt (windowBorder.getTop() * scaleFactor) })).toFloat()); + return globalToLocal (Desktop::getInstance().getDisplays().physicalToLogical (D2DUtilities::toPoint ({ r.left + p.x + roundToInt (windowBorder.getLeft() * scaleFactor), + r.top + p.y + roundToInt (windowBorder.getTop() * scaleFactor) })).toFloat()); } return p.toFloat(); @@ -3759,7 +3576,7 @@ private: Point getCurrentMousePos() noexcept { - return globalToLocal (convertPhysicalScreenPointToLogical (pointFromPOINT (getPOINTFromLParam ((LPARAM) GetMessagePos())), hwnd).toFloat()); + return globalToLocal (convertPhysicalScreenPointToLogical (D2DUtilities::toPoint (getPOINTFromLParam ((LPARAM) GetMessagePos())), hwnd).toFloat()); } LRESULT peerWindowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam) @@ -3771,8 +3588,9 @@ private: if ((styleFlags & windowIgnoresMouseClicks) != 0) return HTTRANSPARENT; - if (! hasTitleBar()) - return HTCLIENT; + if (renderContext != nullptr) + if (auto result = renderContext->getNcHitTestResult()) + return *result; break; @@ -3790,12 +3608,17 @@ private: return 0; case WM_ERASEBKGND: - case WM_NCCALCSIZE: if (hasTitleBar()) break; return 1; + case WM_NCCALCSIZE: + if (renderContext != nullptr) + renderContext->handleNcCalcSize (wParam, lParam); + + break; + //============================================================================== case WM_POINTERUPDATE: if (handlePointerInput (wParam, lParam, false, false)) @@ -3853,6 +3676,18 @@ private: break; + case WM_ENTERSIZEMOVE: + if (renderContext != nullptr) + renderContext->beginResize(); + + break; + + case WM_EXITSIZEMOVE: + if (renderContext != nullptr) + renderContext->endResize(); + + break; + //============================================================================== case WM_SIZING: return handleSizeConstraining (*(RECT*) lParam, wParam); case WM_WINDOWPOSCHANGING: return handlePositionChanging (*(WINDOWPOS*) lParam); @@ -3981,6 +3816,9 @@ private: { component.setVisible (true); handleBroughtToFront(); + + if (renderContext != nullptr) + renderContext->handleShowWindow(); } break; @@ -4472,10 +4310,11 @@ private: IMEHandler imeHandler; bool shouldIgnoreModalDismiss = false; - RectangleList deferredRepaints; ScopedSuspendResumeNotificationRegistration suspendResumeRegistration; std::optional monitorUpdateTimer; + std::unique_ptr renderContext; + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HWNDComponentPeer) }; @@ -4485,14 +4324,568 @@ ModifierKeys HWNDComponentPeer::modifiersAtLastCallback; ComponentPeer* Component::createNewPeer (int styleFlags, void* parentHWND) { - return new HWNDComponentPeer (*this, styleFlags, (HWND) parentHWND, false); + return new HWNDComponentPeer { *this, styleFlags, (HWND) parentHWND, false, 1 }; } -JUCE_API ComponentPeer* createNonRepaintingEmbeddedWindowsPeer (Component& component, void* parentHWND); -JUCE_API ComponentPeer* createNonRepaintingEmbeddedWindowsPeer (Component& component, void* parentHWND) +Image createSnapshotOfNativeWindow (void* nativeWindowHandle) { - return new HWNDComponentPeer (component, ComponentPeer::windowIgnoresMouseClicks, - (HWND) parentHWND, true); + int numDesktopComponents = Desktop::getInstance().getNumComponents(); + + for (int index = 0; index < numDesktopComponents; ++index) + { + auto component = Desktop::getInstance().getComponent (index); + + if (auto peer = dynamic_cast (component->getPeer())) + if (peer->getNativeHandle() == nativeWindowHandle) + return peer->createSnapshot(); + } + + return {}; +} + +class GDIContext : public RenderContext +{ +public: + static constexpr auto name = "Software Renderer"; + + explicit GDIContext (HWNDComponentPeer& peerIn) + : peer (peerIn) + { + peer.setLayeredWindowStyle (false); + + peer.setAlpha (layeredWindowAlpha / 255.0f); + RedrawWindow (peer.getHWND(), + nullptr, + nullptr, + RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN); + } + + const char* getName() const override { return name; } + + void updateBorderSize() override {} + + void setAlpha (float newAlpha) override + { + auto intAlpha = (uint8) jlimit (0, 255, (int) (newAlpha * 255.0f)); + + if (peer.getComponent().isOpaque()) + { + if (newAlpha < 1.0f) + { + peer.setLayeredWindowStyle (true); + [[maybe_unused]] auto ok = SetLayeredWindowAttributes (peer.getHWND(), RGB (0, 0, 0), intAlpha, LWA_ALPHA); + jassert (ok); + } + else + { + peer.setLayeredWindowStyle (false); + RedrawWindow (peer.getHWND(), nullptr, nullptr, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN); + } + } + else + { + peer.setLayeredWindowStyle (true); + + layeredWindowAlpha = intAlpha; + peer.getComponent().repaint(); + } + } + + void handlePaintMessage() override + { + HRGN rgn = CreateRectRgn (0, 0, 0, 0); + const int regionType = GetUpdateRgn (peer.getHWND(), rgn, false); + + PAINTSTRUCT paintStruct; + HDC dc = BeginPaint (peer.getHWND(), &paintStruct); // Note this can immediately generate a WM_NCPAINT + // message and become re-entrant, but that's OK + + // If something in a paint handler calls, e.g. a message box, this can become reentrant and + // corrupt the image it's using to paint into, so do a check here. + static bool reentrant = false; + + if (! reentrant) + { + const ScopedValueSetter setter (reentrant, true, false); + + if (peer.dontRepaint) + peer.getComponent().handleCommandMessage (0); // (this triggers a repaint in the openGL context) + else + performPaint (dc, rgn, regionType, paintStruct); + } + + DeleteObject (rgn); + EndPaint (peer.getHWND(), &paintStruct); + + #if JUCE_MSVC + _fpreset(); // because some graphics cards can unmask FP exceptions + #endif + } + + void repaint (const Rectangle& area) override + { + JUCE_TRACE_EVENT_INT_RECT (etw::repaint, etw::paintKeyword, area); + deferredRepaints.add (area); + } + + void dispatchDeferredRepaints() override + { + for (auto deferredRect : deferredRepaints) + { + auto r = D2DUtilities::toRECT (deferredRect); + InvalidateRect (peer.getHWND(), &r, FALSE); + } + + deferredRepaints.clear(); + } + + void performAnyPendingRepaintsNow() override + { + if (! peer.getComponent().isVisible()) + return; + + dispatchDeferredRepaints(); + + WeakReference localRef (&peer.getComponent()); + MSG m; + + if (! peer.getComponent().isOpaque() || PeekMessage (&m, peer.getHWND(), WM_PAINT, WM_PAINT, PM_REMOVE)) + if (localRef != nullptr) // (the PeekMessage call can dispatch messages, which may delete this comp) + handlePaintMessage(); + } + + Image createSnapshot() override + { + return createGDISnapshotOfNativeWindow (peer.getHWND()); + } + + void onVBlank() override {} + + std::optional getNcHitTestResult() override + { + if (! peer.hasTitleBar()) + return HTCLIENT; + + return {}; + } + + void beginResize() override {} + void endResize() override {} + void handleNcCalcSize (WPARAM, LPARAM) override {} + void handleShowWindow() override {} + +private: + void performPaint (HDC dc, HRGN rgn, int regionType, PAINTSTRUCT& paintStruct) + { + int x = paintStruct.rcPaint.left; + int y = paintStruct.rcPaint.top; + int w = paintStruct.rcPaint.right - x; + int h = paintStruct.rcPaint.bottom - y; + + const bool transparent = ! peer.getComponent().isOpaque(); + + if (transparent) + { + // it's not possible to have a transparent window with a title bar at the moment! + jassert (! peer.hasTitleBar()); + + auto r = getWindowScreenRect (peer.getHWND()); + x = y = 0; + w = r.right - r.left; + h = r.bottom - r.top; + } + + if (w > 0 && h > 0) + { + Image& offscreenImage = offscreenImageGenerator.getImage (transparent, w, h); + + RectangleList contextClip; + const Rectangle clipBounds (w, h); + + bool needToPaintAll = true; + + if (regionType == COMPLEXREGION && ! transparent) + { + HRGN clipRgn = CreateRectRgnIndirect (&paintStruct.rcPaint); + CombineRgn (rgn, rgn, clipRgn, RGN_AND); + DeleteObject (clipRgn); + + std::aligned_storage_t<8192, alignof (RGNDATA)> rgnData; + const DWORD res = GetRegionData (rgn, sizeof (rgnData), (RGNDATA*) &rgnData); + + if (res > 0 && res <= sizeof (rgnData)) + { + const RGNDATAHEADER* const hdr = &(((const RGNDATA*) &rgnData)->rdh); + + if (hdr->iType == RDH_RECTANGLES + && hdr->rcBound.right - hdr->rcBound.left >= w + && hdr->rcBound.bottom - hdr->rcBound.top >= h) + { + needToPaintAll = false; + + auto rects = unalignedPointerCast ((char*) &rgnData + sizeof (RGNDATAHEADER)); + + for (int i = (int) ((RGNDATA*) &rgnData)->rdh.nCount; --i >= 0;) + { + if (rects->right <= x + w && rects->bottom <= y + h) + { + const int cx = jmax (x, (int) rects->left); + contextClip.addWithoutMerging (Rectangle (cx - x, rects->top - y, + rects->right - cx, rects->bottom - rects->top) + .getIntersection (clipBounds)); + } + else + { + needToPaintAll = true; + break; + } + + ++rects; + } + } + } + } + + if (needToPaintAll) + { + contextClip.clear(); + contextClip.addWithoutMerging (Rectangle (w, h)); + } + + ChildWindowClippingInfo childClipInfo = { dc, &peer, &contextClip, Point (x, y), 0 }; + EnumChildWindows (peer.getHWND(), clipChildWindowCallback, (LPARAM) &childClipInfo); + + if (! contextClip.isEmpty()) + { + if (transparent) + for (auto& i : contextClip) + offscreenImage.clear (i); + + { + auto context = peer.getComponent() + .getLookAndFeel() + .createGraphicsContext (offscreenImage, { -x, -y }, contextClip); + + context->addTransform (AffineTransform::scale ((float) peer.getPlatformScaleFactor())); + peer.handlePaint (*context); + } + + static_cast (offscreenImage.getPixelData())->blitToWindow (peer.getHWND(), dc, transparent, x, y, layeredWindowAlpha); + } + + if (childClipInfo.savedDC != 0) + RestoreDC (dc, childClipInfo.savedDC); + } + } + + struct ChildWindowClippingInfo + { + HDC dc; + HWNDComponentPeer* peer; + RectangleList* clip; + Point origin; + int savedDC; + }; + + static BOOL CALLBACK clipChildWindowCallback (HWND hwnd, LPARAM context) + { + if (IsWindowVisible (hwnd)) + { + auto& info = *(ChildWindowClippingInfo*) context; + + if (GetParent (hwnd) == info.peer->getHWND()) + { + auto clip = D2DUtilities::toRectangle (getWindowClientRect (hwnd)); + + info.clip->subtract (clip - info.origin); + + if (info.savedDC == 0) + info.savedDC = SaveDC (info.dc); + + ExcludeClipRect (info.dc, clip.getX(), clip.getY(), clip.getRight(), clip.getBottom()); + } + } + + return TRUE; + } + + //============================================================================== + struct TemporaryImage final : private Timer + { + TemporaryImage() = default; + + Image& getImage (bool transparent, int w, int h) + { + auto format = transparent ? Image::ARGB : Image::RGB; + + if ((! image.isValid()) || image.getWidth() < w || image.getHeight() < h || image.getFormat() != format) + image = Image (new WindowsBitmapImage (format, (w + 31) & ~31, (h + 31) & ~31, false)); + + startTimer (3000); + return image; + } + + void timerCallback() override + { + stopTimer(); + image = {}; + } + + private: + Image image; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TemporaryImage) + }; + + HWNDComponentPeer& peer; + TemporaryImage offscreenImageGenerator; + RectangleList deferredRepaints; + uint8 layeredWindowAlpha = 255; +}; + +class D2DContext : public RenderContext +{ +public: + static constexpr auto name = "Direct2D"; + + explicit D2DContext (HWNDComponentPeer& peerIn) + : peer (peerIn), + direct2DContext (std::make_unique (peer.getHWND(), peer.getComponent().isOpaque())) + { + // Layered windows use the contents of the window back buffer to automatically determine mouse hit testing + // But - Direct2D doesn't fill the window back buffer so the hit tests pass through for transparent windows + // + // Layered windows can use a single RGB colour as a transparency key (like a green screen). So - choose an + // RGB color as the key and call SetLayeredWindowAttributes with LWA_COLORKEY and the key colour. + // + // Then, use an ID2D1HwndRenderTarget when resizing the window to flush the redirection bitmap to that same + // RGB color. + // + // Setting the window class background brush and handling WM_ERASEBKGND didn't work; Windows keeps filling + // the redirection bitmap in with solid black when the window resizes. + // + // Also - only certain colour values seem to work for the transparency key; RGB(0, 0, 1) seems OK + if (peer.getComponent().isOpaque()) + return; + + peer.setLayeredWindowStyle (true); + + auto backgroundKeyColour = Direct2DHwndContext::getBackgroundTransparencyKeyColour(); + [[maybe_unused]] auto ok = SetLayeredWindowAttributes (peer.getHWND(), + RGB (backgroundKeyColour.getRed(), + backgroundKeyColour.getGreen(), + backgroundKeyColour.getBlue()), + 255, + LWA_COLORKEY); + jassert (ok); + } + + const char* getName() const override { return name; } + + void updateBorderSize() override + { + if (peer.getComponent().isVisible()) + direct2DContext->updateSize(); + } + + void setAlpha (float newAlpha) override + { + direct2DContext->setWindowAlpha (newAlpha); + peer.getComponent().repaint(); + } + + void handlePaintMessage() override + { + #if JUCE_DIRECT2D_METRICS + auto paintStartTicks = Time::getHighResolutionTicks(); + #endif + + direct2DContext->addInvalidWindowRegionToDeferredRepaints(); + + #if JUCE_DIRECT2D_METRICS + lastPaintStartTicks = paintStartTicks; + #endif + } + + void repaint (const Rectangle& area) override + { + direct2DContext->addDeferredRepaint (area); + } + + void dispatchDeferredRepaints() override {} + + void performAnyPendingRepaintsNow() override {} + + Image createSnapshot() override + { + return direct2DContext->createSnapshot(); + } + + void onVBlank() override + { + handleDirect2DPaint(); + } + + std::optional getNcHitTestResult() override { return {}; } + + void beginResize() override + { + direct2DContext->startResizing(); + } + + void endResize() override + { + direct2DContext->finishResizing(); + } + + void handleNcCalcSize (WPARAM, LPARAM lParam) override + { + JUCE_TRACE_LOG_D2D_RESIZE (WM_NCCALCSIZE); + + if (! peer.getComponent().isVisible()) + return; + + auto* rect = (RECT*) lParam; + direct2DContext->setSize (rect->right - rect->left, rect->bottom - rect->top); + } + + void handleShowWindow() override + { + direct2DContext->handleShowWindow(); + handleDirect2DPaint(); + } + +private: + void handleDirect2DPaint() + { + #if JUCE_DIRECT2D_METRICS + auto paintStartTicks = Time::getHighResolutionTicks(); + #endif + + // Use the ID2D1DeviceContext to paint a swap chain buffer, then tell the swap chain to present + // the next buffer. + // + // Direct2DLowLevelGraphicsContext::startFrame checks if there are any areas to be painted and if the + // renderer is ready to go; if so, startFrame allocates any needed Direct2D resources, + // and calls ID2D1DeviceContext::BeginDraw + // + // handlePaint() makes various calls into the Direct2DLowLevelGraphicsContext which in turn calls + // the appropriate ID2D1DeviceContext functions to draw rectangles, clip, set the fill color, etc. + // + // Direct2DLowLevelGraphicsContext::endFrame calls ID2D1DeviceContext::EndDraw to finish painting + // and then tells the swap chain to present the next swap chain back buffer. + direct2DContext->setPhysicalPixelScaleFactor ((float) peer.getPlatformScaleFactor()); + + if (direct2DContext->startFrame()) + { + direct2DContext->addTransform (AffineTransform::scale ((float) peer.getPlatformScaleFactor())); + peer.handlePaint (*direct2DContext); + direct2DContext->endFrame(); + + #if JUCE_DIRECT2D_METRICS + if (lastPaintStartTicks > 0) + { + direct2DContext->metrics->addValueTicks (Direct2DMetrics::messageThreadPaintDuration, + Time::getHighResolutionTicks() - paintStartTicks); + direct2DContext->metrics->addValueTicks (Direct2DMetrics::frameInterval, paintStartTicks - lastPaintStartTicks); + } + lastPaintStartTicks = paintStartTicks; + #endif + } + } + + HWNDComponentPeer& peer; + + std::unique_ptr direct2DContext; + + #if JUCE_ETW_TRACELOGGING + struct ETWEventProvider + { + ETWEventProvider() + { + const auto hr = TraceLoggingRegister (::juce::etw::JUCETraceLogProvider); + jassertquiet (SUCCEEDED (hr)); + } + + ~ETWEventProvider() + { + TraceLoggingUnregister (::juce::etw::JUCETraceLogProvider); + } + }; + + SharedResourcePointer etwEventProvider; + #endif + + #if JUCE_DIRECT2D_METRICS + int64 lastPaintStartTicks = 0; + #endif +}; + +using Constructor = std::unique_ptr (*) (HWNDComponentPeer&); +struct ContextDescriptor +{ + const char* name = nullptr; + Constructor construct = nullptr; +}; + +template +inline constexpr ContextDescriptor contextDescriptorList[] +{ + { + T::name, + [] (HWNDComponentPeer& p) -> std::unique_ptr { return std::make_unique (p); } + }... +}; + +// To add a new rendering backend, implement RenderContext for that backend, and then append the backend to this typelist +inline constexpr auto& contextDescriptors = contextDescriptorList; + +void HWNDComponentPeer::setCurrentRenderingEngine (int e) +{ + if (isPositiveAndBelow (e, std::size (contextDescriptors)) && (renderContext == nullptr || getCurrentRenderingEngine() != e)) + renderContext = contextDescriptors[e].construct (*this); +} + +StringArray HWNDComponentPeer::getAvailableRenderingEngines() +{ + StringArray results; + + for (const auto& d : contextDescriptors) + results.add (d.name); + + return results; +} + +int HWNDComponentPeer::getCurrentRenderingEngine() const +{ + jassert (renderContext != nullptr); + + for (const auto [index, d] : enumerate (contextDescriptors, int{})) + if (d.name == renderContext->getName()) + return index; + + return -1; +} + +JUCE_API ComponentPeer* createNonRepaintingEmbeddedWindowsPeer (Component& component, Component* parentComponent); +JUCE_API ComponentPeer* createNonRepaintingEmbeddedWindowsPeer (Component& component, Component* parentComponent) +{ + if (auto parentPeer = parentComponent->getPeer()) + { + // Explicitly set the top-level window to software renderer mode in case + // this is switching from Direct2D to OpenGL + // + // HWNDComponentPeer and Direct2DComponentPeer rely on virtual methods for initialization; hence the call to + // embeddedWindowPeer->initialise() after creating the peer + int styleFlags = ComponentPeer::windowIgnoresMouseClicks; + return new HWNDComponentPeer (component, + styleFlags, + (HWND) parentPeer->getNativeHandle(), + true, /* nonRepainting*/ + 0); + } + + return nullptr; } JUCE_IMPLEMENT_SINGLETON (HWNDComponentPeer::WindowClassHolder) @@ -4576,7 +4969,7 @@ Point MouseInputSource::getCurrentRawMousePosition() POINT mousePos; GetCursorPos (&mousePos); - auto p = pointFromPOINT (mousePos); + auto p = D2DUtilities::toPoint (mousePos); if (isPerMonitorDPIAwareThread()) p = Desktop::getInstance().getDisplays().physicalToLogical (p); @@ -4593,7 +4986,7 @@ void MouseInputSource::setRawMousePosition (Point newPosition) newPositionInt = Desktop::getInstance().getDisplays().logicalToPhysical (newPositionInt); #endif - auto point = POINTFromPoint (newPositionInt); + auto point = D2DUtilities::toPOINT (newPositionInt); SetCursorPos (point.x, point.y); } @@ -4731,7 +5124,7 @@ static const Displays::Display* getCurrentDisplayFromScaleFactor (HWND hwnd) if (auto* peer = HWNDComponentPeer::getOwnerOfWindow (hwnd)) return peer->getComponent().getTopLevelComponent()->getBounds(); - return Desktop::getInstance().getDisplays().physicalToLogical (rectangleFromRECT (getWindowScreenRect (hwnd))); + return Desktop::getInstance().getDisplays().physicalToLogical (D2DUtilities::toRectangle (getWindowScreenRect (hwnd))); }(); const Displays::Display* retVal = nullptr; @@ -4759,22 +5152,24 @@ static const Displays::Display* getCurrentDisplayFromScaleFactor (HWND hwnd) //============================================================================== struct MonitorInfo { - MonitorInfo (bool main, RECT totalArea, RECT workArea, double d) noexcept + MonitorInfo (bool main, RECT totalArea, RECT workArea, double d, std::optional frequency) noexcept : isMain (main), totalAreaRect (totalArea), workAreaRect (workArea), - dpi (d) + dpi (d), + verticalFrequencyHz (frequency) { } bool isMain; RECT totalAreaRect, workAreaRect; double dpi; + std::optional verticalFrequencyHz; }; static BOOL CALLBACK enumMonitorsProc (HMONITOR hm, HDC, LPRECT, LPARAM userInfo) { - MONITORINFO info = {}; + MONITORINFOEX info = {}; info.cbSize = sizeof (info); GetMonitorInfo (hm, &info); @@ -4789,7 +5184,30 @@ static BOOL CALLBACK enumMonitorsProc (HMONITOR hm, HDC, LPRECT, LPARAM userInfo dpi = (dpiX + dpiY) / 2.0; } - ((Array*) userInfo)->add ({ isMain, info.rcMonitor, info.rcWork, dpi }); + // Call EnumDisplayDevices and EnumDisplaySettings to get the refresh rate of the monitor + BOOL ok = TRUE; + std::optional frequency; + for (uint32 deviceNumber = 0; ok; ++deviceNumber) + { + DISPLAY_DEVICEW displayDevice{}; + displayDevice.cb = sizeof (displayDevice); + ok = EnumDisplayDevicesW (nullptr, deviceNumber, &displayDevice, 0); + if (ok && (displayDevice.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)) + { + DEVMODE displaySettings{}; + ok = EnumDisplaySettingsW (displayDevice.DeviceName, ENUM_CURRENT_SETTINGS, &displaySettings); + if (ok) + { + if (String { displayDevice.DeviceName } == String { info.szDevice }) + { + frequency = (double) displaySettings.dmDisplayFrequency; + break; + } + } + } + } + + ((Array*) userInfo)->add ({ isMain, info.rcMonitor, info.rcWork, dpi, frequency }); return TRUE; } @@ -4805,7 +5223,7 @@ void Displays::findDisplays (float masterScale) if (monitors.size() == 0) { auto windowRect = getWindowScreenRect (GetDesktopWindow()); - monitors.add ({ true, windowRect, windowRect, globalDPI }); + monitors.add ({ true, windowRect, windowRect, globalDPI, std::optional{} }); } // make sure the first in the list is the main monitor @@ -4830,8 +5248,8 @@ void Displays::findDisplays (float masterScale) d.scale = (d.dpi / USER_DEFAULT_SCREEN_DPI) * (masterScale / Desktop::getDefaultMasterScale()); } - d.totalArea = rectangleFromRECT (monitor.totalAreaRect); - d.userArea = rectangleFromRECT (monitor.workAreaRect); + d.totalArea = D2DUtilities::toRectangle (monitor.totalAreaRect); + d.userArea = D2DUtilities::toRectangle (monitor.workAreaRect); displays.add (d); } diff --git a/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp b/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp index db1e16a707..eff7e63882 100644 --- a/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp +++ b/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp @@ -168,6 +168,8 @@ void ComponentPeer::handlePaint (LowLevelGraphicsContext& contextToPaintTo) mess up a lot of the calculations that the library needs to do. */ jassert (roundToInt (10.1f) == 10); + + ++peerFrameNumber; } Component* ComponentPeer::getTargetForKeyPress() diff --git a/modules/juce_gui_basics/windows/juce_ComponentPeer.h b/modules/juce_gui_basics/windows/juce_ComponentPeer.h index ac48637b4f..8d266945b3 100644 --- a/modules/juce_gui_basics/windows/juce_ComponentPeer.h +++ b/modules/juce_gui_basics/windows/juce_ComponentPeer.h @@ -322,7 +322,10 @@ public: virtual void handleScreenSizeChange(); //============================================================================== - /** This is called to repaint the component into the given context. */ + /** This is called to repaint the component into the given context. + + Increments the result of getNumFramesPainted(). + */ void handlePaint (LowLevelGraphicsContext& contextToPaintTo); //============================================================================== @@ -561,6 +564,13 @@ public: /** Returns the style requested for this app. */ Style getAppStyle() const { return style; } + /** Returns the number of times that this peer has been painted. + + This is mainly useful when debugging component painting. For example, you might use this to + match logging calls to specific frames. + */ + uint64_t getNumFramesPainted() const { return peerFrameNumber; } + protected: //============================================================================== static void forceDisplayUpdate(); @@ -603,6 +613,7 @@ private: Component* lastDragAndDropCompUnderMouse = nullptr; TextInputTarget* textInputTarget = nullptr; const uint32 uniqueID; + uint64_t peerFrameNumber = 0; bool isWindowMinimised = false; //============================================================================== diff --git a/modules/juce_gui_extra/native/juce_HWNDComponent_windows.cpp b/modules/juce_gui_extra/native/juce_HWNDComponent_windows.cpp index 708892cf60..43d603d184 100644 --- a/modules/juce_gui_extra/native/juce_HWNDComponent_windows.cpp +++ b/modules/juce_gui_extra/native/juce_HWNDComponent_windows.cpp @@ -67,6 +67,8 @@ public: ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { hwnd }; SetWindowPos (hwnd, nullptr, area.getX(), area.getY(), area.getWidth(), area.getHeight(), flagsToSend); + + invalidateHWNDAndChildren(); } } @@ -89,7 +91,7 @@ public: ShowWindow (hwnd, isShowing ? SW_SHOWNA : SW_HIDE); if (isShowing) - InvalidateRect (hwnd, nullptr, 0); + InvalidateRect (hwnd, nullptr, TRUE); } void componentVisibilityChanged() override @@ -120,6 +122,17 @@ public: return {}; } + void invalidateHWNDAndChildren() + { + EnumChildWindows (hwnd, invalidateHwndCallback, 0); + } + + static BOOL WINAPI invalidateHwndCallback (HWND hwnd, LPARAM) + { + InvalidateRect (hwnd, nullptr, TRUE); + return TRUE; + } + HWND hwnd; private: @@ -127,14 +140,14 @@ private: { if (currentPeer != nullptr) { - auto windowFlags = GetWindowLongPtr (hwnd, -16); + auto windowFlags = GetWindowLongPtr (hwnd, GWL_STYLE); using FlagType = decltype (windowFlags); windowFlags &= ~(FlagType) WS_POPUP; windowFlags |= (FlagType) WS_CHILD; - SetWindowLongPtr (hwnd, -16, windowFlags); + SetWindowLongPtr (hwnd, GWL_STYLE, windowFlags); SetParent (hwnd, (HWND) currentPeer->getNativeHandle()); componentMovedOrResized (true, true); diff --git a/modules/juce_opengl/native/juce_OpenGL_windows.h b/modules/juce_opengl/native/juce_OpenGL_windows.h index d674dc2440..e6b4363b0a 100644 --- a/modules/juce_opengl/native/juce_OpenGL_windows.h +++ b/modules/juce_opengl/native/juce_OpenGL_windows.h @@ -35,7 +35,7 @@ namespace juce { -extern ComponentPeer* createNonRepaintingEmbeddedWindowsPeer (Component&, void* parent); +extern ComponentPeer* createNonRepaintingEmbeddedWindowsPeer (Component&, Component* parent); //============================================================================== class OpenGLContext::NativeContext : private ComponentPeer::ScaleFactorListener @@ -295,7 +295,7 @@ private: auto* parentHWND = topComp->getWindowHandle(); ScopedThreadDPIAwarenessSetter setter { parentHWND }; - nativeWindow.reset (createNonRepaintingEmbeddedWindowsPeer (*dummyComponent, parentHWND)); + nativeWindow.reset (createNonRepaintingEmbeddedWindowsPeer (*dummyComponent, topComp)); } if (auto* peer = topComp->getPeer())