From 137d9820b1c8bd3d69e624f795d548ddc9933db8 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 17 Apr 2025 14:03:09 +0100 Subject: [PATCH] Direct2D: Move D2DHelpers to Direct2DGraphicsContext.cpp --- .../juce_Direct2DGraphicsContext_windows.cpp | 232 ++++++++++++++++++ .../native/juce_DirectX_windows.cpp | 229 ----------------- .../native/juce_DirectX_windows.h | 35 --- 3 files changed, 232 insertions(+), 264 deletions(-) diff --git a/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp b/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp index 762601054b..e05a643717 100644 --- a/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp +++ b/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp @@ -35,6 +35,238 @@ namespace juce { +//============================================================================== +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) + { + class ScopedFigure + { + public: + ScopedFigure (ID2D1GeometrySink* s, D2D1_POINT_2F pt, D2D1_FIGURE_BEGIN mode) + : sink (s) + { + sink->BeginFigure (pt, mode); + } + + ~ScopedFigure() + { + if (sink != nullptr) + sink->EndFigure (end); + } + + void setClosed() + { + end = D2D1_FIGURE_END_CLOSED; + } + + private: + ID2D1GeometrySink* sink = nullptr; + D2D1_FIGURE_END end = D2D1_FIGURE_END_OPEN; + + JUCE_DECLARE_NON_COPYABLE (ScopedFigure) + JUCE_DECLARE_NON_MOVEABLE (ScopedFigure) + }; + + // Every call to BeginFigure must have a matching call to EndFigure. But - the Path does not necessarily + // have matching startNewSubPath and closePath markers. + D2D1_POINT_2F lastLocation{}; + std::optional figure; + Path::Iterator it (path); + + const auto doTransform = [&transform] (float x, float y) + { + transform.transformPoint (x, y); + return D2D1_POINT_2F { x, y }; + }; + + const auto updateFigure = [&] (float x, float y) + { + if (! figure.has_value()) + figure.emplace (sink, lastLocation, figureMode); + + lastLocation = doTransform (x, y); + }; + + while (it.next()) + { + switch (it.elementType) + { + case Path::Iterator::lineTo: + updateFigure (it.x1, it.y1); + sink->AddLine (lastLocation); + break; + + case Path::Iterator::quadraticTo: + updateFigure (it.x2, it.y2); + sink->AddQuadraticBezier ({ doTransform (it.x1, it.y1), lastLocation }); + break; + + case Path::Iterator::cubicTo: + updateFigure (it.x3, it.y3); + sink->AddBezier ({ doTransform (it.x1, it.y1), doTransform (it.x2, it.y2), lastLocation }); + break; + + case Path::Iterator::closePath: + if (figure.has_value()) + figure->setClosed(); + + figure.reset(); + break; + + case Path::Iterator::startNewSubPath: + figure.reset(); + lastLocation = doTransform (it.x1, it.y1); + figure.emplace (sink, lastLocation, figureMode); + break; + } + } + } + + 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; + } +}; + //============================================================================== Direct2DGraphicsContext::Direct2DGraphicsContext() = default; Direct2DGraphicsContext::~Direct2DGraphicsContext() = default; diff --git a/modules/juce_graphics/native/juce_DirectX_windows.cpp b/modules/juce_graphics/native/juce_DirectX_windows.cpp index 8ff413abcd..cfcf98de35 100644 --- a/modules/juce_graphics/native/juce_DirectX_windows.cpp +++ b/modules/juce_graphics/native/juce_DirectX_windows.cpp @@ -58,235 +58,6 @@ ScopedGeometryWithSink::~ScopedGeometryWithSink() jassertquiet (SUCCEEDED (hr)); } -//============================================================================== -bool D2DHelpers::isTransformAxisAligned (const AffineTransform& transform) -{ - return transform.mat01 == 0.0f && transform.mat10 == 0.0f; -} - -void D2DHelpers::pathToGeometrySink (const Path& path, - ID2D1GeometrySink* sink, - const AffineTransform& transform, - D2D1_FIGURE_BEGIN figureMode) -{ - class ScopedFigure - { - public: - ScopedFigure (ID2D1GeometrySink* s, D2D1_POINT_2F pt, D2D1_FIGURE_BEGIN mode) - : sink (s) - { - sink->BeginFigure (pt, mode); - } - - ~ScopedFigure() - { - if (sink != nullptr) - sink->EndFigure (end); - } - - void setClosed() - { - end = D2D1_FIGURE_END_CLOSED; - } - - private: - ID2D1GeometrySink* sink = nullptr; - D2D1_FIGURE_END end = D2D1_FIGURE_END_OPEN; - - JUCE_DECLARE_NON_COPYABLE (ScopedFigure) - JUCE_DECLARE_NON_MOVEABLE (ScopedFigure) - }; - - // Every call to BeginFigure must have a matching call to EndFigure. But - the Path does not necessarily - // have matching startNewSubPath and closePath markers. - D2D1_POINT_2F lastLocation{}; - std::optional figure; - Path::Iterator it (path); - - const auto doTransform = [&transform] (float x, float y) - { - transform.transformPoint (x, y); - return D2D1_POINT_2F { x, y }; - }; - - const auto updateFigure = [&] (float x, float y) - { - if (! figure.has_value()) - figure.emplace (sink, lastLocation, figureMode); - - lastLocation = doTransform (x, y); - }; - - while (it.next()) - { - switch (it.elementType) - { - case Path::Iterator::lineTo: - updateFigure (it.x1, it.y1); - sink->AddLine (lastLocation); - break; - - case Path::Iterator::quadraticTo: - updateFigure (it.x2, it.y2); - sink->AddQuadraticBezier ({ doTransform (it.x1, it.y1), lastLocation }); - break; - - case Path::Iterator::cubicTo: - updateFigure (it.x3, it.y3); - sink->AddBezier ({ doTransform (it.x1, it.y1), doTransform (it.x2, it.y2), lastLocation }); - break; - - case Path::Iterator::closePath: - if (figure.has_value()) - figure->setClosed(); - - figure.reset(); - break; - - case Path::Iterator::startNewSubPath: - figure.reset(); - lastLocation = doTransform (it.x1, it.y1); - figure.emplace (sink, lastLocation, figureMode); - break; - } - } -} - -D2D1_POINT_2F D2DHelpers::pointTransformed (Point pt, const AffineTransform& transform) -{ - transform.transformPoint (pt.x, pt.y); - return { (FLOAT) pt.x, (FLOAT) pt.y }; -} - -void D2DHelpers::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); -} - -ComSmartPtr D2DHelpers::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; -} - -ComSmartPtr D2DHelpers::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; -} - -ComSmartPtr D2DHelpers::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; -} - //============================================================================== void DirectWriteGlyphRun::replace (Span> positions, float scale) { diff --git a/modules/juce_graphics/native/juce_DirectX_windows.h b/modules/juce_graphics/native/juce_DirectX_windows.h index 38fc077e4a..e7bcc7b233 100644 --- a/modules/juce_graphics/native/juce_DirectX_windows.h +++ b/modules/juce_graphics/native/juce_DirectX_windows.h @@ -70,41 +70,6 @@ private: std::unique_ptr, FunctionPointerDestructor> handle; }; -//============================================================================== -struct D2DHelpers -{ - static bool isTransformAxisAligned (const AffineTransform& transform); - - static void pathToGeometrySink (const Path& path, - ID2D1GeometrySink* sink, - const AffineTransform& transform, - D2D1_FIGURE_BEGIN figureMode); - - static D2D1_POINT_2F pointTransformed (Point pt, const AffineTransform& transform); - - static void rectToGeometrySink (const Rectangle& rect, - ID2D1GeometrySink* sink, - const AffineTransform& transform, - D2D1_FIGURE_BEGIN figureMode); - - static ComSmartPtr rectListToPathGeometry (ID2D1Factory* factory, - const RectangleList& clipRegion, - const AffineTransform& transform, - D2D1_FILL_MODE fillMode, - D2D1_FIGURE_BEGIN figureMode, - [[maybe_unused]] Direct2DMetrics* metrics); - - static ComSmartPtr pathToPathGeometry (ID2D1Factory* factory, - const Path& path, - const AffineTransform& transform, - D2D1_FIGURE_BEGIN figureMode, - [[maybe_unused]] Direct2DMetrics* metrics); - - static ComSmartPtr pathStrokeTypeToStrokeStyle (ID2D1Factory1* factory, - const PathStrokeType& strokeType); - -}; - //============================================================================== /** Heap storage for a DirectWrite glyph run */ class DirectWriteGlyphRun