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
This commit is contained in:
Artur Wieczorek
2016-07-05 21:21:18 +02:00
parent 6b7ae05e3f
commit 832db47346
3 changed files with 200 additions and 63 deletions

View File

@@ -110,9 +110,9 @@ struct wxFontMetrics
abstract API for drawing on any of them. abstract API for drawing on any of them.
wxWidgets offers an alternative drawing API based on the modern drawing wxWidgets offers an alternative drawing API based on the modern drawing
backends GDI+, CoreGraphics and Cairo. See wxGraphicsContext, wxGraphicsRenderer backends GDI+, CoreGraphics, Cairo and Direct2D. See wxGraphicsContext,
and related classes. There is also a wxGCDC linking the APIs by offering wxGraphicsRenderer and related classes. There is also a wxGCDC linking
the wxDC API on top of a wxGraphicsContext. the APIs by offering the wxDC API on top of a wxGraphicsContext.
wxDC is an abstract base class and cannot be created directly. wxDC is an abstract base class and cannot be created directly.
Use wxPaintDC, wxClientDC, wxWindowDC, wxScreenDC, wxMemoryDC or Use wxPaintDC, wxClientDC, wxWindowDC, wxScreenDC, wxMemoryDC or
@@ -770,10 +770,10 @@ public:
window redraws when only a known area of the screen is damaged. window redraws when only a known area of the screen is damaged.
@remarks @remarks
- Calling GetClippingBox() can only make the clipping region smaller, - Calling this function can only make the clipping region smaller,
never larger. 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. the clipping region exactly to the region specified.
- If resulting clipping region is empty, then all drawing on the DC is - If resulting clipping region is empty, then all drawing on the DC is

View File

@@ -451,12 +451,24 @@ public:
static wxGraphicsContext* Create(); 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; 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; virtual void Clip(wxDouble x, wxDouble y, wxDouble w, wxDouble h) = 0;

View File

@@ -3388,11 +3388,18 @@ private:
ID2D1RenderTarget* GetRenderTarget() const; ID2D1RenderTarget* GetRenderTarget() const;
private: private:
enum ClipMode enum LayerType
{ {
CLIP_MODE_NONE, CLIP_AXIS_ALIGNED_RECT,
CLIP_MODE_AXIS_ALIGNED_RECTANGLE, CLIP_LAYER,
CLIP_MODE_GEOMETRY OTHER_LAYER
};
struct LayerData
{
LayerType type;
D2D1_LAYER_PARAMETERS params;
wxCOMPtr<ID2D1Layer> layer;
}; };
private: private:
@@ -3405,14 +3412,7 @@ private:
// The context owns these pointers and is responsible for releasing them. // The context owns these pointers and is responsible for releasing them.
wxStack<wxCOMPtr<ID2D1DrawingStateBlock> > m_stateStack; wxStack<wxCOMPtr<ID2D1DrawingStateBlock> > m_stateStack;
ClipMode m_clipMode; wxStack<LayerData> m_layers;
bool m_clipLayerAcquired;
// A direct2d layer is a device-dependent resource.
wxCOMPtr<ID2D1Layer> m_clipLayer;
wxStack<wxCOMPtr<ID2D1Layer> > m_layers;
ID2D1RenderTarget* m_cachedRenderTarget; ID2D1RenderTarget* m_cachedRenderTarget;
@@ -3472,9 +3472,7 @@ wxD2DContext::wxD2DContext(wxGraphicsRenderer* renderer, ID2D1Factory* direct2dF
void wxD2DContext::Init() void wxD2DContext::Init()
{ {
m_cachedRenderTarget = NULL; m_cachedRenderTarget = NULL;
m_clipMode = CLIP_MODE_NONE;
m_composition = wxCOMPOSITION_OVER; m_composition = wxCOMPOSITION_OVER;
m_clipLayerAcquired = false;
m_renderTargetHolder->Bind(this); m_renderTargetHolder->Bind(this);
m_enableOffset = true; m_enableOffset = true;
EnsureInitialized(); EnsureInitialized();
@@ -3482,11 +3480,20 @@ void wxD2DContext::Init()
wxD2DContext::~wxD2DContext() wxD2DContext::~wxD2DContext()
{ {
ResetClip(); // Remove all layers from the stack of layers.
while ( !m_layers.empty() ) 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(); HRESULT result = GetRenderTarget()->EndDraw();
@@ -3502,47 +3509,74 @@ ID2D1RenderTarget* wxD2DContext::GetRenderTarget() const
void wxD2DContext::Clip(const wxRegion& region) void wxD2DContext::Clip(const wxRegion& region)
{ {
GetRenderTarget()->Flush();
ResetClip();
wxCOMPtr<ID2D1Geometry> clipGeometry = wxD2DConvertRegionToGeometry(m_direct2dFactory, region); wxCOMPtr<ID2D1Geometry> clipGeometry = wxD2DConvertRegionToGeometry(m_direct2dFactory, region);
if (!m_clipLayerAcquired) wxCOMPtr<ID2D1Layer> clipLayer;
{ GetRenderTarget()->CreateLayer(&clipLayer);
GetRenderTarget()->CreateLayer(&m_clipLayer);
m_clipLayerAcquired = true;
}
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) void wxD2DContext::Clip(wxDouble x, wxDouble y, wxDouble w, wxDouble h)
{ {
GetRenderTarget()->Flush(); LayerData ld;
ResetClip(); 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( GetRenderTarget()->PushAxisAlignedClip(ld.params.contentBounds,
D2D1::RectF(x, y, x + w, y + h),
D2D1_ANTIALIAS_MODE_ALIASED); D2D1_ANTIALIAS_MODE_ALIASED);
// Store layer parameters.
m_clipMode = CLIP_MODE_AXIS_ALIGNED_RECTANGLE; m_layers.push(ld);
} }
void wxD2DContext::ResetClip() void wxD2DContext::ResetClip()
{ {
if (m_clipMode == CLIP_MODE_AXIS_ALIGNED_RECTANGLE) wxStack<LayerData> layersToRestore;
// Remove all clipping layers from the stack of layers.
while ( !m_layers.empty() )
{
LayerData ld = m_layers.top();
m_layers.pop();
if ( ld.type == CLIP_AXIS_ALIGNED_RECT )
{ {
GetRenderTarget()->PopAxisAlignedClip(); GetRenderTarget()->PopAxisAlignedClip();
continue;
} }
if (m_clipMode == CLIP_MODE_GEOMETRY) if ( ld.type == CLIP_LAYER )
{ {
GetRenderTarget()->PopLayer(); GetRenderTarget()->PopLayer();
ld.layer.reset();
continue;
} }
m_clipMode = CLIP_MODE_NONE; GetRenderTarget()->PopLayer();
// Save non-clipping layer
layersToRestore.push(ld);
}
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() void* wxD2DContext::GetNativeContext()
@@ -3633,28 +3667,79 @@ void wxD2DContext::BeginLayer(wxDouble opacity)
{ {
wxCOMPtr<ID2D1Layer> layer; wxCOMPtr<ID2D1Layer> layer;
GetRenderTarget()->CreateLayer(&layer); GetRenderTarget()->CreateLayer(&layer);
m_layers.push(layer);
GetRenderTarget()->PushLayer( LayerData ld;
D2D1::LayerParameters(D2D1::InfiniteRect(), ld.type = OTHER_LAYER;
ld.params = D2D1::LayerParameters(D2D1::InfiniteRect(),
NULL, NULL,
D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
D2D1::IdentityMatrix(), opacity), D2D1::IdentityMatrix(), opacity);
layer); ld.layer = layer;
GetRenderTarget()->PushLayer(ld.params, layer);
// Store layer parameters.
m_layers.push(ld);
} }
void wxD2DContext::EndLayer() void wxD2DContext::EndLayer()
{ {
if (!m_layers.empty()) wxStack<LayerData> layersToRestore;
// Temporarily remove all clipping layers
// above the first standard layer
// and next permanently remove this layer.
while ( !m_layers.empty() )
{ {
wxCOMPtr<ID2D1Layer> 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(); 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(); HRESULT hr = GetRenderTarget()->Flush();
wxCHECK_HRESULT_RET(hr); 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() void wxD2DContext::ReleaseDeviceDependentResources()
{ {
ReleaseResources(); ReleaseResources();
m_clipLayer.reset();
m_clipLayerAcquired = false;
} }
void wxD2DContext::DrawRectangle(wxDouble x, wxDouble y, wxDouble w, wxDouble h) 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() void wxD2DContext::Flush()
{ {
HRESULT result = m_renderTargetHolder->Flush(); wxStack<LayerData> 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(); 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) void wxD2DContext::GetDPI(wxDouble* dpiX, wxDouble* dpiY)