效果着色器链接

Direct2D 使用名为效果着色器链接的优化,该链接将多个效果图呈现传递到单个传递中。

效果着色器链接概述

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

transform graph without shader linking: 4 passes and 3 intermediates.

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

transform graph with shader linking: 1 pass, 0 intermediates.

 

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

使用效果着色器链接

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

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

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

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

transform graph with a linking hazard: 2 passes, 1 intermediate.

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

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

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

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

    无需修改实现各种效果接口(如 ID2D1DrawTransform)的任何代码。

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

    必须提供由 Direct2D 链接的效果着色器的导出函数版本。 此外,还必须继续提供原始的、完整的着色器;这是因为 Direct2D 在运行时选择正确的着色器版本,具体取决于是否将着色器链接应用于图形中的特定链接。

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

  • 帮助程序函数

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

    帮助程序函数:

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

  • 仅像素着色器

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

  • 简单采样与复杂采样

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

    grayscale conversion is an example of simple sampling. the value of a particular output pixel depends only upon the value of the corresponding input pixel.

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

    gaussian blur is an example of complex sampling. the value of the center output pixel depends upon multiple input pixels.

    只有具有简单输入的着色器函数才能让另一个着色器函数提供其输入。 必须向具有复杂输入的着色器函数提供要采样的输入纹理。 这意味着 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> 设置为 <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 将在输入纹理和着色器函数之间插入示例函数,或者输入将由另一个着色器函数的输出提供。

  • 复杂输入

    float4 d2d_uvN  : TEXCOORDN                
    

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

  • 输出位置

    float4 d2d_posScene : SCENE_POSITION                
    

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

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

HLSL 帮助程序

ID3D11Linker 接口

ID3D11FunctionLinkingGraph 接口