Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Win2D poskytuje několik rozhraní API, která představují objekty, které lze nakreslit, které jsou rozdělené do dvou kategorií: obrázky a efekty. Obrázky reprezentované rozhraním ICanvasImage nemají žádné vstupy a lze je přímo nakreslit na daném povrchu. Například CanvasBitmap, VirtualizedCanvasBitmap a CanvasRenderTarget jsou příklady typů obrázků. Na druhou stranu, efekty jsou reprezentovány ICanvasEffect rozhraním. Můžou mít vstupy i další prostředky a můžou použít libovolnou logiku k vytvoření výstupů (jako efekt je také obrázek). Win2D zahrnuje efekty obtékání většiny D2D efektů, například GaussianBlurEffect, TintEffect a LuminanceToAlphaEffect.
Obrázky a efekty je také možné zřetězovat dohromady a vytvářet libovolné grafy, které se pak dají zobrazit ve vaší aplikaci (viz také dokumenty D2D o efektech Direct2D). Společně poskytují extrémně flexibilní systém pro efektivní vytváření složitých grafik. Existují však případy, kdy předdefinované efekty nestačí a můžete chtít vytvořit vlastní efekt Win2D. Pro tuto podporu win2D obsahuje sadu výkonných rozhraní API pro interoperabilitu, která umožňuje definovat vlastní image a efekty, které se můžou bezproblémově integrovat s Win2D.
Návod
Pokud používáte jazyk C# a chcete implementovat vlastní efekt nebo graf efektů, doporučuje se místo implementace efektu úplně od začátku použít ComputeSharp . Podrobné vysvětlení použití této knihovny k implementaci vlastních efektů, které se bezproblémově integrují s Win2D, najdete v níže uvedeném odstavci.
Rozhraní API Platformy:
ICanvasImage,CanvasBitmap,VirtualizedCanvasBitmap,CanvasRenderTarget,CanvasEffect,GaussianBlurEffect,TintEffect,ICanvasLuminanceToAlphaEffectImage,IGraphicsEffectSource,ID2D21Image,ID2D1Factory1,ID2D1Effect
Implementace vlastního ICanvasImage
Nejjednodušší scénář, který lze podpořit, je vytvoření vlastního ICanvasImage. Jak jsme zmínili, jedná se o rozhraní WinRT definované win2D, které představuje všechny druhy obrázků, se kterými může Win2D spolupracovat. Toto rozhraní zveřejňuje pouze dvě GetBounds metody a rozšiřuje IGraphicsEffectSource, což je rozhraní značek představující "nějaký zdroj efektu".
Jak vidíte, nejsou přes toto rozhraní vystavena žádná "funkční" API pro skutečné provedení jakéhokoli kreslení. Abyste mohli implementovat vlastní ICanvasImage objekt, musíte také implementovat ICanvasImageInterop rozhraní, které zveřejňuje veškerou potřebnou logiku pro Win2D k vykreslení obrázku. Toto je rozhraní COM definované ve veřejné hlavičce Microsoft.Graphics.Canvas.native.h, které přichází s Win2D.
Rozhraní je definováno takto:
[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);
}
A také spoléhá na tyto dva typy výčtu ze stejné hlavičky:
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
}
Dvě GetDevice a GetD2DImage metody jsou vše potřebné k implementaci vlastních obrazů (nebo efektů), protože Win2D poskytují body rozšiřitelnosti, které je inicializují na daném zařízení a získají podkladovou image D2D, aby mohly být nakresleny. Správné implementace těchto metod je zásadní, aby se zajistilo správné fungování ve všech podporovaných scénářích.
Pojďme se na ně podívat, jak jednotlivé metody fungují.
Implementace GetDevice
Metoda GetDevice je nejjednodušší ze dvou. Tím načte zařízení plátna přidružené k efektu, aby ho Win2D mohl v případě potřeby zkontrolovat (například zajistit, aby odpovídalo používanému zařízení). Parametr type označuje "typ přidružení" pro vrácené zařízení.
Existují dva hlavní možné případy:
- Pokud je obrázek efektem, měl by umožňovat "realizaci" a "nerealizaci" na více zařízeních. To znamená: daný efekt se vytvoří v neinicializovaném stavu, pak ho lze realizovat, když je při kreslení předáno zařízení, a poté ho můžete dále používat s tímto zařízením, nebo ho můžete přenést do jiného zařízení. V takovém případě efekt obnoví svůj vnitřní stav a pak se znovu aktivuje na novém zařízení. To znamená, že přidružené zařízení plátna se může v průběhu času měnit a může být také
null. Z tohoto důvodu by mělo býttypenastaveno naWIN2D_GET_DEVICE_ASSOCIATION_TYPE_REALIZATION_DEVICEhodnotu a vrácené zařízení by mělo být nastaveno jako aktuální realizační zařízení, pokud je k dispozici. - Některé image mají jedno "vlastnící zařízení", které je přiřazeno při vytváření a nikdy se nezmění. To by například byl případ obrázku představujícího texturu, který je přidělen na konkrétním zařízení a nelze ho přesunout. Při zavolání
GetDeviceby mělo vrátit zařízení pro vytvoření a nastavittypenaWIN2D_GET_DEVICE_ASSOCIATION_TYPE_CREATION_DEVICE. Všimněte si, že pokud je tento typ zadán, vrácené zařízení by nemělo býtnull.
Poznámka:
Win2D může volat GetDevice při rekurzivním procházení grafu efektu, což znamená, že v zásobníku může být více aktivních volání GetD2DImage. Z tohoto důvodu by GetDevice nemělo uzamknout blokující zámek na aktuálním obrázku, protože by mohlo dojít k zablokování. Místo toho by měl použít reentrantní zámek neblokujícím způsobem a vrátit chybu, pokud ho nelze získat. Tím se zajistí, že stejné vlákno, které ji rekurzivně volá, ji úspěšně získá, zatímco souběžná vlákna, která dělají totéž, selžou s elegancí.
Implementace GetD2DImage
GetD2DImage je místo, kde probíhá většina práce. Tato metoda je zodpovědná za načtení objektu ID2D1Image, který může Win2D vykreslit, a případně realizuje aktuální efekt, pokud je to nutné. To také zahrnuje rekurzivní procházení a realizaci grafu efektu pro všechny zdroje, pokud nějaké existují, a také inicializaci jakéhokoli stavu, který může obrázek potřebovat (např. konstantní buffery a další vlastnosti, textury zdrojů atd.).
Přesná implementace této metody je vysoce závislá na typu image a může se hodně lišit, ale obecně platí, že pro libovolný efekt můžete očekávat, že metoda provede následující kroky:
- Zkontrolujte, jestli bylo volání rekurzivní ve stejné instanci, a pokud ano, selže. To je potřeba k detekci cyklů v grafu efektu (např. efekt
Amá účinekBjako zdroj a účinekBmá účinekAjako zdroj). - Získejte zámek instance image pro ochranu před souběžným přístupem.
- Zpracujte cílové DPI podle vstupních příznaků
- Ověřte, jestli vstupní zařízení odpovídá používanému zařízení. Pokud se to neshoduje a aktuální efekt podporuje realizaci, zrušte efekt.
- Uvědomte si účinek na vstupní zařízení. To může v případě potřeby zahrnovat registraci efektu D2D na
ID2D1Factory1objekt načtený ze vstupního zařízení nebo kontextu zařízení. Kromě toho by měly být všechny nezbytné stavy nastaveny na instanci efektu D2D, která se vytváří. - Rekurzivně prochází jakékoli zdroje a přiřazuje je k efektu D2D.
Pokud jde o vstupní příznaky, existuje několik možných případů, které by měly být vlastními efekty správně zpracovány, aby byla zajištěna kompatibilita se všemi ostatními efekty Win2D. Kromě WIN2D_GET_D2D_IMAGE_FLAGS_NONEjsou příznaky, které se mají zpracovat, následující:
-
WIN2D_GET_D2D_IMAGE_FLAGS_READ_DPI_FROM_DEVICE_CONTEXT: v tomto případě je zaručeno, žedevicenebudenull. Účinek by měl zkontrolovat, jestli je cílem kontextu zařízeníID2D1CommandList, a pokud ano, měl by přidat příznakWIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION. Jinak by měl nastavittargetDpi(což je také zaručeno, že nebudenull) na DPI hodnoty načtené ze vstupního kontextu. Mělo by odebratWIN2D_GET_D2D_IMAGE_FLAGS_READ_DPI_FROM_DEVICE_CONTEXTz příznaků. -
WIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATIONaWIN2D_GET_D2D_IMAGE_FLAGS_NEVER_INSERT_DPI_COMPENSATION: používá se při nastavování zdrojů efektů (viz poznámky níže). -
WIN2D_GET_D2D_IMAGE_FLAGS_MINIMAL_REALIZATION: Pokud je nastaveno, přeskočí rekurzivně realizované zdroje efektu a pouze vrátí realizované efekt bez dalších změn. -
WIN2D_GET_D2D_IMAGE_FLAGS_ALLOW_NULL_EFFECT_INPUTS: Pokud je nastavena, je možné, že zdroje efektu, které jsou realizovány, mohou býtnull, pokud je uživatel ještě nenastavil na existující zdroj. -
WIN2D_GET_D2D_IMAGE_FLAGS_UNREALIZE_ON_FAILURE: Pokud je nastavena a zdroj efektu, který má být nastaven, není platný, měl by se efekt zrušit, než dojde k selhání. To znamená, že pokud došlo k chybě při řešení zdrojů efektu po realizaci efektu, měl by se efekt před vrácením chyby volajícímu zrušit.
Pokud jde o příznaky související s DPI, tyto řídí způsob nastavení zdrojů efektů. Aby se zajistila kompatibilita s Win2D, měly by efekty automaticky přidat efekty kompenzace DPI do jejich vstupů v případě potřeby. Oni mohou určit, zda to tak je, následovně:
- Pokud je nastavena
WIN2D_GET_D2D_IMAGE_FLAGS_MINIMAL_REALIZATION, je zapotřebí efekt kompenzace DPI, kdykoli se parametrinputDpinerovná0. - V opačném případě je potřeba kompenzace DPI, pokud
inputDpinení0,WIN2D_GET_D2D_IMAGE_FLAGS_NEVER_INSERT_DPI_COMPENSATIONnení nastavena aWIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATIONje nastavena, nebo vstupní DPI a cílové hodnoty DPI se neshoduje.
Tato logika by se měla použít vždy, když je zdroj realizován a vázán na vstup aktuálního efektu. Všimněte si, že pokud se přidá efekt kompenzace DPI, měl by to být vstup nastavený na podkladový obrázek D2D. Pokud se ale uživatel pokusí načíst obálku WinRT pro tento zdroj, měl by se efekt postarat o zjištění, zda byl použit efekt DPI, a vrátit obálku pro původní zdrojový objekt. To znamená, že efekty kompenzace DPI by měly být pro uživatele efektu transparentní.
Po dokončení veškeré logiky inicializace by měl být výsledný ID2D1Image (podobně jako u objektů Win2D je i efekt D2D také obrázek) připraven k vykreslení pomocí Win2D v cílovém kontextu, který zatím není znám vyvolávačem.
Poznámka:
Správná implementace této metody (a ICanvasImageInterop obecně) je extrémně složitá a je určená pouze pokročilými uživateli, kteří naprosto potřebují větší flexibilitu. Než se pokusíte implementovat ICanvasImageInterop, doporučuje se mít pevné porozumění D2D, Win2D, COM, WinRT a C++. Pokud váš vlastní efekt Win2D také musí zabalit vlastní efekt D2D, budete muset implementovat i vlastní ID2D1Effect objekt (další informace najdete v dokumentaci D2D k vlastním efektům). Tyto dokumenty nejsou vyčerpávajícím popisem veškeré nezbytné logiky (například nepokrývají, jak se mají zdroje efektů zařaďovat a spravovat napříč hranicí D2D/Win2D), proto se doporučuje použít CanvasEffect také implementaci v základu kódu Win2D jako referenční bod pro vlastní efekt a podle potřeby ho upravit.
Implementace GetBounds
Poslední chybějící komponentou pro plnou implementaci vlastního efektu ICanvasImage je potřeba podporovat dvě přetížení GetBounds. Aby bylo možné tento postup usnadnit, Win2D zpřístupňuje export jazyka C, který lze použít k využití existující logiky z Win2D na libovolném vlastním obrazu. Export je následující:
HRESULT GetBoundsForICanvasImageInterop(
ICanvasResourceCreator* resourceCreator,
ICanvasImageInterop* image,
Numerics::Matrix3x2 const* transform,
Rect* rect);
Vlastní obrazy mohou toto rozhraní API vyvolat a předat samy sebe jako parametr image, a poté jednoduše vrátit výsledek volajícím. Parametr transform může být null, pokud není k dispozici žádná transformace.
Optimalizace přístupu k kontextu zařízení
Parametr deviceContext in ICanvasImageInterop::GetD2DImage může být nullněkdy , pokud kontext není bezprostředně k dispozici před vyvoláním. To se provádí záměrně, takže kontext se vytvoří jen laziálně, když je skutečně potřeba. To znamená, že pokud je kontext k dispozici, Win2D ho předá GetD2DImage volání, jinak umožní volaným načíst kontext samostatně, pokud to bude nutné.
Vytvoření kontextu zařízení je poměrně nákladné, takže Win2D zpřístupňuje rozhraní API pro rychlejší načítání kontextu zařízení pomocí svého interního fondu kontextů zařízení. To umožňuje vlastním efektům efektivně pronajmout a vrátit kontexty zařízení spojené s konkrétním zařízením plátna.
Rozhraní API pro pronájem kontextu zařízení jsou definována takto:
[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);
}
Rozhraní ID2D1DeviceContextPool je implementováno CanvasDevice, což je typ Win2D, který implementuje rozhraní ICanvasDevice. Pokud chcete použít pool, na rozhraní zařízení použijte QueryInterface k získání odkazu ID2D1DeviceContextPool, a poté zavolejte ID2D1DeviceContextPool::GetDeviceContextLease pro získání objektu ID2D1DeviceContextLease, abyste měli přístup ke kontextu zařízení. Jakmile už to nebude potřeba, uvolněte zapůjčení. Po uvolnění pronájmu se ujistěte, že se nedotknete kontextu zařízení, protože ho mohou souběžně používat další vlákna.
Povolení vyhledávání obálky WinRT
Jak je vidět v dokumentaci interoperability Win2D, veřejný záhlaví Win2D také zpřístupňuje metodu GetOrCreate (přístupná z aktivační továrny ICanvasFactoryNative nebo pomocí pomocníků C++/CX GetOrCreate definovaných ve stejném záhlaví). Umožňuje to načtení zabalení WinRT z daného nativního prostředku. Umožňuje například načíst nebo vytvořit instanci CanvasDevice z objektu ID2D1Device1, CanvasBitmap z ID2D1Bitmap a tak dále.
Tato metoda také funguje pro všechny vestavěné efekty Win2D: načtení nativního prostředku pro daný efekt a jeho následné použití k získání odpovídající obálky Win2D správně vrátí efekt Win2D, který ho vlastní. Aby vlastní efekty mohly také využívat stejný systém mapování, Win2D poskytuje několik rozhraní API v rozhraní pro spolupráci pro aktivační továrnu pro CanvasDevice, což je typ ICanvasFactoryNative, a také další rozhraní továrny efektů 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);
};
Existuje několik rozhraní API, které je třeba zvážit, protože jsou potřeba k podpoře všech různých scénářů, ve kterých se dají použít efekty Win2D, a také k tomu, jak by vývojáři mohli provádět interoperaci s vrstvou D2D a následně se pokoušet řešit jejich obálky. Pojďme si projít všechna tato rozhraní API.
Metody RegisterWrapper a UnregisterWrapper mají být vyvolány vlastními efekty, aby se přidaly do interní mezipaměti Win2D:
-
RegisterWrapper: registruje nativní zdroj a jeho WinRT obal. Parametrwrapperje nezbytný pro implementaciIWeakReferenceSource, aby mohl být správně uložen do mezipaměti, aniž by způsoboval referenční cykly, které by mohly vést k únikům paměti. Metoda vrátíS_OK, pokud nativní prostředek může být přidán do mezipaměti,S_FALSE, pokud již existuje registrovaná obálka proresource, a kód chyby, pokud nastane chyba. -
UnregisterWrapper: Zruší registraci nativního zdroje a jeho wrapperu. VrátíS_OK, pokud je možné prostředek odebrat,S_FALSEpokudresourceještě nebyl zaregistrovaný, a kód erro, pokud dojde k jiné chybě.
Uživatelské efekty by měly volat RegisterWrapper a UnregisterWrapper vždy, když jsou aktualizovány a deaktivovány, tj. když se vytvoří nový nativní prostředek a je k nim přidružen. Vlastní efekty, které nepodporují provedení (např. efekty s pevným přidruženým zařízením), mohou volat RegisterWrapper a UnregisterWrapper, když jsou vytvořeny a zničeny. Vlastní efekty by se měly postarat, aby se správně odregistrovaly ze všech možných cest v kódu, které by způsobily, že obálka bude neplatná (např. když je objekt finalizován, pokud je implementován ve spravovaném jazyce).
Metody RegisterEffectFactory a UnregisterEffectFactory jsou také určeny k použití vlastními efekty, aby mohly zaregistrovat zpětné volání a vytvořit novou obálku v případě, že se vývojář pokusí vyřešit jednu pro "opuštěný" prostředek D2D.
-
RegisterEffectFactory: zaregistrujte zpětné volání, které přebírá jako vstup stejné parametry, jaké vývojář předalGetOrCreate, a vytvoří nový kontrolovatelný obal pro vstupní efekt. ID efektu se používá jako klíč, takže každý vlastní efekt pro něj může zaregistrovat továrnu při prvním načtení. Samozřejmě, to by mělo být provedeno pouze jednou na typ efektu, a ne pokaždé, když je účinek dosažen. Parametrydevice,resourceawrapperjsou před vyvoláním registrovaného zpětného volání kontrolovány Win2D, takže je zaručeno, že při vyvolánínullnebudouCreateWrapper. Považuje sedpiza nepovinný a dá se ignorovat v případě, že pro něj typ efektu nemá konkrétní použití. Všimněte si, že pokud je vytvořena nová obálka z registrované továrny, měla by tato továrna také zajistit, aby nová obálka byla uložena do mezipaměti (Win2D automaticky nepřidává obálky vytvořené externími továrnami do mezipaměti). -
UnregisterEffectFactory: odebere dříve registrované zpětné volání. Tato možnost se může použít například v případě, že je zabalovač efektu implementován ve spravovaném sestavení, které je vykládáno.
Poznámka:
ICanvasFactoryNative je implementována fabrikou aktivace pro CanvasDevice, kterou lze načíst buď ručním voláním RoGetActivationFactory, nebo pomocí API nástrojů z jazykových rozšiřujících balíčků, které používáte (např. winrt::get_activation_factory v jazyce C++/WinRT). Další informace o tom, jak to funguje, najdete v tématu systému typů WinRT.
V praktickém příkladu, kde toto mapování přichází do hry, zvažte, jak fungují integrované efekty Win2D. Pokud nejsou realizovány, všechny stavy (např. vlastnosti, zdroje atd.) jsou uloženy v interní mezipaměti v každé instanci efektu. Když jsou realizovány, veškerý stav se přenese do nativního zdroje (např. vlastnosti jsou nastaveny na efekt D2D, všechny zdroje jsou vyřešeny a namapovány na vstupy efektu atd.) a dokud je efekt realizován, bude sloužit jako autorita pro stav obálky. To znamená, že pokud je hodnota jakékoli vlastnosti načtena z obalu, vytáhne aktualizovanou hodnotu pro ni z nativního prostředku D2D přidruženého k ní.
Tím se zajistí, že pokud dojde k nějakým změnám přímo u prostředku D2D, budou viditelné i na vnější obálce a tyto dva prvky nikdy nebudou nesynchronizované. Pokud k efektu nedojde, veškerý stav se před uvolněním prostředku přenese zpět z nativního prostředku do stavu obalu. Bude zachována a aktualizována tam až do příštího dosažení efektu. Teď zvažte tuto posloupnost událostí:
- Máte nějaký efekt Win2D (integrovaný nebo vlastní).
- Získáte z něj
ID2D1Image, což jeID2D1Effect. - Vytvoříte instanci vlastního efektu.
- Získáte z toho také
ID2D1Image. - Tento obrázek můžete ručně nastavit jako vstup pro předchozí efekt (prostřednictvím
ID2D1Effect::SetInput). - Pak požádáte o první efekt pro obálku WinRT pro daný vstup.
Vzhledem k tomu, že je účinek realizován (byl dosažen při vyžádání nativního prostředku), použije nativní prostředek jako zdroj pravdy. Proto získá ID2D1Image odpovídající požadovanému zdroji a pokusí se pro něj načíst obálku WinRT. Pokud byl tento vstup získán ze správně přidané dvojice nativního prostředku a obálky WinRT do mezipaměti Win2D, obálka bude přeložena a vrácena volajícím. Pokud ne, přístup k této vlastnosti selže, protože Win2D nedokáže vyřešit obálky WinRT pro efekty, které nevlastní, jelikož neví, jak vytvořit jejich instance.
Tady pomáhají RegisterWrapper a UnregisterWrapper, protože umožňují bezproblémové zapojení vlastních efektů do logiky překladu obalu Win2D, takže správný obal se dá vždy načíst pro libovolný zdroj efektů, bez ohledu na to, zda byl nastaven rozhraním API WinRT, nebo přímo ze základní vrstvy D2D.
Pokud chcete vysvětlit, jak se do hry dostanou také továrny efektu, zvažte tento scénář:
- Uživatel vytvoří instanci vlastní obálky a provede její realizaci.
- Potom získají referenci na podkladový efekt D2D a zachovají ji.
- Pak se efekt zjistí na jiném zařízení. Efekt se zruší a znovu obnoví, a tím vytvoří nový efekt D2D. Předchozí efekt D2D již není přidružená kontrolovatelná obálka v tuto chvíli.
- Uživatel pak zavolá
GetOrCreatena první efekt D2D.
Bez zpětného volání by Win2D jednoduše nedokázal vyřešit obálku, protože neexistuje žádná registrovaná obálka. Pokud je místo toho zaregistrována továrna, je možné vytvořit a vrátit nový obal pro daný efekt D2D, takže scénář bude pro uživatele bezproblémově fungovat.
Implementace vlastního ICanvasEffect
Rozhraní Win2D ICanvasEffect rozšiřuje ICanvasImage, takže všechny předchozí body platí i pro vlastní efekty. Jediným rozdílem je skutečnost, že ICanvasEffect také implementuje další metody specifické pro efekty, jako je zneplatnění zdrojového obdélníku, získání požadovaných obdélníků atd.
Aby to bylo možné, Win2D zpřístupňuje exporty jazyka C, které můžou autoři vlastních efektů používat, aby nemuseli reimplementovat všechny tyto nadbytečné logiky od začátku. To funguje stejným způsobem jako export jazyka C pro GetBounds. Tady jsou dostupné exporty pro efekty:
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);
Pojďme si projít, jak se dají použít:
-
InvalidateSourceRectangleForICanvasImageInteropje určena k podpořeInvalidateSourceRectangle. Jednoduše nastavte vstupní parametry, volejte funkci přímo a postará se o veškerou potřebnou práci. Všimněte si, že parametrimageje aktuální instance efektu, která se implementuje. -
GetInvalidRectanglesForICanvasImageInteroppodporujeGetInvalidRectangles. To také nevyžaduje zvláštní pozornost, kromě nutnosti likvidovat vrácené pole COM, jakmile už není potřeba. -
GetRequiredSourceRectanglesForICanvasImageInteropje sdílená metoda, která podporuje obojíGetRequiredSourceRectangleiGetRequiredSourceRectangles. To znamená, že předá ukazatel na existující pole hodnot, aby se naplnilo; volající mohou předat buď ukazatel na jednu hodnotu (která může být také na zásobníku, aby se zabránilo jednomu přidělení), nebo na celé pole hodnot. Implementace je v obou případech stejná, takže jeden export jazyka C stačí k jejich výkonu.
Vlastní efekty v jazyce C# pomocí ComputeSharp
Jak jsme zmínili, pokud používáte C# a chcete implementovat vlastní efekt, doporučujeme použít knihovnu ComputeSharp . Umožňuje implementovat vlastní stínovače D2D1 zcela v jazyce C# i snadno definovat vlastní efekty, které jsou kompatibilní s Win2D. Stejná knihovna se také používá v Microsoft Storu k napájení několika grafických komponent v aplikaci.
Odkaz na ComputeSharp v projektu můžete přidat prostřednictvím NuGetu: Vyberte balíček ComputeSharp.D2D1.WinUI .
Poznámka:
Mnoho rozhraní API v ComputeSharp.D2D1.* je totožných mezi cíli UWP a WinUI, přičemž jediným rozdílem je obor názvů (končící buď .Uwp nebo .WinUI). Cíl UWP je však v dlouhodobé údržbě a neobsahuje žádné nové funkce. V porovnání s ukázkami zobrazenými pro WinUI může být proto potřeba některé změny kódu. Fragmenty kódu v tomto dokumentu odrážejí povrch rozhraní API ve verzi ComputeSharp.D2D1.WinUI.0.0 (poslední verze cíle UWP je 2.1.0).
ComputeSharp má dvě hlavní komponenty pro integraci s Win2D:
-
PixelShaderEffect<T>: Efekt Win2D, který je poháněn pixelovým shaderem D2D1. Samotný shader je napsaný v jazyce C# pomocí rozhraní API, která poskytuje ComputeSharp. Tato třída také poskytuje vlastnosti pro nastavení zdrojů efektů, konstantních hodnot a dalších. -
CanvasEffect: Základní třída pro vlastní efekty Win2D, které zabalí graf libovolného efektu. Lze jej použít k "zabalení" složitých efektů do snadno použitelného objektu, který lze opakovaně použít v několika částech aplikace.
Tady je příklad vlastního pixelového shaderu (portovaný z tohoto shadertoy shaderu), který se používá s PixelShaderEffect<T> a pak vykreslený na Win2D CanvasControl (všimněte si, že PixelShaderEffect<T> je implementováno v ICanvasImage):
Můžete vidět, jak můžete v pouhých dvou řádcích kódu vytvořit efekt a nakreslit ho přes Win2D. ComputeSharp se postará o veškerou práci potřebnou ke kompilaci shaderu, jeho registraci a správě složité životnosti efektu kompatibilního s Win2D.
V dalším kroku si ukážeme podrobný průvodce, jak vytvořit vlastní efekt Win2D, který také používá vlastní shader pixelů D2D1. Probereme, jak vytvořit shader s ComputeSharp a nastavit jeho vlastnosti a jak vytvořit vlastní graf efektů zabalený do CanvasEffect typu, který se dá ve vaší aplikaci snadno použít.
Návrh efektu
Pro tuto ukázku chceme vytvořit jednoduchý efekt matného skla.
To bude zahrnovat následující komponenty:
- Gaussian rozostření
- Barevný efekt
- Šum (který můžeme procedurálně generovat pomocí shaderu)
Budeme také chtít zpřístupnit vlastnosti pro řízení rozostření a množství šumu. Konečný efekt bude obsahovat "zabalenou" verzi tohoto grafu efektu a bude snadno použitelný pouhým vytvořením instance, nastavením těchto vlastností, propojením zdrojového obrázku a jeho nakreslením. Pojďme začít!
Vytvoření vlastního pixelového shaderu D2D1
Pro přidání šumu k efektu můžeme použít jednoduchý D2D1 pixel shader. Shader vypočítá náhodnou hodnotu na základě souřadnic (která bude fungovat jako počáteční hodnota náhodného čísla) a pak použije tuto hodnotu šumu k výpočtu množství RGB pro daný pixel. Tento šum pak můžeme překrýt přes výsledný obrázek.
Pokud chcete napsat shader pomocí ComputeSharp, stačí definovat partial struct typ, který implementuje ID2D1PixelShader rozhraní, a pak do metody napsat naši logiku Execute . Pro shader pro šum můžeme napsat něco takového:
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);
}
}
Poznámka:
Zatímco shader je napsán zcela v jazyce C#, doporučuje se základní znalost HLSL (programovacího jazyka pro shadery DirectX, na které ComputeSharp transpiluje jazyk C#).
Pojďme si podrobně projít tento shader:
- Shader nemá žádné vstupy, pouze generuje nekonečný obraz s náhodným šumem ve stupních šedé.
- Shader vyžaduje přístup ke souřadnici aktuálního pixelu.
- Shader je předkompilován v době sestavení (pomocí
PixelShader40profilu, který je zaručen, že je k dispozici na libovolném GPU, ve kterém by mohla být aplikace spuštěná). - Atribut
[D2DGeneratedPixelShaderDescriptor]je potřeba k aktivaci zdrojového generátoru, který je součástí ComputeSharp, který bude analyzovat kód jazyka C#, převést ho do HLSL, zkompilovat shader na bajtové kódy atd. - Shader zachytí parametr prostřednictvím svého primárního
float amountkonstruktoru. Generátor zdrojů v ComputeSharp se automaticky postará o extrakci všech zachycených hodnot ve shaderu a přípravě konstantní vyrovnávací paměti, kterou D2D potřebuje k inicializaci stavu shaderu.
A tahle část je hotová! Tento shader vygeneruje vlastní texturu šumu, kdykoli je to potřeba. Dále musíme vytvořit náš zabalený efekt s grafem efektů, který spojuje všechny naše efekty dohromady.
Vytvoření vlastního efektu
Pro snadné použití zabaleného efektu CanvasEffect můžeme použít typ z ComputeSharp. Tento typ poskytuje jednoduchý způsob, jak nastavit veškerou potřebnou logiku pro vytvoření grafu efektu a aktualizovat ho prostřednictvím veřejných vlastností, se kterými můžou uživatelé efektu pracovat. Budeme muset implementovat dvě hlavní metody:
-
BuildEffectGraph: Tato metoda zodpovídá za vytvoření grafu efektu, který chceme nakreslit. To znamená, že potřebuje vytvořit všechny efekty, které potřebujeme, a zaregistrovat výstupní uzel grafu. U účinků, které lze později aktualizovat, se registrace provede s přidruženouCanvasEffectNode<T>hodnotou, která funguje jako vyhledávací klíč k načtení efektů z grafu v případě potřeby. -
ConfigureEffectGraph: Tato metoda aktualizuje graf efektů použitím nastavení, která uživatel nakonfiguroval. Tato metoda se automaticky vyvolá v případě potřeby, přímo před vykreslení efektu a pouze pokud byla od posledního použití efektu změněna alespoň jedna vlastnost efektu.
Náš vlastní efekt lze definovat takto:
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);
}
}
V této třídě jsou čtyři části:
- Nejprve máme pole ke sledování všech proměnlivých stavů, jako jsou efekty, které je možné aktualizovat, a backingová pole pro všechny vlastnosti efektu, které chceme zpřístupnit uživatelům efektu.
- Dále máme vlastnosti ke konfiguraci efektu. Setter každé vlastnosti využívá metodu
SetAndInvalidateEffectGraphposkytnutouCanvasEffect, která automaticky zneplatní efekt, pokud je nastavená hodnota odlišná od aktuální hodnoty. Tím se zajistí, že se efekt nakonfiguruje znovu, jen když je to skutečně nutné. - Nakonec máme výše uvedené metody
BuildEffectGrapha metodyConfigureEffectGraph, které jsme zmínili výše.
Poznámka:
PremultiplyEffect uzel po efektu šumu je velmi důležitý: důvodem je to, že efekty Win2D předpokládají, že výstup je premultiplikovaný, zatímco pixelové shadery obecně pracují s nepremultiplikovanými pixely. Proto nezapomeňte ručně vložit uzly předmnožení a rozdělení před a za vlastní shadery, aby bylo zajištěno, že barvy budou správně zachovány.
Jste připravení kreslit!
A s tím je náš vlastní efekt matného skla připraven! Můžeme ho snadno nakreslit takto:
private void CanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
FrostedGlassEffect effect = new()
{
Source = _canvasBitmap,
BlurAmount = 12,
NoiseAmount = 0.1
};
args.DrawingSession.DrawImage(effect);
}
V tomto příkladu aplikujeme efekt z obslužné rutiny Draw objektu CanvasControlpomocí CanvasBitmap, který jsme dříve načetli jako zdroj. Toto je vstupní obrázek, který použijeme k otestování efektu:
A tady je výsledek:
Poznámka:
Poděkování Dominiku Langeovi za obrázek.
Dodatečné zdroje
- Pro více informací zkontrolujte zdrojový kód Win2D.
- Další informace o ComputeSharp najdete v ukázkových aplikacích
a v jednotkových testech .
Windows developer