Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Win2D innehåller flera API:er som representerar objekt som kan ritas, som är indelade i två kategorier: bilder och effekter. Bilder, som representeras av ICanvasImage
gränssnittet, har inga indata och kan ritas direkt på en viss yta. Till exempel CanvasBitmap
, VirtualizedCanvasBitmap
och CanvasRenderTarget
är exempel på bildtyper. Effekterna representeras å andra sidan av ICanvasEffect
gränssnittet. De kan ha indata och ytterligare resurser och kan använda godtycklig logik för att skapa sina utdata (eftersom en effekt också är en bild). Win2D innehåller effekter som omsluter de flesta D2D-effekter, till exempel GaussianBlurEffect
, TintEffect
och LuminanceToAlphaEffect
.
Bilder och effekter kan också länkas samman för att skapa godtyckliga grafer som sedan kan visas i ditt program (se även D2D-dokumenten om Direct2D-effekter). Tillsammans ger de ett extremt flexibelt system för att skapa komplex grafik på ett effektivt sätt. Det finns dock fall där de inbyggda effekterna inte räcker och du kanske vill skapa en egen Win2D-effekt. För att stödja detta innehåller Win2D en uppsättning kraftfulla interop-API:er som gör det möjligt att definiera anpassade bilder och effekter som sömlöst kan integreras med Win2D.
Tips/Råd
Om du använder C# och vill implementera ett anpassat effekt- eller effektdiagram rekommenderar vi att du använder ComputeSharp i stället för att försöka implementera en effekt från grunden. Se stycket nedan för en detaljerad förklaring av hur du använder det här biblioteket för att implementera anpassade effekter som integreras sömlöst med Win2D.
API:er för plattformar:
ICanvasImage
,CanvasBitmap
,VirtualizedCanvasBitmap
,CanvasRenderTarget
,CanvasEffect
,GaussianBlurEffect
,TintEffect
,ICanvasLuminanceToAlphaEffectImage
,IGraphicsEffectSource
,ID2D21Image
,ID2D1Factory1
,ID2D1Effect
Implementera en anpassad ICanvasImage
Det enklaste scenariot att stödja är att skapa en anpassad ICanvasImage
. Som vi nämnde är detta WinRT-gränssnittet som definieras av Win2D och som representerar alla typer av bilder som Win2D kan interagera med. Det här gränssnittet exponerar bara två GetBounds
metoder och utökar IGraphicsEffectSource
, vilket är ett markörgränssnitt som representerar "någon effektkälla".
Som du ser finns det inga "funktionella" API:er som exponeras av det här gränssnittet för att faktiskt utföra någon ritning. För att implementera ditt eget ICanvasImage
objekt måste du även implementera ICanvasImageInterop
gränssnittet, vilket exponerar all nödvändig logik för att Win2D ska kunna rita avbildningen. Det här är ett COM-gränssnitt som definierats i den publika Microsoft.Graphics.Canvas.native.h
headerfilen och som levereras med Win2D.
Gränssnittet definieras på följande sätt:
[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);
}
Och den förlitar sig också på dessa två uppräkningstyper, från samma rubrik:
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
}
De två metoderna GetDevice
och GetD2DImage
är allt som behövs för att implementera anpassade bilder (eller effekter), eftersom de förser Win2D med utökningspunkterna för att initiera dem på en viss enhet och hämta den underliggande D2D-bilden för att ritas. Att implementera dessa metoder på rätt sätt är viktigt för att säkerställa att saker och ting fungerar korrekt i alla scenarier som stöds.
Vi går igenom dem för att se hur varje metod fungerar.
Implementera GetDevice
Metoden GetDevice
är den enklaste av de två. Vad den gör är att den hämtar canvas-enheten som är associerad med effekten, så att Win2D kan inspektera den vid behov (till exempel för att säkerställa att den matchar den enhet som används). Parametern type
anger "association type" för den returnerade enheten.
Det finns två huvudsakliga möjliga fall:
- Om bilden är en effekt bör den ha stöd för att "aktiveras" och "avaktiveras" på flera enheter. Vad detta innebär är: en given effekt skapas i ett onitialiserat tillstånd, sedan kan den realiseras när en enhet skickas under ritningen, och därefter kan den fortsätta att användas med den enheten, eller så kan den flyttas till en annan enhet. I så fall återställer effekten dess interna tillstånd och realiseras sedan igen på den nya enheten. Det innebär att den associerade enheten kan ändras med tiden, och den kan också vara
null
. På grund av detta skatype
vara inställt påWIN2D_GET_DEVICE_ASSOCIATION_TYPE_REALIZATION_DEVICE
, och den returnerade enheten ska ställas in på den aktuella realiserande enheten, om en är tillgänglig. - Vissa avbildningar har en enda "ägande enhet" som tilldelas vid skapandetillfället och som aldrig kan ändras. Detta skulle till exempel vara fallet för en bild som representerar en struktur, eftersom den allokeras på en specifik enhet och inte kan flyttas. När
GetDevice
anropas ska det returnera skapande enheten och ställa intype
tillWIN2D_GET_DEVICE_ASSOCIATION_TYPE_CREATION_DEVICE
. Observera att när den här typen anges ska den returnerade enheten inte varanull
.
Anmärkning
Win2D kan anropa GetDevice
när du rekursivt passerar ett effektdiagram, vilket innebär att det kan finnas flera aktiva anrop till GetD2DImage
i stacken. På grund av detta bör GetDevice
inte ta ett blockeringslås på den nuvarande bilden, eftersom det potentiellt kan orsaka en deadlock. I stället bör den använda ett återinträdeslås på ett icke-blockerande sätt och returnera ett fel om det inte kan förvärvas. Detta säkerställer att samma tråd som rekursivt anropar den kan få tillgång till den, medan samtidiga trådar som gör detsamma misslyckas på ett tillförlitligt sätt.
Implementera GetD2DImage
GetD2DImage
är där det mesta av arbetet sker. Den här metoden ansvarar för att hämta det ID2D1Image
-objekt som Win2D kan rita, och realisera den aktuella effekten vid behov. Detta inkluderar även rekursivt bläddring och realisering av effektdiagrammet för alla källor, om några, samt initierar alla tillstånd som bilden kan behöva (t.ex. konstanta buffertar och andra egenskaper, resursstrukturer osv.).
Den exakta implementeringen av den här metoden är mycket beroende av bildtypen och kan variera mycket, men generellt sett kan du förvänta dig att metoden utför följande steg för en godtycklig effekt:
- Kontrollera om anropet var rekursivt på samma instans och misslyckas i så fall. Detta krävs för att identifiera cykler i ett effektdiagram (t.ex. har effekten
A
effektB
som källa, och effektenB
har effektA
som källa). - Skaffa ett lås på avbildningsinstansen för att skydda mot samtidig åtkomst.
- Hantera mål-DPIs enligt indataflaggor
- Kontrollera om indataenheten matchar den som eventuellt används. Om den inte överensstämmer och den aktuella effekten stöder genomförande, avaktiverar du effekten.
- Förstå effekten på inmatningsenheten. Detta kan omfatta registrering av D2D-effekten på det
ID2D1Factory1
-objekt som hämtats från inmatningsenheten eller enhetskontexten, om det behövs. Dessutom bör alla nödvändiga tillstånd anges för D2D-effektinstansen som skapas. - Rekursivt traversera eventuella källor och koppla dem till D2D-effekten.
När det gäller indataflaggor finns det flera möjliga fall som anpassade effekter bör hantera korrekt för att säkerställa kompatibilitet med alla andra Win2D-effekter. Exklusive WIN2D_GET_D2D_IMAGE_FLAGS_NONE
är de flaggor som ska hanteras följande:
-
WIN2D_GET_D2D_IMAGE_FLAGS_READ_DPI_FROM_DEVICE_CONTEXT
: i det här falletdevice
är garanterat intenull
. Effekten bör kontrollera om målet för enhetskontexten är enID2D1CommandList
och i så fall lägga till flagganWIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION
. Annars bör den angetargetDpi
(vilket också är garanterat att inte varanull
) till de DPI:er som hämtats från indatakontexten. Sedan bör den ta bortWIN2D_GET_D2D_IMAGE_FLAGS_READ_DPI_FROM_DEVICE_CONTEXT
från flaggorna. -
WIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION
ochWIN2D_GET_D2D_IMAGE_FLAGS_NEVER_INSERT_DPI_COMPENSATION
: används vid inställning av effektkällor (se anteckningar nedan). -
WIN2D_GET_D2D_IMAGE_FLAGS_MINIMAL_REALIZATION
: Om det är inställt hoppar du över den rekursiva realiseringen av källorna för effekten och returnerar bara den realiserade effekten utan några andra ändringar. -
WIN2D_GET_D2D_IMAGE_FLAGS_ALLOW_NULL_EFFECT_INPUTS
: om det anges tillåts implementerade effektkällor att blinull
, om användaren inte har specificerat dem till en befintlig källa ännu. -
WIN2D_GET_D2D_IMAGE_FLAGS_UNREALIZE_ON_FAILURE
: om det anges och en effektkälla som anges inte är giltig bör effekten avaktiveras innan den misslyckas. Det vill säga, om felet inträffade när effektkällorna löstes efter att effekten har realiserats, bör effekten återställas innan felet returneras till anroparen.
När det gäller DPI-relaterade flaggor styr dessa hur effektkällor anges. För att säkerställa kompatibilitet med Win2D bör effekter automatiskt lägga till DPI-kompensationseffekter i sina indata när det behövs. De kan kontrollera om så är fallet:
- Om
WIN2D_GET_D2D_IMAGE_FLAGS_MINIMAL_REALIZATION
anges, behövs en DPI-kompensationseffekt när parameterninputDpi
inte är0
. - Annars krävs DPI-kompensation om
inputDpi
inte0
,WIN2D_GET_D2D_IMAGE_FLAGS_NEVER_INSERT_DPI_COMPENSATION
inte har angetts och antingenWIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION
har angetts, eller så matchar inte indata-DPI:et och mål-DPI-värdena.
Den här logiken bör tillämpas när en källa realiseras och binds till indata för den aktuella effekten. Observera att om en DPI-kompensationseffekt läggs till bör denna användas som indata för den underliggande D2D-avbildningen. Men om användaren försöker hämta WinRT-omslutningen för den källan bör effekten vara noga med att identifiera om en DPI-effekt användes och returnera en omslutning för det ursprungliga källobjektet i stället. DPI-kompensationseffekter bör alltså vara transparenta för användarna av effekten.
När all initieringslogik är klar bör resultatet ID2D1Image
(precis som med Win2D-objekt, eftersom en D2D-effekt också är en bild) vara redo att ritas av Win2D i målkontexten, som ännu inte är känd av mottagaren för tillfället.
Anmärkning
Korrekt implementering av den här metoden (och ICanvasImageInterop
i allmänhet) är mycket komplicerad, och den är bara avsedd att utföras av avancerade användare som absolut behöver den extra flexibiliteten. En gedigen förståelse för D2D, Win2D, COM, WinRT och C++ rekommenderas innan du försöker skriva en ICanvasImageInterop
implementering. Om din anpassade Win2D-effekt också måste omsluta en anpassad D2D-effekt måste du även implementera ditt eget ID2D1Effect
objekt (se D2D-dokumenten om anpassade effekter för mer information om detta). Dessa dokument är inte en fullständig beskrivning av all nödvändig logik (till exempel omfattar de inte hur effektkällor ska ordnas och hanteras över gränsen D2D/Win2D), så vi rekommenderar att du även använder CanvasEffect
implementeringen i Win2D:s kodbas som referenspunkt för en anpassad effekt och ändrar den efter behov.
Implementera GetBounds
Den sista saknade komponenten för att fullt ut implementera en anpassad ICanvasImage
-effekt är att stödja de två GetBounds
-överlagringarna. För att göra detta enkelt exponerar Win2D en C-export som kan användas för att utnyttja den befintliga logiken för detta från Win2D på alla anpassade avbildningar. Exporten är följande:
HRESULT GetBoundsForICanvasImageInterop(
ICanvasResourceCreator* resourceCreator,
ICanvasImageInterop* image,
Numerics::Matrix3x2 const* transform,
Rect* rect);
Anpassade avbildningar kan anropa det här API:et och skicka sig själva som parametern image
och sedan helt enkelt returnera resultatet till sina anropare. Parametern transform
kan vara null
, om ingen transformering är tillgänglig.
Optimera åtkomst till enhetskontext
Parametern deviceContext
i ICanvasImageInterop::GetD2DImage
kan ibland vara null
, om en kontext inte är omedelbart tillgänglig före anropet. Detta görs avsiktligt så att en kontext bara skapas lätt när den faktiskt behövs. Det vill säga, om en kontext är tillgänglig kommer Win2D att skicka den till anropet GetD2DImage
; annars låter det anropare hämta en på egen hand om det behövs.
Det är relativt dyrt att skapa ett enhetssammanhang, så för att göra hämtningen snabbare gör Win2D API:er tillgängliga för att komma åt dess interna enhetssammanhangspool. Detta gör att anpassade effekter kan hyra och returnera enhetskontexter som är associerade med en given canvasenhet på ett effektivt sätt.
API:erna för leasing av enhetskontext definieras på följande sätt:
[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);
}
Gränssnittet ID2D1DeviceContextPool
implementeras av CanvasDevice
, vilket är win2D-typen som implementerar ICanvasDevice
gränssnittet. Om du vill använda poolen använder QueryInterface
du i enhetsgränssnittet för att hämta en ID2D1DeviceContextPool
referens och anropar ID2D1DeviceContextPool::GetDeviceContextLease
sedan för att hämta ett ID2D1DeviceContextLease
objekt för att få åtkomst till enhetskontexten. När det inte längre behövs släpper du lånet. Se till att du inte rör enhetens kontext efter att lånet har släppts, eftersom det kan användas samtidigt av andra trådar.
Aktivera WinRT-wrapper-sökning
Som ses i Win2D-interopdokumentenexponerar Win2D-rubriken även en GetOrCreate
-metod (tillgänglig från aktiveringsfabriken ICanvasFactoryNative
eller via de GetOrCreate
C++/CX-hjälpverktyg som definieras i samma header). På så sätt kan du hämta en WinRT-omslutning från en viss intern resurs. Du kan till exempel hämta eller skapa en CanvasDevice
instans från ett ID2D1Device1
objekt, en CanvasBitmap
från en ID2D1Bitmap
osv.
Den här metoden fungerar också för alla inbyggda Win2D-effekter: genom att hämta den ursprungliga resursen för en given effekt och sedan använda den för att hämta den motsvarande Win2D-wrappare returneras korrekt den Win2D-effekt som äger den. För att anpassade effekter också ska kunna dra nytta av samma mappningssystem exponerar Win2D flera API:er genom aktiveringsfabriken CanvasDevice
, som är av typen ICanvasFactoryNative
, samt ett ytterligare gränssnitt för effektfabriken, 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);
};
Det finns flera API:er att tänka på här, eftersom de behövs för att stödja alla olika scenarier där Win2D-effekter kan användas, samt hur utvecklare kan göra interop med D2D-lagret och sedan försöka lösa omslutningar åt dem. Nu ska vi gå igenom vart och ett av dessa API:er.
Metoderna RegisterWrapper
och UnregisterWrapper
är avsedda att anropas av anpassade effekter för att lägga till sig själva i den interna Win2D-cachen:
-
RegisterWrapper
: registrerar en infödd resurs och dess tillhörande WinRT-omslag. Parameternwrapper
är nödvändig för att implementeraIWeakReferenceSource
också, så att den kan cachelagras korrekt utan att orsaka referenscykler vilket skulle leda till minnesläckor. Metoden returnerarS_OK
om den naturliga resursen kunde läggas till i cacheminnet,S_FALSE
om det redan fanns en registrerad omslag förresource
, eller en felkod om ett fel inträffar. -
UnregisterWrapper
: avregistrerar en inbyggd resurs och dess omslag. ReturnerarS_OK
om resursen kunde tas bort,S_FALSE
omresource
den inte redan har registrerats, och en erro-kod om ett annat fel inträffar.
Anpassade effekter bör anropa RegisterWrapper
och UnregisterWrapper
när de realiseras och orealiseras, dvs. när en ny intern resurs skapas och associeras med dem. Anpassade effekter som inte stöder genomförande (t.ex. de som har en fast associerad enhet) kan anropa RegisterWrapper
och UnregisterWrapper
när de skapas och förstörs. Anpassade effekter bör noga se till att korrekt avregistrera sig från alla möjliga kodsökvägar som skulle kunna göra att omslutningen blir ogiltig, t.ex. när objektet har slutförts, speciellt om det implementeras på ett hanterat språk.
Metoderna RegisterEffectFactory
och UnregisterEffectFactory
är också avsedda att användas av anpassade effekter, så att de också kan registrera ett återanrop för att skapa en ny omslutning om en utvecklare försöker lösa en för en "överbliven" D2D-resurs:
-
RegisterEffectFactory
: registrera ett återanrop som tar in samma parametrar som en utvecklare skickade tillGetOrCreate
och skapar en ny kontrollbar omslutning för indataeffekten. Effekt-ID:t används som nyckel, så att varje anpassad effekt kan registrera en fabrik för den när den först laddas. Naturligtvis bör detta bara göras en gång per effekttyp, och inte varje gång effekten realiseras. Parametrarnadevice
,resource
ochwrapper
kontrolleras av Win2D innan några registrerade återanrop anropas, så de är garanterat intenull
närCreateWrapper
anropas.dpi
Anses vara valfritt och kan ignoreras om effekttypen inte har någon specifik användning för den. Observera att när en ny omslutning skapas från en registrerad fabrik bör den fabriken också se till att den nya omslutningen registreras i cachen (Win2D lägger inte automatiskt till omslutningar som produceras av externa fabriker i cacheminnet). -
UnregisterEffectFactory
: tar bort en tidigare registrerad callback-funktion. Detta kan till exempel användas om en effektomslutning implementeras i en hanterad sammansättning som tas bort.
Anmärkning
ICanvasFactoryNative
implementeras av aktiveringsfabriken för CanvasDevice
, som du kan hämta genom att antingen anropa RoGetActivationFactory
manuellt eller använda hjälp-API:er från de språktillägg som du använder (t.ex. winrt::get_activation_factory
i C++/WinRT). För mer information, se WinRT-typsystem för att förstå hur detta fungerar.
Ett praktiskt exempel på var den här mappningen kommer till användning finns i hur inbyggda Win2D-effekter fungerar. Om de inte realiseras lagras alla tillstånd (t.ex. egenskaper, källor osv.) i en intern cache i varje effektinstans. När de realiseras överförs hela tillståndet till den inhemska resursen (t.ex. egenskaper ställs in på D2D-effekten, alla källor löses och mappas till effektens indatakällor, osv.), och så länge effekten är realiserad kommer den att agera som auktoritet över omslagets tillstånd. Om värdet för en egenskap hämtas från omslutningen hämtas det uppdaterade värdet för den från den interna D2D-resursen som är associerad med den.
Detta säkerställer att om några ändringar görs direkt i D2D-resursen visas de även på den yttre omslutningen, och de två kommer aldrig att vara "osynkroniserade". När effekten är orealiserad överförs alla tillstånd tillbaka från den interna resursen till omslutningstillståndet innan resursen släpps. Den kommer att hållas och uppdateras där till nästa gång effekten realiseras. Tänk nu på den här händelsesekvensen:
- Du har viss Win2D-effekt (antingen inbyggd eller anpassad).
- Du får
ID2D1Image
från det (vilket är enID2D1Effect
). - Du skapar en instans av en anpassad effekt.
- Du får också
ID2D1Image
från det. - Du anger den här bilden manuellt som indata för den tidigare effekten (via
ID2D1Effect::SetInput
). - Sedan ber du om den initiala effekten för WinRT-omslutningen för dessa indata.
Eftersom effekten realiseras (den realiserades när den interna resursen begärdes) kommer den att använda den inbyggda resursen som sanningskälla. Först kommer ID2D1Image
som motsvarar den begärda källan att hämtas, och sedan försöker det att hämta WinRT-omslutningen för den. Om den effekt som indata hämtades från har korrekt lagt till sitt eget par av ursprungliga resurser och WinRT-omslutning i Win2D:s cache, löses omslutningen och returneras till anroparen. Annars misslyckas den egenskapsåtkomsten eftersom Win2D inte kan lösa WinRT-omslutningar för effekter som den inte äger, eftersom den inte vet hur de ska instansieras.
Det är här RegisterWrapper
och UnregisterWrapper
hjälper, eftersom de tillåter anpassade effekter att sömlöst delta i Win2D:s logik för omslutningsupplösning, så att rätt omslag alltid kan hämtas för alla effektkällor, oavsett om den ställts in från WinRT-API:er eller direkt från det underliggande D2D-lagret.
Tänk på det här scenariot för att förklara hur effektfabrikerna också spelar in:
- En användare skapar en instans av en anpassad omslutning och inser vad det innebär.
- De hämtar sedan en referens till den underliggande D2D-effekten och behåller den.
- Sedan realiseras effekten på en annan enhet. Effekten kommer att avrealiseras och omrealiseras, och därigenom skapar den en ny D2D-effekt. Den tidigare D2D-effekten har inte längre ett associerat inspektionsbart omslag vid den här tidpunkten.
- Användaren anropar
GetOrCreate
sedan den första D2D-effekten.
Utan återanrop skulle Win2D bara misslyckas med att lösa en omslutning, eftersom det inte finns någon registrerad omslutning för den. Om en fabrik registreras i stället kan en ny omslutning för D2D-effekten skapas och returneras, så scenariot fortsätter bara att fungera sömlöst för användaren.
Implementera en anpassad ICanvasEffect
Win2D-gränssnittet ICanvasEffect
utökar ICanvasImage
, så alla föregående punkter gäller även för anpassade effekter. Den enda skillnaden är det faktum att ICanvasEffect
även implementerar ytterligare metoder som är specifika för effekter, till exempel att ogiltigförklara en källrektangel, hämta nödvändiga rektanglar och så vidare.
För att stödja detta exponerar Win2D C-exporter som författare av anpassade effekter kan använda, så att de inte behöver implementera all den här extra logiken från grunden. Detta fungerar på samma sätt som C-exporten för GetBounds
. Här är de tillgängliga exporterna för effekter:
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);
Nu ska vi gå vidare med hur de kan användas:
-
InvalidateSourceRectangleForICanvasImageInterop
är avsett att stödjaInvalidateSourceRectangle
. Hantera bara indataparametrarna och anropa den direkt, så gör den resten av jobbet. Observera att parameternimage
är den aktuella effektinstansen som implementeras. -
GetInvalidRectanglesForICanvasImageInterop
stöderGetInvalidRectangles
. Detta kräver inte heller något särskilt övervägande, förutom att du behöver ta bort den returnerade COM-matrisen när den inte längre behövs. -
GetRequiredSourceRectanglesForICanvasImageInterop
är en delad metod som kan stödja bådeGetRequiredSourceRectangle
ochGetRequiredSourceRectangles
. Det vill säga att det tar en pekare till en befintlig matris med värden att fylla i, så anropare kan antingen skicka en pekare till ett enda värde (som också kan finnas på stacken, för att undvika en allokering) eller till en matris med värden. Implementeringen är densamma i båda fallen, så en enda C-export räcker för att driva båda.
Anpassade effekter i C# med ComputeSharp
Som vi nämnde är den rekommenderade metoden att använda ComputeSharp-biblioteket om du använder C# och vill implementera en anpassad effekt. Det gör att du både kan implementera anpassade D2D1-pixelskuggare helt i C#, samt att enkelt definiera anpassade effektdiagram som är kompatibla med Win2D. Samma bibliotek används också i Microsoft Store för att driva flera grafikkomponenter i programmet.
Du kan lägga till en referens till ComputeSharp i projektet via NuGet:
- På UWP väljer du paketet ComputeSharp.D2D1.Uwp .
- I WinAppSDK väljer du paketet ComputeSharp.D2D1.WinUI .
Anmärkning
Många API:er i ComputeSharp.D2D1.* är identiska mellan UWP- och WinAppSDK-målen, den enda skillnaden är namnområdet (slutar antingen .Uwp
eller .WinUI
). UWP-plattformen är dock under regelbundet underhåll och får inte nya funktioner. Därför kan vissa kodändringar behövas jämfört med exemplen som visas här för WinUI. Kodfragmenten i det här dokumentet återspeglar API-ytan från och med ComputeSharp.D2D1.WinUI 3.0.0 (den senaste versionen för UWP-målet är i stället 2.1.0).
Det finns två huvudkomponenter i ComputeSharp för att samarbeta med Win2D.
-
PixelShaderEffect<T>
: en Win2D-effekt som drivs av en D2D1-pixelshader. Själva skuggningen skrivs i C# med hjälp av API:erna som tillhandahålls av ComputeSharp. Den här klassen innehåller också egenskaper för att ange effektkällor, konstanta värden med mera. -
CanvasEffect
: en basklass för anpassade Win2D-effekter som omsluter ett godtyckligt effektdiagram. Det kan användas för att "paketera" komplexa effekter till ett lättanvänt objekt som kan återanvändas i flera delar av ett program.
Här är ett exempel på en anpassad pixelshader (portad från denna shadertoy shader), som används med PixelShaderEffect<T>
och sedan rita på en Win2D-yta CanvasControl
(observera att PixelShaderEffect<T>
implementerar ICanvasImage
):
Du kan se hur du på bara två kodrader kan skapa en effekt och rita den via Win2D. ComputeSharp tar hand om allt arbete som krävs för att kompilera skuggningen, registrera den och hantera den komplexa livslängden för en Win2D-kompatibel effekt.
Nu ska vi se en steg för steg-guide om hur du skapar en anpassad Win2D-effekt som också använder en anpassad D2D1-pixelskuggning. Vi går vidare med hur du skapar en skuggning med ComputeSharp och konfigurerar dess egenskaper och sedan hur du skapar ett anpassat effektdiagram som paketeras i en CanvasEffect
typ som enkelt kan återanvändas i ditt program.
Att designa effekten
I den här demonstrationen vill vi skapa en enkel frostad glaseffekt.
Detta omfattar följande komponenter:
- Gaussisk oskärpa
- Toningseffekt
- Brus (som vi kan generera proceduralt med en shader)
Vi vill också exponera egenskaper för att styra oskärpa och brusmängd. Den slutliga effekten innehåller en "paketerad" version av den här effektdiagrammet och är lätt att använda genom att bara skapa en instans, ange dessa egenskaper, ansluta en källavbildning och sedan rita den. Nu ska vi komma igång!
Skapa en anpassad D2D1-pixelskuggare
För bruset ovanpå effekten kan vi använda en enkel D2D1 pixelskuggning. Shadern beräknar ett slumpmässigt värde baserat på dess koordinater (som fungerar som ett "frö" för det slumpmässiga talet), och använder sedan brusvärdet för att beräkna RGB-värdet för den pixelen. Vi kan sedan blanda det här bruset ovanpå den resulterande bilden.
För att skriva skuggningen med ComputeSharp behöver vi bara definiera en partial struct
typ som implementerar ID2D1PixelShader
gränssnittet och sedan skriva vår logik i Execute
-metoden. För den här brusskuggaren kan vi skriva ungefär så här:
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);
}
}
Anmärkning
Även om skuggningen är helt skriven i C#, rekommenderas grundläggande kunskaper om HLSL (programmeringsspråket för DirectX-skuggningar, som ComputeSharp överför C# till).
Låt oss gå över den här skuggningen i detalj:
- Shadern har inga indata, den producerar bara en ständig bild med slumpmässigt gråskalebrus.
- Skuggningen kräver åtkomst till den aktuella pixelkoordinaten.
- Skuggningen är förkompilerad vid byggtiden (med hjälp av profilen
PixelShader40
, som garanterat är tillgänglig på alla GPU:ar där programmet kan köras). - Attributet
[D2DGeneratedPixelShaderDescriptor]
behövs för att utlösa källgeneratorn som paketerats med ComputeSharp, som analyserar C#-koden, överför den till HLSL, kompilerar skuggningen till bytekod osv. - Shadermodulen samlar in en
float amount
parameter via dess primära konstruktor. Källgeneratorn i ComputeSharp tar automatiskt hand om att extrahera alla insamlade värden i en skuggning och förbereder den konstanta buffert som D2D behöver för att initiera skuggningstillståndet.
Och den här delen är klar! Den här skuggningen genererar vår anpassade brusstruktur när det behövs. Sedan måste vi skapa vår paketerade effekt med effektdiagrammet som kopplar samman alla våra effekter.
Skapa en anpassad effekt
För vår enkla, paketerade effekt kan vi använda typen CanvasEffect
från ComputeSharp. Den här typen är ett enkelt sätt att konfigurera all nödvändig logik för att skapa ett effektdiagram och uppdatera det via offentliga egenskaper som användare av effekten kan interagera med. Det finns två huvudsakliga metoder som vi behöver implementera:
-
BuildEffectGraph
: Den här metoden ansvarar för att skapa det effektdiagram som vi vill rita. Den måste alltså skapa alla effekter vi behöver och registrera utdatanoden för diagrammet. För effekter som kan uppdateras vid ett senare tillfälle görs registreringen med ett associeratCanvasEffectNode<T>
värde, som fungerar som uppslagsnyckel för att hämta effekterna från diagrammet när det behövs. -
ConfigureEffectGraph
: Den här metoden uppdaterar effektdiagrammet genom att tillämpa de inställningar som användaren har konfigurerat. Den här metoden anropas automatiskt när det behövs, precis innan effekten ritas, och endast om minst en effektegenskap har ändrats sedan den senaste gången effekten användes.
Vår anpassade effekt kan definieras på följande sätt:
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);
}
}
Du kan se att det finns fyra avsnitt i den här klassen:
- Först har vi fält för att spåra alla föränderliga tillstånd, till exempel de effekter som kan uppdateras samt bakgrundsfälten för alla effektegenskaper som vi vill exponera för användare av effekten.
- Sedan har vi egenskaper för att konfigurera effekten. Inställaren för varje egenskap använder metoden
SetAndInvalidateEffectGraph
som tillhandahålls avCanvasEffect
, vilket automatiskt gör effekten ogiltig om det värde som anges skiljer sig från det nuvarande. Detta säkerställer att effekten bara konfigureras igen när det verkligen behövs. - Slutligen har vi de
BuildEffectGraph
metoder ochConfigureEffectGraph
metoder som vi nämnde ovan.
Anmärkning
Den PremultiplyEffect
-noden efter brus är mycket viktig: detta beror på att Win2D-effekter förutsätter att utdata är förmultiplcerad, medan pixelskuggor vanligtvis fungerar med icke-premultiplcerade pixlar. Kom därför ihåg att manuellt infoga premultiply/unpremultiply-noder före och efter anpassade shaders för att säkerställa att färgerna bevaras korrekt.
Anmärkning
Den här exempeleffekten använder WinUI 3-namnområden, men samma kod kan också användas på UWP. I så fall blir ComputeSharp.Uwp
namnområdet för ComputeSharp, som matchar paketnamnet.
Redo att rita!
Och med detta är vår anpassade frostade glaseffekt klar! Vi kan enkelt rita den på följande sätt:
private void CanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
FrostedGlassEffect effect = new()
{
Source = _canvasBitmap,
BlurAmount = 12,
NoiseAmount = 0.1
};
args.DrawingSession.DrawImage(effect);
}
I det här exemplet hämtar vi effekten från Draw
-hanterare för en CanvasControl
, med hjälp av en CanvasBitmap
som vi tidigare laddade in som källa. Det här är den indatabild som vi ska använda för att testa effekten:
Och här är resultatet:
Anmärkning
Krediter till Dominic Lange för bilden.
Ytterligare resurser
- Mer information finns i Win2D-källkoden .
- Mer information om ComputeSharp finns i exempelapparna och enhetstesterna .
Windows developer