From d9193d5368990bead256d678fe9a8f3f8edaa691 Mon Sep 17 00:00:00 2001 From: Artur Wieczorek Date: Fri, 12 Aug 2016 21:23:02 +0200 Subject: [PATCH] Fix saving/restoring drawing state for wxGraphicsContext with Direct2D Native ID2D1RenderTarget::SaveDrawingState method stores in ID2D1DrawingStateBlock only transform, antialiasing mode, text-rendering options and tags but not currently set ID2D1Layers (which are used in wxD2DContext for clipping purposes). Therefore current stack of layers has to be stored "manually" alongside ID2D1DrawingStateBlock in a dedicated data structure (LayerData) in PushState() and restored also "manually" in PopState(). Closes #17626. --- interface/wx/graphics.h | 4 +- src/msw/graphicsd2d.cpp | 103 +++++++++++++++++++++++++++++++++++----- 2 files changed, 93 insertions(+), 14 deletions(-) diff --git a/interface/wx/graphics.h b/interface/wx/graphics.h index a19cf5e457..4137f9f071 100644 --- a/interface/wx/graphics.h +++ b/interface/wx/graphics.h @@ -954,8 +954,8 @@ public: virtual void EndLayer() = 0; /** - Push the current state of the context (eg. transformation matrix) on a - stack. + Push the current state (like transformations, clipping region and quality + settings) of the context on a stack. Multiple balanced calls to PushState() and PopState() can be nested. @see PopState() diff --git a/src/msw/graphicsd2d.cpp b/src/msw/graphicsd2d.cpp index ed112ef480..f655640cbc 100644 --- a/src/msw/graphicsd2d.cpp +++ b/src/msw/graphicsd2d.cpp @@ -3424,6 +3424,17 @@ private: D2D1_LAYER_PARAMETERS params; wxCOMPtr layer; wxCOMPtr geometry; + D2D1_MATRIX_3X2_F transformMatrix; + }; + + struct StateData + { + // A ID2D1DrawingStateBlock represents the drawing state of a render target: + // the anti aliasing mode, transform, tags, and text-rendering options. + // The context owns these pointers and is responsible for releasing them. + wxCOMPtr drawingState; + // We need to store also current layers. + wxStack layers; }; private: @@ -3431,10 +3442,7 @@ private: wxSharedPtr m_renderTargetHolder; - // A ID2D1DrawingStateBlock represents the drawing state of a render target: - // the anti aliasing mode, transform, tags, and text-rendering options. - // The context owns these pointers and is responsible for releasing them. - wxStack > m_stateStack; + wxStack m_stateStack; wxStack m_layers; @@ -3545,6 +3553,8 @@ void wxD2DContext::Clip(wxDouble x, wxDouble y, wxDouble w, wxDouble h) void wxD2DContext::SetClipLayer(ID2D1Geometry* clipGeometry) { + EnsureInitialized(); + wxCOMPtr clipLayer; HRESULT hr = GetRenderTarget()->CreateLayer(&clipLayer); wxCHECK_HRESULT_RET(hr); @@ -3555,6 +3565,9 @@ void wxD2DContext::SetClipLayer(ID2D1Geometry* clipGeometry) wxD2DConvertAntialiasMode(m_antialias)); ld.layer = clipLayer; ld.geometry = clipGeometry; + // We need to store CTM to be able to re-apply + // the layer at the original position later on. + GetRenderTarget()->GetTransform(&ld.transformMatrix); GetRenderTarget()->PushLayer(ld.params, clipLayer); // Store layer parameters. @@ -3587,15 +3600,22 @@ void wxD2DContext::ResetClip() wxCHECK_HRESULT_RET(hr); // Re-apply all remaining non-clipping layers. + // First, save current transformation matrix. + D2D1_MATRIX_3X2_F currTransform; + GetRenderTarget()->GetTransform(&currTransform); while ( !layersToRestore.empty() ) { LayerData ld = layersToRestore.top(); layersToRestore.pop(); + // Restore layer at original position. + GetRenderTarget()->SetTransform(&ld.transformMatrix); GetRenderTarget()->PushLayer(ld.params, ld.layer); // Store layer parameters. m_layers.push(ld); } + // Restore current transformation matrix. + GetRenderTarget()->SetTransform(&currTransform); } void* wxD2DContext::GetNativeContext() @@ -3684,6 +3704,8 @@ bool wxD2DContext::SetCompositionMode(wxCompositionMode compositionMode) void wxD2DContext::BeginLayer(wxDouble opacity) { + EnsureInitialized(); + wxCOMPtr layer; HRESULT hr = GetRenderTarget()->CreateLayer(&layer); wxCHECK_HRESULT_RET(hr); @@ -3695,6 +3717,9 @@ void wxD2DContext::BeginLayer(wxDouble opacity) D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, D2D1::IdentityMatrix(), opacity); ld.layer = layer; + // We need to store CTM to be able to re-apply + // the layer at the original position later on. + GetRenderTarget()->GetTransform(&ld.transformMatrix); GetRenderTarget()->PushLayer(ld.params, layer); @@ -3733,6 +3758,9 @@ void wxD2DContext::EndLayer() } // Re-apply all stored clipping layers. + // First, save current transformation matrix. + D2D1_MATRIX_3X2_F currTransform; + GetRenderTarget()->GetTransform(&currTransform); while ( !layersToRestore.empty() ) { LayerData ld = layersToRestore.top(); @@ -3740,6 +3768,8 @@ void wxD2DContext::EndLayer() if ( ld.type == CLIP_LAYER ) { + // Restore layer at original position. + GetRenderTarget()->SetTransform(&ld.transformMatrix); GetRenderTarget()->PushLayer(ld.params, ld.layer); } else @@ -3749,6 +3779,8 @@ void wxD2DContext::EndLayer() // Store layer parameters. m_layers.push(ld); } + // Restore current transformation matrix. + GetRenderTarget()->SetTransform(&currTransform); } void wxD2DContext::Translate(wxDouble dx, wxDouble dy) @@ -3855,23 +3887,63 @@ void wxD2DContext::DrawIcon(const wxIcon& icon, wxDouble x, wxDouble y, wxDouble void wxD2DContext::PushState() { - ID2D1Factory* wxGetD2DFactory(wxGraphicsRenderer* renderer); + EnsureInitialized(); - wxCOMPtr drawStateBlock; - wxGetD2DFactory(GetRenderer())->CreateDrawingStateBlock(&drawStateBlock); - GetRenderTarget()->SaveDrawingState(drawStateBlock); + StateData state; + m_direct2dFactory->CreateDrawingStateBlock(&state.drawingState); + GetRenderTarget()->SaveDrawingState(state.drawingState); + state.layers = m_layers; - m_stateStack.push(drawStateBlock); + m_stateStack.push(state); } void wxD2DContext::PopState() { - wxCHECK_RET(!m_stateStack.empty(), wxT("No state to pop")); + wxCHECK_RET(!m_stateStack.empty(), wxS("No state to pop")); - wxCOMPtr drawStateBlock = m_stateStack.top(); + // Remove all layers from the stack of layers. + while ( !m_layers.empty() ) + { + LayerData ld = m_layers.top(); + m_layers.pop(); + + GetRenderTarget()->PopLayer(); + ld.layer.reset(); + ld.geometry.reset(); + } + + // Retrieve state data. + StateData state; + state = m_stateStack.top(); m_stateStack.pop(); - GetRenderTarget()->RestoreDrawingState(drawStateBlock); + // Restore all saved layers. + wxStack layersToRestore; + // We have to restore layers on the stack from "bottom" to "top", + // so we have to create a "reverted" stack. + while ( !state.layers.empty() ) + { + LayerData ld = state.layers.top(); + state.layers.pop(); + + layersToRestore.push(ld); + } + // And next set layers from the top of "reverted" stack. + while ( !layersToRestore.empty() ) + { + LayerData ld = layersToRestore.top(); + layersToRestore.pop(); + + // Restore layer at original position. + GetRenderTarget()->SetTransform(&ld.transformMatrix); + GetRenderTarget()->PushLayer(ld.params, ld.layer); + + // Store layer parameters. + m_layers.push(ld); + } + + // Restore drawing state. + GetRenderTarget()->RestoreDrawingState(state.drawingState); } void wxD2DContext::GetTextExtent( @@ -4090,16 +4162,23 @@ void wxD2DContext::Flush() } // Re-apply all layers. + // First, save current transformation matrix. + D2D1_MATRIX_3X2_F currTransform; + GetRenderTarget()->GetTransform(&currTransform); while ( !layersToRestore.empty() ) { LayerData ld = layersToRestore.top(); layersToRestore.pop(); + // Restore layer at original position. + GetRenderTarget()->SetTransform(&ld.transformMatrix); GetRenderTarget()->PushLayer(ld.params, ld.layer); // Store layer parameters. m_layers.push(ld); } + // Restore current transformation matrix. + GetRenderTarget()->SetTransform(&currTransform); } void wxD2DContext::GetDPI(wxDouble* dpiX, wxDouble* dpiY)