层概述

本概述介绍使用 Direct2D 层的基础知识。 其中包含以下各节。

什么是层?

ID2D1Layer 对象表示的层使应用程序能够操作一组绘制操作。 通过将层“推送”到呈现目标来使用层。 呈现器目标的后续绘制操作将定向到层。 完成图层后,将从呈现器目标“弹出”该层,该目标将图层的内容复合回呈现目标。

与画笔一样,层是由呈现目标创建的与设备相关的资源。 层可以在包含创建该层的呈现目标所在的资源域中的任何呈现器目标上使用。 但是,层资源一次只能由一个呈现目标使用。 有关资源的详细信息,请参阅 资源概述

尽管层提供强大的渲染技术来产生有趣的效果,但由于与管理层和层资源相关的各种成本,应用程序中过多的层可能会对其性能产生负面影响。 例如,填充或清除层,然后将其混合回去的成本,尤其是在高端硬件上。 然后是管理层资源的成本。 如果经常重新分配这些资源,则针对 GPU 产生的停止将是最重要的问题。 设计应用程序时,请尝试最大程度地重用层资源。

Windows 8 及更高版本中的层

Windows 8引入了新的层相关 API,这些 API 可简化层的性能,并为层添加功能。

ID2D1DeviceContext 和 PushLayer

ID2D1DeviceContext 接口派生自 ID2D1RenderTarget 接口,是Windows 8中显示 Direct2D 内容的关键,有关此接口的详细信息,请参阅设备和设备上下文。 使用设备上下文接口,可以跳过调用 CreateLayer 方法,然后将 NULL 传递给 ID2D1DeviceContext::P ushLayer 方法。 Direct2D 自动管理层资源,并且可以在层和效果图之间共享资源。

D2D1_LAYER_PARAMETERS1和D2D1_LAYER_OPTIONS1

D2D1_LAYER_PARAMETERS1 结构与 D2D1_LAYER_PARAMETERS 相同,只是结构的最终成员现在是D2D1_LAYER_OPTIONS1枚举。

D2D1_LAYER_OPTIONS1 没有 ClearType 选项,并且有两个可用于提高性能的不同选项:

混合模式

从 Windows 8 开始,设备上下文具有基元混合模式,用于确定如何将每个基元与目标图面混合。 调用 PushLayer 方法时,此模式也适用于层。

例如,如果使用层剪辑透明度的基元,请在设备上下文上设置 D2D1_PRIMITIVE_BLEND_COPY 模式,以获取正确的结果。 复制模式使设备上下文根据层的几何掩码,将每个像素的所有 4 个颜色通道(包括 alpha 通道)与目标图面的内容进行线性内插。

交互操作

从 Windows 8 开始,Direct2D 支持在推送层或剪辑时与 Direct3D 和 GDI 进行互操作。 在推送层以与 GDI 互操作时调用 ID2D1GdiInteropRenderTarget::GetDC 。 调用 ID2D1DeviceContext::Flush ,然后呈现到基础图面以与 Direct3D 互操作。 由你负责使用 Direct3D 或 GDI 在图层或剪辑内呈现。 如果尝试在图层外部呈现或剪裁,则结果未定义。

创建层

使用层需要熟悉 CreateLayerPushLayerPopLayer 方法以及 D2D1_LAYER_PARAMETERS 结构,该结构包含一组定义层使用方式的参数数据。 以下列表介绍了方法和结构。

  • 调用 CreateLayer 方法以创建层资源。

    注意

    从 Windows 8 开始,可以跳过调用 CreateLayer 方法,然后将 NULL 传递给 ID2D1DeviceContext 接口上的 PushLayer 方法。 这更简单,允许 Direct2D 自动管理层资源并在层和效果图之间共享资源。

     

  • 在) 调用呈现目标 BeginDraw 方法后开始绘制 (后,可以使用 PushLayer 方法。 PushLayer 方法将指定的层添加到呈现目标,以便目标接收所有后续的绘制操作,直到调用 PopLayer。 此方法采用通过调用 CreateLayer 返回的 ID2D1Layer 对象和 D2D1_LAYER_PARAMETERS 结构中的 layerParameters。 下表描述了 结构的字段。

    字段 说明
    contentBounds 层的内容边界。 内容不会在这些边界之外呈现。 此参数默认为 InfiniteRect。 使用默认值时,内容边界实际上是呈现目标的边界。
    geometricMask (可选) 层应剪裁到的区域,由 ID2D1Geometry 定义。 如果不应将图层剪裁为几何图形,则设置为 NULL
    maskAntialiasMode 一个 值,该值指定由 geometricMask 字段指定的几何掩码的抗锯齿模式。
    maskTransform 一个 值,该值指定组合层时应用于几何掩码的转换。 这是相对于世界变化的。
    不透明度 层的不透明度值。 合成到目标时,层中每个资源的不透明度乘以此值。
    opacityBrush (可选) 用于修改图层不透明度的画笔。 画笔映射到层,每个映射画笔像素的 alpha 通道与相应的层像素相乘。 如果层不应具有不透明蒙板,则设置为 NULL
    layerOptions 一个 值,该值指定层是否打算使用 ClearType 抗锯齿呈现文本。 此参数默认为 off。 启用它可使 ClearType 正常工作,但会导致呈现速度略慢。

     

    注意

    从 Windows 8 开始,无法在层中使用 ClearType 呈现,因此 layerOptions 参数应始终设置为 D2D1_LAYER_OPTIONS_NONE

     

    为方便起见,Direct2D 提供 D2D1::LayerParameters 方法来帮助创建 D2D1_LAYER_PARAMETERS 结构。

  • 若要将图层的内容复合到呈现目标中,请调用 PopLayer 方法。 在调用 EndDraw 方法之前,必须调用 PopLayer 方法。

以下示例演示如何使用 CreateLayerPushLayerPopLayer。 除设置为 ID2D1RadialGradientBrush的 opacityBrush 之外,D2D1_LAYER_PARAMETERS结构中的所有字段都设置为默认值。

// Create a layer.
ID2D1Layer *pLayer = NULL;
hr = pRT->CreateLayer(NULL, &pLayer);

if (SUCCEEDED(hr))
{
    pRT->SetTransform(D2D1::Matrix3x2F::Translation(300, 250));

    // Push the layer with the content bounds.
    pRT->PushLayer(
        D2D1::LayerParameters(
            D2D1::InfiniteRect(),
            NULL,
            D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
            D2D1::IdentityMatrix(),
            1.0,
            m_pRadialGradientBrush,
            D2D1_LAYER_OPTIONS_NONE),
        pLayer
        );

    pRT->DrawBitmap(m_pBambooBitmap, D2D1::RectF(0, 0, 190, 127));

    pRT->FillRectangle(
        D2D1::RectF(25.f, 25.f, 50.f, 50.f), 
        m_pSolidColorBrush
        );
    pRT->FillRectangle(
        D2D1::RectF(50.f, 50.f, 75.f, 75.f),
        m_pSolidColorBrush
        ); 
    pRT->FillRectangle(
        D2D1::RectF(75.f, 75.f, 100.f, 100.f),
        m_pSolidColorBrush
        );    
 
    pRT->PopLayer();
}
SafeRelease(&pLayer);

此示例中已省略代码。

请注意,调用 PushLayerPopLayer 时,请确保每个 PushLayer 都有匹配的 PopLayer 调用。 如果 PopLayer 调用数多于 PushLayer 调用,则呈现器目标处于错误状态。 如果在弹出所有未完成的层之前调用 Flush ,则呈现目标将置于错误状态并返回错误。 若要清除错误状态,请使用 EndDraw

内容边界

contentBounds 设置要绘制到层的内容的限制。 只有内容边界内的内容才会复合回呈现目标。

以下示例演示如何指定 contentBounds ,以便将原始图像剪裁为内容边界,左上角位于 (10,108) ,右下角位于 (121,177) 。 下图显示了原始图像以及将图像剪辑到内容边界的结果。

原始图片上的内容边界和生成的已剪裁图片的插图

HRESULT DemoApp::RenderWithLayerWithContentBounds(ID2D1RenderTarget *pRT)
{
    
    HRESULT hr = S_OK;

    // Create a layer.
    ID2D1Layer *pLayer = NULL;
    hr = pRT->CreateLayer(NULL, &pLayer);

    if (SUCCEEDED(hr))
    {
        pRT->SetTransform(D2D1::Matrix3x2F::Translation(300, 0));

        // Push the layer with the content bounds.
        pRT->PushLayer(
            D2D1::LayerParameters(D2D1::RectF(10, 108, 121, 177)),
            pLayer
            );

        pRT->DrawBitmap(m_pWaterBitmap, D2D1::RectF(0, 0, 128, 192));
        pRT->PopLayer();
    }

    SafeRelease(&pLayer);

    return hr;
    
}

此示例中已省略代码。

注意

如果指定 几何掩码,则生成的剪裁图像会进一步受到影响。 有关详细信息,请参阅 几何掩码 部分。

 

几何掩码

几何蒙板是由 ID2D1Geometry 对象定义的剪裁或切口,在由呈现器目标绘制层时遮掩层。 可以使用 D2D1_LAYER_PARAMETERS 结构的 geometrMask 字段将结果屏蔽为几何图形。 例如,如果要显示由块字母“A”屏蔽的图像,可以先创建表示方块字母“A”的几何图形,然后将该几何图形用作层的几何掩码。 然后,在推送层后,可以绘制图像。 弹出层会导致图像被剪裁成块字母“A”形状。

以下示例演示如何创建包含山形状的 ID2D1PathGeometry ,然后将路径几何传递给 PushLayer。 然后,它绘制位图和正方形。 如果图层中只有要呈现的位图,请使用带固定位图画笔的 FillGeometry 来提高效率。 下图显示该示例的输出。

应用山的几何面具后的叶子图片和生成的图片的插图

第一个示例定义要用作掩码的几何图形。

hr = m_pD2DFactory->CreatePathGeometry(&m_pPathGeometry);
    
if(SUCCEEDED(hr))
{
    ID2D1GeometrySink *pSink = NULL;
    // Write to the path geometry using the geometry sink.
    hr = m_pPathGeometry->Open(&pSink);

    if (SUCCEEDED(hr))
    {
        pSink->SetFillMode(D2D1_FILL_MODE_WINDING);
        pSink->BeginFigure(
            D2D1::Point2F(0, 90),
            D2D1_FIGURE_BEGIN_FILLED
            );

        D2D1_POINT_2F points[7] = {
           D2D1::Point2F(35, 30),
           D2D1::Point2F(50, 50),
           D2D1::Point2F(70, 45),
           D2D1::Point2F(105, 90),
           D2D1::Point2F(130, 90),
           D2D1::Point2F(150, 60),
           D2D1::Point2F(170, 90)
           };

        pSink->AddLines(points, 7);
        pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
        hr = pSink->Close();
    }
    SafeRelease(&pSink);
       }

下一个示例使用几何图形作为层的掩码。

HRESULT DemoApp::RenderWithLayerWithGeometricMask(ID2D1RenderTarget *pRT)
{
    
    HRESULT hr;

    // Create a layer.
    ID2D1Layer *pLayer = NULL;
    hr = pRT->CreateLayer(NULL, &pLayer);

    if (SUCCEEDED(hr))
    {
        pRT->SetTransform(D2D1::Matrix3x2F::Translation(300, 450));

        pRT->PushLayer(
            D2D1::LayerParameters(D2D1::InfiniteRect(), m_pPathGeometry),
            pLayer
            );

        pRT->DrawBitmap(m_pLeafBitmap, D2D1::RectF(0, 0, 198, 132));

        pRT->FillRectangle(
            D2D1::RectF(50.f, 50.f, 75.f, 75.f), 
            m_pSolidColorBrush
            ); 
        pRT->FillRectangle(
            D2D1::RectF(75.f, 75.f, 100.f, 100.f),
            m_pSolidColorBrush
            );        

        pRT->PopLayer();
    }

    SafeRelease(&pLayer);

    return hr;
    
}

此示例中已省略代码。

注意

通常,如果指定几何掩码,则可以使用 contentBounds 的默认值 InfiniteRect

如果 contentBounds 为 NULL,而 geometricMask 为非 NULL,则应用掩码转换后,内容边界实际上是几何掩码的边界。

如果 contentBounds 为非 NULL,而 geometricMask 为非 NULL,则转换后的几何掩码将针对内容边界进行有效剪裁,并假定内容边界为无限。

 

不透明度掩码

不透明蒙板是由画笔或位图描述的掩码,应用于另一个对象,使该对象部分或完全透明。 它允许将画笔的 alpha 通道用作内容掩码。 例如,可以定义从不透明到透明的径向渐变画笔,以创建晕影效果。

以下示例使用 ID2D1RadialGradientBrush (m_pRadialGradientBrush) 作为不透明度掩码。 然后,它绘制位图和正方形。 如果图层中只有要呈现的位图,请使用带固定位图画笔的 FillGeometry 来提高效率。 下图显示了此示例的输出。

树图和应用不透明度掩码后生成的图片的插图

HRESULT DemoApp::RenderWithLayerWithOpacityMask(ID2D1RenderTarget *pRT)
{   

    HRESULT hr = S_OK;

    // Create a layer.
    ID2D1Layer *pLayer = NULL;
    hr = pRT->CreateLayer(NULL, &pLayer);

    if (SUCCEEDED(hr))
    {
        pRT->SetTransform(D2D1::Matrix3x2F::Translation(300, 250));

        // Push the layer with the content bounds.
        pRT->PushLayer(
            D2D1::LayerParameters(
                D2D1::InfiniteRect(),
                NULL,
                D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
                D2D1::IdentityMatrix(),
                1.0,
                m_pRadialGradientBrush,
                D2D1_LAYER_OPTIONS_NONE),
            pLayer
            );

        pRT->DrawBitmap(m_pBambooBitmap, D2D1::RectF(0, 0, 190, 127));

        pRT->FillRectangle(
            D2D1::RectF(25.f, 25.f, 50.f, 50.f), 
            m_pSolidColorBrush
            );
        pRT->FillRectangle(
            D2D1::RectF(50.f, 50.f, 75.f, 75.f),
            m_pSolidColorBrush
            ); 
        pRT->FillRectangle(
            D2D1::RectF(75.f, 75.f, 100.f, 100.f),
            m_pSolidColorBrush
            );    
 
        pRT->PopLayer();
    }
    SafeRelease(&pLayer);
   
    return hr;
    
}

此示例中已省略代码。

注意

此示例使用层将不透明度掩码应用于单个对象,使示例尽可能简单。 将不透明度掩码应用于单个对象时,使用 FillOpacityMaskFillGeometry 方法比使用层更高效。

 

有关如何在不使用层的情况下应用不透明掩码的说明,请参阅 不透明度掩码概述

层的替代项

如前所述,过多的层可能会对应用程序的性能产生负面影响。 若要提高性能,请尽可能避免使用层;请改用其替代项。 下面的代码示例演示如何使用 PushAxisAlignedClipPopAxisAlignedClip 来剪辑区域,以替代使用具有内容边界的层。

pRT->PushAxisAlignedClip(
    D2D1::RectF(20, 20, 100, 100),
    D2D1_ANTIALIAS_MODE_PER_PRIMITIVE
    );

pRT->FillRectangle(D2D1::RectF(0, 0, 200, 133), m_pOriginalBitmapBrush);
pRT->PopAxisAlignedClip();

同样,当层中只有一个内容要呈现时,将 FillGeometry 与固定的位图画笔一起使用作为替代方法,以替代使用具有不透明度蒙板的层,如以下示例所示。

        m_pRenderTarget->FillGeometry(
            m_pRectGeo, 
            m_pLinearFadeFlowersBitmapBrush, 
            m_pLinearGradientBrush
            );

作为使用带几何蒙板的图层的替代方法,请考虑使用位图掩码剪裁区域,如以下示例所示。

// D2D1_ANTIALIAS_MODE_ALIASED must be set for FillOpacityMask
// to function properly.
m_pRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);

m_pRenderTarget->FillOpacityMask(
    m_pBitmapMask,
    m_pOriginalBitmapBrush,
    D2D1_OPACITY_MASK_CONTENT_GRAPHICS,
    &rcBrushRect,
    NULL
    );

m_pRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);

最后,如果要将不透明度应用于单个基元,应将不透明度乘以成画笔颜色,然后呈现基元。 不需要层或不透明掩码位图。

float opacity = 0.9f;

ID2D1SolidColorBrush *pBrush = NULL;
hr = pCompatibleRenderTarget->CreateSolidColorBrush(
    D2D1::ColorF(D2D1::ColorF(0.93f, 0.94f, 0.96f, 1.0f * opacity)),
    &pBrush
    );

m_pRenderTarget->FillRectangle(
    D2D1::RectF(50.0f, 50.0f, 75.0f, 75.0f), 
    pBrush
    ); 

剪裁任意形状

此处的图显示了将剪辑应用于图像的结果。

一个图像,该图像显示剪辑前后的图像示例。

可以通过使用带几何图形掩码的层或带有不透明度画笔的 FillGeometry 方法来获取此结果。

下面是使用层的示例:

// Call PushLayer() and pass in the clipping geometry.
m_d2dContext->PushLayer(
    D2D1::LayerParameters(
        boundsRect,
        geometricMask));

下面是使用 FillGeometry 方法的示例:

// Create an opacity bitmap and render content.
m_d2dContext->CreateBitmap(size, nullptr, 0,
    D2D1::BitmapProperties(
        D2D1_BITMAP_OPTIONS_TARGET,
        D2D1::PixelFormat(
            DXGI_FORMAT_A8_UNORM,
            D2D1_ALPHA_MODE_PREMULTIPLIED),
        dpiX, dpiY),
    &opacityBitmap);

m_d2dContext->SetTarget(opacityBitmap.Get());
m_d2dContext->BeginDraw();
…
m_d2dContext->EndDraw();

// Create an opacity brush from the opacity bitmap.
m_d2dContext->CreateBitmapBrush(opacityBitmap.Get(),
    D2D1::BitmapBrushProperties(),
    D2D1::BrushProperties(),
    &bitmapBrush);

// Call the FillGeometry method and pass in the clip geometry and the opacity brush
m_d2dContext->FillGeometry( 
    clipGeometry.Get(),
    brush.Get(),
    opacityBrush.Get()); 

在此代码示例中,调用 PushLayer 方法时,不会传入应用创建的层。 Direct2D 会为你创建一个层。 Direct2D 能够管理此资源的分配和销毁,而无需应用进行任何参与。 这允许 Direct2D 在内部重复使用层并应用资源管理优化。

注意

在Windows 8对层的使用进行了许多优化,建议尽可能尝试使用层 API 而不是 FillGeometry

 

轴对齐剪辑

如果要剪裁的区域与绘图图面的轴对齐,而不是任意对齐。 这种情况适用于使用剪辑矩形而不是层。 性能提升更适用于别名几何图形,而不是抗锯齿几何图形。 有关轴对齐剪辑的详细信息,请参阅 PushAxisAlignedClip 主题。

Direct2D 参考