Efek kustom

Direct2D dikirim dengan pustaka efek yang melakukan berbagai operasi gambar umum. Lihat topik efek bawaan untuk daftar lengkap efek. Untuk fungsionalitas yang tidak dapat dicapai dengan efek bawaan, Direct2D memungkinkan Anda menulis efek kustom Anda sendiri menggunakan HLSL standar. Anda dapat menggunakan efek kustom ini bersama efek bawaan yang dikirim dengan Direct2D.

Untuk melihat contoh efek shader piksel, puncak, dan komputasi lengkap, lihat sampel SDK D2DCustomEffects.

Dalam topik ini, kami menunjukkan kepada Anda langkah-langkah dan konsep yang Anda butuhkan untuk merancang dan membuat efek kustom dengan fitur penuh.

Pengantar: Apa yang ada di dalam efek?

diagram efek bayangan jatuh.

Secara konseptual, efek Direct2D melakukan tugas pencitraan, seperti mengubah kecerahan, membatalkan jenuh gambar, atau seperti yang ditunjukkan di atas, membuat bayangan jatuh. Untuk aplikasi, mereka sederhana. Mereka dapat menerima nol atau lebih gambar input, mengekspos beberapa properti yang mengontrol operasi mereka, dan menghasilkan satu gambar output.

Ada empat bagian berbeda dari efek kustom yang bertanggung jawab atas penulis efek:

  1. Antarmuka Efek: Antarmuka efek secara konseptual menentukan bagaimana aplikasi berinteraksi dengan efek kustom (seperti berapa banyak input yang diterima efek dan properti apa yang tersedia). Antarmuka efek mengelola grafik transformasi, yang berisi operasi pencitraan aktual.
  2. Grafik transformasi: Setiap efek membuat grafik transformasi internal yang terdiri dari transformasi individual. Setiap transformasi mewakili satu operasi gambar. Efeknya bertanggung jawab untuk menghubungkan transformasi ini bersama-sama menjadi grafik untuk melakukan efek pencitraan yang dimaksudkan. Efek dapat menambahkan, menghapus, memodifikasi, dan menyusun ulang transformasi sebagai respons terhadap perubahan pada properti eksternal efek.
  3. Transformasi: Transformasi mewakili satu operasi gambar. Tujuan utamanya adalah untuk menampung shader yang dijalankan untuk setiap piksel output. Untuk itu, bertanggung jawab untuk menghitung ukuran baru gambar outputnya berdasarkan logika dalam shader-nya. Ini juga harus menghitung area gambar inputnya yang perlu dibaca oleh shader untuk merender wilayah output yang diminta.
  4. Shader: Shader dijalankan terhadap input transformasi pada GPU (atau CPU jika penyajian perangkat lunak ditentukan saat aplikasi membuat perangkat Direct3D). Efek shader ditulis dalam High Level Shading Language (HLSL) dan dikompilasi ke dalam kode byte selama kompilasi efek, yang kemudian dimuat oleh efek selama run-time. Dokumen referensi ini menjelaskan cara menulis HLSL yang mematuhi Direct2D. Dokumentasi Direct3D berisi gambaran umum HLSL dasar.

Membuat antarmuka efek

Antarmuka efek menentukan bagaimana aplikasi berinteraksi dengan efek kustom. Untuk membuat antarmuka efek, kelas harus menerapkan ID2D1EffectImpl, menentukan metadata yang menjelaskan efek (seperti namanya, jumlah input dan properti), dan membuat metode yang mendaftarkan efek kustom untuk digunakan dengan Direct2D.

Setelah semua komponen untuk antarmuka efek diimplementasikan, header kelas akan muncul seperti ini:

#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.
};

Menerapkan ID2D1EffectImpl

Antarmuka ID2D1EffectImpl berisi tiga metode yang harus Anda terapkan:

Initialize(ID2D1EffectContext *pContextInternal, ID2D1TransformGraph *pTransformGraph)

Direct2D memanggil metode Inisialisasi setelah metode ID2D1DeviceContext::CreateEffect telah dipanggil oleh aplikasi. Anda dapat menggunakan metode ini untuk melakukan inisialisasi internal atau operasi lain yang diperlukan untuk efeknya. Selain itu, Anda dapat menggunakannya untuk membuat grafik transformasi awal efek.

SetGraph(ID2D1TransformGraph *pTransformGraph)

Direct2D memanggil metode SetGraph saat jumlah input ke efek diubah. Meskipun sebagian besar efek memiliki jumlah input yang konstan, yang lain seperti efek Komposit mendukung jumlah input variabel. Metode ini memungkinkan efek ini memperbarui grafik transformasinya sebagai respons terhadap jumlah input yang berubah. Jika efek tidak mendukung jumlah input variabel, metode ini cukup mengembalikan E_NOTIMPL.

PrepareForRender (D2D1_CHANGE_TYPE changeType)

Metode PrepareForRender memberikan kesempatan bagi efek untuk melakukan operasi apa pun sebagai respons terhadap perubahan eksternal. Direct2D memanggil metode ini tepat sebelum merender efek jika setidaknya salah satu dari ini benar:

  • Efek sebelumnya telah diinisialisasi tetapi belum digambar.
  • Properti efek telah berubah sejak panggilan gambar terakhir.
  • Status konteks Direct2D panggilan (seperti DPI) telah berubah sejak panggilan gambar terakhir.

Menerapkan metode pendaftaran efek dan panggilan balik

Aplikasi harus mendaftarkan efek dengan Direct2D sebelum membuat instans. Pendaftaran ini dilingkup ke instans pabrik Direct2D, dan harus diulang setiap kali aplikasi dijalankan. Untuk mengaktifkan pendaftaran ini, efek kustom mendefinisikan GUID unik, metode publik yang mendaftarkan efek, dan metode panggilan balik privat yang mengembalikan instans efek.

Menentukan GUID

Anda harus menentukan GUID yang secara unik mengidentifikasi efek untuk pendaftaran dengan Direct2D. Aplikasi ini menggunakan hal yang sama untuk mengidentifikasi efek saat memanggil ID2D1DeviceContext::CreateEffect.

Kode ini menunjukkan mendefinisikan GUID seperti itu untuk efeknya. Anda harus membuat GUID uniknya sendiri menggunakan alat pembuatan GUID seperti guidgen.exe.

// 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);

Menentukan metode pendaftaran publik

Selanjutnya, tentukan metode publik untuk dipanggil aplikasi untuk mendaftarkan efek dengan Direct2D. Karena pendaftaran efek khusus untuk instans pabrik Direct2D, metode menerima antarmuka ID2D1Factory1 sebagai parameter. Untuk mendaftarkan efek, metode kemudian memanggil ID2D1Factory1::RegisterEffectFromString API pada parameter ID2D1Factory1 .

API ini menerima string XML yang menjelaskan metadata, input, dan properti efek. Metadata untuk efek hanya untuk tujuan informasi, dan dapat dikueri oleh aplikasi melalui antarmuka ID2D1Properties . Data input dan properti, di sisi lain, digunakan oleh Direct2D dan mewakili fungsionalitas efek.

String XML untuk efek sampel minimal diperlihatkan di sini. Menambahkan properti kustom ke XML tercakup dalam bagian Menambahkan properti kustom ke efek.

#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>
        );

Menentukan metode panggilan balik pabrik efek

Efeknya juga harus menyediakan metode panggilan balik privat yang mengembalikan instans efek melalui satu parameter IUnknown**. Pointer ke metode ini disediakan untuk Direct2D ketika efek terdaftar melalui ID2D1Factory1::RegisterEffectFromString API melalui parameter PD2D1_EFFECT_FACTORY\.

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;
}

Menerapkan antarmuka IUnknown

Akhirnya, efeknya harus mengimplementasikan antarmuka IUnknown untuk kompatibilitas dengan COM.

Membuat grafik transformasi efek

Efek dapat menggunakan beberapa transformasi yang berbeda (operasi gambar individual) untuk menciptakan efek pencitraan yang diinginkan. Untuk mengontrol urutan di mana transformasi ini diterapkan ke gambar input, efek mengaturnya menjadi grafik transformasi. Grafik transformasi dapat menggunakan efek dan transformasi yang disertakan dalam Direct2D serta transformasi kustom yang dibuat oleh pembuat efek.

Menggunakan transformasi yang disertakan dengan Direct2D

Ini adalah transformasi yang paling umum digunakan yang disediakan dengan Direct2D.

Membuat grafik transformasi simpul tunggal

Setelah Anda membuat transformasi, input efek perlu dihubungkan ke input transformasi, dan output transformasi perlu dihubungkan ke output efek. Ketika efek hanya berisi satu transformasi, Anda dapat menggunakan metode ID2D1TransformGraph::SetSingleTransformNode untuk menyelesaikannya dengan mudah.

Anda dapat membuat atau mengubah transformasi dalam metode Inisialisasi atau SetGraph efek menggunakan parameter ID2D1TransformGraph yang disediakan. Jika efek perlu membuat perubahan pada grafik transformasi dalam metode lain di mana parameter ini tidak tersedia, efeknya dapat menyimpan parameter ID2D1TransformGraph sebagai variabel anggota kelas dan mengaksesnya di tempat lain, seperti PrepareForRender atau metode panggilan balik properti kustom.

Contoh metode Inisialisasi diperlihatkan di sini. Metode ini membuat grafik transformasi simpul tunggal yang mengimbangi gambar dengan seratus piksel di setiap sumbu.

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;
}

Membuat grafik transformasi multi-simpul

Menambahkan beberapa transformasi ke grafik transformasi efek memungkinkan efek untuk melakukan beberapa operasi gambar secara internal yang disajikan ke aplikasi sebagai efek terpadu tunggal.

Seperti disebutkan di atas, grafik transformasi efek dapat diedit dalam metode efek apa pun menggunakan parameter ID2D1TransformGraph yang diterima dalam metode Inisialisasi efek. API berikut pada antarmuka tersebut dapat digunakan untuk membuat atau memodifikasi grafik transformasi efek:

AddNode(ID2D1TransformNode *pNode)

Metode AddNode , berlaku, 'mendaftarkan' transformasi dengan efek , dan harus dipanggil sebelum transformasi dapat digunakan dengan salah satu metode grafik transformasi lainnya.

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

Metode ConnectToEffectInput menghubungkan input gambar efek ke input transformasi. Input efek yang sama dapat disambungkan ke beberapa transformasi.

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

Metode ConnectNode menghubungkan output transformasi ke input transformasi lain. Output transformasi dapat dihubungkan ke beberapa transformasi.

SetOutputNode(ID2D1TransformNode *pNode)

Metode SetOutputNode menghubungkan output transformasi ke output efek. Karena efek hanya memiliki satu output, hanya satu transformasi yang dapat ditetapkan sebagai 'simpul output'.

Kode ini menggunakan dua transformasi terpisah untuk membuat efek terpadu. Dalam hal ini, efeknya adalah bayangan jatuh yang diterjemahkan.

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;
}

Menambahkan properti kustom ke efek

Efek dapat menentukan properti kustom yang memungkinkan aplikasi mengubah perilaku efek selama runtime. Ada tiga langkah untuk menentukan properti untuk efek kustom:

Menambahkan metadata properti ke data pendaftaran efek

Menambahkan properti ke XML pendaftaran

Anda harus menentukan properti efek kustom selama pendaftaran awal efek dengan Direct2D. Pertama, Anda harus memperbarui XML pendaftaran efek dalam metode pendaftaran publiknya dengan properti baru:

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>
        );

Saat Anda menentukan properti efek di XML, properti tersebut memerlukan nama, jenis, dan nama tampilan. Nama tampilan properti, serta nilai kategori, penulis, dan deskripsi efek keseluruhan dapat dan harus dilokalkan.

Untuk setiap properti, efek dapat secara opsional menentukan nilai default, min, dan maks. Nilai-nilai ini hanya untuk penggunaan informasi. Mereka tidak diberlakukan oleh Direct2D. Terserah Anda untuk mengimplementasikan logika default/min/maks yang ditentukan di kelas efek sendiri.

Nilai jenis yang tercantum dalam XML untuk properti harus cocok dengan jenis data terkait yang digunakan oleh metode getter dan setter properti. Nilai XML terkait untuk setiap tipe data diperlihatkan dalam tabel ini:

Jenis Data Nilai XML yang sesuai
PWSTR string
BOOL bool
UINT uint32
INT int32
FLOAT float
D2D_VECTOR_2F vektor2
D2D_VECTOR_3F vector3
D2D_VECTOR_4F vektor4
D2D_MATRIX_3X2_F matriks3x2
D2D_MATRIX_4X3_F matriks4x3
D2D_MATRIX_4X4_F matriks4x4
D2D_MATRIX_5X4_F matriks5x4
BYTE[] blob
IUnknown* iunknown
ID2D1ColorContext* colorcontext
CLSID Clsid
Enumerasi (D2D1_INTERPOLATION_MODE, dll.) enum

 

Memetakan properti baru ke metode getter dan setter

Selanjutnya, efek harus memetakan properti baru ini ke metode getter dan setter. Ini dilakukan melalui array D2D1_PROPERTY_BINDING yang diteruskan ke metode ID2D1Factory1::RegisterEffectFromString .

Array D2D1_PROPERTY_BINDING terlihat seperti ini:

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".
        )
};

Setelah Anda membuat array XML dan pengikatan, teruskan ke metode RegisterEffectFromString :

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.
    );

Makro D2D1_VALUE_TYPE_BINDING memerlukan kelas efek untuk mewarisi dari ID2D1EffectImpl sebelum antarmuka lainnya.

Properti kustom untuk efek diindeks dalam urutan yang dideklarasikan dalam XML, dan setelah dibuat dapat diakses oleh aplikasi menggunakan metode ID2D1Properties::SetValue dan ID2D1Properties::GetValue . Untuk kenyamanan, Anda dapat membuat enumerasi publik yang mencantumkan setiap properti dalam file header efek:

typedef enum SAMPLEEFFECT_PROP
{
    SAMPLEFFECT_PROP_OFFSET = 0
};

Membuat metode getter dan setter untuk properti

Langkah selanjutnya adalah membuat metode getter dan setter untuk properti baru. Nama metode harus cocok dengan yang ditentukan dalam array D2D1_PROPERTY_BINDING . Selain itu, jenis properti yang ditentukan dalam XML efek harus cocok dengan jenis parameter metode setter dan nilai pengembalian metode getter.

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;
}

Memperbarui transformasi efek sebagai respons terhadap perubahan properti

Untuk benar-benar memperbarui output gambar efek sebagai respons terhadap perubahan properti, efeknya perlu mengubah transformasi yang mendasarnya. Ini biasanya dilakukan dalam metode PrepareForRender efek yang secara otomatis dipanggil Direct2D ketika salah satu properti efek telah diubah. Namun, transformasi dapat diperbarui dalam salah satu metode efek: seperti Inisialisasi atau metode setter properti efek.

Misalnya, jika efek berisi ID2D1OffsetTransform dan ingin memodifikasi nilai offsetnya sebagai respons terhadap properti Offset efek yang diubah, itu akan menambahkan kode berikut di PrepareForRender:

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;
}

Membuat transformasi kustom

Untuk mengimplementasikan operasi gambar di luar apa yang disediakan di Direct2D, Anda harus menerapkan transformasi kustom. Transformasi kustom dapat mengubah gambar input secara acak melalui penggunaan shader HLSL kustom.

Transformasi mengimplementasikan salah satu dari dua antarmuka yang berbeda tergantung pada jenis shader yang mereka gunakan. Transformasi menggunakan shader piksel dan/atau vertex harus mengimplementasikan ID2D1DrawTransform, sementara transformasi menggunakan shader komputasi harus menerapkan ID2D1ComputeTransform. Antarmuka ini mewarisi dari ID2D1Transform. Bagian ini berfokus pada penerapan fungsionalitas yang umum untuk keduanya.

Antarmuka ID2D1Transform memiliki empat metode untuk diimplementasikan:

GetInputCount

Metode ini mengembalikan bilangan bulat yang mewakili jumlah input untuk transformasi.

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

MapInputRectsToOutputRect

Direct2D memanggil metode MapInputRectsToOutputRect setiap kali transformasi dirender. Direct2D melewati persegi panjang yang mewakili batas masing-masing input ke transformasi. Transformasi kemudian bertanggung jawab untuk menghitung batas gambar output. Ukuran persegi panjang untuk semua metode pada antarmuka ini (ID2D1Transform) didefinisikan dalam piksel, bukan DIP.

Metode ini juga bertanggung jawab untuk menghitung wilayah output yang buram berdasarkan logika shader dan wilayah buram dari setiap input. Wilayah buram gambar didefinisikan sebagai tempat saluran alfa adalah '1' untuk keseluruhan persegi panjang. Jika tidak jelas apakah output transformasi buram, persegi panjang buram output harus diatur ke (0, 0, 0, 0) sebagai nilai aman. Direct2D menggunakan info ini untuk melakukan pengoptimalan penyajian dengan konten 'buram terjamin'. Jika nilai ini tidak akurat, nilai ini dapat mengakibatkan penyajian yang salah.

Anda dapat memodifikasi perilaku penyajian transformasi (seperti yang didefinisikan dalam bagian 6 hingga 8) selama metode ini. Namun, Anda tidak dapat memodifikasi transformasi lain dalam grafik transformasi, atau tata letak grafik itu sendiri di sini.

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;
}

Untuk contoh yang lebih kompleks, pertimbangkan bagaimana operasi kabur sederhana akan diwakili:

Jika operasi kabur menggunakan radius 5 piksel, ukuran persegi output harus diperluas sebesar 5 piksel, seperti yang ditunjukkan di bawah ini. Saat memodifikasi koordinat persegi panjang, transformasi harus memastikan bahwa logikanya tidak menyebabkan over/underflow dalam koordinat persegi panjang.

// 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;
}

Karena gambar kabur, wilayah gambar yang buram sekarang mungkin sebagian transparan. Ini karena area di luar gambar default ke hitam transparan dan transparansi ini akan digabungkan ke dalam gambar di sekitar tepi. Transformasi harus mencerminkan ini dalam perhitungan persegi panjang buram outputnya:

// 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;

Perhitungan ini divisualisasikan di sini:

ilustrasi perhitungan persegi panjang.

Untuk informasi selengkapnya tentang metode ini, lihat halaman referensi MapInputRectsToOutputRect .

MapOutputRectToInputRects

Direct2D memanggil metode MapOutputRectToInputRects setelah MapInputRectsToOutputRect. Transformasi harus menghitung bagian gambar mana yang perlu dibaca untuk merender wilayah output yang diminta dengan benar.

Seperti sebelumnya, jika efek memetakan piksel 1-1 secara ketat, efek tersebut dapat meneruskan persegi panjang output ke persegi panjang input:

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;
}

Demikian juga, jika transformasi menyusut atau memperluas gambar (seperti contoh kabur di sini), piksel sering menggunakan piksel sekitarnya untuk menghitung nilainya. Dengan kabur, piksel dirata-ratakan dengan piksel di sekitarnya, bahkan jika berada di luar batas gambar input. Perilaku ini tercermin dalam perhitungan. Seperti sebelumnya, transformasi memeriksa luapan saat memperluas koordinat persegi panjang.

// 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;

Gambar ini memvisualisasikan perhitungan. Direct2D secara otomatis mengambil sampel piksel hitam transparan di mana gambar input tidak ada, memungkinkan kabur dipadukan secara bertahap dengan konten yang ada di layar.

ilustrasi efek pengambilan sampel piksel hitam transparan di luar persegi panjang.

Jika pemetaan tidak sepele, maka metode ini harus mengatur persegi panjang input ke area maksimum untuk menjamin hasil yang benar. Untuk melakukan ini, atur tepi kiri dan atas ke INT_MIN, dan tepi kanan dan bawah ke INT_MAX.

Untuk informasi selengkapnya tentang metode ini, lihat topik MapOutputRectToInputRects .

MapInvalidRect

Direct2D juga memanggil metode MapInvalidRect . Namun, tidak seperti metode MapInputRectsToOutputRect dan MapOutputRectToInputRects Direct2D tidak dijamin untuk memanggilnya pada waktu tertentu. Metode ini secara konseptual memutuskan bagian mana dari output transformasi yang perlu dirender ulang sebagai respons terhadap sebagian atau semua perubahan inputnya. Ada tiga skenario berbeda untuk menghitung rect transformasi yang tidak valid.

Transformasi dengan pemetaan piksel satu-ke-satu

Untuk transformasi yang memetakan piksel 1-1, cukup teruskan persegi panjang input yang tidak valid ke persegi output yang tidak valid:

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;
}

Transformasi dengan pemetaan piksel banyak ke banyak

Saat piksel output transformasi bergantung pada area sekitarnya, persegi panjang input yang tidak valid harus diperluas secara sesuai. Hal ini untuk mencerminkan bahwa piksel di sekitar persegi panjang input yang tidak valid juga akan terpengaruh dan menjadi tidak valid. Misalnya, lima piksel kabur menggunakan perhitungan berikut:

// 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;

Transformasi dengan pemetaan piksel kompleks

Untuk transformasi di mana piksel input dan output tidak memiliki pemetaan sederhana, seluruh output dapat ditandai sebagai tidak valid. Misalnya, jika transformasi hanya menghasilkan warna rata-rata input, seluruh output transformasi berubah jika bahkan sebagian kecil input diubah. Dalam hal ini, persegi panjang output yang tidak valid harus diatur ke persegi panjang yang tidak terbatas secara logis (ditunjukkan di bawah). Direct2D secara otomatis menjepit ini ke batas output.

// 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);

Untuk informasi selengkapnya tentang metode ini, lihat topik MapInvalidRect .

Setelah metode ini diimplementasikan, header transformasi Anda akan berisi yang berikut:

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.
};

Menambahkan shader piksel ke transformasi kustom

Setelah transformasi dibuat, transformasi perlu menyediakan shader yang akan memanipulasi piksel gambar. Bagian ini mencakup langkah-langkah untuk menggunakan shader piksel dengan transformasi kustom.

Menerapkan ID2D1DrawTransform

Untuk menggunakan shader piksel, transformasi harus mengimplementasikan antarmuka ID2D1DrawTransform , yang mewarisi dari antarmuka ID2D1Transform yang dijelaskan di bagian 5. Antarmuka ini berisi satu metode baru untuk diimplementasikan:

SetDrawInfo(ID2D1DrawInfo *pDrawInfo)

Direct2D memanggil metode SetDrawInfo saat transformasi pertama kali ditambahkan ke grafik transformasi efek. Metode ini menyediakan parameter ID2D1DrawInfo yang mengontrol bagaimana transformasi dirender. Lihat topik ID2D1DrawInfo untuk metode yang tersedia di sini.

Jika transformasi memilih untuk menyimpan parameter ini sebagai variabel anggota kelas, objek drawInfo dapat diakses dan diubah dari metode lain seperti setter properti atau MapInputRectsToOutputRect. Terutama, metode ini tidak dapat dipanggil dari metode MapOutputRectToInputRects atau MapInvalidRect pada ID2D1Transform.

Membuat GUID untuk shader piksel

Selanjutnya, transformasi harus menentukan GUID unik untuk shader piksel itu sendiri. Ini digunakan ketika Direct2D memuat shader ke dalam memori, serta ketika transformasi memilih shader piksel mana yang akan digunakan untuk eksekusi. Alat seperti guidgen.exe, yang disertakan dengan Visual Studio, dapat digunakan untuk menghasilkan GUID acak.

// 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);

Memuat shader piksel dengan Direct2D

Shader piksel harus dimuat ke dalam memori sebelum dapat digunakan oleh transformasi.

Untuk memuat shader piksel ke dalam memori, transformasi harus membaca kode byte shader yang dikompilasi dari . File CSO yang dihasilkan oleh Visual Studio (lihat dokumentasi Direct3D untuk detailnya) ke dalam array byte. Teknik ini ditunjukkan secara rinci dalam sampel D2DCustomEffects SDK.

Setelah data shader dimuat ke dalam array byte, panggil metode LoadPixelShader pada objek ID2D1EffectContext efek. Direct2D mengabaikan panggilan ke LoadPixelShader ketika shader dengan GUID yang sama telah dimuat.

Setelah shader piksel dimuat ke dalam memori, transformasi perlu memilihnya untuk dieksekusi dengan meneruskan GUID-nya ke metode SetPixelShader pada parameter ID2D1DrawInfo yang disediakan selama metode SetDrawInfo . Shader piksel harus sudah dimuat ke dalam memori sebelum dipilih untuk eksekusi.

Mengubah operasi shader dengan buffer konstanta

Untuk mengubah cara shader dijalankan, transformasi dapat meneruskan buffer konstan ke shader piksel. Untuk melakukannya, transformasi mendefinisikan struct yang berisi variabel yang diinginkan di header kelas:

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

Transformasi kemudian memanggil metode ID2D1DrawInfo::SetPixelShaderConstantBuffer pada parameter ID2D1DrawInfo yang disediakan dalam metode SetDrawInfo untuk meneruskan buffer ini ke shader.

HLSL juga perlu menentukan struct yang sesuai yang mewakili buffer konstanta. Variabel yang terkandung dalam struktur shader harus cocok dengan variabel dalam struktur transformasi.

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

Setelah buffer ditentukan, nilai yang terkandung di dalamnya dapat dibaca dari mana saja dalam shader piksel.

Menulis shader piksel untuk Direct2D

Transformasi Direct2D menggunakan shader yang ditulis menggunakan HLSL standar. Namun, ada beberapa konsep utama untuk menulis shader piksel yang dijalankan dari konteks transformasi. Untuk contoh lengkap shader piksel yang berfungsi penuh, lihat sampel SDK D2DCustomEffects.

Direct2D secara otomatis memetakan input transformasi ke objek Texture2D dan SamplerState di HLSL. Texture2D pertama terletak di register t0, dan SamplerState pertama terletak di register s0. Setiap input tambahan terletak di register berikutnya yang sesuai (t1 dan s1 misalnya). Data piksel untuk input tertentu dapat diambil sampelnya dengan memanggil Sampel pada objek Texture2D dan meneruskan objek SamplerState yang sesuai dan koordinat texel.

Shader piksel kustom dijalankan sekali untuk setiap piksel yang dirender. Setiap kali shader dijalankan, Direct2D secara otomatis menyediakan tiga parameter yang mengidentifikasi posisi eksekusinya saat ini:

  • Output ruang adegan: Parameter ini mewakili posisi eksekusi saat ini dalam hal permukaan target keseluruhan. Ini didefinisikan dalam piksel dan nilai min/maksnya sesuai dengan batas persegi yang dikembalikan oleh MapInputRectsToOutputRect.
  • Output clip-space: Parameter ini digunakan oleh Direct3D, dan tidak boleh digunakan dalam shader piksel transformasi.
  • Input ruang texel: Parameter ini mewakili posisi eksekusi saat ini dalam tekstur input tertentu. Shader tidak boleh mengambil dependensi apa pun tentang bagaimana nilai ini dihitung. Ini hanya boleh menggunakannya untuk mengambil sampel input shader piksel, seperti yang ditunjukkan pada kode di bawah ini:
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;
}

Menambahkan shader vertex ke transformasi kustom

Anda dapat menggunakan shader vertex untuk mencapai skenario pencitraan yang berbeda dari shader piksel. Secara khusus, shader vertex dapat melakukan efek gambar berbasis geometri dengan mengubah simpul yang terdiri dari gambar. Shader vertex dapat digunakan secara independen dari atau bersama dengan shader piksel yang ditentukan transformasi. Jika shader vertex tidak ditentukan, Direct2D menggantikan shader vertex default untuk digunakan dengan shader piksel kustom.

Proses untuk menambahkan shader vertex ke transformasi kustom mirip dengan shader piksel - transformasi mengimplementasikan antarmuka ID2D1DrawTransform , membuat GUID, dan (opsional) meneruskan buffer konstan ke shader. Namun, ada beberapa langkah tambahan utama yang unik untuk shader vertex:

Membuat buffer vertex

Shader puncak menurut definisi dijalankan pada simpul yang diteruskan ke simpul, bukan piksel individual. Untuk menentukan simpul untuk dieksekusi shader, transformasi membuat buffer vertex untuk diteruskan ke shader. Tata letak buffer vertex berada di luar lingkup dokumen ini. Silakan lihat referensi Direct3D untuk detailnya, atau sampel D2DCustomEffects SDK untuk implementasi sampel.

Setelah membuat buffer vertex dalam memori, transformasi menggunakan metode CreateVertexBuffer pada objek ID2D1EffectContext efek yang berisi untuk meneruskan data tersebut ke GPU. Sekali lagi, lihat sampel D2DCustomEffects SDK untuk implementasi sampel.

Jika tidak ada buffer vertex yang ditentukan oleh transformasi, Direct2D melewati buffer vertex default yang mewakili lokasi gambar persegi panjang.

Mengubah SetDrawInfo untuk menggunakan shader vertex

Seperti halnya shader piksel, transformasi harus memuat dan memilih shader vertex untuk eksekusi. Untuk memuat shader vertex, ia memanggil metode LoadVertexShader pada metode ID2D1EffectContext yang diterima dalam metode Inisialisasi efek. Untuk memilih shader vertex untuk eksekusi, ia memanggil SetVertexProcessing pada parameter ID2D1DrawInfo yang diterima dalam metode SetDrawInfo transformasi. Metode ini menerima GUID untuk shader vertex yang dimuat sebelumnya serta (opsional) buffer vertex yang dibuat sebelumnya agar shader dapat dijalankan.

Menerapkan shader vertex Direct2D

Transformasi gambar dapat berisi shader piksel dan shader vertex. Jika transformasi mendefinisikan shader piksel dan shader vertex, maka output dari shader vertex diberikan langsung ke shader piksel: aplikasi dapat menyesuaikan tanda tangan pengembalian shader vertex / parameter shader piksel selama konsisten.

Di sisi lain, jika transformasi hanya berisi shader vertex, dan bergantung pada shader piksel pass-through default Direct2D, transformasi harus mengembalikan output default berikut:

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

Shader vertex menyimpan hasil transformasi vertex-nya dalam variabel output Scene-space shader. Untuk menghitung output Clip-space dan variabel input Ruang Texel, Direct2D secara otomatis menyediakan matriks konversi dalam buffer konstan:

// 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;
};

Sampel kode shader vertex dapat ditemukan di bawah ini yang menggunakan matriks konversi untuk menghitung ruang klip dan texel yang benar yang diharapkan oleh Direct2D:

// 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;  
}

Kode di atas dapat digunakan sebagai titik awal untuk shader vertex. Ini hanya melewati gambar input tanpa melakukan transformasi apa pun. Sekali lagi, lihat sampel SDK D2DCustomEffects untuk transformasi berbasis shader vertex yang diimplementasikan sepenuhnya.

Jika tidak ada buffer vertex yang ditentukan oleh transformasi, Direct2D menggantikan buffer vertex default yang mewakili lokasi gambar persegi panjang. Parameter ke shader vertex diubah menjadi parameter dari output shader default:

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

Shader vertex mungkin tidak memodifikasi parameter sceneSpaceOutput dan clipSpaceOutput-nya . Ini harus mengembalikan mereka tidak berubah. Namun, ini dapat mengubah parameter texelSpaceInput untuk setiap gambar input. Jika transformasi juga berisi shader piksel kustom, shader vertex masih dapat meneruskan parameter kustom tambahan langsung ke shader piksel. Selain itu, buffer kustom matriks konversi sceneSpace (b0) tidak lagi disediakan.

Menambahkan shader komputasi ke transformasi kustom

Akhirnya, transformasi kustom dapat menggunakan shader komputasi untuk skenario tertentu yang ditargetkan. Shader komputasi dapat digunakan untuk mengimplementasikan efek gambar kompleks yang memerlukan akses arbitrer ke buffer gambar input dan output. Misalnya, algoritma histogram dasar tidak dapat diimplementasikan dengan shader piksel karena keterbatasan akses memori.

Karena shader komputasi memiliki persyaratan tingkat fitur perangkat keras yang lebih tinggi daripada shader piksel, shader piksel harus digunakan jika memungkinkan untuk menerapkan efek tertentu. Secara khusus, shader komputasi hanya berjalan pada sebagian besar kartu tingkat DirectX 10 dan yang lebih tinggi. Jika transformasi memilih untuk menggunakan shader komputasi, transformasi harus memeriksa dukungan perangkat keras yang sesuai selama instansiasi selain menerapkan antarmuka ID2D1ComputeTransform .

Memeriksa dukungan Compute Shader

Jika efek menggunakan shader komputasi, efek harus memeriksa dukungan shader komputasi selama pembuatannya menggunakan metode ID2D1EffectContext::CheckFeatureSupport . Jika GPU tidak mendukung shader komputasi, efeknya harus mengembalikan D2DERR_INSUFFICIENT_DEVICE_CAPABILITIES.

Ada dua jenis shader komputasi berbeda yang dapat digunakan transformasi: Shader Model 4 (DirectX 10) dan Shader Model 5 (DirectX 11). Ada batasan tertentu untuk shader Model 4 Shader. Lihat dokumentasi Direct3D untuk detailnya. Transformasi dapat berisi kedua jenis shader, dan dapat kembali ke Shader Model 4 jika diperlukan: lihat sampel D2DCustomEffects SDK untuk implementasi ini.

Menerapkan ID2D1ComputeTransform

Antarmuka ini berisi dua metode baru untuk diimplementasikan selain yang ada di ID2D1Transform:

SetComputeInfo(ID2D1ComputeInfo *pComputeInfo)

Seperti halnya shader piksel dan vertex, Direct2D memanggil metode SetComputeInfo saat transformasi pertama kali ditambahkan ke grafik transformasi efek. Metode ini menyediakan parameter ID2D1ComputeInfo yang mengontrol bagaimana transformasi dirender. Ini termasuk memilih shader komputasi untuk dijalankan melalui metode ID2D1ComputeInfo::SetComputeShader . Jika transformasi memilih untuk menyimpan parameter ini sebagai variabel anggota kelas, transformasi dapat diakses dan diubah dari metode transformasi atau efek apa pun dengan pengecualian metode MapOutputRectToInputRects dan MapInvalidRect . Lihat topik ID2D1ComputeInfo untuk metode lain yang tersedia di sini.

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

Sedangkan shader piksel dijalankan berdasarkan per piksel dan shader vertex dijalankan berdasarkan per puncak, shader komputasi dijalankan berdasarkan per-'threadgroup'. Threadgroup mewakili sejumlah utas yang dijalankan secara bersamaan pada GPU. Kode HLSL shader komputasi memutuskan berapa banyak utas yang harus dijalankan per grup alur. Efek menskalakan jumlah grup alur sehingga shader menjalankan jumlah waktu yang diinginkan, tergantung pada logika shader.

Metode CalculateThreadgroups memungkinkan transformasi untuk menginformasikan Direct2D berapa banyak grup utas yang diperlukan, berdasarkan ukuran gambar dan pengetahuan transformasi sendiri tentang shader.

Frekuensi shader komputasi dijalankan adalah produk dari jumlah threadgroup yang ditentukan di sini dan anotasi 'numthreads' dalam HLSL shader komputasi. Misalnya, jika transformasi mengatur dimensi threadgroup menjadi (2,2,1) shader menentukan (3,3,1) thread per threadgroup, maka 4 threadgroup akan dijalankan, masing-masing dengan 9 utas di dalamnya, dengan total 36 instans utas.

Skenario umum adalah memproses satu piksel output untuk setiap instans shader komputasi. Untuk menghitung jumlah grup utas untuk skenario ini, transformasi membagi lebar dan tinggi gambar dengan dimensi x dan y masing-masing dari anotasi 'numthreads' dalam HLSL shader komputasi.

Yang penting, jika pembagian ini dilakukan, maka jumlah grup utas yang diminta harus selalu dibulatkan ke bilangan bulat terdekat, jika tidak, piksel 'sisa' tidak akan dijalankan. Jika shader (misalnya) menghitung satu piksel dengan setiap utas, kode metode akan muncul sebagai berikut.

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;
}

HLSL menggunakan kode berikut untuk menentukan jumlah utas di setiap grup utas:

// 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(
...

Selama eksekusi, threadgroup saat ini dan indeks utas saat ini diteruskan sebagai parameter ke metode shader:

#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
    )
{
...

Membaca data gambar

Shader komputasi mengakses gambar input transformasi sebagai tekstur dua dimensi tunggal:

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

Namun, seperti pemisah piksel, data gambar tidak dijamin dimulai pada (0, 0) pada tekstur. Sebagai gantinya, Direct2D menyediakan konstanta sistem yang memungkinkan shader untuk mengimbangi offset apa pun:

// 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;
}

Setelah buffer konstanta dan metode pembantu di atas ditentukan, shader dapat mengambil sampel data gambar menggunakan yang berikut:

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
        );

Menulis data gambar

Direct2D mengharapkan shader untuk menentukan buffer output agar gambar yang dihasilkan ditempatkan. Dalam Shader Model 4 (DirectX 10), ini harus berupa buffer dimensi tunggal karena kendala fitur:

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

Tekstur output diindeks baris terlebih dahulu untuk memungkinkan seluruh gambar disimpan.

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

Di sisi lain, shader Shader Model 5 (DirectX 11) dapat menggunakan tekstur output dua dimensi:

RWTexture2D<float4> OutputTexture : register(t1);

Dengan shader Model 5 Shader, Direct2D menyediakan parameter 'outputOffset' tambahan dalam buffer konstan. Output shader harus diimbangi dengan jumlah ini:

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

Shader Shader Model 5 shader pass-through yang telah selesai ditunjukkan di bawah ini. Di dalamnya, masing-masing utas shader komputasi membaca dan menulis satu piksel gambar input.

#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;

Kode di bawah ini menunjukkan shader Model 4 versi shader yang setara. Perhatikan bahwa shader sekarang dirender menjadi buffer output satu dimensi.

#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;
}

Sampel D2DCustomEffects SDK