Freigeben über


Benutzerdefinierte Effekte

Direct2D- enthält eine Bibliothek mit Effekten, die eine Vielzahl gängiger Bildvorgänge ausführen. Die vollständige Liste der Effekte finden Sie im thema integrierten Effekte. Für Funktionen, die mit den integrierten Effekten nicht erreicht werden können, können Sie mit Direct2D eigene benutzerdefinierte Effekte mithilfe von Standard-HLSL-schreiben. Sie können diese benutzerdefinierten Effekte zusammen mit den integrierten Effekten verwenden, die mit Direct2D ausgeliefert werden.

Beispiele für einen vollständigen Pixel-, Vertex- und Compute-Shadereffekt finden Sie im D2DCustomEffects SDK-Beispiel.

In diesem Thema zeigen wir Ihnen die Schritte und Konzepte, die Sie zum Entwerfen und Erstellen eines vollständig bereitgestellten benutzerdefinierten Effekts benötigen.

Einführung: Was ist innerhalb eines Effekts?

Schlagschatteneffektdiagramm.

Konzeptionell führt ein Direct2D- Effekt eine Imageerstellungsaufgabe aus, z. B. das Ändern der Helligkeit, das Aufheben der Sättigung eines Bilds oder wie oben gezeigt, das Erstellen eines Schlagschatten. Für die App sind sie einfach. Sie können null oder mehr Eingabebilder akzeptieren, mehrere Eigenschaften verfügbar machen, die ihren Vorgang steuern, und ein einzelnes Ausgabebild generieren.

Es gibt vier verschiedene Teile eines benutzerdefinierten Effekts, für den ein Effektautor verantwortlich ist:

  1. Effektschnittstelle: Die Effektschnittstelle definiert konzeptionell, wie eine App mit einem benutzerdefinierten Effekt interagiert (z. B. wie viele Eingaben der Effekt akzeptiert und welche Eigenschaften verfügbar sind). Die Effektschnittstelle verwaltet ein Transformationsdiagramm, das die tatsächlichen Imageerstellungsvorgänge enthält.
  2. Transformationsdiagramm: Jeder Effekt erstellt ein internes Transformationsdiagramm, das aus einzelnen Transformationen besteht. Jede Transformation stellt einen einzelnen Bildvorgang dar. Der Effekt ist dafür verantwortlich, diese Transformationen zusammen mit einem Diagramm zu verknüpfen, um den beabsichtigten bildgebenden Effekt auszuführen. Ein Effekt kann Transformationen als Reaktion auf Änderungen an den externen Eigenschaften des Effekts hinzufügen, entfernen, ändern und neu anordnen.
  3. Transformation: Eine Transformation stellt einen einzelnen Bildvorgang dar. Der Hauptzweck besteht darin, die Shader zu enthalten, die für jedes Ausgabepixel ausgeführt werden. Zu diesem Zweck ist sie für die Berechnung der neuen Größe des Ausgabebilds basierend auf der Logik in den Shadern verantwortlich. Außerdem muss berechnet werden, aus welchem Bereich des Eingabebilds die Shader lesen müssen, um den angeforderten Ausgabebereich zu rendern.
  4. Shader: Ein Shader wird für die Eingabe der Transformation auf der GPU ausgeführt (oder CPU, wenn das Softwarerendering angegeben wird, wenn die App das Direct3D-Gerät erstellt). Effektshader werden in high Level Shading Language (HLSL-) geschrieben und während der Kompilierung des Effekts in Bytecode kompiliert, der dann während der Laufzeit vom Effekt geladen wird. In diesem Referenzdokument wird beschrieben, wie sie Direct2D--kompatible HLSL schreiben. Die Direct3D-Dokumentation enthält eine grundlegende HLSL-Übersicht.

Erstellen einer Effektschnittstelle

Die Effektschnittstelle definiert, wie eine App mit dem benutzerdefinierten Effekt interagiert. Zum Erstellen einer Effektschnittstelle muss eine Klasse ID2D1EffectImpl implementieren, Metadaten definieren, die den Effekt beschreiben (z. B. Name, Eingabeanzahl und Eigenschaften), und Methoden erstellen, die den benutzerdefinierten Effekt für die Verwendung mit Direct2D-registrieren.

Sobald alle Komponenten für eine Effektschnittstelle implementiert wurden, wird der Klassenheader wie folgt angezeigt:

#include <d2d1_1.h>
#include <d2d1effectauthor.h>  
#include <d2d1effecthelpers.h>

// Example GUID used to uniquely identify the effect. It is passed to Direct2D during
// effect registration, and used by the developer to identify the effect for any
// ID2D1DeviceContext::CreateEffect calls in the app. The app should create
// a unique name for the effect, as well as a unique GUID using a generation tool.
DEFINE_GUID(CLSID_SampleEffect, 0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);

class SampleEffect : public ID2D1EffectImpl
{
public:
    // 2.1 Declare ID2D1EffectImpl implementation methods.
    IFACEMETHODIMP Initialize(
        _In_ ID2D1EffectContext* pContextInternal,
        _In_ ID2D1TransformGraph* pTransformGraph
        );

    IFACEMETHODIMP PrepareForRender(D2D1_CHANGE_TYPE changeType);
    IFACEMETHODIMP SetGraph(_In_ ID2D1TransformGraph* pGraph);

    // 2.2 Declare effect registration methods.
    static HRESULT Register(_In_ ID2D1Factory1* pFactory);
    static HRESULT CreateEffect(_Outptr_ IUnknown** ppEffectImpl);

    // 2.3 Declare IUnknown implementation methods.
    IFACEMETHODIMP_(ULONG) AddRef();
    IFACEMETHODIMP_(ULONG) Release();
    IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _Outptr_ void** ppOutput);

private:
    // Constructor should be private since it should never be called externally.
    SampleEffect();

    LONG m_refCount; // Internal ref count used by AddRef() and Release() methods.
};

Implementieren von ID2D1EffectImpl

Die ID2D1EffectImpl Schnittstelle enthält drei Methoden, die Sie implementieren müssen:

Initialize(ID2D1EffectContext *pContextInternal, ID2D1TransformGraph *pTransformGraph)

Direct2D- ruft die Initialize-Methode auf, nachdem die ID2D1DeviceContext::CreateEffect-Methode von der App aufgerufen wurde. Sie können diese Methode verwenden, um interne Initialisierungen oder andere Vorgänge auszuführen, die für den Effekt erforderlich sind. Darüber hinaus können Sie es verwenden, um das anfängliche Transformationsdiagramm des Effekts zu erstellen.

SetGraph(ID2D1TransformGraph *pTransformGraph)

Direct2D- ruft die SetGraph--Methode auf, wenn die Anzahl der Eingaben für den Effekt geändert wird. Während die meisten Effekte eine konstante Anzahl von Eingaben aufweisen, unterstützen andere Wie der zusammengesetzten Effekt eine variable Anzahl von Eingaben. Diese Methode ermöglicht es diesen Effekten, ihr Transformationsdiagramm als Reaktion auf eine sich ändernde Eingabeanzahl zu aktualisieren. Wenn ein Effekt keine variable Eingabeanzahl unterstützt, kann diese Methode einfach E_NOTIMPL zurückgeben.

PrepareForRender (D2D1_CHANGE_TYPE changeType)

Die PrepareForRender--Methode bietet die Möglichkeit, Effekte als Reaktion auf externe Änderungen auszuführen. Direct2D- ruft diese Methode direkt vor dem Rendern eines Effekts auf, wenn mindestens einer der folgenden Erfüllt ist:

  • Der Effekt wurde zuvor initialisiert, aber noch nicht gezeichnet.
  • Eine Effekteigenschaft hat sich seit dem letzten Draw-Aufruf geändert.
  • Der Status des aufrufenden Direct2D- Kontext (z. B. DPI) wurde seit dem letzten Draw-Aufruf geändert.

Implementieren der Effektregistrierungs- und Rückrufmethoden

Apps müssen Effekte mit Direct2D- registrieren, bevor sie instanziiert werden. Diese Registrierung ist auf eine Instanz einer Direct2D-Factory festgelegt und muss jedes Mal wiederholt werden, wenn die App ausgeführt wird. Um diese Registrierung zu aktivieren, definiert ein benutzerdefinierter Effekt eine eindeutige GUID, eine öffentliche Methode, die den Effekt registriert, und eine private Rückrufmethode, die eine Instanz des Effekts zurückgibt.

Definieren einer GUID

Sie müssen eine GUID definieren, die den Effekt für die Registrierung mit Direct2D-eindeutig identifiziert. Die App verwendet dasselbe, um den Effekt zu identifizieren, wenn er ID2D1DeviceContext::CreateEffectaufruft.

Dieser Code veranschaulicht das Definieren einer solchen GUID für einen Effekt. Sie müssen eine eigene eindeutige GUID mit einem GUID-Generierungstool wie guidgen.exeerstellen.

// Example GUID used to uniquely identify the effect. It is passed to Direct2D during
// effect registration, and used by the developer to identify the effect for any
// ID2D1DeviceContext::CreateEffect calls in the app. The app should create
// a unique name for the effect, as well as a unique GUID using a generation tool.
DEFINE_GUID(CLSID_SampleEffect, 0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);

Definieren einer öffentlichen Registrierungsmethode

Definieren Sie als Nächstes eine öffentliche Methode für die App, um den Effekt mit Direct2D-zu registrieren. Da die Effektregistrierung spezifisch für eine Instanz einer Direct2D-Factory ist, akzeptiert die Methode eine ID2D1Factory1 Schnittstelle als Parameter. Um den Effekt zu registrieren, ruft die Methode dann die ID2D1Factory1::RegisterEffectFromString API für den ID2D1Factory1 Parameter auf.

Diese API akzeptiert eine XML-Zeichenfolge, die die Metadaten, Eingaben und Eigenschaften des Effekts beschreibt. Die Metadaten für einen Effekt dienen nur Informationszwecken und können von der App über die ID2D1Properties Schnittstelle abgefragt werden. Die Eingabe- und Eigenschaftsdaten werden dagegen von Direct2D- verwendet und stellen die Funktionalität des Effekts dar.

Hier wird eine XML-Zeichenfolge für einen minimalen Beispieleffekt angezeigt. Das Hinzufügen benutzerdefinierter Eigenschaften zum XML-Code wird im Abschnitt zum Hinzufügen benutzerdefinierter Eigenschaften zu einem Effektabschnitt behandelt.

#define XML(X) TEXT(#X) // This macro creates a single string from multiple lines of text.

PCWSTR pszXml =
    XML(
        <?xml version='1.0'?>
        <Effect>
            <!-- System Properties -->
            <Property name='DisplayName' type='string' value='SampleEffect'/>
            <Property name='Author' type='string' value='Contoso'/>
            <Property name='Category' type='string' value='Sample'/>
            <Property name='Description' type='string' value='This is a demo effect.'/>
            <Inputs>
                <Input name='SourceOne'/>
                <!-- <Input name='SourceTwo'/> -->
                <!-- Additional inputs go here. -->
            </Inputs>
            <!-- Custom Properties go here. -->
        </Effect>
        );

Definieren einer Effect Factory-Rückrufmethode

Der Effekt muss auch eine private Rückrufmethode bereitstellen, die eine Instanz des Effekts über einen einzelnen IUnknown**-Parameter zurückgibt. Ein Zeiger auf diese Methode wird für Direct2D- bereitgestellt, wenn der Effekt über die ID2D1Factory1::RegisterEffectFromString-API über den PD2D1_EFFECT_FACTORY\-Parameter registriert wird.

HRESULT __stdcall SampleEffect::CreateEffect(_Outptr_ IUnknown** ppEffectImpl)
{
    // This code assumes that the effect class initializes its reference count to 1.
    *ppEffectImpl = static_cast<ID2D1EffectImpl*>(new SampleEffect());

    if (*ppEffectImpl == nullptr)
    {
        return E_OUTOFMEMORY;
    }

    return S_OK;
}

Implementieren der IUnknown-Schnittstelle

Schließlich muss der Effekt die IUnknown-Schnittstelle zur Kompatibilität mit COM implementieren.

Erstellen des Transformationsdiagramms des Effekts

Ein Effekt kann mehrere verschiedene Transformationen (einzelne Bildvorgänge) verwenden, um seinen gewünschten Bilddarstellungseffekt zu erzeugen. Um die Reihenfolge zu steuern, in der diese Transformationen auf das Eingabebild angewendet werden, ordnet der Effekt sie in einem Transformationsdiagramm an. Ein Transformationsdiagramm kann die Effekte und Transformationen nutzen, die in Direct2D- enthalten sind, sowie benutzerdefinierte Transformationen, die vom Effektautor erstellt wurden.

Verwenden von Transformationen, die in Direct2D enthalten sind

Dies sind die am häufigsten verwendeten Transformationen, die mit Direct2D-bereitgestellt werden.

Erstellen eines Transformationsdiagramms mit einem einzigen Knoten

Nachdem Sie eine Transformation erstellt haben, muss die Eingabe des Effekts mit der Eingabe der Transformation verbunden sein, und die Ausgabe der Transformation muss mit der Ausgabe des Effekts verbunden werden. Wenn ein Effekt nur eine einzelne Transformation enthält, können Sie die ID2D1TransformGraph::SetSingleTransformNode Methode verwenden, um dies auf einfache Weise zu erreichen.

Sie können eine Transformation im Initialize oder SetGraph- Methoden mithilfe des bereitgestellten ID2D1TransformGraphGraph-Parameters erstellen oder ändern. Wenn ein Effekt Änderungen am Transformationsdiagramm in einer anderen Methode vornehmen muss, bei der dieser Parameter nicht verfügbar ist, kann der Effekt den ID2D1TransformGraph-Parameter als Membervariable der Klasse speichern und an anderer Stelle darauf zugreifen, z. B. PrepareForRender oder eine benutzerdefinierte Eigenschaftsrückrufmethode.

Hier sehen Sie ein Beispiel Initialize Methode. Diese Methode erstellt ein Transformationsdiagramm mit einem einzigen Knoten, das das Bild um einhundert Pixel auf jeder Achse versetzt.

IFACEMETHODIMP SampleEffect::Initialize(
    _In_ ID2D1EffectContext* pEffectContext,
    _In_ ID2D1TransformGraph* pTransformGraph
    )
{
    HRESULT hr = pEffectContext->CreateOffsetTransform(
        D2D1::Point2L(100,100),  // Offsets the input by 100px in each axis.
        &m_pOffsetTransform
        );

    if (SUCCEEDED(hr))
    {
        // Connects the effect's input to the transform's input, and connects
        // the transform's output to the effect's output.
        hr = pTransformGraph->SetSingleTransformNode(m_pOffsetTransform);
    }

    return hr;
}

Erstellen eines Transformationsdiagramms mit mehreren Knoten

Durch das Hinzufügen mehrerer Transformationen zum Transformationsdiagramm eines Effekts können Effekte intern mehrere Bildvorgänge ausführen, die einer App als einzelner einheitlicher Effekt präsentiert werden.

Wie oben erwähnt, kann das Transformationsdiagramm des Effekts in jeder Effektmethode mit dem ID2D1TransformGraph Parameter bearbeitet werden, der in der Initialize--Methode des Effekts empfangen wird. Die folgenden APIs auf dieser Schnittstelle können zum Erstellen oder Ändern des Transformationsdiagramms eines Effekts verwendet werden:

AddNode(ID2D1TransformNode *pNode)

Die AddNode Methode registriert die Transformation mit dem Effekt und muss aufgerufen werden, bevor die Transformation mit einer der anderen Transformationsdiagrammmethoden verwendet werden kann.

ConnectToEffectInput(UINT32 toEffectInputIndex, ID2D1TransformNode *pNode, UINT32 toNodeInputIndex)

Die ConnectToEffectInput Methode verbindet die Bildeingabe des Effekts mit der Eingabe einer Transformation. Die gleiche Effekteingabe kann mit mehreren Transformationen verbunden werden.

ConnectNode(ID2D1TransformNode *pFromNode, ID2D1TransformNode *pToNode, UINT32 toNodeInputIndex)

Die ConnectNode-Methode verbindet die Ausgabe einer Transformation mit der Eingabe einer anderen Transformation. Eine Transformationsausgabe kann mit mehreren Transformationen verbunden werden.

SetOutputNode(ID2D1TransformNode *pNode)

Die SetOutputNode Methode verbindet die Ausgabe einer Transformation mit der Ausgabe des Effekts. Da ein Effekt nur eine Ausgabe hat, kann nur eine einzelne Transformation als "Ausgabeknoten" festgelegt werden.

Dieser Code verwendet zwei separate Transformationen, um einen einheitlichen Effekt zu erstellen. In diesem Fall ist der Effekt ein übersetzter Schlagschatten.

IFACEMETHODIMP SampleEffect::Initialize(
    _In_ ID2D1EffectContext* pEffectContext, 
    _In_ ID2D1TransformGraph* pTransformGraph
    )
{   
    // Create the shadow effect.
    HRESULT hr = pEffectContext->CreateEffect(CLSID_D2D1Shadow, &m_pShadowEffect);

    // Create the shadow transform from the shadow effect.
    if (SUCCEEDED(hr))
    {
        hr = pEffectContext->CreateTransformNodeFromEffect(m_pShadowEffect, &m_pShadowTransform);
    }

    // Create the offset transform.
    if (SUCCEEDED(hr))
    {
        hr = pEffectContext->CreateOffsetTransform(
            D2D1::Point2L(0,0),
            &m_pOffsetTransform
            );
    }

    // Register both transforms with the effect graph.
    if (SUCCEEDED(hr))
    {
        hr = pTransformGraph->AddNode(m_pShadowTransform);
    }

    if (SUCCEEDED(hr))
    {
        hr = pTransformGraph->AddNode(m_pOffsetTransform);
    }

    // Connect the custom effect's input to the shadow transform's input.
    if (SUCCEEDED(hr))
    {
        hr = pTransformGraph->ConnectToEffectInput(
            0,                  // Input index of the effect.
            m_pShadowTransform, // The receiving transform.
            0                   // Input index of the receiving transform.
            );
    }

    // Connect the shadow transform's output to the offset transform's input.
    if (SUCCEEDED(hr))
    {
        hr = pTransformGraph->ConnectNode(
            m_pShadowTransform, // 'From' node.
            m_pOffsetTransform, // 'To' node.
            0                   // Input index of the 'to' node. There is only one output for the 'From' node.
            );
    }

    // Connect the offset transform's output to the custom effect's output.
    if (SUCCEEDED(hr))
    {
        hr = pTransformGraph->SetOutputNode(
            m_pOffsetTransform
            );
    }

    return hr;
}

Hinzufügen von benutzerdefinierten Eigenschaften zu einem Effekt

Effekte können benutzerdefinierte Eigenschaften definieren, mit denen eine App das Verhalten des Effekts während der Laufzeit ändern kann. Es gibt drei Schritte zum Definieren einer Eigenschaft für einen benutzerdefinierten Effekt:

Hinzufügen der Eigenschaftenmetadaten zu den Registrierungsdaten des Effekts

Hinzufügen einer Eigenschaft zur Registrierungs-XML

Sie müssen die Eigenschaften eines benutzerdefinierten Effekts während der erstregistrierung mit Direct2D-definieren. Zuerst müssen Sie die Registrierungs-XML des Effekts in der öffentlichen Registrierungsmethode mit der neuen Eigenschaft aktualisieren:

PCWSTR pszXml =
    TEXT(
        <?xml version='1.0'?>
        <Effect>
            <!-- System Properties -->
            <Property name='DisplayName' type='string' value='SampleEffect'/>
            <Property name='Author' type='string' value='Contoso'/>
            <Property name='Category' type='string' value='Sample'/>
            <Property name='Description'
                type='string'
                value='Translates an image by a user-specifiable amount.'/>
            <Inputs>
                <Input name='Source'/>
                <!-- Additional inputs go here. -->
            </Inputs>
            <!-- Custom Properties go here. -->
            <Property name='Offset' type='vector2'>
                <Property name='DisplayName' type='string' value='Image Offset'/>
                <!— Optional sub-properties -->
                <Property name='Min' type='vector2' value='(-1000.0, -1000.0)' />
                <Property name='Max' type='vector2' value='(1000.0, 1000.0)' />
                <Property name='Default' type='vector2' value='(0.0, 0.0)' />
            </Property>
        </Effect>
        );

Wenn Sie eine Effekteigenschaft in XML definieren, benötigt sie einen Namen, einen Typ und einen Anzeigenamen. Der Anzeigename einer Eigenschaft sowie die Kategorie, den Autor und die Beschreibungswerte des Gesamteffekts können und sollten lokalisiert werden.

Für jede Eigenschaft kann ein Effekt optional Standard-, Min- und Höchstwerte angeben. Diese Werte dienen nur zur Informationsverwendung. Sie werden nicht durch Direct2D-erzwungen. Es liegt an Ihnen, jede angegebene Standard-/Min/Max-Logik in der Effektklasse selbst zu implementieren.

Der im XML-Code für die Eigenschaft aufgeführte Typ muss mit dem entsprechenden Datentyp übereinstimmen, der von den Getter- und Settermethoden der Eigenschaft verwendet wird. Entsprechende XML-Werte für jeden Datentyp werden in dieser Tabelle angezeigt:

Datentyp Entsprechender XML-Wert
PWSTR Schnur
BOOL Bool
UINT uint32
INT int32
SCHWEBEN schweben
D2D_VECTOR_2F Vector2
D2D_VECTOR_3F Vector3
D2D_VECTOR_4F vector4
D2D_MATRIX_3X2_F matrix3x2
D2D_MATRIX_4X3_F matrix4x3
D2D_MATRIX_4X4_F matrix4x4
D2D_MATRIX_5X4_F matrix5x4
BYTE[] Blob
IUnknown* iunknown
ID2D1ColorContext-* colorcontext
CLSID clsid
Aufzählung (D2D1_INTERPOLATION_MODEusw.) Enumeration

 

Zuordnen der neuen Eigenschaft zu Getter- und Settermethoden

Als Nächstes muss der Effekt diese neue Eigenschaft den Getter- und Settermethoden zuordnen. Dies erfolgt über das D2D1_PROPERTY_BINDING Array, das an die ID2D1Factory1::RegisterEffectFromString--Methode übergeben wird.

Das D2D1_PROPERTY_BINDING Array sieht wie folgt aus:

const D2D1_PROPERTY_BINDING bindings[] =
{
    D2D1_VALUE_TYPE_BINDING(
        L"Offset",      // The name of property. Must match name attribute in XML.
        &SetOffset,     // The setter method that is called on "SetValue".
        &GetOffset      // The getter method that is called on "GetValue".
        )
};

Nachdem Sie das XML- und Bindungsarray erstellt haben, übergeben Sie sie an die RegisterEffectFromString--Methode:

pFactory->RegisterEffectFromString(
    CLSID_SampleEffect,  // GUID defined in class header file.
    pszXml,              // Previously-defined XML that describes effect.
    bindings,            // The previously-defined property bindings array.
    ARRAYSIZE(bindings), // Number of entries in the property bindings array.    
    CreateEffect         // Static method that returns an instance of the effect's class.
    );

Für das D2D1_VALUE_TYPE_BINDING Makro muss die Effektklasse vor einer anderen Schnittstelle von ID2D1EffectImpl- erben.

Benutzerdefinierte Eigenschaften für einen Effekt werden in der Reihenfolge indiziert, in der sie im XML deklariert werden, und sobald erstellt wurde, kann von der App mithilfe der methoden ID2D1Properties::SetValue und ID2D1Properties::GetValue zugegriffen werden. Aus Gründen der Einfachheit können Sie eine öffentliche Enumeration erstellen, die jede Eigenschaft in der Headerdatei des Effekts auflistet:

typedef enum SAMPLEEFFECT_PROP
{
    SAMPLEFFECT_PROP_OFFSET = 0
};

Erstellen der Getter- und Settermethoden für die Eigenschaft

Der nächste Schritt besteht darin, die Getter- und Settermethoden für die neue Eigenschaft zu erstellen. Die Namen der Methoden müssen mit den im D2D1_PROPERTY_BINDING Array angegebenen übereinstimmen. Darüber hinaus muss der im XML-Code des Effekts angegebene Eigenschaftstyp mit dem Typ des Parameters der Settermethode und dem Rückgabewert der Getter-Methode übereinstimmen.

HRESULT SampleEffect::SetOffset(D2D_VECTOR_2F offset)
{
    // Method must manually clamp to values defined in XML.
    offset.x = min(offset.x, 1000.0f); 
    offset.x = max(offset.x, -1000.0f); 

    offset.y = min(offset.y, 1000.0f); 
    offset.y = max(offset.y, -1000.0f); 

    m_offset = offset;

    return S_OK;
}

D2D_VECTOR_2F SampleEffect::GetOffset() const
{
    return m_offset;
}

Aktualisieren der Transformationen des Effekts als Reaktion auf Eigenschaftsänderungen

Um die Bildausgabe eines Effekts als Reaktion auf eine Eigenschaftsänderung tatsächlich zu aktualisieren, muss der Effekt seine zugrunde liegenden Transformationen ändern. Dies geschieht in der Regel in der PrepareForRender-Methode des Effekts, die Direct2 D automatisch aufruft, wenn eine der Eigenschaften eines Effekts geändert wurde. Transformationen können jedoch in einer der Methoden des Effekts aktualisiert werden: z. B. Initialize oder die Eigenschaftensatzmethoden des Effekts.

Wenn ein Effekt beispielsweise eine ID2D1OffsetTransform- enthielt und als Reaktion auf die Offset-Eigenschaft des Effekts geändert werden soll, würde er den folgenden Code in PrepareForRenderhinzufügen:

IFACEMETHODIMP SampleEffect::PrepareForRender(D2D1_CHANGE_TYPE changeType)
{
    // All effect properties are DPI independent (specified in DIPs). In this offset
    // example, the offset value provided must be scaled from DIPs to pixels to ensure
    // a consistent appearance at different DPIs (excluding minor scaling artifacts).
    // A context's DPI can be retrieved using the ID2D1EffectContext::GetDPI API.
    
    D2D1_POINT_2L pixelOffset;
    pixelOffset.x = static_cast<LONG>(m_offset.x * (m_dpiX / 96.0f));
    pixelOffset.y = static_cast<LONG>(m_offset.y * (m_dpiY / 96.0f));
    
    // Update the effect's offset transform with the new offset value.
    m_pOffsetTransform->SetOffset(pixelOffset);

    return S_OK;
}

Erstellen einer benutzerdefinierten Transformation

Um Bildvorgänge zu implementieren, die nicht in Direct2D-bereitgestellt werden, müssen Sie benutzerdefinierte Transformationen implementieren. Benutzerdefinierte Transformationen können mithilfe von benutzerdefinierten HLSL-Shadern beliebig ein Eingabebild ändern.

Transformationen implementieren je nach verwendeten Shadertypen eine von zwei verschiedenen Schnittstellen. Transformationen mit Pixel- und/oder Vertex-Shadern müssen ID2D1DrawTransform-implementieren, während Transformationen mit Compute-Shadern ID2D1ComputeTransform-implementieren müssen. Diese Schnittstellen erben beide von ID2D1Transform-. Dieser Abschnitt konzentriert sich auf die Implementierung der Funktionen, die für beides gemeinsam sind.

Die ID2D1Transform Schnittstelle verfügt über vier Methoden zum Implementieren:

GetInputCount

Diese Methode gibt eine ganze Zahl zurück, die die Eingabeanzahl für die Transformation darstellt.

IFACEMETHODIMP_(UINT32) GetInputCount() const
{
    return 1;
}

MapInputRectsToOutputRect

Direct2D- ruft die MapInputRectsToOutputRect- Methode bei jedem Rendern der Transformation auf. Direct2D übergibt ein Rechteck, das die Grenzen der einzelnen Eingaben an die Transformation darstellt. Die Transformation ist dann für die Berechnung der Grenzen des Ausgabebilds verantwortlich. Die Größe der Rechtecke für alle Methoden auf dieser Schnittstelle (ID2D1Transform) werden in Pixeln und nicht in DIPs definiert.

Diese Methode ist auch für die Berechnung des Bereichs der Ausgabe verantwortlich, die auf der Logik des Shaders und den undurchsichtigen Bereichen der einzelnen Eingaben basiert. Ein undurchsichtiger Bereich eines Bilds wird so definiert, dass der Alphakanal "1" für die gesamte Anzahl des Rechtecks ist. Wenn unklar ist, ob die Ausgabe einer Transformation undurchsichtig ist, sollte das undurchsichtige Rechteck für die Ausgabe auf (0, 0, 0, 0, 0) als sicheren Wert festgelegt werden. Direct2D- verwendet diese Informationen, um Renderingoptimierungen mit garantiert undurchsichtigen Inhalten durchzuführen. Wenn dieser Wert ungenau ist, kann es zu einem falschen Rendering führen.

Sie können das Renderingverhalten der Transformation (gemäß Definition in Abschnitten 6 bis 8) während dieser Methode ändern. Sie können jedoch keine anderen Transformationen im Transformationsdiagramm oder das Diagrammlayout selbst ändern.

IFACEMETHODIMP SampleTransform::MapInputRectsToOutputRect(
    _In_reads_(inputRectCount) const D2D1_RECT_L* pInputRects,
    _In_reads_(inputRectCount) const D2D1_RECT_L* pInputOpaqueSubRects,
    UINT32 inputRectCount,
    _Out_ D2D1_RECT_L* pOutputRect,
    _Out_ D2D1_RECT_L* pOutputOpaqueSubRect
    )
{
    // This transform is designed to only accept one input.
    if (inputRectCount != 1)
    {
        return E_INVALIDARG;
    }

    // The output of the transform will be the same size as the input.
    *pOutputRect = pInputRects[0];
    // Indicate that the image's opacity has not changed.
    *pOutputOpaqueSubRect = pInputOpaqueSubRects[0];
    // The size of the input image can be saved here for subsequent operations.
    m_inputRect = pInputRects[0];

    return S_OK;
}

Berücksichtigen Sie für ein komplexeres Beispiel, wie ein einfacher Weichzeichnervorgang dargestellt wird:

Wenn ein Weichzeichnervorgang einen Radius von 5 Pixeln verwendet, muss die Größe des Ausgaberechtecks um 5 Pixel erweitert werden, wie unten dargestellt. Beim Ändern von Rechteckkoordinaten muss eine Transformation sicherstellen, dass ihre Logik keine Über-/Unterläufe in den Rechteckkoordinaten verursacht.

// Expand output image by 5 pixels.

// Do not expand empty input rectangles.
if (pInputRects[0].right  > pInputRects[0].left &&
    pInputRects[0].bottom > pInputRects[0].top
    )
{
    pOutputRect->left   = ((pInputRects[0].left   - 5) < pInputRects[0].left  ) ? (pInputRects[0].left   - 5) : LONG_MIN;
    pOutputRect->top    = ((pInputRects[0].top    - 5) < pInputRects[0].top   ) ? (pInputRects[0].top    - 5) : LONG_MIN;
    pOutputRect->right  = ((pInputRects[0].right  + 5) > pInputRects[0].right ) ? (pInputRects[0].right  + 5) : LONG_MAX;
    pOutputRect->bottom = ((pInputRects[0].bottom + 5) > pInputRects[0].bottom) ? (pInputRects[0].bottom + 5) : LONG_MAX;
}

Da das Bild verschwommen ist, kann ein Bereich des Bilds, das undurchsichtig war, jetzt teilweise transparent sein. Dies liegt daran, dass der Bereich außerhalb des Bilds standardmäßig transparent schwarz ist und diese Transparenz in das Bild um die Ränder gemischt wird. Die Transformation muss dies in ihren ausgabedurchsichtigen Rechteckberechnungen widerspiegeln:

// Shrink opaque region by 5 pixels.
pOutputOpaqueSubRect->left   = pInputOpaqueSubRects[0].left   + 5;
pOutputOpaqueSubRect->top    = pInputOpaqueSubRects[0].top    + 5;
pOutputOpaqueSubRect->right  = pInputOpaqueSubRects[0].right  - 5;
pOutputOpaqueSubRect->bottom = pInputOpaqueSubRects[0].bottom - 5;

Diese Berechnungen werden hier visualisiert:

Abbildung der Rechteckberechnung.

Weitere Informationen zu dieser Methode finden Sie auf der MapInputRectsToOutputRect Referenzseite.

MapOutputRectToInputRects

Direct2D- ruft die MapOutputRectToInputRects Methode nach MapInputRectsToOutputRectauf. Die Transformation muss berechnen, aus welchem Teil des Bilds es gelesen werden muss, um den angeforderten Ausgabebereich korrekt zu rendern.

Wenn ein Effekt pixel 1-1 strikt zuordnet, kann er das Ausgaberechteck an das Eingaberechteck übergeben:

IFACEMETHODIMP SampleTransform::MapOutputRectToInputRects(
    _In_ const D2D1_RECT_L* pOutputRect,
    _Out_writes_(inputRectCount) D2D1_RECT_L* pInputRects,
    UINT32 inputRectCount
    ) const
{
    // This transform is designed to only accept one input.
    if (inputRectCount != 1)
    {
        return E_INVALIDARG;
    }

    // The input needed for the transform is the same as the visible output.
    pInputRects[0] = *pOutputRect;
    return S_OK;
}

Ebenso verwenden Pixel, wenn eine Transformation ein Bild verkleinern oder erweitert (z. B. das Weichzeichnerbeispiel hier), pixel häufig die umgebenden Pixel, um ihren Wert zu berechnen. Bei einem Weichzeichnen wird ein Pixel mit seinen umgebenden Pixeln gemittelt, auch wenn sie sich außerhalb der Grenzen des Eingabebilds befinden. Dieses Verhalten spiegelt sich in der Berechnung wider. Wie zuvor überprüft die Transformation beim Erweitern der Koordinaten eines Rechtecks auf Überläufe.

// Expand the input rectangle to reflect that more pixels need to 
// be read from than are necessarily rendered in the effect's output.
pInputRects[0].left   = ((pOutputRect->left   - 5) < pOutputRect->left  ) ? (pOutputRect->left   - 5) : LONG_MIN;
pInputRects[0].top    = ((pOutputRect->top    - 5) < pOutputRect->top   ) ? (pOutputRect->top    - 5) : LONG_MIN;
pInputRects[0].right  = ((pOutputRect->right  + 5) > pOutputRect->right ) ? (pOutputRect->right  + 5) : LONG_MAX;
pInputRects[0].bottom = ((pOutputRect->bottom + 5) > pOutputRect->bottom) ? (pOutputRect->bottom + 5) : LONG_MAX;

In dieser Abbildung wird die Berechnung visualisiert. Direct2D- zeigt automatisch transparente schwarze Pixel an, in denen das Eingabebild nicht vorhanden ist, sodass der Weichzeichner schrittweise mit dem vorhandenen Inhalt auf dem Bildschirm kombiniert werden kann.

Abbildung eines Effekts, der transparente schwarze Pixel außerhalb eines Rechtecks abgibt.

Wenn die Zuordnung nicht trivial ist, sollte diese Methode das Eingaberechteck auf den maximalen Bereich festlegen, um korrekte Ergebnisse zu gewährleisten. Legen Sie dazu die linken und oberen Ränder auf INT_MIN und die rechten und unteren Ränder auf INT_MAX fest.

Weitere Informationen zu dieser Methode finden Sie im Thema MapOutputRectToInputRects Thema.

MapInvalidRect

Direct2D- ruft auch die MapInvalidRect--Methode auf. Im Gegensatz zu den MapInputRectsToOutputRect und MapOutputRectToInputRects Methoden Direct2D ist jedoch nicht garantiert, es zu einem bestimmten Zeitpunkt aufzurufen. Diese Methode entscheidet konzeptionell, welcher Teil der Ausgabe einer Transformation als Reaktion auf einen Teil oder alle Eingaben neu gerendert werden muss. Es gibt drei verschiedene Szenarien, für die das ungültige Rechteck einer Transformation berechnet werden soll.

Transformationen mit 1:1-Pixelzuordnung

Für Transformationen, die Pixel 1-1 zuordnen, übergeben Sie einfach das ungültige Eingaberechteck an das ungültige Ausgaberechteck:

IFACEMETHODIMP SampleTransform::MapInvalidRect(
    UINT32 inputIndex,
    D2D1_RECT_L invalidInputRect,
    _Out_ D2D1_RECT_L* pInvalidOutputRect
    ) const
{
    // This transform is designed to only accept one input.
    if (inputIndex != 0)
    {
        return E_INVALIDARG;
    }

    // If part of the transform's input is invalid, mark the corresponding
    // output region as invalid. 
    *pInvalidOutputRect = invalidInputRect;

    return S_OK;
}

Transformationen mit n:n-Pixelzuordnungen

Wenn die Ausgabepixel einer Transformation von ihrem Umgebenden Bereich abhängig sind, muss das ungültige Eingaberechteck entsprechend erweitert werden. Dies ist darauf hinzuweisen, dass Pixel, die das ungültige Eingaberechteck umgeben, ebenfalls betroffen sind und ungültig werden. Beispielsweise verwendet ein fünf Pixel-Weichzeichner die folgende Berechnung:

// Expand the input invalid rectangle by five pixels in each direction. This
// reflects that a change in part of the given input image will cause a change
// in an expanded part of the output image (five pixels in each direction).
pInvalidOutputRect->left   = ((invalidInputRect.left   - 5) < invalidInputRect.left  ) ? (invalidInputRect.left   - 5) : LONG_MIN;
pInvalidOutputRect->top    = ((invalidInputRect.top    - 5) < invalidInputRect.top   ) ? (invalidInputRect.top    - 5) : LONG_MIN;
pInvalidOutputRect->right  = ((invalidInputRect.right  + 5) > invalidInputRect.right ) ? (invalidInputRect.right  + 5) : LONG_MAX;
pInvalidOutputRect->bottom = ((invalidInputRect.bottom + 5) > invalidInputRect.bottom) ? (invalidInputRect.bottom + 5) : LONG_MAX;

Transformationen mit komplexer Pixelzuordnung

Bei Transformationen, bei denen Eingabe- und Ausgabepixel keine einfache Zuordnung aufweisen, kann die gesamte Ausgabe als ungültig markiert werden. Wenn beispielsweise eine Transformation einfach die durchschnittliche Farbe der Eingabe ausgibt, ändert sich die gesamte Ausgabe der Transformation, wenn auch ein kleiner Teil der Eingabe geändert wird. In diesem Fall sollte das ungültige Ausgaberechteck auf ein logisch unendliches Rechteck festgelegt werden (siehe unten). Direct2D- klammert dies automatisch an die Grenzen der Ausgabe.

// If any change in the input image affects the entire output, the
// transform should set pInvalidOutputRect to a logically infinite rect.
*pInvalidOutputRect = D2D1::RectL(LONG_MIN, LONG_MIN, LONG_MAX, LONG_MAX);

Weitere Informationen zu dieser Methode finden Sie im Thema MapInvalidRect.

Nachdem diese Methoden implementiert wurden, enthält der Header der Transformation Folgendes:

class SampleTransform : public ID2D1Transform 
{
public:
    SampleTransform();

    // ID2D1TransformNode Methods:
    IFACEMETHODIMP_(UINT32) GetInputCount() const;
    
    // ID2D1Transform Methods:
    IFACEMETHODIMP MapInputRectsToOutputRect(
        _In_reads_(inputRectCount) const D2D1_RECT_L* pInputRects,
        _In_reads_(inputRectCount) const D2D1_RECT_L* pInputOpaqueSubRects,
        UINT32 inputRectCount,
        _Out_ D2D1_RECT_L* pOutputRect,
        _Out_ D2D1_RECT_L* pOutputOpaqueSubRect
        );    

    IFACEMETHODIMP MapOutputRectToInputRects(
        _In_ const D2D1_RECT_L* pOutputRect,
        _Out_writes_(inputRectCount) D2D1_RECT_L* pInputRects,
        UINT32 inputRectCount
        ) const;

    IFACEMETHODIMP MapInvalidRect(
        UINT32 inputIndex,
        D2D1_RECT_L invalidInputRect,
        _Out_ D2D1_RECT_L* pInvalidOutputRect 
        ) const;

    // IUnknown Methods:
    IFACEMETHODIMP_(ULONG) AddRef();
    IFACEMETHODIMP_(ULONG) Release();
    IFACEMETHODIMP QueryInterface(REFIID riid, _Outptr_ void** ppOutput);

private:
    LONG m_cRef; // Internal ref count used by AddRef() and Release() methods.
    D2D1_RECT_L m_inputRect; // Stores the size of the input image.
};

Hinzufügen eines Pixelshadrs zu einer benutzerdefinierten Transformation

Nachdem eine Transformation erstellt wurde, muss sie einen Shader bereitstellen, der die Bildpixel bearbeitet. In diesem Abschnitt werden die Schritte zum Verwenden eines Pixelshadrs mit einer benutzerdefinierten Transformation behandelt.

Implementieren von ID2D1DrawTransform

Um einen Pixelshader zu verwenden, muss die Transformation die ID2D1DrawTransform- Schnittstelle implementieren, die von der in Abschnitt 5 beschriebenen ID2D1Transform-Schnittstelle erbt. Diese Schnittstelle enthält eine neue Methode, die implementiert werden soll:

SetDrawInfo(ID2D1DrawInfo *pDrawInfo)

Direct2D- ruft die SetDrawInfo Methode auf, wenn die Transformation zum ersten Mal dem Transformationsdiagramm eines Effekts hinzugefügt wird. Diese Methode stellt einen ID2D1DrawInfo- Parameter bereit, der steuert, wie die Transformation gerendert wird. Die hier verfügbaren Methoden finden Sie im Thema ID2D1DrawInfo.

Wenn die Transformation den Parameter als Klassenmemembevariable speichert, kann auf das drawInfo-Objekt zugegriffen und von anderen Methoden wie Eigenschaftensettern oder MapInputRectsToOutputRectgeändert werden. Es kann nicht aus den MapOutputRectToInputRects oder MapInvalidRect- Methoden für ID2D1Transform-aufgerufen werden.

Erstellen einer GUID für den Pixelshader

Als Nächstes muss die Transformation eine eindeutige GUID für den Pixelshader selbst definieren. Dies wird verwendet, wenn Direct2D- den Shader in den Arbeitsspeicher lädt, und wenn die Transformation auswäht, welcher Pixelshader für die Ausführung verwendet werden soll. Tools wie guidgen.exe, die in Visual Studio enthalten sind, können verwendet werden, um eine zufällige GUID zu generieren.

// Example GUID used to uniquely identify HLSL shader. Passed to Direct2D during
// shader load, and used by the transform to identify the shader for the
// ID2D1DrawInfo::SetPixelShader method. The effect author should create a
// unique name for the shader as well as a unique GUID using
// a GUID generation tool.
DEFINE_GUID(GUID_SamplePixelShader, 0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);

Laden des Pixelshadrs mit Direct2D

Ein Pixelshader muss in den Arbeitsspeicher geladen werden, bevor er von der Transformation verwendet werden kann.

Um den Pixelshader in den Arbeitsspeicher zu laden, sollte die Transformation den kompilierten Shaderbytecode aus dem . CSO-Datei, die von Visual Studio generiert wird (details hierzu finden Sie in Direct3D- Dokumentation) in einem Bytearray. Diese Technik wird im D2DCustomEffects SDK-Beispielausführlich veranschaulicht.

Nachdem die Shaderdaten in ein Bytearray geladen wurden, rufen Sie die LoadPixelShader--Methode für das ID2D1EffectContext--Objekt des Effekts auf. Direct2D- ignoriert Aufrufe von LoadPixelShader-, wenn ein Shader mit derselben GUID bereits geladen wurde.

Nachdem ein Pixelshader in den Arbeitsspeicher geladen wurde, muss die Transformation sie für die Ausführung auswählen, indem die GUID an die SetPixelShader--Methode für die ID2D1DrawInfo- Parameter übergeben wird, der während der SetDrawInfo--Methode bereitgestellt wird. Der Pixelshader muss bereits in den Arbeitsspeicher geladen werden, bevor er für die Ausführung ausgewählt wird.

Ändern des Shadervorgangs mit Konstantenpuffern

Um die Ausführung eines Shaders zu ändern, übergibt eine Transformation möglicherweise einen Konstantenpuffer an den Pixelshader. Dazu definiert eine Transformation eine Struktur, die die gewünschten Variablen im Klassenheader enthält:

// This struct defines the constant buffer of the pixel shader.
struct
{
    float valueOne;
    float valueTwo;
} m_constantBuffer;

Die Transformation ruft dann die ID2D1DrawInfo::SetPixelShaderConstantBuffer Methode für die ID2D1DrawInfo Parameter auf, der in der SetDrawInfo--Methode bereitgestellt wird, um diesen Puffer an den Shader zu übergeben.

Die HLSL- muss auch eine entsprechende Struktur definieren, die den Konstantenpuffer darstellt. Die variablen, die in der Struktur des Shaders enthalten sind, müssen mit den Variablen in der Struktur der Transformation übereinstimmen.

cbuffer constants : register(b0)
{
    float valueOne : packoffset(c0.x);
    float valueTwo : packoffset(c0.y);
};

Nachdem der Puffer definiert wurde, können die darin enthaltenen Werte von überall im Pixelshader gelesen werden.

Schreiben eines Pixelshadrs für Direct2D

Direct2D- Transformationen verwenden Shader, die mit standardmäßigen HLSL-erstellt wurden. Es gibt jedoch einige wichtige Konzepte zum Schreiben eines Pixelshadrs, der aus dem Kontext einer Transformation ausgeführt wird. Ein vollständiges Beispiel für einen voll funktionsfähigen Pixelshader finden Sie im D2DCustomEffects SDK-Beispiel.

Direct2D- ordnet die Eingaben einer Transformation automatisch Texture2D- und SamplerState- Objekte im HLSL zu. Die erste Texture2D- befindet sich bei register t0, und die erste SamplerState- befindet sich bei register s0. Jede zusätzliche Eingabe befindet sich in den nächsten entsprechenden Registern (z. B. t1 und s1). Pixeldaten für eine bestimmte Eingabe können durch Aufrufen von Sample für das Texture2D--Objekt und übergeben das entsprechende SamplerState-Objekt und die Texelkoordinaten.

Ein benutzerdefinierter Pixelshader wird einmal für jedes gerenderte Pixel ausgeführt. Jedes Mal, wenn der Shader ausgeführt wird, stellt Direct2D- automatisch drei Parameter bereit, die die aktuelle Ausführungsposition identifizieren:

  • Ausgabe des Szenenbereichs: Dieser Parameter stellt die aktuelle Ausführungsposition in Bezug auf die Gesamtzieloberfläche dar. Sie wird in Pixeln definiert, und die Min/Max-Werte entsprechen den Grenzen des Rechtecks, das von MapInputRectsToOutputRectzurückgegeben wird.
  • Clip-Space-Ausgabe: Dieser Parameter wird von Direct3D verwendet und darf nicht im Pixelshader einer Transformation verwendet werden.
  • Texel-Leerzeicheneingabe: Dieser Parameter stellt die aktuelle Ausführungsposition in einer bestimmten Eingabetextur dar. Ein Shader sollte keine Abhängigkeiten davon übernehmen, wie dieser Wert berechnet wird. Es sollte nur zum Beispiel für die Eingabe des Pixelshaders verwendet werden, wie im folgenden Code dargestellt:
Texture2D InputTexture : register(t0);
SamplerState InputSampler : register(s0);

float4 main(
    float4 clipSpaceOutput  : SV_POSITION,
    float4 sceneSpaceOutput : SCENE_POSITION,
    float4 texelSpaceInput0 : TEXCOORD0
    ) : SV_Target
{
    // Samples pixel from ten pixels above current position.

    float2 sampleLocation =
        texelSpaceInput0.xy    // Sample position for the current output pixel.
        + float2(0,-10)        // An offset from which to sample the input, specified in pixels.
        * texelSpaceInput0.zw; // Multiplier that converts pixel offset to sample position offset.

    float4 color = InputTexture.Sample(
        InputSampler,          // Sampler and Texture must match for a given input.
        sampleLocation
        );

    return color;
}

Hinzufügen eines Vertex-Shaders zu einer benutzerdefinierten Transformation

Sie können Vertex-Shader verwenden, um unterschiedliche Imageerstellungsszenarien als Pixelshader auszuführen. Insbesondere können Vertex-Shader geometriebasierte Bildeffekte ausführen, indem Scheitelpunkte transformiert werden, die ein Bild umfassen. Vertex-Shader können unabhängig von oder in Verbindung mit transformationsspezifischen Pixelshadern verwendet werden. Wenn kein Vertex-Shader angegeben ist, Direct2D ersetzt in einem Standard-Vertex-Shader für die Verwendung mit dem benutzerdefinierten Pixelshader.

Der Prozess zum Hinzufügen eines Vertex-Shaders zu einer benutzerdefinierten Transformation ähnelt dem eines Pixelshaders– die Transformation implementiert die ID2D1DrawTransform-Schnittstelle, erstellt eine GUID und (optional) übergibt Konstantenpuffer an den Shader. Es gibt jedoch einige wichtige zusätzliche Schritte, die für Vertex-Shader eindeutig sind:

Erstellen eines Vertexpuffers

Ein Vertex-Shader nach Definition wird für an ihn übergebene Scheitelpunkte ausgeführt, nicht für einzelne Pixel. Um die Scheitelpunkte für den auszuführenden Shader anzugeben, erstellt eine Transformation einen Vertexpuffer, der an den Shader übergeben werden soll. Das Layout von Vertexpuffern liegt außerhalb des Umfangs dieses Dokuments. Weitere Informationen finden Sie in der Direct3D-Referenz, oder das D2DCustomEffects SDK-Beispiel für eine Beispielimplementierung.

Nach dem Erstellen eines Vertexpuffers im Arbeitsspeicher verwendet die Transformation die CreateVertexBuffer- Methode für die ID2D1EffectContext- Objekt des enthaltenden Effekts, um diese Daten an die GPU zu übergeben. Weitere Informationen finden Sie im D2DCustomEffects SDK-Beispiel für eine Beispielimplementierung.

Wenn keine Vertexpuffer durch die Transformation angegeben werden, übergibt Direct2D- einen Standardvertexpuffer, der die rechteckige Bildposition darstellt.

Ändern von SetDrawInfo zur Verwendung eines Vertex-Shaders

Wie bei Pixelshadern muss die Transformation einen Vertex-Shader für die Ausführung laden und auswählen. Zum Laden des Vertex-Shaders ruft er die LoadVertexShader--Methode für die ID2D1EffectContext- Methode auf, die in der Initialize-Methode des Effekts empfangen wurde. Um den Vertex-Shader für die Ausführung auszuwählen, ruft er SetVertexProcessing- für die ID2D1DrawInfo Parameter auf, der in der SetDrawInfo--Methode der Transformation empfangen wird. Diese Methode akzeptiert eine GUID für einen zuvor geladenen Vertex-Shader sowie (optional) einen zuvor erstellten Vertexpuffer, für den der Shader ausgeführt werden soll.

Implementieren eines Direct2D-Vertex-Shaders

Eine Zeichnungstransformation kann sowohl einen Pixelshader als auch einen Vertex-Shader enthalten. Wenn eine Transformation sowohl einen Pixelshader als auch einen Vertex-Shader definiert, wird die Ausgabe des Vertex-Shaders direkt an den Pixelshader übergeben: Die App kann die Rückgabesignatur des Vertex-Shaders /die Parameter des Pixelshadrs anpassen, solange sie konsistent sind.

Wenn eine Transformation hingegen nur einen Vertex-Shader enthält und auf Direct2D-standardmäßigen Pass-Through-Pixelshader basiert, muss die folgende Standardausgabe zurückgegeben werden:

struct VSOut
{
    float4 clipSpaceOutput  : SV_POSITION; 
    float4 sceneSpaceOutput : SCENE_POSITION;
    float4 texelSpaceInput0 : TEXCOORD0;  
};

Ein Vertex-Shader speichert das Ergebnis seiner Vertextransformationen in der Ausgabevariable "Szenenbereich" des Shaders. Um die Clip-Space-Ausgabe und die Texelraumeingabevariablen zu berechnen, stellt Direct2D- automatisch Konvertierungsmatrizen in einem Konstantenpuffer bereit:

// Constant buffer b0 is used to store the transformation matrices from scene space
// to clip space. Depending on the number of inputs to the vertex shader, there
// may be more or fewer "sceneToInput" matrices.
cbuffer Direct2DTransforms : register(b0)
{
    float2x1 sceneToOutputX;
    float2x1 sceneToOutputY;
    float2x1 sceneToInput0X;
    float2x1 sceneToInput0Y;
};

Beispiel-Vertex-Shadercode finden Sie unten, der die Konvertierungsmatrizen verwendet, um die richtigen Clip- und Texelräume zu berechnen, die von Direct2D-erwartet werden:

// Constant buffer b0 is used to store the transformation matrices from scene space
// to clip space. Depending on the number of inputs to the vertex shader, there
// may be more or fewer "sceneToInput" matrices.
cbuffer Direct2DTransforms : register(b0)
{
    float2x1 sceneToOutputX;
    float2x1 sceneToOutputY;
    float2x1 sceneToInput0X;
    float2x1 sceneToInput0Y;
};

// Default output structure. This can be customized if transform also contains pixel shader.
struct VSOut
{
    float4 clipSpaceOutput  : SV_POSITION; 
    float4 sceneSpaceOutput : SCENE_POSITION;
    float4 texelSpaceInput0 : TEXCOORD0;  
};

// The parameter(s) passed to the vertex shader are defined by the vertex buffer's layout
// as specified by the transform. If no vertex buffer is specified, Direct2D passes two
// triangles representing the rectangular image with the following layout:
//
//    float4 outputScenePosition : OUTPUT_SCENE_POSITION;
//
//    The x and y coordinates of the outputScenePosition variable represent the image's
//    position on the screen. The z and w coordinates are used for perspective and
//    depth-buffering.

VSOut GeometryVS(float4 outputScenePosition : OUTPUT_SCENE_POSITION) 
{
    VSOut output;

    // Compute Scene-space output (vertex simply passed-through here). 
    output.sceneSpaceOutput.x = outputScenePosition.x;
    output.sceneSpaceOutput.y = outputScenePosition.y;
    output.sceneSpaceOutput.z = outputScenePosition.z;
    output.sceneSpaceOutput.w = outputScenePosition.w;

    // Generate standard Clip-space output coordinates.
    output.clipSpaceOutput.x = (output.sceneSpaceOutput.x * sceneToOutputX[0]) +
        output.sceneSpaceOutput.w * sceneToOutputX[1];

    output.clipSpaceOutput.y = (output.sceneSpaceOutput.y * sceneToOutputY[0]) + 
        output.sceneSpaceOutput.w * sceneToOutputY[1];

    output.clipSpaceOutput.z = output.sceneSpaceOutput.z;
    output.clipSpaceOutput.w = output.sceneSpaceOutput.w;

    // Generate standard Texel-space input coordinates.
    output.texelSpaceInput0.x = (outputScenePosition.x * sceneToInput0X[0]) + sceneToInput0X[1];
    output.texelSpaceInput0.y = (outputScenePosition.y * sceneToInput0Y[0]) + sceneToInput0Y[1];
    output.texelSpaceInput0.z = sceneToInput0X[0];
    output.texelSpaceInput0.w = sceneToInput0Y[0];

    return output;  
}

Der obige Code kann als Ausgangspunkt für einen Vertex-Shader verwendet werden. Es durchläuft lediglich das Eingabebild, ohne Transformationen auszuführen. Sehen Sie sich erneut das D2DCustomEffects SDK-Beispiel für eine vollständig implementierte Vertex-Shader-basierte Transformation an.

Wenn keine Vertexpuffer durch die Transformation angegeben werden, ersetzt Direct2D in einem Standardvertexpuffer, der die rechteckige Bildposition darstellt. Die Parameter für den Vertex-Shader werden in die Parameter der Standard-Shaderausgabe geändert:

struct VSIn
{
    float4 clipSpaceOutput  : SV_POSITION; 
    float4 sceneSpaceOutput : SCENE_POSITION;
    float4 texelSpaceInput0 : TEXCOORD0;  
};

Der Vertex-Shader kann seine sceneSpaceOutput- und clipSpaceOutput Parameter nicht ändern. Sie müssen sie unverändert zurückgeben. Es kann jedoch die texelSpaceInput Parameter für jedes Eingabebild ändern. Wenn die Transformation auch einen benutzerdefinierten Pixelshader enthält, kann der Vertex-Shader weiterhin zusätzliche benutzerdefinierte Parameter direkt an den Pixelshader übergeben. Darüber hinaus wird der sceneSpace benutzerdefinierten Puffer (b0) nicht mehr bereitgestellt.

Hinzufügen eines Computeshaders zu einer benutzerdefinierten Transformation

Schließlich können benutzerdefinierte Transformationen Compute-Shader für bestimmte zielorientierte Szenarien verwenden. Computeshader können verwendet werden, um komplexe Bildeffekte zu implementieren, die beliebigen Zugriff auf Eingabe- und Ausgabebildpuffer erfordern. Ein einfacher Histogrammalgorithmus kann beispielsweise aufgrund von Einschränkungen des Speicherzugriffs nicht mit einem Pixelshader implementiert werden.

Da Compute-Shader höhere Anforderungen auf Hardwarefeatureebene haben als Pixelshader, sollten Pixel-Shader verwendet werden, wenn möglich, um einen bestimmten Effekt zu implementieren. Insbesondere werden Compute-Shader nur auf den meisten DirectX 10-Levelkarten und höher ausgeführt. Wenn sich eine Transformation für die Verwendung eines Computeshaders entscheidet, muss sie zusätzlich zur Implementierung der ID2D1ComputeTransform--Schnittstelle nach der entsprechenden Hardwareunterstützung suchen.

Überprüfen der Computeshaderunterstützung

Wenn ein Effekt einen Compute-Shader verwendet, muss er während der Erstellung mithilfe der ID2D1EffectContext::CheckFeatureSupport-Methode auf die Computeshaderunterstützung überprüfen. Wenn die GPU keine Computeshader unterstützt, muss der Effekt D2DERR_INSUFFICIENT_DEVICE_CAPABILITIESzurückgeben.

Es gibt zwei verschiedene Arten von Computeshadern, die eine Transformation verwenden kann: Shadermodell 4 (DirectX 10) und Shadermodell 5 (DirectX 11). Es gibt bestimmte Einschränkungen für Shadermodell 4-Shader. Ausführliche Informationen finden Sie in der Direct3D--Dokumentation. Transformationen können beide Shadertypen enthalten und bei Bedarf auf Shadermodell 4 zurückgreifen: Eine Implementierung finden Sie im D2DCustomEffects SDK-Beispiel.

Implementieren von ID2D1ComputeTransform

Diese Schnittstelle enthält zwei neue Methoden, die zusätzlich zu den methoden in ID2D1Transformimplementiert werden sollen:

SetComputeInfo(ID2D1ComputeInfo *pComputeInfo)

Wie bei Pixel- und Vertex-Shadern ruft Direct2D- die SetComputeInfo Methode auf, wenn die Transformation zum ersten Mal dem Transformationsdiagramm eines Effekts hinzugefügt wird. Diese Methode stellt einen ID2D1ComputeInfo Parameter bereit, der steuert, wie die Transformation gerendert wird. Dazu gehört die Auswahl des Computeshaders, der über die ID2D1ComputeInfo::SetComputeShader-Methode ausgeführt werden soll. Wenn die Transformation diesen Parameter als Klassenmemembevariable speichert, kann er von jeder Transformations- oder Effektmethode mit Ausnahme der MapOutputRectToInputRects- und MapInvalidRect--Methoden geändert werden. Weitere hier verfügbare Methoden finden Sie im Thema ID2D1ComputeInfo.

CalculateThreadgroups(const D2D1_RECT_L *pOutputRect, UINT32 *pDimensionX, UINT32 *pDimensionY, UINT32 *pDimensionZ)

Während Pixelshader pro Pixel und Vertex-Shader pro Vertex ausgeführt werden, werden Compute-Shader pro Threadgruppe ausgeführt. Eine Threadgruppe stellt eine Reihe von Threads dar, die gleichzeitig auf der GPU ausgeführt werden. Der HLSL-Code des Computeshaders bestimmt, wie viele Threads pro Threadgruppe ausgeführt werden sollen. Der Effekt skaliert die Anzahl der Threadgruppen, sodass der Shader je nach Logik des Shaders die gewünschte Anzahl von Malen ausführt.

Die CalculateThreadgroups Methode ermöglicht es der Transformation, Direct2D- zu informieren, wie viele Threadgruppen erforderlich sind, basierend auf der Größe des Bilds und den eigenen Kenntnissen des Shaders.

Die Häufigkeit, mit der der Compute-Shader ausgeführt wird, ist ein Produkt der hier angegebenen Threadgruppenanzahl und die Anmerkung "Numthreads" im Compute-Shader HLSL-. Wenn die Transformation beispielsweise die Threadgruppenabmessungen auf (2,2,1) festlegt, gibt der Shader Threads (3,3,1) pro Threadgruppe an, dann werden 4 Threadgruppen ausgeführt, die jeweils 9 Threads enthalten, für insgesamt 36 Threadinstanzen.

Ein gängiges Szenario besteht darin, ein Ausgabepixel für jede Instanz des Compute-Shaders zu verarbeiten. Um die Anzahl der Threadgruppen für dieses Szenario zu berechnen, dividiert die Transformation die Breite und Höhe des Bilds durch die entsprechenden x- und y-Dimensionen der Anmerkung "Numthreads" im Computeshader HLSL-.

Wenn diese Division ausgeführt wird, muss die Anzahl der angeforderten Threadgruppen immer auf die nächste ganze Zahl aufgerundet werden, andernfalls werden die "Restpixel" nicht ausgeführt. Wenn ein Shader (z. B.) ein einzelnes Pixel mit jedem Thread berechnet, wird der Code der Methode wie folgt angezeigt.

IFACEMETHODIMP SampleTransform::CalculateThreadgroups(
    _In_ const D2D1_RECT_L* pOutputRect,
    _Out_ UINT32* pDimensionX,
    _Out_ UINT32* pDimensionY,
    _Out_ UINT32* pDimensionZ
    )
{    
    // The input image's dimensions are divided by the corresponding number of threads in each
    // threadgroup. This is specified in the HLSL, and in this example is 24 for both the x and y
    // dimensions. Dividing the image dimensions by these values calculates the number of
    // thread groups that need to be executed.

    *pDimensionX = static_cast<UINT32>(
         ceil((m_inputRect.right - m_inputRect.left) / 24.0f);

    *pDimensionY = static_cast<UINT32>(
         ceil((m_inputRect.bottom - m_inputRect.top) / 24.0f);

    // The z dimension is set to '1' in this example because the shader will
    // only be executed once for each pixel in the two-dimensional input image.
    // This value can be increased to perform additional executions for a given
    // input position.
    *pDimensionZ = 1;

    return S_OK;
}

Die HLSL- verwendet den folgenden Code, um die Anzahl der Threads in jeder Threadgruppe anzugeben:

// numthreads(x, y, z)
// This specifies the number of threads in each dispatched threadgroup. 
// For Shader Model 4, z == 1 and x*y*z <= 768. For Shader Model 5, z <= 64 and x*y*z <= 1024.
[numthreads(24, 24, 1)]
void main(
...

Während der Ausführung werden die aktuelle Threadgruppe und der aktuelle Threadindex als Parameter an die Shadermethode übergeben:

#define NUMTHREADS_X 24
#define NUMTHREADS_Y 24

// numthreads(x, y, z)
// This specifies the number of threads in each dispatched threadgroup.
// For Shader Model 4, z == 1 and x*y*z <= 768. For Shader Model 5, z <= 64 and x*y*z <= 1024.
[numthreads(NUMTHREADS_X, NUMTHREADS_Y, 1)]
void main(
    // dispatchThreadId - Uniquely identifies a given execution of the shader, most commonly used parameter.
    // Definition: (groupId.x * NUM_THREADS_X + groupThreadId.x, groupId.y * NUMTHREADS_Y + groupThreadId.y,
    // groupId.z * NUMTHREADS_Z + groupThreadId.z)
    uint3 dispatchThreadId  : SV_DispatchThreadID,

    // groupThreadId - Identifies an individual thread within a thread group.
    // Range: (0 to NUMTHREADS_X - 1, 0 to NUMTHREADS_Y - 1, 0 to NUMTHREADS_Z - 1)
    uint3 groupThreadId     : SV_GroupThreadID,

    // groupId - Identifies which thread group the individual thread is being executed in.
    // Range defined in ID2D1ComputeTransform::CalculateThreadgroups.
    uint3 groupId           : SV_GroupID, 

    // One dimensional indentifier of a compute shader thread within a thread group.
    // Range: (0 to NUMTHREADS_X * NUMTHREADS_Y * NUMTHREADS_Z - 1)
    uint  groupIndex        : SV_GroupIndex
    )
{
...

Lesen von Bilddaten

Computeshader greifen als zweidimensionale Textur auf das Eingabebild der Transformation zu:

Texture2D<float4> InputTexture : register(t0);
SamplerState InputSampler : register(s0);

Wie Pixelshader werden die Daten des Bilds jedoch nicht garantiert mit der Textur (0, 0) beginnen. Stattdessen stellt Direct2D- Systemkonstanten bereit, mit denen Shader einen Offset ausgleichen können:

// These are default constants passed by D2D.
cbuffer systemConstants : register(b0)
{
    int4 resultRect; // Represents the input rectangle to the shader in terms of pixels.
    float2 sceneToInput0X;
    float2 sceneToInput0Y;
};

// The image does not necessarily begin at (0,0) on InputTexture. The shader needs
// to use the coefficients provided by Direct2D to map the requested image data to
// where it resides on the texture.
float2 ConvertInput0SceneToTexelSpace(float2 inputScenePosition)
{
    float2 ret;
    ret.x = inputScenePosition.x * sceneToInput0X[0] + sceneToInput0X[1];
    ret.y = inputScenePosition.y * sceneToInput0Y[0] + sceneToInput0Y[1];
    
    return ret;
}

Nachdem der obige Konstantenpuffer und die Hilfsmethode definiert wurden, kann der Shader Bilddaten mithilfe der folgenden Beispieldaten testen:

float4 color = InputTexture.SampleLevel(
        InputSampler, 
        ConvertInput0SceneToTexelSpace(
            float2(xIndex + .5, yIndex + .5) + // Add 0.5 to each coordinate to hit the center of the pixel.
            resultRect.xy // Offset sampling location by input image offset.
            ),
        0
        );

Schreiben von Bilddaten

Direct2D- erwartet, dass ein Shader einen Ausgabepuffer für das resultierende Bild definiert. In Shadermodell 4 (DirectX 10) muss dies aufgrund von Featureeinschränkungen eindimensionaler Puffer sein:

// Shader Model 4 does not support RWTexture2D, must use 1D buffer instead.
RWStructuredBuffer<float4> OutputTexture : register(t1);

Die Ausgabetextur wird zuerst indiziert, damit das gesamte Bild gespeichert werden kann.

uint imageWidth = resultRect[2] - resultRect[0];
uint imageHeight = resultRect[3] - resultRect[1];
OutputTexture[yIndex * imageWidth + xIndex] = color;

Andererseits können Shadermodell 5 (DirectX 11)-Shader zweidimensionale Ausgabetexturen verwenden:

RWTexture2D<float4> OutputTexture : register(t1);

Mit Shadermodell 5-Shadern stellt Direct2D- einen zusätzlichen Parameter "outputOffset" im Konstantenpuffer bereit. Die Ausgabe des Shaders sollte um diesen Betrag versetzt werden:

OutputTexture[uint2(xIndex, yIndex) + outputOffset.xy] = color;

Unten wird ein abgeschlossener Pass-Through-Shadermodell 5-Compute-Shader angezeigt. In diesem Thread liest und schreibt jeder der Compute-Shaderthreads ein einzelnes Pixel des Eingabebilds.

#define NUMTHREADS_X 24
#define NUMTHREADS_Y 24

Texture2D<float4> InputTexture : register(t0);
SamplerState InputSampler : register(s0);

RWTexture2D<float4> OutputTexture : register(t1);

// These are default constants passed by D2D.
cbuffer systemConstants : register(b0)
{
    int4 resultRect; // Represents the region of the output image.
    int2 outputOffset;
    float2 sceneToInput0X;
    float2 sceneToInput0Y;
};

// The image does not necessarily begin at (0,0) on InputTexture. The shader needs
// to use the coefficients provided by Direct2D to map the requested image data to
// where it resides on the texture.
float2 ConvertInput0SceneToTexelSpace(float2 inputScenePosition)
{
    float2 ret;
    ret.x = inputScenePosition.x * sceneToInput0X[0] + sceneToInput0X[1];
    ret.y = inputScenePosition.y * sceneToInput0Y[0] + sceneToInput0Y[1];
    
    return ret;
}

// numthreads(x, y, z)
// This specifies the number of threads in each dispatched threadgroup.
// For Shader Model 5, z <= 64 and x*y*z <= 1024
[numthreads(NUMTHREADS_X, NUMTHREADS_Y, 1)]
void main(
    // dispatchThreadId - Uniquely identifies a given execution of the shader, most commonly used parameter.
    // Definition: (groupId.x * NUM_THREADS_X + groupThreadId.x, groupId.y * NUMTHREADS_Y + groupThreadId.y,
    // groupId.z * NUMTHREADS_Z + groupThreadId.z)
    uint3 dispatchThreadId  : SV_DispatchThreadID,

    // groupThreadId - Identifies an individual thread within a thread group.
    // Range: (0 to NUMTHREADS_X - 1, 0 to NUMTHREADS_Y - 1, 0 to NUMTHREADS_Z - 1)
    uint3 groupThreadId     : SV_GroupThreadID,

    // groupId - Identifies which thread group the individual thread is being executed in.
    // Range defined in DFTVerticalTransform::CalculateThreadgroups.
    uint3 groupId           : SV_GroupID, 

    // One dimensional indentifier of a compute shader thread within a thread group.
    // Range: (0 to NUMTHREADS_X * NUMTHREADS_Y * NUMTHREADS_Z - 1)
    uint  groupIndex        : SV_GroupIndex
    )
{
    uint xIndex = dispatchThreadId.x;
    uint yIndex = dispatchThreadId.y;

    uint imageWidth = resultRect.z - resultRect.x;
    uint imageHeight = resultRect.w - resultRect.y;

    // It is likely that the compute shader will execute beyond the bounds of the input image, since the shader is
    // executed in chunks sized by the threadgroup size defined in ID2D1ComputeTransform::CalculateThreadgroups.
    // For this reason each shader should ensure the current dispatchThreadId is within the bounds of the input
    // image before proceeding.
    if (xIndex >= imageWidth || yIndex >= imageHeight)
    {
        return;
    }

    float4 color = InputTexture.SampleLevel(
        InputSampler, 
        ConvertInput0SceneToTexelSpace(
            float2(xIndex + .5, yIndex + .5) + // Add 0.5 to each coordinate to hit the center of the pixel.
            resultRect.xy // Offset sampling location by image offset.
            ),
        0
        );

    OutputTexture[uint2(xIndex, yIndex) + outputOffset.xy] = color;

Der folgende Code zeigt die entsprechende Shadermodell 4-Version des Shaders. Beachten Sie, dass der Shader jetzt in einem eindimensionalen Ausgabepuffer gerendert wird.

#define NUMTHREADS_X 24
#define NUMTHREADS_Y 24

Texture2D<float4> InputTexture : register(t0);
SamplerState InputSampler : register(s0);

// Shader Model 4 does not support RWTexture2D, must use one-dimensional buffer instead.
RWStructuredBuffer<float4> OutputTexture : register(t1);

// These are default constants passed by D2D. See PixelShader and VertexShader
// projects for how to pass custom values into a shader.
cbuffer systemConstants : register(b0)
{
    int4 resultRect; // Represents the region of the output image.
    float2 sceneToInput0X;
    float2 sceneToInput0Y;
};

// The image does not necessarily begin at (0,0) on InputTexture. The shader needs
// to use the coefficients provided by Direct2D to map the requested image data to
// where it resides on the texture.
float2 ConvertInput0SceneToTexelSpace(float2 inputScenePosition)
{
    float2 ret;
    ret.x = inputScenePosition.x * sceneToInput0X[0] + sceneToInput0X[1];
    ret.y = inputScenePosition.y * sceneToInput0Y[0] + sceneToInput0Y[1];
    
    return ret;
}

// numthreads(x, y, z)
// This specifies the number of threads in each dispatched threadgroup.
// For Shader Model 4, z == 1 and x*y*z <= 768
[numthreads(NUMTHREADS_X, NUMTHREADS_Y, 1)]
void main(
    // dispatchThreadId - Uniquely identifies a given execution of the shader, most commonly used parameter.
    // Definition: (groupId.x * NUM_THREADS_X + groupThreadId.x, groupId.y * NUMTHREADS_Y + groupThreadId.y, groupId.z * NUMTHREADS_Z + groupThreadId.z)
    uint3 dispatchThreadId  : SV_DispatchThreadID,

    // groupThreadId - Identifies an individual thread within a thread group.
    // Range: (0 to NUMTHREADS_X - 1, 0 to NUMTHREADS_Y - 1, 0 to NUMTHREADS_Z - 1)
    uint3 groupThreadId     : SV_GroupThreadID,

    // groupId - Identifies which thread group the individual thread is being executed in.
    // Range defined in DFTVerticalTransform::CalculateThreadgroups
    uint3 groupId           : SV_GroupID, 

    // One dimensional indentifier of a compute shader thread within a thread group.
    // Range: (0 to NUMTHREADS_X * NUMTHREADS_Y * NUMTHREADS_Z - 1)
    uint  groupIndex        : SV_GroupIndex
    )
{
    uint imageWidth = resultRect[2] - resultRect[0];
    uint imageHeight = resultRect[3] - resultRect[1];

    uint xIndex = dispatchThreadId.x;
    uint yIndex = dispatchThreadId.y;

    // It is likely that the compute shader will execute beyond the bounds of the input image, since the shader is executed in chunks sized by
    // the threadgroup size defined in ID2D1ComputeTransform::CalculateThreadgroups. For this reason each shader should ensure the current
    // dispatchThreadId is within the bounds of the input image before proceeding.
    if (xIndex >= imageWidth || yIndex >= imageHeight)
    {
        return;
    }

    float4 color = InputTexture.SampleLevel(
        InputSampler, 
        ConvertInput0SceneToTexelSpace(
            float2(xIndex + .5, yIndex + .5) + // Add 0.5 to each coordinate to hit the center of the pixel.
            resultRect.xy // Offset sampling location by image offset.
            ),
        0
        );

    OutputTexture[yIndex * imageWidth + xIndex] = color;
}

D2DCustomEffects SDK-Beispiel