Egyéni effektusok implementálása

A Win2D számos API-t biztosít a rajzolható objektumok ábrázolására, amelyek két kategóriába sorolhatók: képek és effektusok. A felület által ICanvasImage ábrázolt képek nem rendelkeznek bemenetekkel, és közvetlenül egy adott felületen rajzolhatók. Ilyenek például a CanvasBitmapVirtualizedCanvasBitmapCanvasRenderTarget képtípusok. Az effektusokat viszont az ICanvasEffect interfész képviseli. Bemenetekkel és további erőforrásokkal is rendelkezhetnek, és tetszőleges logikát alkalmazhatnak a kimeneteik előállításához (mivel az effektus egy kép is). A Win2D magába foglalja a legtöbb D2D-effektust, mint például a GaussianBlurEffect, a TintEffect és a LuminanceToAlphaEffect.

A képek és az effektusok összekapcsolhatók, így tetszőleges diagramokat hozhat létre, amelyek ezután megjeleníthetők az alkalmazásban (a Direct2D-effektusok D2D-dokumentumaira is hivatkozva). Ezek együttesen rendkívül rugalmas rendszert biztosítanak az összetett grafikák hatékony elkészítéséhez. Vannak azonban olyan esetek, amikor a beépített effektusok nem elegendőek, és érdemes lehet saját Win2D-effektust létrehozni. Ennek támogatása érdekében a Win2D számos hatékony interop API-t tartalmaz, amelyek lehetővé teszik egyéni képek és effektusok definiálását, amelyek zökkenőmentesen integrálhatók a Win2D-vel.

Jótanács

Ha C#-ot használ, és egyéni effektus- vagy effektusgráfot szeretne implementálni, javasoljuk, hogy a ComputeSharpet használja ahelyett, hogy teljesen új effektust próbálna implementálni. Az alábbi bekezdésben részletes leírást talál arról, hogyan használható ez a kódtár a Win2D-vel zökkenőmentesen integrálható egyéni effektusok implementálásához.

Platform API-k:ICanvasImage, CanvasBitmap, VirtualizedCanvasBitmap, CanvasRenderTarget, CanvasEffect, GaussianBlurEffect, TintEffect, ICanvasLuminanceToAlphaEffectImage, IGraphicsEffectSource, ID2D21Image, ID2D1Factory1, ID2D1Effect

Az egyéni ICanvasImage megvalósítása

A legegyszerűbb támogatási forgatókönyv egy egyéni ICanvasImagelétrehozása. Ahogy említettük, ez a Win2D által definiált WinRT-felület, amely minden olyan képet jelöl, amellyel a Win2D képes együttműködni. Ez az interfész csak két GetBounds metódust tesz elérhetővé, és kiterjeszti IGraphicsEffectSourceazt, amely egy "valamilyen hatásforrást" jelképező jelölőfelület.

Mint látható, a felület nem fedi fel a "funkcionális" API-kat, hogy ténylegesen bármilyen rajzot végrehajtson. A saját ICanvasImage objektum implementálásához implementálnia kell a ICanvasImageInterop felületet is, amely elérhetővé teszi a Win2D-hez szükséges összes logikát a kép rajzolásához. Ez egy COM felület, amely a Win2D-vel érkezik, és nyilvános Microsoft.Graphics.Canvas.native.h fejlécben van definiálva.

Az interfész a következőképpen van definiálva:

[uuid("E042D1F7-F9AD-4479-A713-67627EA31863")]
class ICanvasImageInterop : IUnknown
{
    HRESULT GetDevice(
        ICanvasDevice** device,
        WIN2D_GET_DEVICE_ASSOCIATION_TYPE* type);

    HRESULT GetD2DImage(
        ICanvasDevice* device,
        ID2D1DeviceContext* deviceContext,
        WIN2D_GET_D2D_IMAGE_FLAGS flags,
        float targetDpi,
        float* realizeDpi,
        ID2D1Image** ppImage);
}

És a két enumerálási típusra is támaszkodik ugyanabból a fejlécből:

enum WIN2D_GET_DEVICE_ASSOCIATION_TYPE
{
    WIN2D_GET_DEVICE_ASSOCIATION_TYPE_UNSPECIFIED,
    WIN2D_GET_DEVICE_ASSOCIATION_TYPE_REALIZATION_DEVICE,
    WIN2D_GET_DEVICE_ASSOCIATION_TYPE_CREATION_DEVICE
}

enum WIN2D_GET_D2D_IMAGE_FLAGS
{
    WIN2D_GET_D2D_IMAGE_FLAGS_NONE,
    WIN2D_GET_D2D_IMAGE_FLAGS_READ_DPI_FROM_DEVICE_CONTEXT,
    WIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION,
    WIN2D_GET_D2D_IMAGE_FLAGS_NEVER_INSERT_DPI_COMPENSATION,
    WIN2D_GET_D2D_IMAGE_FLAGS_MINIMAL_REALIZATION,
    WIN2D_GET_D2D_IMAGE_FLAGS_ALLOW_NULL_EFFECT_INPUTS,
    WIN2D_GET_D2D_IMAGE_FLAGS_UNREALIZE_ON_FAILURE
}

Az egyéni rendszerképek (vagy effektusok) implementálásához mind a két GetDevice és GetD2DImage a metódus szükséges, mivel a Win2D-nek biztosítják a bővíthetőségi pontokat az adott eszközön való inicializálásukhoz és a mögöttes D2D-rendszerkép lekéréséhez a rajzoláshoz. Ezeknek a módszereknek a helyes implementálása kritikus fontosságú annak biztosítása érdekében, hogy a dolgok megfelelően működjenek minden támogatott forgatókönyvben.

Nézzük meg, hogyan működnek az egyes metódusok.

Megvalósítás GetDevice

A GetDevice módszer a legegyszerűbb a kettő közül. Ez azt jelenti, hogy lekéri az effektushoz társított vászoneszközt, hogy a Win2D szükség esetén megvizsgálhassa azt (például annak biztosítása érdekében, hogy megfeleljen a használt eszköznek). A type paraméter a visszaadott eszköz társítástípusát jelzi.

Két fő lehetséges eset lehetséges:

  • Ha a kép egy effektus, akkor támogatnia kell a "megvalósított" és a "nem megvalósított" állapotokat több eszközön. Ez azt jelenti, hogy egy adott effektus nem inicializált állapotban jön létre, majd akkor jön létre, ha egy eszköz átadása a rajz közben történik, és ezt követően továbbra is használhatja az adott eszközzel, vagy áthelyezhető egy másik eszközre. Ebben az esetben az effektus alaphelyzetbe állítja a belső állapotát, majd újra megvalósítja magát az új eszközön. Ez azt jelenti, hogy a társított vászneszköz idővel változhat, és akár nullis lehet. Emiatt a type értékét WIN2D_GET_DEVICE_ASSOCIATION_TYPE_REALIZATION_DEVICE-re kell állítani, és a visszaadott eszközt az aktuális megvalósítási eszközre kell állítani, ha van ilyen.
  • Egyes képek egyetlen "tulajdonosi eszközzel" rendelkeznek, amely a létrehozáskor van hozzárendelve, és soha nem módosítható. Ez a helyzet például egy textúra képe esetében, mivel az egy adott eszközön van lefoglalva, és nem helyezhető át. Amikor a GetDevice meghívásra kerül, a létrehozási eszközt kell visszaadnia, és be kell állítania a type értékét WIN2D_GET_DEVICE_ASSOCIATION_TYPE_CREATION_DEVICE-re. Kérjük, vegye figyelembe, hogy ha ez a típus meg van határozva, a visszaadott eszköz nem lehet null.

Megjegyzés:

A Win2D meghívhatja a GetDevice-t rekurzívan áthaladva egy effektusdiagramon, ami azt jelenti, hogy több aktív hívás is lehet a veremben a GetD2DImage-re. Emiatt GetDevice nem szabad blokkolni az aktuális kép zárolását, mivel ez potenciálisan holtpontot okozhat. Ehelyett egy újra beléphető zárat kell nem blokkoló módon használnia, és hibát kell jeleznie, ha nem szerezhető be. Ez biztosítja, hogy ugyanaz a szál rekurzívan meghívva sikeresen megszerzi azt, míg az azonos műveletet végző egyidejű szálak zökkenőmentesen sikertelenek lesznek.

Megvalósítás GetD2DImage

A legtöbb munkát a GetD2DImage-n végzik. Ez a módszer felel a ID2D1Image Win2D által rajzolható objektum lekéréséért, szükség esetén az aktuális hatás felismerése érdekében. Ez magában foglalja a rekurzív bejárást és az effektus gráfjának megvalósítását minden forrásnál, ha van ilyen, valamint a képhez szükséges állapotok inicializálását (pl. állandó pufferek és egyéb tulajdonságok, erőforrás-textúrák stb.).

Ennek a módszernek a pontos implementációja nagymértékben függ a kép típusától, és sokat változhat, de általában tetszőleges hatás esetén elvárható, hogy a metódus végrehajtsa a következő lépéseket:

  • Ellenőrizze, hogy a hívás rekurzív volt-e ugyanazon a példányon, és ha igen, meghiúsult-e. Ez a ciklusok észlelésére szükséges az effektusdiagramon (például az effektus A esetében az B hat mint forrás, és az effektus B esetében az A hat mint forrás).
  • Szerezzen be egy zárolást a képpéldányon az egyidejű hozzáférés elleni védelem érdekében.
  • A cél DPI-k kezelése a bemeneti jelzők alapján
  • Ellenőrizze, hogy a bemeneti eszköz megfelel-e a használatban lévő eszköznek, ha van ilyen. Ha nem egyezik, és az aktuális effektus támogatja a megvalósítást, szüntesse meg az effektus megvalósítását.
  • A bemeneti eszközre gyakorolt hatás felismerése. Ez magában foglalhatja a D2D-effektus regisztrálását a ID2D1Factory1 bemeneti eszközről vagy eszközkörnyezetből lekért objektumra, ha szükséges. Emellett minden szükséges állapotot be kell állítani a létrehozandó D2D-effektuspéldányon.
  • Rekurzívan lépkedjen át minden forráson, és kösse őket a D2D-effektushoz.

A bemeneti jelzőkkel kapcsolatban számos olyan eset van, amelyet az egyéni effektusoknak megfelelően kell kezelniük, hogy minden más Win2D-effektussal kompatibilisek legyenek. WIN2D_GET_D2D_IMAGE_FLAGS_NONE kivételével, a következő jelzőket kell kezelni:

  • WIN2D_GET_D2D_IMAGE_FLAGS_READ_DPI_FROM_DEVICE_CONTEXT: ebben az esetben device garantáltan nem lesz null. Az effektusnak ellenőriznie kell, hogy az eszközkörnyezeti cél egy ID2D1CommandList-e, és ha igen, adja hozzá a WIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION jelzőt. Ellenkező esetben be kell állítani a targetDpi-t (amely szintén garantáltan nem null) a bemeneti környezetből lekért DPI-értékekre. Ezután el kell távolítania WIN2D_GET_D2D_IMAGE_FLAGS_READ_DPI_FROM_DEVICE_CONTEXT a jelölőkből.
  • WIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION és WIN2D_GET_D2D_IMAGE_FLAGS_NEVER_INSERT_DPI_COMPENSATION: az effektusforrások beállításakor használatos (lásd az alábbi megjegyzéseket).
  • WIN2D_GET_D2D_IMAGE_FLAGS_MINIMAL_REALIZATION: ha be van állítva, kihagyja az effektus forrásainak rekurzív realizálását, és csak a realizált effektust adja vissza más módosítások nélkül.
  • WIN2D_GET_D2D_IMAGE_FLAGS_ALLOW_NULL_EFFECT_INPUTS: ha be van állítva, a megvalósítandó effektusforrások engedélyezettek, nullha a felhasználó még nem állította be őket egy meglévő forrásra.
  • WIN2D_GET_D2D_IMAGE_FLAGS_UNREALIZE_ON_FAILURE: ha be van állítva, és a beállított effektusforrás érvénytelen, az effektusnak el kell engednie magát, mielőtt hibát jelez. Vagyis, ha a hiba az effektus megvalósítása után az effektus forrásainak feloldása közben történt, akkor az effektusnak önmagát kell visszavonnia, mielőtt visszaadja a hibát a hívónak.

A DPI-vel kapcsolatos jelzők tekintetében ezek szabályozzák a effektusforrások beállítását. A Win2D-vel való kompatibilitás biztosítása érdekében az effektusoknak szükség esetén automatikusan hozzá kell adniuk a DPI-kompenzációs effektusokat a bemeneteikhez. Szabályozhatják, hogy ez így van-e:

  • Ha WIN2D_GET_D2D_IMAGE_FLAGS_MINIMAL_REALIZATION be van állítva, DPI-kompenzációs effektusra van szükség, ha a inputDpi paraméter nem 0.
  • Ellenkező esetben DPI-kompenzációra van szükség, ha inputDpi nincs 0, WIN2D_GET_D2D_IMAGE_FLAGS_NEVER_INSERT_DPI_COMPENSATION nincs beállítva, és vagy WIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION be van állítva, vagy a bemeneti DPI és a cél DPI-értékek nem egyeznek.

Ezt a logikát akkor kell alkalmazni, amikor egy forrás megvalósítása folyamatban van, és az aktuális effektus bemenetéhez van kötve. Vegye figyelembe, hogy ha DPI-kompenzációs effektus kerül hozzáadásra, ennek kell lennie az alapul szolgáló D2D kép bemenetének. Ha azonban a felhasználó megpróbálja lekérni a WinRT-burkolót a forráshoz, az effektusnak ügyelnie kell arra, hogy észlelje a DPI-effektus használatát, és ehelyett egy burkolót adjon vissza az eredeti forrásobjektumhoz. Vagyis a DPI-kompenzációs hatásoknak átláthatónak kell lenniük az effektus felhasználói számára.

Az összes inicializálási logika befejezése után az eredményként kapott ID2D1Image (hasonlóan a Win2D-objektumokhoz, a D2D-effektus is egy kép) készen kell álljon arra, hogy a Win2D a célkörnyezeten megrajzolja, amelyet a hívás fogadója ebben az időpontban még nem ismer.

Megjegyzés:

Ennek a módszernek a helyes végrehajtása (és általában a ICanvasImageInterop) rendkívül bonyolult, és csak haladó felhasználók végezhetik el, akiknek feltétlenül szükségük van az extra rugalmasságra. A D2D, a Win2D, a COM, a WinRT és a C++ alapos ismerete ajánlott az ICanvasImageInterop implementáció megírása előtt. Ha az egyéni Win2D-effektusnak körül kell vennie egy egyéni D2D-effektust is, akkor saját ID2D1Effect objektumot is implementálnia kell (erről további információt a D2D dokumentációban az egyéni effektusokról). Ezek a dokumentumok nem teljes körű leírást adnak az összes szükséges logikáról (például nem fedik le, hogy az effektusforrások hogyan legyenek rendezve és felügyelve a D2D/Win2D határán), ezért ajánlott a Win2D kódbázisában lévő implementációt is referenciapontként használni CanvasEffect egy egyéni effektushoz, és szükség szerint módosítani.

Megvalósítás GetBounds

Az egyéni ICanvasImage effektusok teljes implementálásához az utolsó hiányzó összetevő a két GetBounds túlterhelés támogatása. Ennek megkönnyítése érdekében a Win2D egy C-exportálást tesz elérhetővé, amely a Win2D meglévő logikájának kihasználására használható bármilyen egyéni rendszerképen. Az exportálás a következő:

HRESULT GetBoundsForICanvasImageInterop(
    ICanvasResourceCreator* resourceCreator,
    ICanvasImageInterop* image,
    Numerics::Matrix3x2 const* transform,
    Rect* rect);

Az egyéni képek meghívhatják ezt az API-t, magukat image paraméterként átadhatják, majd egyszerűen visszaadhatják az eredményt a hívóiknak. A transform paraméter lehet null, ha nem érhető el átalakítás.

Eszközkörnyezet-hozzáférések optimalizálása

A deviceContext paramétere a ICanvasImageInterop::GetD2DImage-ben néha nulllehet, ha a környezet nem érhető el azonnal a meghívás előtt. Ez szándékosan történik, így a környezet csak akkor jön létre lazán, amikor ténylegesen szükség van rá. Vagyis, ha egy környezet elérhető, a Win2D átadja azt a GetD2DImage hívásnak, ellenkező esetben a hívók saját maguk is lekérhetik, ha szükséges.

Az eszközkörnyezet létrehozása viszonylag költséges, ezért az API-kat úgy tervezték, hogy hozzáférést biztosítsanak a Win2D belső eszközkörnyezet-készletéhez, ezáltal gyorsabban elérhetővé téve az eszközkörnyezetet. Ez lehetővé teszi, hogy az egyéni effektusok hatékonyan béreljenek és küldjenek vissza egy adott vászoneszközhöz társított eszközkörnyezeteket.

Az eszközkörnyezet-bérlet API-k a következőképpen vannak definiálva:

[uuid("A0928F38-F7D5-44DD-A5C9-E23D94734BBB")]
interface ID2D1DeviceContextLease : IUnknown
{
    HRESULT GetD2DDeviceContext(ID2D1DeviceContext** deviceContext);
}

[uuid("454A82A1-F024-40DB-BD5B-8F527FD58AD0")]
interface ID2D1DeviceContextPool : IUnknown
{
    HRESULT GetDeviceContextLease(ID2D1DeviceContextLease** lease);
}

A ID2D1DeviceContextPool felületet a CanvasDevice interfészt implementáló Win2D típus, ICanvasDevice implementálja. A medence használatához használja az QueryInterface-át az eszközfelületen egy ID2D1DeviceContextPool-referencia megszerzéséhez, majd hívja meg a ID2D1DeviceContextPool::GetDeviceContextLease-t egy ID2D1DeviceContextLease objektum megszerzéséhez az eszközkörnyezet elérésére. Ha már nincs rá szükség, engedje fel a bérletet. Ügyeljen arra, hogy a bérlet kiadása után ne érintse meg az eszközkörnyezetet, mivel azt más szálak egyidejűleg használhatják.

A WinRT-burkolók keresésének engedélyezése

Ahogy az Win2D interop docs-ban látható, a Win2D nyilvános fejléce egy GetOrCreate metódust is elérhetővé tesz (elérhető a ICanvasFactoryNative aktiválási gyárból, vagy az ugyanabban a fejlécben definiált GetOrCreate C++/CX-segédeken keresztül). Ez lehetővé teszi a WinRT-burkoló lekérését egy adott natív erőforrásból. Így például lekérhet vagy létrehozhat egy példányt CanvasDevice egy ID2D1Device1 objektumból, egy CanvasBitmap objektumból ID2D1Bitmapstb.

Ez a módszer az összes beépített Win2D-effektus esetében is működik: natív erőforrás lekérése egy adott effektushoz, majd ezzel lekéri a megfelelő Win2D burkolót, így helyesen visszaadja az azt birtokló Win2D-effektust. Annak érdekében, hogy az egyéni effektusok ugyanazt a térképzési rendszert is kihasználhassák, a Win2D több API-t is elérhetővé tesz az interop interfészben az CanvasDevice aktiválási gyár számára, amely a ICanvasFactoryNative típus, valamint egy további effektusgyár interfész: ICanvasEffectFactoryNative.

[uuid("29BA1A1F-1CFE-44C3-984D-426D61B51427")]
class ICanvasEffectFactoryNative : IUnknown
{
    HRESULT CreateWrapper(
        ICanvasDevice* device,
        ID2D1Effect* resource,
        float dpi,
        IInspectable** wrapper);
};

[uuid("695C440D-04B3-4EDD-BFD9-63E51E9F7202")]
class ICanvasFactoryNative : IInspectable
{
    HRESULT GetOrCreate(
        ICanvasDevice* device,
        IUnknown* resource,
        float dpi,
        IInspectable** wrapper);

    HRESULT RegisterWrapper(IUnknown* resource, IInspectable* wrapper);

    HRESULT UnregisterWrapper(IUnknown* resource);

    HRESULT RegisterEffectFactory(
        REFIID effectId,
        ICanvasEffectFactoryNative* factory);

    HRESULT UnregisterEffectFactory(REFIID effectId);
};

Itt számos API-t kell figyelembe venni, mivel ezekre szükség van az összes olyan forgatókönyv támogatásához, ahol a Win2D-effektusok használhatók, valamint hogy a fejlesztők hogyan kommunikálhatnak a D2D-réteggel, majd megpróbálhatják feloldani a burkolókat. Tekintsük át ezeket az API-kat.

A RegisterWrapper és UnregisterWrapper metódusokat úgy tervezték, hogy az egyéni effektusok meghívhassák őket, és így hozzáadhassák magukat a belső Win2D gyorsítótárhoz.

  • RegisterWrapper: regisztrál egy natív erőforrást és annak tulajdonában lévő WinRT-burkolót. A wrapper paraméter szükséges a IWeakReferenceSourceis megvalósításához, hogy megfelelően gyorsítótárazható legyen anélkül, hogy referenciaciklusokat okozna, ami memóriaszivárgáshoz vezetne. A metódus S_OK-t ad vissza, ha a natív erőforrás hozzáadható a gyorsítótárhoz, S_FALSE-t, ha már van regisztrált burkoló a resourceszámára, és hiba esetén egy hibakódot ad vissza.
  • UnregisterWrapper: töröl egy natív erőforrást és annak burkolóját. Visszaadja S_OK , ha az erőforrás eltávolítható, S_FALSE ha resource még nincs regisztrálva, és egy erro-kódot, ha egy másik hiba történik.

Az egyéni effektusoknak mindig meg kell hívniuk a RegisterWrapper és UnregisterWrapper függvényt, amikor megvalósulnak, vagy megszűnnek, vagyis amikor egy új natív erőforrás jön létre és társul hozzájuk. Az egyéni effektusok, amelyek nem támogatják a megvalósítást (például azok, amelyek rögzített társított eszközzel rendelkeznek), meghívhatják a RegisterWrapper és UnregisterWrapper függvényeket, amikor azokat létrehozzák és megsemmisítik. Az egyéni effektusoknak gondoskodniuk kell arról, hogy megfelelően töröljék magukat az összes lehetséges kódútvonalról, ami miatt a burkoló érvénytelenné válik (például az objektum véglegesítésekor is, ha felügyelt nyelven implementálják).

A RegisterEffectFactory és UnregisterEffectFactory metódusok azt a célt szolgálják, hogy egyéni effektusok is használhassák őket, így visszahívási lehetőséget is regisztrálhatnak egy új burkoló létrehozására arra az esetre, ha egy fejlesztő megpróbál feloldani egyet egy "árva" D2D-erőforráshoz.

  • RegisterEffectFactory: visszahívás regisztrálása, amely ugyanazokat a paramétereket veszi át, amelyeket egy fejlesztő adott át GetOrCreate-nek, és új vizsgálható burkolót hoz létre a bemeneti effektushoz. Az effektusazonosító kulcsként van használva, így minden egyéni effektus regisztrálhat egy komponenst, amikor az először betöltődik. Ezt természetesen csak effektustípusonként egyszer szabad elvégezni, és nem minden alkalommal, amikor az effektus megvalósul. A device, resource és wrapper paramétereket a Win2D ellenőrzi, mielőtt bármilyen regisztrált visszahívást kezdeményezne, így garantáltan nem lesznek null-ek, amikor a CreateWrapper-t meghívják. Ez dpi nem kötelező, és figyelmen kívül hagyható abban az esetben, ha az effektustípusnak nincs konkrét felhasználási lehetősége. Vegye figyelembe, hogy ha egy regisztrált gyárból hoz létre új burkolót, az adott gyárnak arról is gondoskodnia kell, hogy az új burkoló regisztrálva legyen a gyorsítótárban (a Win2D nem adja hozzá automatikusan a külső gyárak által előállított burkolókat a gyorsítótárhoz).
  • UnregisterEffectFactory: eltávolít egy korábban regisztrált callback-et. Ez például akkor használható, ha egy effektusburkolót egy kiürített felügyelt szerelvényben implementálnak.

Megjegyzés:

ICanvasFactoryNative-t a CanvasDeviceaktiválási gyára valósítja meg, amelyet manuálisan hívhat meg a RoGetActivationFactoryhasználatával, vagy segéd-API-ket használhat az ön által használt nyelvi kiterjesztésekből (például winrt::get_activation_factory a C++/WinRT-ben). További információkért lásd: a WinRT típusrendszer, amely bemutatja, hogyan működik ez.

Gyakorlati példa arra, hogy hol jön létre ez a leképezés, fontolja meg a beépített Win2D-effektusok működését. Ha ezek nem valósulnak meg, minden állapot (például tulajdonságok, források stb.) egy belső gyorsítótárban lesz tárolva minden effektuspéldányban. Amikor megvalósul, az összes állapot átkerül a natív erőforrásba (például a tulajdonságok a D2D-effektuson vannak beállítva, minden forrás fel van oldva, és a hatás bemeneteiként vannak leképezve stb.), és amíg a hatás realizálódik, a burkoló állapotának autoritásaként fog működni. Vagyis ha bármelyik tulajdonság értékét lekéri a burkolóból, az a hozzá társított natív D2D-erőforrásból fogja lekérni a frissített értéket.

Ez biztosítja, hogy ha bármilyen módosítás közvetlenül a D2D-erőforráson történik, azok a külső burkolón is láthatók lesznek, és a kettő soha nem lesz "szinkronizálva". Ha az effektus nem realizálódik, a rendszer az erőforrás felszabadítása előtt az összes állapotot visszaküldi a natív erőforrásból a burkolóállapotba. A következő alkalomig, amikor a hatás érvénybe lép, megőrizve és frissítve lesz ott. Most fontolja meg az események sorozatát:

  • Van néhány Win2D-effektusod (beépített vagy egyéni).
  • A ID2D1Image onnan származik (ami egy ID2D1Effect).
  • Egy egyéni effektus példányát hozza létre.
  • Ebből is megkapod a ID2D1Image-t.
  • Ezt a képet manuálisan állíthatja be bemenetként az előző effektushoz (via ID2D1Effect::SetInput).
  • Ezután kérje meg ezt az első effektust a WinRT burkolójához a bemenethez.

Mivel a hatás megvalósult (a natív erőforrás kérésekor valósult meg), a natív erőforrást fogja használni az igazság forrásaként. Ennek megfelelően lekéri a kért forráshoz tartozó ID2D1Image-t, és megpróbálja lekérni hozzá a WinRT burkolót. Ha a bemenet lekérése eredményeként létrejött hatás helyesen hozzáadja az általa kezelt natív erőforrást és annak WinRT burkolóját a Win2D gyorsítótárhoz, akkor a burkoló feloldásra kerül, és a hívóknak vissza lesz adva. Ha nem, ez a tulajdonsághozzáférés sikertelen lesz, mivel a Win2D nem tudja feloldani a WinRT burkolóit a nem saját effektusok esetében, mivel nem tudja, hogyan kell példányosítani őket.

Itt RegisterWrapper és UnregisterWrapper segítenek, mivel lehetővé teszik, hogy az egyéni effektusok zökkenőmentesen részt vegyenek a Win2D burkoló feloldási logikájában, így a megfelelő burkoló mindig lekérhető bármilyen effektusforráshoz, függetlenül attól, hogy a WinRT API-król vagy közvetlenül a mögöttes D2D rétegről lett-e beállítva.

Az effektusgyárak hatásának magyarázatához tekintse meg ezt a forgatókönyvet:

  • A felhasználó létrehoz egy egyéni burkolópéldányt, és megvalósítja azt
  • Ezután egy hivatkozást kapnak a mögöttes D2D-effektusra, és megtartják azt.
  • Ezután a hatás egy másik eszközön valósul meg. Az effektus inaktiválódik, majd újra aktiválódik, és ezzel új D2D-effektust hoz létre. Az előző D2D-effektus már nem rendelkezik ellenőrizhető társított burkolóval.
  • A felhasználó ezután meghívja az első D2D-effektusban lévő GetOrCreate-t.

Visszahívás nélkül a Win2D egyszerűen nem old fel egy burkolót, mivel nincs regisztrált burkoló. Ha ehelyett egy gyárat regisztrálnak, akkor egy új csomagoló objektum hozható létre és adható vissza a D2D-effektushoz, így a felhasználó számára a folyamat zökkenőmentesen működik.

Az egyéni ICanvasEffect megvalósítása

A Win2D ICanvasEffect felület kiterjeszthető ICanvasImage, így az összes korábbi pont az egyéni effektusokra is vonatkozik. Az egyetlen különbség az a tény, hogy ICanvasEffect a hatásokra vonatkozó további módszereket is implementál, például egy forrás téglalap érvénytelenítését, a szükséges téglalapok beszerzését stb.

Ennek támogatásához a Win2D olyan C-exportálásokat tesz elérhetővé, amelyeket az egyéni effektusok készítői használhatnak, így nem kell teljesen új logikát alkalmazniuk. Ez ugyanúgy működik, mint GetBoundsC exportálása. Íme az elérhető lehetőségek exportálásra az effektusokhoz:

HRESULT InvalidateSourceRectangleForICanvasImageInterop(
    ICanvasResourceCreatorWithDpi* resourceCreator,
    ICanvasImageInterop* image,
    uint32_t sourceIndex,
    Rect const* invalidRectangle);

HRESULT GetInvalidRectanglesForICanvasImageInterop(
    ICanvasResourceCreatorWithDpi* resourceCreator,
    ICanvasImageInterop* image,
    uint32_t* valueCount,
    Rect** valueElements);

HRESULT GetRequiredSourceRectanglesForICanvasImageInterop(
    ICanvasResourceCreatorWithDpi* resourceCreator,
    ICanvasImageInterop* image,
    Rect const* outputRectangle,
    uint32_t sourceEffectCount,
    ICanvasEffect* const* sourceEffects,
    uint32_t sourceIndexCount,
    uint32_t const* sourceIndices,
    uint32_t sourceBoundsCount,
    Rect const* sourceBounds,
    uint32_t valueCount,
    Rect* valueElements);

Nézzük meg, hogyan használhatók:

  • InvalidateSourceRectangleForICanvasImageInterop célja, hogy támogassa InvalidateSourceRectangle. Egyszerűen helyezze el a bemeneti paramétereket, és hívja meg közvetlenül, és gondoskodik az összes szükséges munkáról. Vegye figyelembe, hogy a image paraméter az aktuálisan implementált effektuspéldány.
  • GetInvalidRectanglesForICanvasImageInterop támogatja GetInvalidRectangles a. Ez nem igényel különösebb megfontolást, kivéve, ha a visszaadott COM-tömböt el kell helyezni, ha már nincs rá szükség.
  • GetRequiredSourceRectanglesForICanvasImageInterop egy megosztott módszer, amely támogatja mind GetRequiredSourceRectangle a GetRequiredSourceRectangles. Ez azt jelenti, hogy egy már létező, kitöltendő értéktömbre kap egy mutatót, így a hívók vagy egyetlen értékre (amely a veremben is lehet, egy foglalás elkerülése érdekében), vagy egy értéktömbre adhatnak át mutatót. A megvalósítás mindkét esetben megegyezik, így egyetlen C-exportálás elegendő mindkettő végrehajtásához.

Egyéni effektusok a C#-ban a ComputeSharp használatával

Ahogy már említettük, ha C#-ot használ, és egyéni effektust szeretne implementálni, az ajánlott módszer a ComputeSharp-kódtár használata. Lehetővé teszi az egyéni D2D1 képpontárnyékolók teljes C#-ban való implementálását, valamint a Win2D-vel kompatibilis egyéni effektusdiagramok egyszerű meghatározását. Ugyanez a kódtár a Microsoft Store-ban is használható az alkalmazás több grafikus összetevőjének energiaellátásához.

A Project ComputeSharpre mutató hivatkozást a NuGet használatával adhat hozzá: Válassza ki a ComputeSharp.D2D1.WinUI csomagot.

Megjegyzés:

Számos API a ComputeSharp.D2D1.* nevű könyvtárban azonos az UWP- és a WinUI-célok között, az egyetlen különbség a névtérben található végződés (ami vagy .Uwp vagy .WinUI). Az UWP-cél azonban tartós karbantartás alatt áll, és nem kap új funkciókat. Ezért a WinUI-hoz itt bemutatott mintákhoz képest szükség lehet néhány kódmódosításra. A dokumentum kódrészletei a ComputeSharp.D2D1.WinUI.0.0 API-felületet tükrözik (az UWP-cél utolsó kiadása 2.1.0).

A ComputeSharp két fő összetevőt tartalmaz a Win2D-vel való együttműködéshez:

  • PixelShaderEffect<T>: egy D2D1 képpontos árnyékoló által működtetett Win2D-effektus. Maga a shader C# nyelven íródott a ComputeSharp által biztosított API-k használatával. Ez az osztály tulajdonságokat is biztosít az effektusforrások, az állandó értékek és egyebek beállításához.
  • CanvasEffect: egy tetszőleges effektusdiagramot burkoló egyéni Win2D-effektusok alaposztálya. Használható összetett effektusok "csomagolására" egy könnyen használható objektumba, amely az alkalmazás több részében újra felhasználható.

Íme egy példa egy egyéni pixelárnyékolóra (ebből a shadertoy árnyékolóból), amelyet a PixelShaderEffect<T> használ, majd Win2D-CanvasControl-ra rajzol (vegye figyelembe, hogy PixelShaderEffect<T> megvalósítja a ICanvasImage).

Egy minta képpontárnyékoló, amely végtelen színes hatszögeket jelenít meg, egy Win2D-vezérlőre van rajzolva, és egy alkalmazásablakban fut.

Láthatja, hogy mindössze két sornyi kódban hogyan hozhat létre effektust, és rajzolhatja meg a Win2D-en keresztül. A ComputeSharp gondoskodik az árnyékoló fordításához, regisztrálásához és a Win2D-kompatibilis effektusok összetett élettartamának kezeléséhez szükséges összes munkáról.

Ezután lépésről lépésre mutatjuk be, hogyan hozhat létre egyéni Win2D-effektust, amely szintén egyéni D2D1 képpontárnyékolót használ. Bemutatjuk, hogyan készíthet árnyékolót a ComputeSharp használatával, és hogyan állíthatja be a tulajdonságait, majd hogyan hozhat létre egyéni effektusdiagramot egy CanvasEffect olyan típusba csomagolva, amely könnyen újra felhasználható az alkalmazásban.

Az effektus megtervezése

Ehhez a bemutatóhoz egy egyszerű fagyos üvegeffektust szeretnénk létrehozni.

Ez a következő összetevőket foglalja magában:

  • Gauss-elmosódás
  • Színező hatás
  • Zaj (amelyet egy árnyékolóval eljárási módon tudunk generálni)

A tulajdonságokat is elérhetővé szeretnénk tenni az elmosódás és a zaj mennyiségének szabályozásához. A végső effektus az effektusdiagram "csomagolt" verzióját fogja tartalmazni, és egyszerűen használható egy példány létrehozásával, a tulajdonságok beállításával, egy forráslemezkép csatlakoztatásával és rajzolásával. Lássunk hozzá!

Egyéni D2D1 képpontárnyékoló létrehozása

Az effektuson felüli zajhoz használhatunk egy egyszerű D2D1 képpontos árnyékolót. A shader a koordinátái alapján kiszámít egy véletlenszerű értéket (amely a véletlenszerű szám "magjaként" fog működni), majd ezt a zajértéket fogja használni az adott képpont RGB-mennyiségének kiszámításához. Ezután ezt a zajt az eredményül kapott kép fölé vegyíthetjük.

Ahhoz, hogy a ComputeSharp segítségével megírhassuk az árnyékolót, meg kell határoznunk egy partial struct , az ID2D1PixelShader interfészt implementáló típust, majd meg kell írnunk a logikát a Execute metódusban. Ehhez a zajárnyékolóhoz a következőhöz hasonlót írhatunk:

using ComputeSharp;
using ComputeSharp.D2D1;

[D2DInputCount(0)]
[D2DRequiresScenePosition]
[D2DShaderProfile(D2D1ShaderProfile.PixelShader40)]
[D2DGeneratedPixelShaderDescriptor]
public readonly partial struct NoiseShader(float amount) : ID2D1PixelShader
{
    /// <inheritdoc/>
    public float4 Execute()
    {
        // Get the current pixel coordinate (in pixels)
        int2 position = (int2)D2D.GetScenePosition().XY;

        // Compute a random value in the [0, 1] range for each target pixel. This line just
        // calculates a hash from the current position and maps it into the [0, 1] range.
        // This effectively provides a "random looking" value for each pixel.
        float hash = Hlsl.Frac(Hlsl.Sin(Hlsl.Dot(position, new float2(41, 289))) * 45758.5453f);

        // Map the random value in the [0, amount] range, to control the strength of the noise
        float alpha = Hlsl.Lerp(0, amount, hash);

        // Return a white pixel with the random value modulating the opacity
        return new(1, 1, 1, alpha);
    }
}

Megjegyzés:

Bár a shader írása teljes egészében C#-ban történik, a HLSL alapszintű ismerete (a DirectX-árnyékolók programozási nyelve, amelyre a ComputeSharp lefordítja a C#-ot) ajánlott.

Tekintsük át részletesen ezt az árnyékolót:

  • Az árnyékoló nem rendelkezik bemenetekkel, csak egy végtelen képet hoz létre véletlenszerű szürkeárnyalatos zajjal.
  • Az árnyékolónak hozzá kell férnie az aktuális képpontkoordinátához.
  • A shader előre le van állítva a buildeléskor (a PixelShader40 profil használatával, amely garantáltan elérhető lesz bármely GPU-n, ahol az alkalmazás futhat).
  • Az [D2DGeneratedPixelShaderDescriptor] attribútum szükséges a ComputeSharp-hez csomagolt forrásgenerátor aktiválásához, amely elemzi a C#-kódot, átalakítja a HLSL-be, lefordítja a shadert bájtkódra stb.
  • A shader egy paramétert float amount rögzít az elsődleges konstruktoron keresztül. A ComputeSharp forrásgenerátora automatikusan gondoskodik az összes rögzített érték kinyeréséről egy árnyékolóban, és előkészíti a D2D által az árnyékoló állapotának inicializálásához szükséges állandó puffert.

És ez a rész kész! Ez az árnyékoló szükség esetén létrehozza az egyéni zajmintázatunkat. Ezután létre kell hoznunk a csomagolt effektust az összes effektust összekapcsoló effektusdiagrammal.

Egyéni effektus létrehozása

A könnyen használható, csomagolt effektushoz használhatjuk a CanvasEffect ComputeSharp típusát. Ez a típus egyszerű módot kínál az effektusdiagram létrehozásához és nyilvános tulajdonságokon keresztüli frissítéséhez szükséges összes logika beállításához, amelyet az effektus felhasználói használhatnak. Két fő módszert kell implementálnunk:

  • BuildEffectGraph: ez a módszer felel a rajzolni kívánt effektusdiagram elkészítéséért. Vagyis létre kell hoznia minden szükséges effektust, és regisztrálnia kell a kimeneti csomópontot a gráfhoz. A később frissíthető effektusok esetében a regisztráció egy társított CanvasEffectNode<T> értékkel történik, amely keresési kulcsként szolgál az effektusok szükség szerinti lekéréséhez a gráfból.
  • ConfigureEffectGraph: ez a módszer a felhasználó által konfigurált beállítások alkalmazásával frissíti az effektusdiagramot. A rendszer szükség esetén automatikusan meghívja ezt a metódust közvetlenül az effektus rajzolása előtt, és csak akkor, ha az effektus legutóbbi használata óta legalább egy effektustulajdonság módosult.

Egyéni effektusunk a következőképpen határozható meg:

using ComputeSharp.D2D1.WinUI;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;

public sealed class FrostedGlassEffect : CanvasEffect
{
    private static readonly CanvasEffectNode<GaussianBlurEffect> BlurNode = new();
    private static readonly CanvasEffectNode<PixelShaderEffect<NoiseShader>> NoiseNode = new();

    private ICanvasImage? _source;
    private double _blurAmount;
    private double _noiseAmount;

    public ICanvasImage? Source
    {
        get => _source;
        set => SetAndInvalidateEffectGraph(ref _source, value);
    }

    public double BlurAmount
    {
        get => _blurAmount;
        set => SetAndInvalidateEffectGraph(ref _blurAmount, value);
    }

    public double NoiseAmount
    {
        get => _noiseAmount;
        set => SetAndInvalidateEffectGraph(ref _noiseAmount, value);
    }

    /// <inheritdoc/>
    protected override void BuildEffectGraph(CanvasEffectGraph effectGraph)
    {
        // Create the effect graph as follows:
        //
        // ┌────────┐   ┌──────┐
        // │ source ├──►│ blur ├─────┐
        // └────────┘   └──────┘     ▼
        //                       ┌───────┐   ┌────────┐
        //                       │ blend ├──►│ output │
        //                       └───────┘   └────────┘
        //    ┌───────┐              ▲   
        //    │ noise ├──────────────┘
        //    └───────┘
        //
        GaussianBlurEffect gaussianBlurEffect = new();
        BlendEffect blendEffect = new() { Mode = BlendEffectMode.Overlay };
        PixelShaderEffect<NoiseShader> noiseEffect = new();
        PremultiplyEffect premultiplyEffect = new();

        // Connect the effect graph
        premultiplyEffect.Source = noiseEffect;
        blendEffect.Background = gaussianBlurEffect;
        blendEffect.Foreground = premultiplyEffect;

        // Register all effects. For those that need to be referenced later (ie. the ones with
        // properties that can change), we use a node as a key, so we can perform lookup on
        // them later. For others, we register them anonymously. This allows the effect
        // to autommatically and correctly handle disposal for all effects in the graph.
        effectGraph.RegisterNode(BlurNode, gaussianBlurEffect);
        effectGraph.RegisterNode(NoiseNode, noiseEffect);
        effectGraph.RegisterNode(premultiplyEffect);
        effectGraph.RegisterOutputNode(blendEffect);
    }

    /// <inheritdoc/>
    protected override void ConfigureEffectGraph(CanvasEffectGraph effectGraph)
    {
        // Set the effect source
        effectGraph.GetNode(BlurNode).Source = Source;

        // Configure the blur amount
        effectGraph.GetNode(BlurNode).BlurAmount = (float)BlurAmount;

        // Set the constant buffer of the shader
        effectGraph.GetNode(NoiseNode).ConstantBuffer = new NoiseShader((float)NoiseAmount);
    }
}

Ebben az osztályban négy rész található.

  • Először is vannak olyan mezők, amelyek nyomon követik az összes mutable állapotot, például a frissíthető effektusokat, valamint az összes olyan effektustulajdonság háttérmezőit, amelyeket elérhetővé szeretnénk tenni az effektus felhasználói számára.
  • A következő lépésben az effektus konfigurálásához tulajdonságokkal rendelkezünk. Az egyes tulajdonságok beállítója a SetAndInvalidateEffectGrapháltal közzétett CanvasEffect metódust használja, amely automatikusan érvényteleníti a hatást, ha a beállított érték eltér a jelenlegitől. Ez biztosítja, hogy az effektus csak akkor legyen újra konfigurálva, ha valóban szükséges.
  • Végül pedig rendelkezünk a fent említett BuildEffectGraph és ConfigureEffectGraph módszerekkel.

Megjegyzés:

A zajhatás utáni PremultiplyEffect csomópont nagyon fontos: ennek az az oka, hogy a Win2D-effektusok feltételezik, hogy a kimenet előre összeszorzott, míg a képpontárnyékolók általában nem előre összeszorzott képpontokkal működnek. Ezért ne felejtse el manuálisan hozzáadni a premultiply/unpremultiply csomópontokat az egyedi árnyékolók elé és mögé, hogy a színek megfelelően megmaradjanak.

Készen áll a rajzolásra!

És ezzel az egyéni fagyos üveg effektusunk készen áll! Könnyen megrajzolhatjuk az alábbiak szerint:

private void CanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
    FrostedGlassEffect effect = new()
    {
        Source = _canvasBitmap,
        BlurAmount = 12,
        NoiseAmount = 0.1
    };

    args.DrawingSession.DrawImage(effect);
}

Ebben a példában az Draw kezelőből származó hatást visszük át egy CanvasControl-re, egy CanvasBitmap segítségével, amelyet korábban forrásként betöltöttünk. Ezt a bemeneti képet fogjuk használni az effektus teszteléséhez:

néhány hegy képe felhős ég alatt

És itt van az eredmény:

a fenti kép elmosódott verzióját

Megjegyzés:

Köszönet Dominic Lange-nak a képért.

További erőforrások