From 832db47346cd581e967a97384b4346328586b230 Mon Sep 17 00:00:00 2001 From: Artur Wieczorek Date: Tue, 5 Jul 2016 21:21:18 +0200 Subject: [PATCH] Fixed layer management in Direct2D renderer. ID2D1RenderTarget::PushAxisAlignedClip/PopAxisAlignedClip used to clip the region (with wxGraphicsRenderer::Clip) and ID2D1RenderTarget::PushLayer/PopLayer used to rendering to the transparent layer (with wxGraphicsRenderer::BeginLayer) are non independent but have to be used in the controlled sequences: "A PushAxisAlignedClip and PopAxisAlignedClip pair can occur around or within a PushLayer and PopLayer, but cannot overlap" (and of course finally each Push* call must have a matching Pop* call). To control the sequence of access to the AxisAlignedClips and Layers there is implemented a wxStack data member holding Clips/Layers parameters which reflects a physical stack of respective Clips/Layers in ID2D1RenderTarget. This way we know in which order to pop and what to pop from ID2D1RenderTarget stack if there is a need to do so. Closes #17590 --- interface/wx/dc.h | 10 +- interface/wx/graphics.h | 16 ++- src/msw/graphicsd2d.cpp | 237 ++++++++++++++++++++++++++++++---------- 3 files changed, 200 insertions(+), 63 deletions(-) diff --git a/interface/wx/dc.h b/interface/wx/dc.h index 748e0a7812..2def4d79f3 100644 --- a/interface/wx/dc.h +++ b/interface/wx/dc.h @@ -110,9 +110,9 @@ struct wxFontMetrics abstract API for drawing on any of them. wxWidgets offers an alternative drawing API based on the modern drawing - backends GDI+, CoreGraphics and Cairo. See wxGraphicsContext, wxGraphicsRenderer - and related classes. There is also a wxGCDC linking the APIs by offering - the wxDC API on top of a wxGraphicsContext. + backends GDI+, CoreGraphics, Cairo and Direct2D. See wxGraphicsContext, + wxGraphicsRenderer and related classes. There is also a wxGCDC linking + the APIs by offering the wxDC API on top of a wxGraphicsContext. wxDC is an abstract base class and cannot be created directly. Use wxPaintDC, wxClientDC, wxWindowDC, wxScreenDC, wxMemoryDC or @@ -770,10 +770,10 @@ public: window redraws when only a known area of the screen is damaged. @remarks - - Calling GetClippingBox() can only make the clipping region smaller, + - Calling this function can only make the clipping region smaller, never larger. - - You need to call DestroyClippingRegion() if you want to set + - You need to call DestroyClippingRegion() first if you want to set the clipping region exactly to the region specified. - If resulting clipping region is empty, then all drawing on the DC is diff --git a/interface/wx/graphics.h b/interface/wx/graphics.h index 327ada51d8..83ec2846ad 100644 --- a/interface/wx/graphics.h +++ b/interface/wx/graphics.h @@ -451,12 +451,24 @@ public: static wxGraphicsContext* Create(); /** - Clips drawings to the specified region. + Sets the clipping region to the intersection of the given region + and the previously set clipping region. + The clipping region is an area to which drawing is restricted. + + @remarks + - Calling this function can only make the clipping region smaller, + never larger. + + - You need to call ResetClip() first if you want to set the clipping + region exactly to the region specified. + + - If resulting clipping region is empty, then all drawing upon the context + is clipped out (all changes made by drawing operations are masked out). */ virtual void Clip(const wxRegion& region) = 0; /** - Clips drawings to the specified rectangle. + @overload */ virtual void Clip(wxDouble x, wxDouble y, wxDouble w, wxDouble h) = 0; diff --git a/src/msw/graphicsd2d.cpp b/src/msw/graphicsd2d.cpp index ac6dde7d33..632cca4ffe 100644 --- a/src/msw/graphicsd2d.cpp +++ b/src/msw/graphicsd2d.cpp @@ -3388,11 +3388,18 @@ private: ID2D1RenderTarget* GetRenderTarget() const; private: - enum ClipMode + enum LayerType { - CLIP_MODE_NONE, - CLIP_MODE_AXIS_ALIGNED_RECTANGLE, - CLIP_MODE_GEOMETRY + CLIP_AXIS_ALIGNED_RECT, + CLIP_LAYER, + OTHER_LAYER + }; + + struct LayerData + { + LayerType type; + D2D1_LAYER_PARAMETERS params; + wxCOMPtr layer; }; private: @@ -3405,14 +3412,7 @@ private: // The context owns these pointers and is responsible for releasing them. wxStack > m_stateStack; - ClipMode m_clipMode; - - bool m_clipLayerAcquired; - - // A direct2d layer is a device-dependent resource. - wxCOMPtr m_clipLayer; - - wxStack > m_layers; + wxStack m_layers; ID2D1RenderTarget* m_cachedRenderTarget; @@ -3472,9 +3472,7 @@ wxD2DContext::wxD2DContext(wxGraphicsRenderer* renderer, ID2D1Factory* direct2dF void wxD2DContext::Init() { m_cachedRenderTarget = NULL; - m_clipMode = CLIP_MODE_NONE; m_composition = wxCOMPOSITION_OVER; - m_clipLayerAcquired = false; m_renderTargetHolder->Bind(this); m_enableOffset = true; EnsureInitialized(); @@ -3482,11 +3480,20 @@ void wxD2DContext::Init() wxD2DContext::~wxD2DContext() { - ResetClip(); - - while (!m_layers.empty()) + // Remove all layers from the stack of layers. + while ( !m_layers.empty() ) { - EndLayer(); + LayerData ld = m_layers.top(); + m_layers.pop(); + + if ( ld.type == CLIP_AXIS_ALIGNED_RECT ) + { + GetRenderTarget()->PopAxisAlignedClip(); + } + else + { + GetRenderTarget()->PopLayer(); + } } HRESULT result = GetRenderTarget()->EndDraw(); @@ -3502,47 +3509,74 @@ ID2D1RenderTarget* wxD2DContext::GetRenderTarget() const void wxD2DContext::Clip(const wxRegion& region) { - GetRenderTarget()->Flush(); - ResetClip(); - wxCOMPtr clipGeometry = wxD2DConvertRegionToGeometry(m_direct2dFactory, region); - if (!m_clipLayerAcquired) - { - GetRenderTarget()->CreateLayer(&m_clipLayer); - m_clipLayerAcquired = true; - } + wxCOMPtr clipLayer; + GetRenderTarget()->CreateLayer(&clipLayer); - GetRenderTarget()->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), clipGeometry), m_clipLayer); + LayerData ld; + ld.type = CLIP_LAYER; + ld.params = D2D1::LayerParameters(D2D1::InfiniteRect(), clipGeometry); + ld.layer = clipLayer; - m_clipMode = CLIP_MODE_GEOMETRY; + GetRenderTarget()->PushLayer(ld.params, clipLayer); + // Store layer parameters. + m_layers.push(ld); } void wxD2DContext::Clip(wxDouble x, wxDouble y, wxDouble w, wxDouble h) { - GetRenderTarget()->Flush(); - ResetClip(); + LayerData ld; + ld.type = CLIP_AXIS_ALIGNED_RECT; + ld.params = D2D1::LayerParameters(D2D1::RectF(x, y, x + w, y + h), + NULL, D2D1_ANTIALIAS_MODE_ALIASED); - GetRenderTarget()->PushAxisAlignedClip( - D2D1::RectF(x, y, x + w, y + h), - D2D1_ANTIALIAS_MODE_ALIASED); - - m_clipMode = CLIP_MODE_AXIS_ALIGNED_RECTANGLE; + GetRenderTarget()->PushAxisAlignedClip(ld.params.contentBounds, + D2D1_ANTIALIAS_MODE_ALIASED); + // Store layer parameters. + m_layers.push(ld); } void wxD2DContext::ResetClip() { - if (m_clipMode == CLIP_MODE_AXIS_ALIGNED_RECTANGLE) + wxStack layersToRestore; + // Remove all clipping layers from the stack of layers. + while ( !m_layers.empty() ) { - GetRenderTarget()->PopAxisAlignedClip(); - } + LayerData ld = m_layers.top(); + m_layers.pop(); + + if ( ld.type == CLIP_AXIS_ALIGNED_RECT ) + { + GetRenderTarget()->PopAxisAlignedClip(); + continue; + } + + if ( ld.type == CLIP_LAYER ) + { + GetRenderTarget()->PopLayer(); + ld.layer.reset(); + continue; + } - if (m_clipMode == CLIP_MODE_GEOMETRY) - { GetRenderTarget()->PopLayer(); + // Save non-clipping layer + layersToRestore.push(ld); } - m_clipMode = CLIP_MODE_NONE; + HRESULT hr = GetRenderTarget()->Flush(); + wxCHECK_HRESULT_RET(hr); + + // Re-apply all remaining non-clipping layers. + while ( !layersToRestore.empty() ) + { + LayerData ld = layersToRestore.top(); + layersToRestore.pop(); + + GetRenderTarget()->PushLayer(ld.params, ld.layer); + // Store layer parameters. + m_layers.push(ld); + } } void* wxD2DContext::GetNativeContext() @@ -3633,28 +3667,79 @@ void wxD2DContext::BeginLayer(wxDouble opacity) { wxCOMPtr layer; GetRenderTarget()->CreateLayer(&layer); - m_layers.push(layer); - GetRenderTarget()->PushLayer( - D2D1::LayerParameters(D2D1::InfiniteRect(), - NULL, - D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, - D2D1::IdentityMatrix(), opacity), - layer); + LayerData ld; + ld.type = OTHER_LAYER; + ld.params = D2D1::LayerParameters(D2D1::InfiniteRect(), + NULL, + D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, + D2D1::IdentityMatrix(), opacity); + ld.layer = layer; + + GetRenderTarget()->PushLayer(ld.params, layer); + + // Store layer parameters. + m_layers.push(ld); } void wxD2DContext::EndLayer() { - if (!m_layers.empty()) + wxStack layersToRestore; + // Temporarily remove all clipping layers + // above the first standard layer + // and next permanently remove this layer. + while ( !m_layers.empty() ) { - wxCOMPtr topLayer = m_layers.top(); + LayerData ld = m_layers.top(); + m_layers.pop(); + if ( ld.type == CLIP_AXIS_ALIGNED_RECT ) + { + GetRenderTarget()->PopAxisAlignedClip(); + layersToRestore.push(ld); + continue; + } + + if ( ld.type == CLIP_LAYER ) + { + GetRenderTarget()->PopLayer(); + layersToRestore.push(ld); + continue; + } + + // We found a non-clipping layer to remove. GetRenderTarget()->PopLayer(); + ld.layer.reset(); + break; + } + if ( m_layers.empty() ) + { HRESULT hr = GetRenderTarget()->Flush(); wxCHECK_HRESULT_RET(hr); + } - m_layers.pop(); + // Re-apply all stored clipping layers. + while ( !layersToRestore.empty() ) + { + LayerData ld = layersToRestore.top(); + layersToRestore.pop(); + + if ( ld.type == CLIP_AXIS_ALIGNED_RECT ) + { + GetRenderTarget()->PushAxisAlignedClip(ld.params.contentBounds, + ld.params.maskAntialiasMode); + } + else if ( ld.type == CLIP_LAYER ) + { + GetRenderTarget()->PushLayer(ld.params, ld.layer); + } + else + { + wxFAIL_MSG( wxS("Invalid layer type") ); + } + // Store layer parameters. + m_layers.push(ld); } } @@ -3880,9 +3965,6 @@ void wxD2DContext::AdjustRenderTargetSize() void wxD2DContext::ReleaseDeviceDependentResources() { ReleaseResources(); - - m_clipLayer.reset(); - m_clipLayerAcquired = false; } void wxD2DContext::DrawRectangle(wxDouble x, wxDouble y, wxDouble w, wxDouble h) @@ -3975,12 +4057,55 @@ void wxD2DContext::DrawEllipse(wxDouble x, wxDouble y, wxDouble w, wxDouble h) void wxD2DContext::Flush() { - HRESULT result = m_renderTargetHolder->Flush(); + wxStack layersToRestore; + // Temporarily remove all layers from the stack of layers. + while ( !m_layers.empty() ) + { + LayerData ld = m_layers.top(); + m_layers.pop(); - if (result == (HRESULT)D2DERR_RECREATE_TARGET) + if ( ld.type == CLIP_AXIS_ALIGNED_RECT ) + { + GetRenderTarget()->PopAxisAlignedClip(); + } + else + { + GetRenderTarget()->PopLayer(); + } + + // Save layer data. + layersToRestore.push(ld); + } + + HRESULT hr = m_renderTargetHolder->Flush(); + + if ( hr == (HRESULT)D2DERR_RECREATE_TARGET ) { ReleaseDeviceDependentResources(); } + else + { + wxCHECK_HRESULT_RET(hr); + } + + // Re-apply all layers. + while ( !layersToRestore.empty() ) + { + LayerData ld = layersToRestore.top(); + layersToRestore.pop(); + + if ( ld.type == CLIP_AXIS_ALIGNED_RECT ) + { + GetRenderTarget()->PushAxisAlignedClip(ld.params.contentBounds, + ld.params.maskAntialiasMode); + } + else + { + GetRenderTarget()->PushLayer(ld.params, ld.layer); + } + // Store layer parameters. + m_layers.push(ld); + } } void wxD2DContext::GetDPI(wxDouble* dpiX, wxDouble* dpiY)