Anpassade effekter
Direct2D- levereras med ett bibliotek med effekter som utför en mängd vanliga bildåtgärder. I avsnittet inbyggda effekter finns en fullständig lista över effekter. För funktioner som inte kan uppnås med de inbyggda effekterna kan du med Direct2D skriva egna anpassade effekter med standard HLSL-. Du kan använda dessa anpassade effekter tillsammans med de inbyggda effekter som levereras med Direct2D.
Om du vill se exempel på en fullständig pixel-, hörn- och beräkningsskuggningseffekt kan du läsa D2DCustomEffects SDK-exempel.
I det här avsnittet visar vi de steg och begrepp som du behöver för att utforma och skapa en helt aktuell anpassad effekt.
Introduktion: Vad finns inuti en effekt?
Konceptuellt utför en Direct2D- effekt en avbildningsuppgift, som att ändra ljusstyrka, avmätta en bild eller, som visas ovan, skapa en skugga. För appen är de enkla. De kan acceptera noll eller fler indatabilder, exponera flera egenskaper som styr deras åtgärd och generera en enda utdataavbildning.
Det finns fyra olika delar av en anpassad effekt som en effektförfattare ansvarar för:
- Effektgränssnitt: Effektgränssnittet definierar konceptuellt hur en app interagerar med en anpassad effekt (till exempel hur många indata effekten accepterar och vilka egenskaper som är tillgängliga). Effektgränssnittet hanterar ett transformeringsdiagram som innehåller de faktiska avbildningsåtgärderna.
- Transformera diagram: Varje effekt skapar ett internt transformeringsdiagram som består av enskilda transformeringar. Varje transformering representerar en enskild bildåtgärd. Effekten är ansvarig för att länka dessa transformeringar till en graf för att utföra den avsedda avbildningseffekten. En effekt kan lägga till, ta bort, ändra och ordna om transformeringar som svar på ändringar i effektens externa egenskaper.
- Transformering: En transformering representerar en enskild bildåtgärd. Dess huvudsakliga syfte är att hysa skuggningar som körs för varje utdatapixel. Därför ansvarar den för att beräkna den nya storleken på utdatabilden baserat på logiken i dess skuggor. Den måste också beräkna vilket område i indatabilden som skuggarna behöver läsa från för att återge den begärda utdataregionen.
- Skuggning: En skuggning körs mot transformatorns indata på GPU:n (eller CPU om programvarurendering anges när appen skapar Direct3D-enheten). Effektskuggor skrivs i högnivåskuggningsspråk (HLSL) och kompileras till bytekod under effektens kompilering, som sedan läses in av effekten under körningen. Det här referensdokumentet beskriver hur du skriver Direct2D--kompatibel HLSL. Direct3D-dokumentationen innehåller en grundläggande HLSL-översikt.
Skapa ett effektgränssnitt
Effektgränssnittet definierar hur en app interagerar med den anpassade effekten. För att skapa ett effektgränssnitt måste en klass implementera ID2D1EffectImpl, definiera metadata som beskriver effekten (till exempel dess namn, antal indata och egenskaper) och skapa metoder som registrerar den anpassade effekten för användning med Direct2D-.
När alla komponenter för ett effektgränssnitt har implementerats visas klassens rubrik så här:
#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.
};
Implementera ID2D1EffectImpl
Gränssnittet ID2D1EffectImpl innehåller tre metoder som du måste implementera:
Initialize(ID2D1EffectContext *pContextInternal, ID2D1TransformGraph *pTransformGraph)
Direct2D- anropar metoden Initiera efter att metoden ID2D1DeviceContext::CreateEffect har anropats av appen. Du kan använda den här metoden för att utföra intern initiering eller andra åtgärder som behövs för effekten. Dessutom kan du använda den för att skapa effektens första transformeringsdiagram.
SetGraph(ID2D1TransformGraph *pTransformGraph)
Direct2D anropar metoden SetGraph när antalet indata till effekten ändras. Även om de flesta effekter har ett konstant antal indata, stöder andra som sammansatt effekt ett variabelt antal indata. Med den här metoden kan dessa effekter uppdatera transformeringsdiagrammet som svar på ett ändrat antal indata. Om en effekt inte stöder ett variabelt indataantal kan den här metoden helt enkelt returnera E_NOTIMPL.
PrepareForRender (D2D1_CHANGE_TYPE changeType)
Metoden PrepareForRender ger en möjlighet för effekter att utföra åtgärder som svar på externa ändringar. Direct2D- anropar den här metoden precis innan den återger en effekt om minst en av dessa stämmer:
- Effekten har initierats tidigare men har ännu inte ritats.
- En effektegenskap har ändrats sedan det senaste anropet.
- Tillståndet för anropande Direct2D kontext (som DPI) har ändrats sedan det senaste anropet.
Implementera metoderna för effektregistrering och motringning
Appar måste registrera effekter med Direct2D- innan de instansieras. Den här registreringen är begränsad till en instans av en Direct2D-fabrik och måste upprepas varje gång appen körs. För att aktivera den här registreringen definierar en anpassad effekt ett unikt GUID, en offentlig metod som registrerar effekten och en privat återanropsmetod som returnerar en instans av effekten.
Definiera ett GUID
Du måste definiera ett GUID som unikt identifierar effekten för registrering med Direct2D-. Appen använder samma för att identifiera effekten när den anropar ID2D1DeviceContext::CreateEffect.
Den här koden visar hur du definierar ett sådant GUID för en effekt. Du måste skapa ett eget unikt GUID med hjälp av ett GUID-genereringsverktyg som 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);
Definiera en offentlig registreringsmetod
Definiera sedan en offentlig metod för appen att anropa för att registrera effekten med Direct2D-. Eftersom effektregistrering är specifik för en instans av en Direct2D-fabrik accepterar metoden ett ID2D1Factory1-gränssnitt som en parameter. För att registrera effekten anropar metoden sedan ID2D1Factory1::RegisterEffectFromString API på parametern ID2D1Factory1.
Det här API:et accepterar en XML-sträng som beskriver effektens metadata, indata och egenskaper. Metadata för en effekt är endast i informationssyfte och kan efterfrågas av appen via ID2D1Egenskaper-gränssnittet. Indata och egenskapsdata används å andra sidan av Direct2D- och representerar effektens funktioner.
En XML-sträng för en minimal exempeleffekt visas här. Tillägg av anpassade egenskaper till XML beskrivs i avsnittet Lägga till anpassade egenskaper i en effekt.
#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>
);
Definiera en återanropsmetod för effektfabriken
Effekten måste också tillhandahålla en privat motringningsmetod som returnerar en instans av effekten via en enda IUnknown**-parameter. En pekare till den här metoden ges till Direct2D- när effekten registreras via ID2D1Factory1::RegisterEffectFromString API via parametern 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;
}
Implementera IUnknown-gränssnittet
Slutligen måste effekten implementera IUnknown-gränssnittet för kompatibilitet med COM.
Skapa effektens transformeringsdiagram
En effekt kan använda flera olika transformeringar (enskilda bildåtgärder) för att skapa önskad bildeffekt. För att styra i vilken ordning dessa transformeringar tillämpas på indatabilden ordnas de i ett transformeringsdiagram. Ett transformeringsdiagram kan använda de effekter och transformeringar som ingår i Direct2D- samt anpassade transformeringar som skapats av effektförfattaren.
Använda transformeringar som ingår i Direct2D
Det här är de vanligaste transformeringar som tillhandahålls med Direct2D-.
- ID2D1BlendTransform: Den här transformeringen blandar ett godtyckligt antal indata baserat på en blandningsbeskrivning, se avsnittet D2D1_BLEND_DESCRIPTION och exempel på Direct2D-sammansatta effektlägen för blandningsexempel. Den här transformeringen returneras av metoden ID2D1EffectContext::CreateBlendTransform.
- ID2D1BorderTransform: Den här transformeringen expanderar en bilds utdatastorlek enligt D2D1_EXTEND_MODE angivet. Den här transformeringen returneras av metoden ID2D1EffectContext::CreateBorderTransform.
- ID2D1OffsetTransform: Den här transformeringen förskjuter (översätter) indata med ett helt antal bildpunkter. Den här transformeringen returneras av metoden ID2D1EffectContext::CreateOffsetTransform.
- Alla befintliga effekter kan behandlas som en transformering i syfte att skapa transformeringsdiagram med hjälp av metoden ID2D1EffectContext::CreateTransformNodeFromEffect.
Skapa ett transformeringsdiagram med en nod
När du har skapat en transformering måste effektens indata anslutas till transformens indata, och transformens utdata måste anslutas till effektens utdata. När en effekt bara innehåller en enda transformering kan du använda metoden ID2D1TransformGraph::SetSingleTransformNode för att enkelt åstadkomma detta.
Du kan skapa eller ändra en transformering i effektens Initiera eller SetGraph- metoder med hjälp av den angivna ID2D1TransformGraph-parametern. Om en effekt behöver göra ändringar i transformeringsdiagrammet i en annan metod där den här parametern inte är tillgänglig kan effekten spara parametern ID2D1TransformGraph som en medlemsvariabel för klassen och komma åt den någon annanstans, till exempel PrepareForRender eller en anpassad egenskapsåteranropsmetod.
Ett exempel metoden Initiera visas här. Den här metoden skapar ett transformeringsdiagram med en nod som förskjuter bilden med 100 bildpunkter på varje axel.
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;
}
Skapa ett transformeringsdiagram för flera noder
Genom att lägga till flera transformeringar i en effekts transformeringsdiagram kan effekter internt utföra flera bildåtgärder som presenteras för en app som en enda enhetlig effekt.
Som nämnts ovan kan effektens transformeringsdiagram redigeras i vilken effektmetod som helst med hjälp av parametern ID2D1TransformGraph som tas emot i effektens Initialize-metoden. Följande API:er i gränssnittet kan användas för att skapa eller ändra en effekts transformeringsdiagram:
AddNode(ID2D1TransformNode *pNode)
Metoden AddNode i praktiken "registrerar" transformeringen med effekten och måste anropas innan transformeringen kan användas med någon av de andra transformeringsgrafmetoderna.
ConnectToEffectInput(UINT32 toEffectInputIndex, ID2D1TransformNode *pNode, UINT32 toNodeInputIndex)
Metoden ConnectToEffectInput ansluter effektens bildindata till en transformerings indata. Samma effektindata kan anslutas till flera transformeringar.
ConnectNode(ID2D1TransformNode *pFromNode, ID2D1TransformNode *pToNode, UINT32 toNodeInputIndex)
Metoden ConnectNode ansluter en transformerings utdata till en annan transformerings indata. Ett transformeringsutdata kan anslutas till flera transformeringar.
SetOutputNode(ID2D1TransformNode *pNode)
Metoden SetOutputNode ansluter en transformerings utdata till effektens utdata. Eftersom en effekt bara har en utdata kan endast en enda transformering betecknas som "utdatanoden".
Den här koden använder två separata transformeringar för att skapa en enhetlig effekt. I det här fallet är effekten en översatt skugga.
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;
}
Lägga till anpassade egenskaper till en effekt
Effekter kan definiera anpassade egenskaper som gör att en app kan ändra effektens beteende under körningen. Det finns tre steg för att definiera en egenskap för en anpassad effekt:
Lägg till egenskapsmetadata i effektens registreringsdata
Lägg till egenskap i XML för registrering
Du måste definiera egenskaperna för en anpassad effekt under effektens första registrering med Direct2D-. Först måste du uppdatera effektens registrerings-XML i dess offentliga registreringsmetod med den nya egenskapen:
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>
);
När du definierar en effektegenskap i XML behöver den ett namn, en typ och ett visningsnamn. En egenskaps visningsnamn samt den övergripande effektens värden för kategori, författare och beskrivning kan och bör lokaliseras.
För varje egenskap kan en effekt ange standardvärden, min- och maxvärden. Dessa värden är endast för informationsanvändning. De tillämpas inte av Direct2D-. Det är upp till dig att implementera all angiven standardlogik/min/max-logik i själva effektklassen.
Typvärdet som anges i XML för egenskapen måste matcha motsvarande datatyp som används av egenskapens metoder för getter och setter. Motsvarande XML-värden för varje datatyp visas i den här tabellen:
Datatyp | Motsvarande XML-värde |
---|---|
PWSTR | sträng |
BOOL | Bool |
UINT | uint32 |
INT | int32 |
FLYTA | flyta |
D2D_VECTOR_2F | vektor2 |
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[] | klick |
IUnknown* | iunknown |
ID2D1ColorContext* | colorcontext |
CLSID | clsid |
Uppräkning (D2D1_INTERPOLATION_MODEosv.) | uppräkning |
Mappa den nya egenskapen till getter- och settermetoder
Därefter måste effekten mappa den här nya egenskapen till getter- och settermetoder. Detta görs via den D2D1_PROPERTY_BINDING matris som skickas till metoden ID2D1Factory1::RegisterEffectFromString.
Matrisen D2D1_PROPERTY_BINDING ser ut så här:
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".
)
};
När du har skapat XML- och bindningsmatrisen skickar du dem till metoden 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.
);
Det D2D1_VALUE_TYPE_BINDING makrot kräver att effektklassen ärver från ID2D1EffectImpl före något annat gränssnitt.
Anpassade egenskaper för en effekt indexeras i den ordning de deklareras i XML och när de har skapats kan appen komma åt dem med hjälp av ID2D1Properties::SetValue och ID2D1Properties::GetValue metoder. För enkelhetens skull kan du skapa en offentlig uppräkning som visar varje egenskap i effektens huvudfil:
typedef enum SAMPLEEFFECT_PROP
{
SAMPLEFFECT_PROP_OFFSET = 0
};
Skapa getter- och setter-metoderna för egenskapen
Nästa steg är att skapa metoderna getter och setter för den nya egenskapen. Namnen på metoderna måste matcha de som anges i matrisen D2D1_PROPERTY_BINDING. Dessutom måste egenskapstypen som anges i effektens XML matcha typen av setter-metodens parameter och getter-metodens returvärde.
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;
}
Uppdateringseffektens transformeringar som svar på egenskapsändring
För att faktiskt uppdatera en effekts bildutdata som svar på en egenskapsändring måste effekten ändra dess underliggande transformeringar. Detta görs vanligtvis i effektens Metoden PrepareForRender som Direct2D- automatiskt anropar när en av en effekts egenskaper har ändrats. Transformeringar kan dock uppdateras i någon av effektens metoder: till exempel Initiera eller effektens egenskapsuppsättningsmetoder.
Om en effekt till exempel innehöll en ID2D1OffsetTransform och ville ändra dess förskjutningsvärde som svar på att effektens offsetegenskap ändras, skulle den lägga till följande kod i 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;
}
Skapa en anpassad transformering
Om du vill implementera avbildningsåtgärder utöver vad som anges i Direct2D-måste du implementera anpassade transformeringar. Anpassade transformeringar kan godtyckligt ändra en indatabild med hjälp av anpassade HLSL-skuggningar.
Transformeringar implementerar ett av två olika gränssnitt beroende på vilka typer av skuggningar de använder. Transformeringar med pixel- och/eller hörnskuggor måste implementera ID2D1DrawTransform, medan transformeringar med beräkningsskuggor måste implementera ID2D1ComputeTransform. Båda dessa gränssnitt ärver från ID2D1Transform. Det här avsnittet fokuserar på att implementera de funktioner som är gemensamma för båda.
Gränssnittet ID2D1Transform har fyra metoder att implementera:
GetInputCount
Den här metoden returnerar ett heltal som representerar indataantalet för transformeringen.
IFACEMETHODIMP_(UINT32) GetInputCount() const
{
return 1;
}
MapInputRectsToOutputRect
Direct2D- anropar metoden MapInputRectsToOutputRect varje gång transformeringen återges. Direct2D skickar en rektangel som representerar gränserna för var och en av indata till transformeringen. Transformen ansvarar sedan för att beräkna gränserna för utdatabilden. Storleken på rektanglarna för alla metoder i det här gränssnittet (ID2D1Transform) definieras i bildpunkter, inte DIP:er.
Den här metoden ansvarar också för att beräkna regionen för utdata som är ogenomskinlig baserat på logiken i dess skuggning och de ogenomskinliga regionerna för varje indata. En täckande region i en bild definieras som den där alfakanalen är "1" för hela rektangeln. Om det är oklart om en transformerings utdata är ogenomskinlig ska utdatagenomskinlig rektangel anges till (0, 0, 0, 0) som ett säkert värde. Direct2D- använder den här informationen för att utföra återgivningsoptimeringar med "garanterat ogenomskinlig" innehåll. Om det här värdet är felaktigt kan det leda till felaktig återgivning.
Du kan ändra transformeringsbeteendet (enligt definitionen i avsnitt 6 till och med 8) under den här metoden. Du kan dock inte ändra andra transformeringar i transformeringsdiagrammet eller själva graflayouten här.
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;
}
För ett mer komplext exempel bör du överväga hur en enkel oskärpa skulle representeras:
Om en oskärpa använder en radie på 5 bildpunkter måste storleken på utdatarektangeln expandera med 5 bildpunkter, enligt nedan. När du ändrar rektangelkoordinater måste en transformering se till att dess logik inte orsakar några över-/underflöden i rektangelkoordinaterna.
// 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;
}
Eftersom bilden är suddig kan en region av bilden som var ogenomskinlig nu vara delvis transparent. Det beror på att området utanför bilden som standard är transparent svart och den här transparensen blandas i bilden runt kanterna. Transformen måste återspegla detta i utdatagenomskinliga rektangelberäkningar:
// 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;
Dessa beräkningar visualiseras här:
Mer information om den här metoden finns i referenssidan MapInputRectsToOutputRect.
MapOutputRectToInputRects
Direct2D anropar metoden MapOutputRectToInputRects efter MapInputRectsToOutputRect. Transformen måste beräkna vilken del av avbildningen den behöver läsa från för att den begärda utdataregionen ska återges korrekt.
Som tidigare, om en effekt strikt mappar bildpunkter 1-1, kan den skicka utdatarektangeln till indatarektangeln:
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;
}
På samma sätt, om en transformering krymper eller expanderar en bild (som exemplet med oskärpa här), använder pixlar ofta omgivande pixlar för att beräkna sitt värde. Med en oskärpa är en pixel i genomsnitt med sina omgivande bildpunkter, även om de ligger utanför gränserna för indatabilden. Det här beteendet återspeglas i beräkningen. Precis som tidigare söker transformeringen efter spill när koordinaterna för en rektangel expanderas.
// 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;
Den här bilden visualiserar beräkningen. Direct2D- tar automatiskt exempel på transparenta svarta pixlar där indatabilden inte finns, vilket gör att oskärpan kan blandas gradvis med det befintliga innehållet på skärmen.
Om mappningen inte är trivial bör den här metoden ange indatarektangeln till det maximala området för att garantera korrekta resultat. För att göra detta ställer du in vänster- och toppkanterna på INT_MIN, och de högra och nedre kanterna till INT_MAX.
Mer information om den här metoden finns i avsnittet MapOutputRectToInputRects.
MapInvalidRect
Direct2D anropar även metoden MapInvalidRect. Men till skillnad från MapInputRectsToOutputRect och MapOutputRectToInputRects metoderna Direct2D är inte garanterat att anropa den vid någon viss tidpunkt. Den här metoden bestämmer konceptuellt vilken del av en transformerings utdata som måste återges på nytt som svar på att en del av eller alla dess indata ändras. Det finns tre olika scenarier för vilka du kan beräkna en transformerings ogiltiga rekt.
Transformerar med en-till-en-pixelmappning
För transformeringar som mappar bildpunkter 1–1 skickar du helt enkelt den ogiltiga indatarektangeln till den ogiltiga utdatarektangeln:
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;
}
Transformerar med många-till-många-pixelmappning
När en transformerings utdatapixlar är beroende av deras omgivande område måste den ogiltiga indatarektangeln expanderas. Detta återspeglar att pixlar som omger den ogiltiga indatarektangeln också påverkas och blir ogiltiga. Till exempel använder en oskärpa på fem bildpunkter följande beräkning:
// 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;
Transformerar med komplex pixelmappning
För transformeringar där in- och utdatapixlar inte har en enkel mappning kan hela utdata markeras som ogiltiga. Om en transformering till exempel bara matar ut den genomsnittliga färgen på indata ändras hela utdata från transformeringen om även en liten del av indata ändras. I det här fallet ska den ogiltiga utdatarektangeln anges till en logiskt oändlig rektangel (visas nedan). Direct2D automatiskt klämmer fast detta till gränserna för utdata.
// 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);
Mer information om den här metoden finns i avsnittet MapInvalidRect.
När dessa metoder har implementerats innehåller transformens huvud följande:
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.
};
Lägga till en pixelskuggning i en anpassad transformering
När en transformering har skapats måste den tillhandahålla en skuggning som ändrar bildpunkterna. Det här avsnittet beskriver stegen för att använda en pixelskuggare med en anpassad transformering.
Implementera ID2D1DrawTransform
Om du vill använda en pixelskuggning måste transformeringen implementera ID2D1DrawTransform--gränssnittet, som ärver från gränssnittet ID2D1Transform som beskrivs i avsnitt 5. Det här gränssnittet innehåller en ny metod att implementera:
SetDrawInfo(ID2D1DrawInfo *pDrawInfo)
Direct2D anropar metoden SetDrawInfo när transformering först läggs till i en effekts transformeringsdiagram. Den här metoden innehåller en ID2D1DrawInfo parameter som styr hur transformering återges. Se avsnittet ID2D1DrawInfo för de metoder som finns här.
Om transformeringen väljer att lagra den här parametern som en klassmedlemsvariabel kan drawInfo-objektet nås och ändras från andra metoder, till exempel egenskapsuppsättningar eller MapInputRectsToOutputRect. I synnerhet kan den inte anropas från MapOutputRectToInputRects eller MapInvalidRect metoder på ID2D1Transform.
Skapa ett GUID för pixelskuggaren
Sedan måste transformen definiera ett unikt GUID för själva pixelskuggningen. Detta används när Direct2D- läser in skuggningen i minnet, samt när transformeringen väljer vilken pixelskuggning som ska användas för körning. Verktyg som guidgen.exe, som ingår i Visual Studio, kan användas för att generera ett slumpmässigt GUID.
// 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);
Läser in pixelskuggaren med Direct2D
En pixelskuggning måste läsas in i minnet innan den kan användas av transformen.
Om du vill läsa in pixelskuggaren i minnet ska transformeringen läsa den kompilerade skuggningsbytekoden från . CSO-fil som genereras av Visual Studio (se Direct3D- dokumentation för mer information) i en bytematris. Den här tekniken visas i detalj i D2DCustomEffects SDK-exempel.
När skuggningsdata har lästs in i en bytematris anropar du metoden LoadPixelShader på effektens ID2D1EffectContext-objekt. Direct2D ignorerar anrop till LoadPixelShader när en skuggning med samma GUID redan har lästs in.
När en pixelskuggare har lästs in i minnet måste transformeringen välja den för körning genom att skicka sitt GUID till metoden SetPixelShader på ID2D1DrawInfo-parametern som angavs under metoden SetDrawInfo. Pixelskuggningen måste redan läsas in i minnet innan den väljs för körning.
Ändra skuggningsåtgärd med konstanta buffertar
Om du vill ändra hur en skuggning körs kan en transformering skicka en konstant buffert till pixelskuggaren. För att göra det definierar en transformering en struct som innehåller önskade variabler i klassrubriken:
// This struct defines the constant buffer of the pixel shader.
struct
{
float valueOne;
float valueTwo;
} m_constantBuffer;
Transformen anropar sedan metoden ID2D1DrawInfo::SetPixelShaderConstantBuffer på ID2D1DrawInfo parametern som anges i metoden SetDrawInfo för att skicka bufferten till skuggningen.
HLSL- måste också definiera en motsvarande struct som representerar konstantbufferten. Variablerna som finns i skuggningens struct måste matcha dem i transformens struct.
cbuffer constants : register(b0)
{
float valueOne : packoffset(c0.x);
float valueTwo : packoffset(c0.y);
};
När bufferten har definierats kan värdena som finns i läsas var som helst i pixelskuggningen.
Skriva en pixelskuggning för Direct2D
Direct2D transformeringar använder skuggningar som skapats med standard HLSL-. Det finns dock några viktiga begrepp för att skriva en pixelskuggning som körs från kontexten för en transformering. Ett slutfört exempel på en fullt funktionell pixelskuggning finns i D2DCustomEffects SDK-exempel.
Direct2D- mappar automatiskt en transformerings indata till Texture2D- och SamplerState objekt i HLSL. Den första Texture2D finns i register t0 och den första SamplerState finns på register s0. Varje ytterligare indata finns i nästa motsvarande register (t1 och s1 till exempel). Pixeldata för en viss indata kan samplas genom att anropa Exempel på objektet Texture2D och skicka motsvarande SamplerState-objektet och texelkoordinaterna.
En anpassad pixelskuggning körs en gång för varje pixel som återges. Varje gång skuggningen körs tillhandahåller Direct2D- automatiskt tre parametrar som identifierar dess aktuella körningsposition:
- Scenutrymmesutdata: Den här parametern representerar den aktuella körningspositionen när det gäller den övergripande målytan. Det definieras i pixlar och dess min/max-värden motsvarar gränserna för rektangeln som returneras av MapInputRectsToOutputRect.
- Utdata från clip-space: Den här parametern används av Direct3D och får inte användas i en transformerings pixelskuggning.
- Texel-space-indata: Den här parametern representerar den aktuella körningspositionen i en viss indatastruktur. En skuggning bör inte ha några beroenden för hur det här värdet beräknas. Den bör bara använda den för att prova pixelskuggarens indata, enligt koden nedan:
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;
}
Lägga till en hörnskuggning i en anpassad transformering
Du kan använda hörnskuggor för att utföra olika avbildningsscenarier än pixelskuggare. I synnerhet kan hörnskuggor utföra geometribaserade bildeffekter genom att transformera hörn som utgör en bild. Hörnskuggor kan användas oberoende av eller tillsammans med transformerade pixelskuggor. Om en hörnskuggning inte har angetts ersätter Direct2D i en standardhörnskuggning som ska användas med den anpassade pixelskuggningen.
Processen för att lägga till en hörnskuggning i en anpassad transformering liknar den för en pixelskuggare – transformeringen implementerar ID2D1DrawTransform-gränssnittet, skapar ett GUID och (valfritt) skickar konstanta buffertar till skuggningen. Det finns dock några viktiga ytterligare steg som är unika för hörnskuggor:
Skapa en brytpunktsbuffert
En hörnskuggning per definition körs på hörn som skickas till den, inte enskilda bildpunkter. Om du vill ange hörnen som skuggningen ska köras på skapar en transformering en brytpunktsbuffert som ska skickas till skuggningen. Layouten för hörnbuffertar ligger utanför det här dokumentets omfång. Mer information finns i Direct3D-referens, eller D2DCustomEffects SDK-exempel för en exempelimplementering.
När du har skapat en brytpunktsbuffert i minnet använder transformen metoden CreateVertexBuffer på den innehållande effektens ID2D1EffectContext-objekt för att skicka dessa data till GPU:n. Se återigen D2DCustomEffects SDK-exempel för en exempelimplementering.
Om ingen brytpunktsbuffert anges av transformeringen skickar Direct2D- en standardhörnbuffert som representerar den rektangulära bildplatsen.
Ändra SetDrawInfo för att använda en hörnskuggning
Precis som med pixelskuggare måste transformeringen läsas in och välja en hörnskuggning för körning. Om du vill läsa in hörnskuggningen anropas metoden LoadVertexShader på metoden ID2D1EffectContext metod som togs emot i effektens initialiseringsmetod. Om du vill välja hörnskuggning för körning anropas SetVertexProcessing- på parametern ID2D1DrawInfo som tagits emot i transformeringsmetoden SetDrawInfo. Den här metoden accepterar ett GUID för en tidigare inläst hörnskuggning samt (valfritt) en tidigare skapad brytpunktsbuffert som skuggningen ska köras på.
Implementera en Direct2D-hörnskuggning
En ritad transformering kan innehålla både en pixelskuggare och en hörnskuggning. Om en transformering definierar både en pixelskuggare och en hörnskuggning ges utdata från hörnskuggningen direkt till pixelskuggaren: appen kan anpassa retursignaturen för hörnskuggningen/parametrarna för pixelskuggaren så länge de är konsekventa.
Om en transformering å andra sidan bara innehåller en hörnskuggning och förlitar sig på Direct2D-standardpunktskuggning för direktströmning, måste den returnera följande standardutdata:
struct VSOut
{
float4 clipSpaceOutput : SV_POSITION;
float4 sceneSpaceOutput : SCENE_POSITION;
float4 texelSpaceInput0 : TEXCOORD0;
};
En hörnskuggning lagrar resultatet av hörntransformeringar i skuggningens scen-space-utdatavariabel. För att beräkna clip-space-utdata och indatavariablerna för Texel-space Direct2D- automatiskt konverteringsmatriser i en konstant buffert:
// 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;
};
Exempel på skuggningskod för hörn finns nedan som använder konverteringsmatriserna för att beräkna rätt klipp- och texelutrymmen som förväntas av 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;
}
Ovanstående kod kan användas som utgångspunkt för en hörnskuggning. Den passerar bara genom indatabilden utan att utföra några transformeringar. Se återigen D2DCustomEffects SDK-exempel för en fullständigt implementerad hörnskuggningsbaserad transformering.
Om ingen brytpunktsbuffert anges av transformeringen ersätter Direct2D i en standardhörnbuffert som representerar den rektangulära bildplatsen. Parametrarna till hörnskuggningen ändras till standardutdata för skuggning:
struct VSIn
{
float4 clipSpaceOutput : SV_POSITION;
float4 sceneSpaceOutput : SCENE_POSITION;
float4 texelSpaceInput0 : TEXCOORD0;
};
Hörnskuggningen kanske inte ändrar parametrarna sceneSpaceOutput och clipSpaceOutput. De måste returneras oförändrade. Den kan dock ändra texelSpaceInput parametrar för varje indatabild. Om transformen också innehåller en anpassad pixelskuggning kan hörnskuggningen fortfarande skicka ytterligare anpassade parametrar direkt till pixelskuggningen. Dessutom tillhandahålls inte längre den anpassade bufferten för sceneSpace- konverteringsmatriser (b0).
Lägga till en beräkningsskuggning i en anpassad transformering
Slutligen kan anpassade transformeringar använda beräkningsskuggor för vissa målscenarier. Beräkningsskuggor kan användas för att implementera komplexa bildeffekter som kräver godtycklig åtkomst till indata- och utdatabildbuffertar. En grundläggande histogramalgoritm kan till exempel inte implementeras med en pixelskuggning på grund av begränsningar i minnesåtkomsten.
Eftersom beräkningsskuggare har högre krav på maskinvarufunktionsnivå än pixelskuggare bör pixelskuggare användas när det är möjligt för att implementera en given effekt. Mer specifikt körs beräkningsskuggor endast på de flesta DirectX 10-nivåkort och högre. Om en transformering väljer att använda en beräkningsskuggning måste den söka efter lämpligt maskinvarustöd under instansieringen, förutom att implementera ID2D1ComputeTransform-gränssnittet.
Söker efter stöd för Compute Shader
Om en effekt använder en beräkningsskuggning måste den söka efter stöd för beräkningsskuggning när den skapas med hjälp av metoden ID2D1EffectContext::CheckFeatureSupport. Om GPU:n inte stöder beräkningskuggare måste effekten returnera D2DERR_INSUFFICIENT_DEVICE_CAPABILITIES.
Det finns två olika typer av beräkningsskuggor som en transformering kan använda: Shader Model 4 (DirectX 10) och Shader Model 5 (DirectX 11). Det finns vissa begränsningar för Skuggningsmodell 4-skuggningar. Mer information finns i dokumentationen om Direct3D. Transformeringar kan innehålla båda typerna av skuggningar och kan återgå till Shader Model 4 vid behov: se D2DCustomEffects SDK-exempel för en implementering av detta.
Implementera ID2D1ComputeTransform
Det här gränssnittet innehåller två nya metoder att implementera utöver de i ID2D1Transform:
SetComputeInfo(ID2D1ComputeInfo *pComputeInfo)
Precis som med pixel- och hörnskuggor anropar Direct2D metoden SetComputeInfo när transformeringen först läggs till i en effekts transformeringsdiagram. Den här metoden innehåller en ID2D1ComputeInfo parameter som styr hur transformering återges. Detta inkluderar att välja den beräkningsskuggning som ska köras via metoden ID2D1ComputeInfo::SetComputeShader. Om transformeringen väljer att lagra den här parametern som en klassmedlemsvariabel kan den nås och ändras från valfri transformerings- eller effektmetod med undantag för MapOutputRectToInputRects och MapInvalidRect- metoder. Se avsnittet ID2D1ComputeInfo för andra metoder som är tillgängliga här.
CalculateThreadgroups(const D2D1_RECT_L *pOutputRect, UINT32 *pDimensionX, UINT32 *pDimensionY, UINT32 *pDimensionZ)
Medan pixelskuggare körs per pixel och hörnskuggor körs per hörn körs beräkningsskuggor per trådgrupp. En trådgrupp representerar ett antal trådar som körs samtidigt på GPU:n. HLSL-koden för beräkningsskuggning bestämmer hur många trådar som ska köras per trådgrupp. Effekten skalar antalet trådgrupper så att skuggningen kör önskat antal gånger, beroende på skuggningens logik.
Med metoden CalculateThreadgroups kan transformeringen informera Direct2D hur många trådgrupper som krävs, baserat på bildens storlek och transformeringens egen kunskap om skuggningen.
Antalet gånger beräkningsskuggningen körs är en produkt av antalet trådgrupper som anges här och anteckningen "numthreads" i beräkningsskuggningen HLSL-. Om transformeringen till exempel anger trådgruppsdimensionerna till (2,2,1) som skuggningen anger (3,3,1) trådar per trådgrupp, körs 4 trådgrupper, var och en med 9 trådar i sig, för totalt 36 trådinstanser.
Ett vanligt scenario är att bearbeta en utdatapixel för varje instans av beräkningsskuggningen. För att beräkna antalet trådgrupper för det här scenariot delar transformeringen bildens bredd och höjd med respektive x- och y-dimensioner i kommentaren "numthreads" i beräkningsskuggningen HLSL-.
Viktigt är att om den här divisionen utförs måste antalet begärda trådgrupper alltid avrundas upp till närmaste heltal, annars körs inte restpixlarna på. Om en skuggning (till exempel) beräknar en enskild pixel med varje tråd visas metodens kod på följande sätt.
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- använder följande kod för att ange antalet trådar i varje trådgrupp:
// 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(
...
Under körningen skickas den aktuella trådgruppen och det aktuella trådindexet som parametrar till skuggningsmetoden:
#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
)
{
...
Läsa bilddata
Beräkningsskuggor har åtkomst till transformens indatabild som en enda tvådimensionell textur:
Texture2D<float4> InputTexture : register(t0);
SamplerState InputSampler : register(s0);
Men precis som pixelskuggare är bildens data inte garanterade att börja vid (0, 0) på strukturen. I stället tillhandahåller Direct2D- systemkonstanter som gör det möjligt för skuggare att kompensera för eventuella förskjutningar:
// 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;
}
När den konstanta bufferten och hjälpmetoden ovan har definierats kan skuggningen exempel på bilddata med hjälp av följande:
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
);
Skriva bilddata
Direct2D- förväntar sig att en skuggning ska definiera en utdatabuffert för den resulterande bilden. I Shader Model 4 (DirectX 10) måste detta vara en endimensionell buffert på grund av funktionsbegränsningar:
// Shader Model 4 does not support RWTexture2D, must use 1D buffer instead.
RWStructuredBuffer<float4> OutputTexture : register(t1);
Utdatastrukturen indexeras rad-först så att hela bilden kan lagras.
uint imageWidth = resultRect[2] - resultRect[0];
uint imageHeight = resultRect[3] - resultRect[1];
OutputTexture[yIndex * imageWidth + xIndex] = color;
Å andra sidan kan Shader Model 5-skuggningar (DirectX 11) använda tvådimensionella utdatastrukturer:
RWTexture2D<float4> OutputTexture : register(t1);
Med Shader Model 5-skuggningar ger Direct2D- ytterligare en "outputOffset"-parameter i konstantbufferten. Skuggningens utdata bör förskjutas med följande belopp:
OutputTexture[uint2(xIndex, yIndex) + outputOffset.xy] = color;
En slutförd skuggning av shader model 5 visas nedan. I den läser och skriver var och en av beräkningsskuggningstrådarna en bildpunkt i indatabilden.
#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;
Koden nedan visar motsvarande Shader Model 4-version av skuggningen. Observera att skuggningen nu renderas till en endimensionell utdatabuffert.
#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;
}
Relaterade ämnen