效果着色器链接

Direct2D 使用称为效果着色器链接的优化,它将多个效果图呈现传递合并到一个通道中。

效果着色器链接概述

效果着色器链接优化基于 HLSL 着色器链接,这是 Direct3D 11.2 功能,允许通过链接预编译的着色器函数在运行时生成像素和顶点着色器。 下图演示了效果图中效果着色器链接的概念。 第一个图显示了具有四个呈现转换的典型 Direct2D 效果图。 如果没有着色器链接,每个转换都会使用呈现通道,并且需要中间图面;此图总共需要 4 次传递和 3 次中间。

不带着色器链接的转换图:4 次传递和 3 个中间。

第二个图显示了相同的效果图,其中每个呈现转换都已替换为一个可链接函数版本。 Direct2D 能够链接整个图形,并一次性执行它,而无需任何中间项。 这可以显著减少 GPU 执行时间,并减少 GPU 内存消耗峰值。

具有着色器链接的转换图:1 个传递,0 个中间。

 

效果着色器链接对效果中的单个转换进行操作;这意味着,如果具有多个有效转换,则即使具有单个效果的图形也可能受益于着色器链接。

使用效果着色器链接

如果要构建使用效果的 Direct2D 应用程序,则无需执行任何操作即可利用效果着色器链接。 Direct2D 会自动分析效果图,以确定链接每个转换的最佳方法。

效果作者负责以支持效果着色器链接的方式实现其效果;有关详细信息,请参阅下面的 创作与着色器链接兼容的自定义效果 部分。 所有内置效果都支持着色器链接。

Direct2D 仅在有利的情况下链接相邻呈现转换。 在确定是否链接两个转换时,它考虑了多个因素。 例如,如果其中一个转换使用顶点着色器或计算着色器,则不执行着色器链接,因为只能链接像素着色器。 此外,如果创作的效果与着色器链接不兼容,则周围的转换将不会与其链接。

如果存在此类链接危险,则 Direct2D 不会链接与危险相邻的任何转换,但仍会尝试链接图形的其余部分。

具有链接风险的转换图:2 次通过,1 次中间。

创作与着色器链接兼容的自定义效果

如果要创作自己的自定义 Direct2D 效果,则需要确保其转换支持效果着色器链接。 这需要对以前自定义效果的实现方式进行一些细微更改。 如果自定义效果中的转换不支持着色器链接,则 Direct2D 不会将其与效果图中与其相邻的任何转换链接。

作为自定义效果作者,你应该了解几个关键概念和要求:

  • 没有对效果接口实现进行更改

    无需修改任何实现各种效果接口的代码,例如 ID2D1DrawTransform

  • 提供着色器的完整和导出函数版本

    必须提供可通过 Direct2D 链接的效果着色器的导出函数版本。 此外,还必须继续提供原始的完整着色器;这是因为 Direct2D 在运行时会根据是否将着色器链接应用于图中的特定链接来选择正确的着色器版本。

    如果转换仅通过 ID2D1EffectContext::LoadPixelShader) 提供全像素着色器 blob (,则不会将其链接到相邻转换。

  • 帮助程序函数

    Direct2D 提供 HLSL 帮助程序函数 和宏,这些函数和宏将自动生成着色器的完整和导出函数版本。 可以在 d2d1effecthelpers.hlsli 中找到这些帮助程序。 此外,HLSL 编译器 (FXC) 允许将导出函数着色器插入到完整着色器的专用字段中。 这样,只需创作着色器一次,同时将这两个版本传递到 Direct2D。 d2d1effecthelpers.hlsli 和 FXC 编译器都作为 Windows SDK 的一部分包含在内。

    帮助程序函数:

    还可以手动创作每个着色器的两个版本,并编译它们两次,前提是满足 以下导出函数规范中所述的规范

  • 仅像素着色器

    Direct2D 不支持链接计算或顶点着色器。 但是,如果效果同时使用顶点着色器和像素着色器,则像素着色器的输出仍可链接。

  • 简单采样与复杂采样

    着色器函数链接的工作原理是将一个像素着色器通道的输出连接到后续像素着色器通道的输入。 仅当使用像素着色器只需要单个输入值来执行其计算时,才有可能实现此情况;此值通常来自在顶点着色器发出的纹理坐标处对输入纹理采样。 这种像素着色器据说可以执行简单的采样。

    灰度转换是简单采样的一个示例。特定输出像素的值仅取决于相应输入像素的值。

    某些像素着色器(例如高斯模糊)从多个输入样本(而不仅仅是单个样本)计算其输出。 这种像素着色器据说会执行复杂的采样。

    高斯模糊是复杂采样的一个示例。中心输出像素的值取决于多个输入像素。

    只有具有简单输入的着色器函数才能由另一个着色器函数提供其输入。 必须向具有复杂输入的着色器函数提供要采样的输入纹理。 这意味着 Direct2D 不会将具有复杂输入的着色器链接到其前置项。

    使用 Direct2D HLSL 帮助程序时,必须在 HLSL 中指示着色器是使用复杂输入还是简单输入。

与链接兼容的效果着色器示例

以下代码片段使用 D2D 帮助程序表示与链接兼容的简单效果着色器:

#define D2D_INPUT_COUNT 1
#define D2D_INPUT0_SIMPLE
#include “d2d1effecthelpers.hlsli”

D2D_PS_ENTRY(LinkingCompatiblePixelShader)
{
    float4 input = D2DGetInput(0);
    input.rgb *= input.a;
    return input;
}          

在此简短的示例中,请注意,不会声明任何函数参数,输入数和每个输入的类型在输入函数之前声明,输入是通过调用 D2DGetInput 检索的,并且必须在包含帮助程序文件之前定义预处理器指令。

与链接兼容的着色器必须同时提供常规单通道像素着色器和导出着色器函数。 当与着色器编译脚本结合使用时, D2D_PS_ENTRY 宏允许从同一代码生成其中的每一个。

编译完整着色器时,宏将扩展为以下代码,该代码具有与 D2D 效果兼容的输入签名。

Texture2D<float4> InputTexture0;
SamplerState InputSampler0;

float4 LinkingCompatiblePixelShader(
    float4 pos   : SV_POSITION,
    float4 posScene : SCENE_POSITION,
    float4 uv0  : TEXCOORD0
    ) : SV_Target
    {
        float4 input = InputTexture0.Sample(InputSampler0, uv0.xy);
        input.rgb *= input.a;
        return input;
    }    

编译相同代码的导出函数版本时,将生成以下代码:

// Shader function version
export float4 LinkingCompatiblePixelShader_Function(
    float4 input0 : INPUT0)
    {
        input.rgb *= input.a;
        return input;
    }      

请注意,通常通过对 Texture2D 进行采样检索的纹理输入已替换为函数输入 (input0) 。

若要查看编写链接兼容效果所需执行的操作的完整分步说明,请参阅 自定义效果教程Direct2D 自定义图像效果示例

编译链接兼容着色器

为了可链接,传递给 D2D 的像素着色器 Blob 必须同时包含着色器的完整和导出函数版本。 这是通过将编译的导出函数嵌入到D3D_BLOB_PRIVATE_DATA区域来实现的。

使用 D2D 帮助程序函数创作着色器时,必须在编译时定义 D2D 编译目标。 编译目标类型D2D_FULL_SHADER和D2D_FUNCTION。

编译与链接兼容的效果着色器的过程分为两步:

注意

使用 Visual Studio 编译效果时,应创建一个批处理文件,用于执行 FXC 命令,并将此批处理文件作为在编译步骤之前运行的自定义生成步骤运行。

 

步骤 1:编译导出函数

fxc /T <shadermodel> <MyShaderFile>.hlsl /D D2D_FUNCTION /D D2D_ENTRY=<entry> /Fl <MyShaderFile>.fxlib           

若要编译着色器的导出函数版本,必须将以下标志传递给 FXC。

标志 描述
/T <ShaderModel> 将 ShaderModel> 设置为 <FXC 语法中定义的相应像素着色器配置文件。 这必须是“HLSL 着色器链接”下列出的配置文件之一。
<MyShaderFile.hlsl> 将 <MyShaderFile> 设置为 HLSL 文件的名称。
/D D2D_FUNCTION 此定义指示 FXC 编译着色器的导出函数版本。
/D D2D_ENTRY=<entry> 将 条目>设置为<在 D2D_PS_ENTRY 宏中定义的 HLSL 入口点的名称。
/Fl <MyShaderFile.fxlib> 将 MyShaderfile> 设置为<要存储着色器的导出函数版本的位置。 请注意,.fxlib 扩展只是为了便于识别。

步骤 2:编译完整的着色器并嵌入导出函数

fxc /T ps_<shadermodel> <MyShaderFile>.hlsl /D D2D_FULL_SHADER /D D2D_ENTRY=<entry> /E <entry> /setprivate <MyShaderFile>.fxlib /Fo <MyShader>.cso /Fh <MyShader>.h           

若要使用嵌入的导出版本编译着色器的完整版本,必须将以下标志传递给 FXC。

标志 描述
/T <ShaderModel> 将 ShaderModel> 设置为 <FXC 语法中定义的相应像素着色器配置文件。 这必须是对应于步骤 1 中指定的链接配置文件的像素着色器配置文件。
<MyShaderFile.hlsl> 将 <MyShaderFile> 设置为 HLSL 文件的名称。
/D D2D_FULL_SHADER 此定义指示 FXC 编译着色器的完整版本。
/D D2D_ENTRY=<entry> 将 条目>设置为<在 D2D_PS_ENTRY () 宏中定义的 HLSL 入口点的名称。
/E <条目> 将 条目>设置为<在 D2D_PS_ENTRY () 宏中定义的 HLSL 入口点的名称。
/setprivate <MyShaderFile.fxlib> 此参数指示 FXC 将步骤 1 中生成的导出函数着色器嵌入到D3D_BLOB_PRIVATE_DATA区域。
/Fo <MyShader.cso> 将 MyShader> 设置为<要存储最终组合编译着色器的位置。
/Fh <MyShader.h> 将 MyShader> 设置为<要存储最终组合标头的位置。

导出函数规范

可以在不使用 D2D 提供的帮助程序的情况下创作兼容的效果着色器(但不建议这样做)。 必须小心确保完整着色器签名和导出函数输入签名都符合 D2D 规范。

完整着色器的规范与早期 Windows 版本相同。 简言之,像素着色器输入参数必须SV_POSITION、SCENE_POSITION以及每个效果输入一个 TEXCOORD。

对于导出函数,该函数必须返回 float4,并且其输入必须是以下类型之一:

  • 简单输入

    float4 d2d_inputN : INPUTN         
    

    对于简单输入,D2D 将在输入纹理和着色器函数之间插入 Sample 函数,或者输入将由另一个着色器函数的输出提供。

  • 复杂输入

    float4 d2d_uvN  : TEXCOORDN                
    

    对于复杂输入,D2D 将仅传递纹理坐标,如Windows 8文档中所述。

  • 输出位置

    float4 d2d_posScene : SCENE_POSITION                
    

    只能定义一个SCENE_POSITION输入。 仅在必要时才应包含此参数,因为每个链接着色器只有一个函数可以使用此参数。

语义必须如上所述定义,因为 D2D 将检查语义以决定如何将函数链接在一起。 如果任何函数输入与上述类型之一不匹配,则将拒绝该函数进行着色器链接。

HLSL 帮助程序

ID3D11Linker 接口

ID3D11FunctionLinkingGraph 接口