Aracılığıyla paylaş


Özel efektler uygulama

Win2D, çizilebilen nesneleri temsil eden ve iki kategoriye ayrılmış çeşitli API'ler sağlar: resimler ve efektler. Belirli bir yüzeye doğrudan çizilebilen görüntüler, ICanvasImage arabirimi ile temsil edilir ve girişleri yoktur. Örneğin, CanvasBitmapVirtualizedCanvasBitmap ve CanvasRenderTarget görüntü türlerine örnektir. Efektler ise arabirim tarafından ICanvasEffect temsil edilir. Bunlar hem girişlere hem de ek kaynaklara sahip olabilir ve çıkışlarını üretmek için rastgele mantık uygulayabilir (bir etki aynı zamanda bir görüntüdür). Win2D, , GaussianBlurEffect, ve TintEffect gibi LuminanceToAlphaEffect efektleri içerir.

Görüntüler ve efektler, daha sonra uygulamanızda görüntülenebilen rastgele grafikler oluşturmak için birlikte zincirlenebilir ( Direct2D efektleriyle ilgili D2D belgelerine de bakın). Birlikte, karmaşık grafikleri verimli bir şekilde yazmak için son derece esnek bir sistem sağlarlar. Ancak yerleşik efektlerin yeterli olmadığı durumlar vardır ve kendi Win2D efektinizi oluşturmak isteyebilirsiniz. Bunu desteklemek için Win2D, Win2D ile sorunsuz bir şekilde tümleştirilebilen özel görüntülerin ve efektlerin tanımlanmasına olanak tanıyan bir dizi güçlü birlikte çalışma API'sini içerir.

Tavsiye

C# kullanıyorsanız ve özel bir efekt veya efekt grafı uygulamak istiyorsanız, sıfırdan bir efekt uygulamaya çalışmak yerine ComputeSharp kullanmanız önerilir. Win2D ile sorunsuz bir şekilde tümleşen özel efektler uygulamak için bu kitaplığın nasıl kullanılacağına ilişkin ayrıntılı bir açıklama için aşağıdaki paragrafa bakın.

Platform API'leri:ICanvasImage, CanvasBitmap, VirtualizedCanvasBitmap, CanvasRenderTarget, CanvasEffect, , GaussianBlurEffect, TintEffectICanvasLuminanceToAlphaEffectImageIGraphicsEffectSourceID2D21Image, ID2D1Factory1ID2D1Effect

Özel ICanvasImage'ı uygulamak

Destek için en basit senaryo, özel bir ICanvasImage oluşturmak. Bahsettiğimiz gibi, bu Win2D tarafından tanımlanan ve Win2D'nin birlikte çalışabilir olduğu her türlü görüntüyü temsil eden WinRT arabirimidir. Bu arabirim yalnızca iki GetBounds yöntemi kullanıma sunar ve "bazı etki kaynaklarını" temsil eden bir işaretçi arabirimi olan öğesini genişletir IGraphicsEffectSource.

Gördüğünüz gibi, bu arabirim tarafından herhangi bir çizim gerçekleştirmek için kullanıma sunulan "işlevsel" API'ler yoktur. Kendi ICanvasImage nesnenizi uygulamak için, ICanvasImageInterop arabirimini de uygulamanız gerekir; bu arabirim, Win2D'nin görüntüyü çizmesi için gerekli tüm mantığı kullanıma sunar. Bu, Win2D ile birlikte gelen genel Microsoft.Graphics.Canvas.native.h şablonunda tanımlanan bir COM arabirimidir.

Arabirim aşağıdaki gibi tanımlanır:

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

Ayrıca aynı üst bilgiden bu iki numaralandırma türüne de dayanır:

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
}

İki GetDevice ve GetD2DImage yöntemi, Win2D'ye belirli bir cihazda başlatma ve temel alınan D2D görüntüsünü çizim yapmak için alma konusunda genişletilebilirlik noktaları sağladığından, özel görüntüleri (veya efektleri) uygulamak için yeterlidir. Bu yöntemlerin doğru uygulanması, desteklenen tüm senaryolarda işlerin düzgün çalıştığından emin olmak için kritik öneme sahiptir.

Her yöntemin nasıl çalıştığını görmek için bunların üzerinden geçelim.

GetDevice Uygulamak

GetDevice yöntemi, ikisinin en basitidir. Bu, etkiyle ilişkili canvas aygıtını alır, böylece Win2D gerekirse onu inceleyebilir (örneğin, kullanımdaki cihazla eşleşip eşleşmediğini kontrol etmek için). parametresi, type döndürülen cihaz için "ilişkilendirme türünü" gösterir.

İki olası durum vardır:

  • Görüntü bir etkiyse, birden çok cihazda "görüntülenmiş" ve "görüntülenmemiş" olmayı desteklemelidir. Bunun anlamı şudur: Belirli bir etki başlatılmamış bir durumda oluşturulur, ardından çizim sırasında bir cihaz geçirildiğinde gerçekleştirilebilir ve bundan sonra bu cihazla kullanılmaya devam edebilir veya farklı bir cihaza taşınabilir. Bu durumda, etki iç durumunu sıfırlar ve ardından yeni cihazda kendini yeniden gerçekleştirir. Bu, ilişkili tuval cihazının zaman içinde değişebileceği ve nullda olabileceği anlamına gelir. Bu nedenle, typeWIN2D_GET_DEVICE_ASSOCIATION_TYPE_REALIZATION_DEVICEolarak ayarlanmalıdır ve döndürülen cihaz, eğer varsa, geçerli gerçekleştirme cihazına ayarlanmalıdır.
  • Bazı görüntüler oluşturma zamanında atanan ve hiçbir zaman değişemeyecek tek bir "sahip olan cihaza" sahiptir. Örneğin, kaplamayı temsil eden bir görüntü, belirli bir cihazda yer aldığı için taşınamaz ve bu durum onun için geçerli olabilir. GetDevice çağrıldığında, oluşturucu cihazı döndürmeli ve type'i WIN2D_GET_DEVICE_ASSOCIATION_TYPE_CREATION_DEVICEolarak ayarlamalıdır. Belirli bir tür belirtildiğinde, döndürülen cihazın nullolmaması gerekir.

Uyarı

Win2D, özyinelemeli olarak bir efekt grafini dolaşırken çağrı GetDevice'ı yapabilir; bu da yığında GetD2DImage'e birden çok etkin çağrı yapılabileceği anlamına gelir. Bu nedenle, GetDevice potansiyel deadlock oluşturmamak için geçerli görüntü üzerinde bir engelleyici kilit almamalıdır. Bunun yerine, alınamazsa bir hata döndürecek şekilde engelleyici olmayan ve yeniden girişimli bir kilit kullanmalıdır. Bu, aynı iş parçacığının özyinelemeli olarak çağrılmasının başarıyla edindiğini garanti ederken, aynı şeyi yapmaya çalışan eşzamanlı iş parçacıkları sorunsuz bir şekilde başarısız olur.

GetD2DImage Uygulamak

GetD2DImage , işin büyük bir kısmının gerçekleştiği yerdir. Bu yöntem, Win2D'nin ID2D1Image çizebileceği nesneyi almak ve gerekirse mevcut etkiyi uygulamakla sorumludur. Bu ayrıca, varsa tüm kaynaklar için efekt grafiğinin özyinelemeli olarak geçişini ve gerçekleşimini sağlamayı ve görüntünün ihtiyaç duyabileceği herhangi bir durumu başlatmayı (örneğin, sabit arabellekler ve diğer özellikler, kaynak dokular gibi) içerir.

Bu yöntemin tam olarak uygulanması, görüntü türüne büyük ölçüde bağlıdır ve çok farklılık gösterebilir, ancak genellikle rastgele bir etki için yönteminin aşağıdaki adımları gerçekleştirmesini bekleyebilirsiniz:

  • Çağrının aynı örnekte özyinelemeli olup olmadığını kontrol edin ve öyleyse işlemi sonlandırın. Bu, bir efekt grafiğindeki döngüleri algılamak için gereklidir (örneğin, etki A, kaynak olarak etki B'i etkisini alır, ve etki B, kaynak olarak A etkisini alır).
  • Eşzamanlı erişime karşı korumak için görüntü örneğinde bir kilit alın.
  • Giriş bayraklarına göre hedef DPI'leri işleme
  • Giriş cihazının kullanımdaki cihazla eşleşip eşleşmediğini (varsa) doğrulayın. Eşleşmiyorsa ve geçerli efekt gerçekleştirme işlevini destekliyorsa, etkiyi gerçekleştirmeyi iptal edin.
  • Giriş cihazı üzerindeki etkisini fark edin. Bu, gerekirse giriş cihazından veya cihaz bağlamından alınan nesne üzerinde ID2D1Factory1 D2D efektinin kaydedilmesini içerebilir. Ayrıca, tüm gerekli durum oluşturulmakta olan D2D efekt örneğinde ayarlanmalıdır.
  • Kaynakları özyinelemeli olarak geçerek, bunları D2D efektiyle bağlayın.

Giriş bayraklarıyla ilgili olarak, özel efektlerin diğer tüm Win2D efektleriyle uyumluluğu sağlamak için düzgün bir şekilde işlemesi gereken birkaç olası durum vardır. WIN2D_GET_D2D_IMAGE_FLAGS_NONEhariç, ele alınacak bayraklar şunlardır:

  • WIN2D_GET_D2D_IMAGE_FLAGS_READ_DPI_FROM_DEVICE_CONTEXT: Bu durumda, device'in nullolmayacağı garanti edilir. Etki, cihaz bağlam hedefinin bir ID2D1CommandList olup olmadığını denetlemeli ve öyleyse WIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION bayrağını eklemelidir. Aksi takdirde, giriş bağlamından alınan DPI'lere targetDpi'nın ayarlanması gerekir (nullolmaması da garanti edilir). Ardından bayraklardan WIN2D_GET_D2D_IMAGE_FLAGS_READ_DPI_FROM_DEVICE_CONTEXT çıkarmalı.
  • WIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION ve WIN2D_GET_D2D_IMAGE_FLAGS_NEVER_INSERT_DPI_COMPENSATION: efekt kaynaklarını ayarlarken kullanılır (aşağıdaki notlara bakın).
  • WIN2D_GET_D2D_IMAGE_FLAGS_MINIMAL_REALIZATION: ayarlanırsa, efektin kaynaklarını yinelemeli olarak gerçekleştirmeyi atlar ve yalnızca gerçekleştirilen efekti başka bir değişiklik olmadan döndürür.
  • WIN2D_GET_D2D_IMAGE_FLAGS_ALLOW_NULL_EFFECT_INPUTS: Ayarlandığında, kullanıcı henüz onları mevcut bir kaynağa ayarlamadıysa, gerçekleştirilen etki kaynaklarının nullolmasına izin verilir.
  • WIN2D_GET_D2D_IMAGE_FLAGS_UNREALIZE_ON_FAILURE: Ayarlanırsa ve ayarlanmakta olan bir efekt kaynağı geçerli değilse, etkisi başarısız olmadan önce etkisiz hale getirilmelidir. Yani, etki gerçekleştirildikten sonra etki kaynakları çözümlenirken bir hata oluşursa, hatayı çağırana döndürmeden önce efektin kendini geri alması gerekir.

DPI ile ilgili bayraklar, etki kaynaklarının nasıl ayarlandığını kontrol eder. Win2D ile uyumluluğu sağlamak için, efektler gerektiğinde girişlerine otomatik olarak DPI telafi efektleri eklemelidir. Böyle olup olmadığını denetleyebiliyorlar:

  • Eğer WIN2D_GET_D2D_IMAGE_FLAGS_MINIMAL_REALIZATION ayarlanmışsa, inputDpi parametresi 0 olmadığında bir DPI telafi etkisi gerekir.
  • Aksi takdirde, inputDpi0değilse, WIN2D_GET_D2D_IMAGE_FLAGS_NEVER_INSERT_DPI_COMPENSATION ayarlanmadıysa ve WIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION ayarlanmadıysa veya giriş DPI'si ile hedef DPI değerleri eşleşmiyorsa DPI telafisi gerekir.

Bu mantık, bir kaynak gerçekleştirilirken ve geçerli efektin bir girişine bağlı olduğunda uygulanmalıdır. DPI dengeleme efekti eklenirse, bunun temel alınan D2D görüntüsüne ayarlanmış giriş olması gerektiğini unutmayın. Ancak, kullanıcı bu kaynak için WinRT sarmalayıcısını almaya çalışırsa, işlem, bir DPI efektinin kullanılıp kullanılmadığını algılamaya ve bunun yerine özgün kaynak nesne için bir sarmalayıcı döndürmeye dikkat etmelidir. Yani DPI telafi efektleri, etkinin kullanıcıları için saydam olmalıdır.

Tüm başlatma mantığı tamamlandıktan sonra, elde edilen ID2D1Image (Win2D nesnelerinde olduğu gibi, D2D efekti de bir görüntüdür), henüz çağıran tarafından bilinmeyen hedef bağlamda Win2D tarafından çizime hazır olmalıdır.

Uyarı

Bu yöntemin (ve genel olarak ) doğru şekilde uygulanması son derece karmaşık ve yalnızca ek esnekliğe kesinlikle ihtiyaç duyan ileri düzey kullanıcılar tarafından yapılması amaçlanır. Uygulama yazmaya çalışmadan önce D2D, Win2D, COM, WinRT ve C++ hakkında sağlam bir ICanvasImageInterop anlayış önerilir. Özel Win2D efektinizin özel bir D2D efektini de sarmalamanız gerekiyorsa, kendi ID2D1Effect nesnenizi de uygulamanız gerekir (bu konuda daha fazla bilgi için özel efektlerle ilgili D2D belgelerine bakın). Bu belgeler gerekli tüm mantığın kapsamlı bir açıklaması değildir (örneğin, etki kaynaklarının D2D/Win2D sınırında nasıl sıralanması ve yönetilmesi gerektiğini kapsamaz), bu nedenle Win2D'nin kod tabanındaki uygulamanın özel bir etki için başvuru noktası olarak kullanılması CanvasEffect ve gerektiğinde değiştirilmesi önerilir.

GetBounds Uygulamak

Özelleştirilmiş bir ICanvasImage etkisinin tam olarak uygulanması için eksik olan son bileşen, iki GetBounds aşırı yüklenmesini desteklemektir. Kolaylık sağlamak amacıyla Win2D, herhangi bir özel görüntüde Win2D'nin mevcut mantığından yararlanmak için kullanılabilecek bir C dışa aktarıcı sunar. Dışarı aktarma aşağıdaki gibidir:

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

Özel görüntüler bu API'yi çağırabilir, kendilerini image parametresi olarak iletebilir ve ardından sonucu çağıranlarına döndürebilir. Dönüştürme mevcut değilse, transform parametresi nullolabilir.

Cihaz bağlamı erişimlerini iyileştirme

Çağrıdan önce bir bağlam hemen kullanılamıyorsa, deviceContext'deki ICanvasImageInterop::GetD2DImage parametresi bazen nullolabilir. Bu, kasıtlı olarak yapılır, böylece bir bağlam yalnızca gerçekten gerekli olduğunda tembelce oluşturulur. Yani, bir bağlam mevcutsa, Win2D bunu GetD2DImage çağrısına iletir, aksi takdirde gerekli olduğunda çağrılanların kendi başlarına bir bağlam edinmesine imkan tanır.

Cihaz bağlamı oluşturmak nispeten maliyetlidir, bu yüzden bir cihaz bağlamını daha hızlı almak için Win2D, kendi iç cihaz bağlam havuzuna erişim sağlayan API'ler sunar. Bu, özel efektlerin belirli bir tuval cihazıyla ilişkili cihaz bağlamlarını verimli bir şekilde kiralamasını ve döndürmesini sağlar.

Cihaz bağlamı kiralama API'leri aşağıdaki gibi tanımlanır:

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

ID2D1DeviceContextPool arabirimini uygulayan tür olan Win2D, CanvasDevice arabirimini ICanvasDevice tarafından uygular. Havuzu kullanmak için cihaz arabiriminde QueryInterface kullanarak bir ID2D1DeviceContextPool referansı alın ve ardından ID2D1DeviceContextPool::GetDeviceContextLease çağırarak cihaz bağlamına erişmek üzere bir ID2D1DeviceContextLease nesnesi edinin. Bu artık gerekli olmadığında kirayı serbest bırakın. Kira süresi dolduğunda aygıt bağlamına dokunmamaya özen gösterin, çünkü başka iş parçacıkları tarafından eş zamanlı olarak kullanılabilir.

WinRT sarmalayıcı aramasını etkinleştirme

Win2D birlikte çalışma belgelerindegörüldüğü gibi, Win2D açık üst bilgi aynı zamanda bir GetOrCreate yöntemi açıklar. Bu yöntem ICanvasFactoryNative etkinleştirme fabrikasından veya aynı üst bilgide tanımlanan GetOrCreate C++/CX yardımcılarından erişilebilir. Bu, belirli bir yerel kaynaktan WinRT sarmalayıcısını almaya olanak tanır. Örneğin, bir CanvasDevice nesnesinden bir ID2D1Device1 örneği, bir CanvasBitmap'ten bir ID2D1Bitmap vb. almanıza veya oluşturmanıza olanak tanır.

Bu yöntem tüm yerleşik Win2D efektleri için de çalışır: belirli bir efekt için yerel kaynağın alınması ve ilgili Win2D sarmalayıcısını almak için bunun kullanılması, bu etki için sahip olan Win2D efektini doğru bir şekilde döndürür. Özel efektlerin aynı eşleme sisteminden de faydalanabilmesi için Win2D, birlikte çalışma arabiriminde CanvasDevice için etkinleştirme fabrikası olan ICanvasFactoryNative türünü ve ek bir efekt fabrikası arabirimi olan ICanvasEffectFactoryNativeiçeren çeşitli API'leri kullanıma açar:

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

Burada göz önünde bulundurulması gereken birkaç API vardır, çünkü Win2D efektlerinin kullanılabilmesi için çeşitli senaryoların yanı sıra geliştiricilerin D2D katmanıyla birlikte çalışmayı nasıl gerçekleştirebileceği ve ardından bunlar için sarmalayıcıları çözümlemeye çalışması gerekir. Şimdi bu API'lerin her birinin üzerinden geçelim.

RegisterWrapper ve UnregisterWrapper yöntemleri, kendilerini iç Win2D önbelleğine eklemek için özel efektler tarafından çağrılmaya yöneliktir:

  • RegisterWrapper: Yerel bir kaynağı ve bu kaynağa sahip olan WinRT sarmalayıcısını kaydeder. wrapper parametresinin IWeakReferenceSource'i de implement etmek için gerekli olması, bellek sızıntılarına neden olabilecek başvuru döngülerinden kaçınarak doğru şekilde önbelleğe alınabilmesine olanak tanır. Yöntem, yerel kaynak önbelleğe eklenebildiğinde S_OK döndürür, S_FALSE için zaten kayıtlı bir sarmalayıcı varsa resource döndürür ve hata meydana geldiğinde bir hata kodu döndürür.
  • UnregisterWrapper: Yerel kaynağın ve sarmalayıcının kaydını kaldırıyor. Kaynağın kaldırılması durumunda S_OK, S_FALSE henüz kaydedilmediyse resource ve başka bir hata oluşursa bir hata kodu döndürür.

Özel efektler, yeni bir yerel kaynak oluşturulup bunlarla ilişkilendirildiğinde veya bu kaynaklarla ilişkilendirme sona erdiğinde RegisterWrapper ve UnregisterWrapper'i çağırmalıdır. Gerçekleştirmeyi desteklemeyen özel efektler (örneğin, sabit bir ilişkili cihaza sahip olanlar) oluşturulduğunda ve yok edildiğinde RegisterWrapper ve UnregisterWrapper çağrı yapabilir. Özel efektler, sarmalayıcının geçersiz hale gelmesine neden olabilecek tüm olası kod yollarından kendilerini doğru bir şekilde kayıttan çıkarmalıdır (örneğin, nesne yönetilen bir dilde uygulanıyorsa, nesnenin ne zaman sonlandırıldığı da dahil olmak üzere).

RegisterEffectFactory ve UnregisterEffectFactory yöntemleri, özel efektler tarafından da kullanılmak üzere tasarlanmıştır; böylece bir geliştirici "bağımsız bırakılmış" bir D2D kaynağı için bir sarmalayıcı çözmeye çalıştığı durumda, yeni bir sarmalayıcı oluşturmak için bir geri çağırma kaydedebilirler.

  • RegisterEffectFactory: bir geliştiricinin GetOrCreateile aynı parametrelerin girişini alan bir geri çağırma kaydetmek ve giriş efekti için yeni bir denetlenebilir sarmalayıcı oluşturmak amacıyla. Efekt kimliği bir anahtar olarak kullanılır, böylece her özel efekt ilk yüklendiğinde bunun için bir fabrikayı kaydedebilir. Tabii ki, bu efekt türü başına yalnızca bir kez yapılmalıdır, etki her gerçekleştiğinde yapılmamalıdır. Win2D, herhangi bir kayıtlı geri çağırmayı çağırmadan önce device, resource ve wrapper parametrelerini denetler, bu nedenle null çağrıldığında bu parametrelerin CreateWrapper olmaması garanti edilir. dpi isteğe bağlı olarak kabul edilir ve efekt türünün belirli bir kullanımı olmaması durumunda yoksayılabilir. Kayıtlı bir fabrikadan yeni sarmalayıcı oluşturulduğunda, bu fabrikanın yeni sarmalayıcının önbellekte kayıtlı olduğundan da emin olması gerektiğini unutmayın (Win2D, dış fabrikalar tarafından üretilen sarmalayıcıları önbelleğe otomatik olarak eklemez).
  • UnregisterEffectFactory: Önceden kaydedilen bir geri çağırmayı kaldırır. Örneğin, bir etki sarmalayıcısı kaldırılmakta olan yönetilen bir derlemede uygulanırsa bu kullanılabilir.

Uyarı

ICanvasFactoryNative, CanvasDeviceiçin etkinleştirme fabrikası tarafından uygulanır. Bunu ya RoGetActivationFactory'yi manuel olarak çağırarak ya da kullandığınız dil uzantılarından yardımcı API'leri kullanarak alabilirsiniz (örneğin, C++/WinRT'de winrt::get_activation_factory). Daha fazla bilgi için, bunun nasıl çalıştığını öğrenmek için WinRT tür sistemi hakkında bilgi almak üzere'e bakın.

Bu eşlemenin nerede devreye girdiğine ilişkin pratik bir örnek için yerleşik Win2D efektlerinin nasıl çalıştığını göz önünde bulundurun. Bunlar gerçekleştirilmezse, tüm durum (örn. özellikler, kaynaklar vb.) her efekt örneğinde bir iç önbellekte depolanır. Bunlar gerçekleştiğinde, tüm durum yerel kaynağa aktarılır (örneğin, özellikler D2D etkisine göre ayarlanır, tüm kaynaklar çözümlenir ve efekt girişlerine eşlenir vb.) ve etki gerçekleştiği sürece sarmalayıcının durumu üzerinde yetkili olarak hareket eder. Yani, herhangi bir özelliğin değeri sarmalayıcıdan getirilirse, onunla ilişkilendirilmiş yerel D2D kaynağından güncellenmiş değeri alır.

Bu, direkt D2D kaynağında herhangi bir değişiklik yapılırsa, bunların dış kaplamada da görünür olmasını ve iki tarafın hiçbir zaman "senkronize olmamış" olmamasını sağlar. Etki gerçekleşmediğinde, kaynak serbest bırakılmadan önce tüm durum yerel kaynaktan sarmalayıcı duruma geri aktarılır. Etkinin bir sonraki gerçekleştirilişine kadar orada tutulacak ve güncelleştirilecektir. Şimdi şu olay dizisini göz önünde bulundurun:

  • Win2D efektiniz vardır (yerleşik veya özel).
  • ID2D1Image'ı alırsınız (bu bir ID2D1Effect).
  • Özel bir efektin örneğini oluşturursunuz.
  • Bundan da ID2D1Image alırsınız.
  • Bu görüntüyü önceki efektin girişi olarak el ile ayarlarsınız (aracılığıyla ID2D1Effect::SetInput).
  • Ardından, bu girişin WinRT sarmalayıcısı için ilk etkisini talep edersiniz.

Etki gerçekleştiğinden (yerel kaynak istendiğinde gerçekleştirildi), gerçeğin kaynağı olarak yerel kaynağı kullanır. Bu nedenle, istenen kaynağa karşılık gelen ID2D1Image'ı alır ve bunun için WinRT sarmalayıcısını elde etmeye çalışır. Bu girişin alındığı etki kendi yerel kaynak çiftini ve WinRT sarmalayıcısını Win2D önbelleğine doğru şekilde eklemişse, sarmalayıcı çözümlenir ve çağıranlara döndürülür. Aksi takdirde, Win2D sahip olmadığı efektler için WinRT sarmalayıcılarını çözemez, çünkü bunların nasıl oluşturulacağını bilmez. Bu nedenle, bu özellik erişimi başarısız olur.

RegisterWrapper ve UnregisterWrapper burada yardım ederler, çünkü özel efektlerin Win2D'nin sarmalayıcı çözümleme mantığına sorunsuz bir şekilde katılmasını sağlarlar. Bu sayede, efekt kaynağı WinRT API'lerinden veya temel alınan D2D katmanından doğrudan ayarlanmış olsa bile, doğru sarmalayıcı her zaman alınabilir.

Etki fabrikalarının da nasıl devreye girdiği hakkında bilgi edinmek için şu senaryoyu göz önünde bulundurun:

  • Kullanıcı özel sarmalayıcının bir örneğini oluşturur ve bunu fark eder
  • Ardından, onlar temel alınan D2D etkisine bir referans alır ve bunu korur.
  • Ardından etki farklı bir cihazda gerçekleştirilir. Etki gerçekleşmeyecek ve yeniden gerçekleşecek, ve bu süreçte yeni bir D2D efekti oluşturacaktır. Önceki D2D etkisi artık bir ilişkili denetlenebilir sarmalayıcı olarak işlev görmüyor.
  • Kullanıcı daha sonra ilk D2D efektini çağırır GetOrCreate .

Geri çağırma olmadan Win2D, çözülmesi gereken sarmalayıcı için bir kayıt bulunmadığından, yalnızca sarmalayıcıyı çözümlemekte başarısız olur. Bunun yerine bir fabrika kayıtlıysa, bu D2D efekti için yeni bir sarmalayıcı oluşturulabilir ve döndürülebilir, bu nedenle senaryo kullanıcı için sorunsuz çalışmaya devam eder.

Özel ICanvasEffect'ı uygulamak

Win2D ICanvasEffect arabirimi genişletir ICanvasImage, bu nedenle önceki tüm noktalar özel efektler için de geçerlidir. Tek fark, bir kaynak dikdörtgenin ICanvasEffect geçersiz kılınması, gerekli dikdörtgenlerin alınması vb. gibi efektlere özgü ek yöntemlerin de uygulanmasıdır.

Bunu desteklemek için Win2D, özel efekt yazarlarının kullanabileceği C dışarı aktarmalarını kullanıma sunar, böylece tüm bu ek mantığı sıfırdan yeniden uygulamak zorunda kalmazlar. Bu, GetBounds için C dışa aktarma yöntemiyle aynı şekilde çalışır. Efektler için kullanılabilir ihraçlar şunlardır:

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

Şimdi bunların nasıl kullanılabileceğini ele alalım:

  • InvalidateSourceRectangleForICanvasImageInterop, InvalidateSourceRectangle'i desteklemek içindir. Giriş parametrelerini hazırlamanız ve doğrudan çağırmanız yeterlidir ve gerekli tüm çalışmalarla ilgilenir. parametresinin image uygulanmakta olan geçerli efekt örneği olduğuna dikkat edin.
  • GetInvalidRectanglesForICanvasImageInterop destekler GetInvalidRectangles. Bu ayrıca, döndürülen COM dizisinin artık gerekli olmadığında atılması dışında özel bir değerlendirme gerektirmez.
  • GetRequiredSourceRectanglesForICanvasImageInterop hem GetRequiredSourceRectangle hem de GetRequiredSourceRectangles destekleyebilen paylaşılan bir yöntemdir. Başka bir ifadeyle, doldurulacak mevcut bir değer dizisinin işaretçisini alır, böylece çağıranlar tek bir değere (bir ayırmayı önlemek için yığında da olabilir) veya bir değer dizisine bir işaretçi geçirebilir. Her iki durumda da uygulama aynıdır, bu nedenle her ikisini de desteklemek için tek bir C dışarı aktarma yeterlidir.

ComputeSharp kullanarak C# dilinde özel efektler

Belirttiğimiz gibi, C# kullanıyorsanız ve özel bir etki uygulamak istiyorsanız, önerilen yaklaşım ComputeSharp kitaplığını kullanmaktır. Hem özel D2D1 piksel gölgelendiricilerini tamamen C# dilinde uygulamanıza hem de Win2D ile uyumlu özel efekt graflarını kolayca tanımlamanıza olanak tanır. Aynı kitaplık, uygulamadaki çeşitli grafik bileşenlerini desteklemek için Microsoft Store'da da kullanılır.

Projenizde NuGet aracılığıyla ComputeSharp'a başvuru ekleyebilirsiniz: ComputeSharp.D2D1.WinUI paketini seçin.

Uyarı

ComputeSharp.D2D1.* içindeki birçok API, UWP ve WinUI hedeflerinde aynıdır; yalnızca namespace'ler arasında (ya .Uwp ya da .WinUI ile biter) fark vardır. Ancak UWP hedefi sürekli bakımda ve yeni özellikler almıyor. Bu nedenle, WinUI için burada gösterilen örneklerle karşılaştırıldığında bazı kod değişiklikleri gerekebilir. Bu belgedeki kod parçacıkları ComputeSharp.D2D1.WinUI.0.0 itibarıyla API yüzeyini yansıtır (UWP hedefinin son sürümü 2.1.0'dır).

ComputeSharp'ta Win2D ile birlikte çalışabilir iki ana bileşen vardır:

  • PixelShaderEffect<T>: D2D1 piksel gölgelendiricisi tarafından desteklenen bir Win2D efekti. Gölgelendiricinin kendisi, ComputeSharp tarafından sağlanan API'ler kullanılarak C# dilinde yazılır. Bu sınıf ayrıca efekt kaynaklarını, sabit değerleri ve daha fazlasını ayarlamak için özellikler sağlar.
  • CanvasEffect: Rastgele bir efekt grafını sarmalayan özel Win2D efektleri için temel sınıf. Karmaşık efektleri bir uygulamanın çeşitli bölümlerinde yeniden kullanılabilecek kullanımı kolay bir nesneye "paketlemek" için kullanılabilir.

Burada, shadertoy gölgelendirici'den çevrilmiş özel bir piksel gölgelendirici örneği verilmiştir. Bu, PixelShaderEffect<T> ile birlikte kullanılır ve ardından Win2D CanvasControl üzerine çizilir (PixelShaderEffect<T>'ün ICanvasImageuyguladığını unutmayın).

Win2D denetimi üzerine çizilen ve uygulama penceresinde çalışırken görüntülenen, sonsuz renkli altıgenleri gösteren bir örnek piksel gölgelendirici

Yalnızca iki kod satırıyla nasıl etki oluşturabileceğinizi ve Win2D aracılığıyla nasıl çizebileceğinizi görebilirsiniz. ComputeSharp, gölgelendiriciyi derlemek, kaydetmek ve Win2D uyumlu bir efektin karmaşık ömrünü yönetmek için gereken tüm işleri üstlenir.

Şimdi de özel D2D1 piksel gölgelendiricisi kullanan özel bir Win2D efekti oluşturma hakkında adım adım kılavuza göz atalım. ComputeSharp ile gölgelendirici yazma ve özelliklerini ayarlama ve ardından uygulamanızda kolayca yeniden kullanılabilecek bir CanvasEffect türe paketlenmiş özel efekt grafı oluşturma adımlarını gözden geçireceğiz.

Efekti tasarlama

Bu tanıtım için basit bir buzlu cam efekti oluşturmak istiyoruz.

Bu, aşağıdaki bileşenleri içerir:

  • Gauss bulanıklığı
  • Renk tonu efekti
  • Gürültü (bir shader ile yordamsal olarak üretebildiğimiz)

Ayrıca bulanıklık ve gürültü miktarını denetlemek için özellikleri kullanıma sunmamız gerekir. Son efekt, bu efekt grafiğinin "paketlenmiş" bir sürümünü içerir ve yalnızca bir örnek oluşturarak, bu özellikleri ayarlayarak, bir kaynak görüntüyü bağlayarak ve sonra çizerek kolayca kullanılabilir. Haydi başlayalım!

Özel D2D1 piksel gölgelendiricisi oluşturma

Efektin üzerindeki gürültü için basit bir D2D1 piksel gölgelendirici kullanabiliriz. Gölgelendirici, koordinatlarına göre rastgele bir değer hesaplar (rastgele sayı için bir "çekirdek" görevi görür) ve ardından bu pikselin RGB miktarını hesaplamak için bu kirlilik değerini kullanır. Ardından bu gürültüyü sonuçta elde edilen görüntünün üzerine karıştırabiliriz.

ComputeSharp ile bir gölgelendirici yazmak için, önce partial struct türünde ve ID2D1PixelShader arabirimini uygulayan bir tanım yapmamız, ardından ise mantığımızı Execute yöntemine yazmamız yeterlidir. Bu gürültü gölgelendiricisi için aşağıdakine benzer bir şey yazabiliriz:

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

Uyarı

Gölgelendirici tamamen C# dilinde yazılmış olsa da, DirectX gölgelendiricileri için programlama dili olan HLSL hakkında temel bilgi önerilir (ComputeSharp, C# kodunu HLSL'ye dönüştürür).

Şimdi bu gölgelendiricinin üzerinden ayrıntılı olarak geçelim:

  • Gölgelendiricinin girişi yoktur, sadece rastgele gri tonlamalı gürültü içeren sonsuz bir görüntü üretir.
  • Gölgelendirici geçerli piksel koordinatlarına erişim gerektirir.
  • Gölgelendirici, yapım sırasında önceden derlenmiştir (uygulamanın çalışabileceği herhangi bir GPU'da bulunması garanti edilen PixelShader40 profili kullanılarak).
  • [D2DGeneratedPixelShaderDescriptor] Özniteliği, ComputeSharp ile birlikte gelen kaynak oluşturucuyu tetiklemek için gereklidir. Bu, C# kodunu analiz edecek, HLSL'ye çevirecek, gölgelendiriciyi bayt koduna derleyecektir.
  • Gölgelendirici, float amount aracılığıyla bir parametre yakalar. ComputeSharp'taki kaynak oluşturucu, gölgelendiricide yakalanan tüm değerleri ayıklamayı ve gölgelendirici durumunu başlatmak için D2D'nin ihtiyaç duyduğu sabit arabelleği hazırlamayı otomatik olarak üstlenir.

Ve bu kısım bitti! Bu gölgelendirici, ihtiyaç duyulduğunda özel gürültü dokumuzu oluşturur. Ardından, tüm efektlerimizi birbirine bağlayan efekt grafiğiyle paketlenmiş efektimizi oluşturmamız gerekiyor.

Özel efekt oluşturma

Kullanımı kolay, paketlenmiş efektimiz için ComputeSharp'tan CanvasEffect türünü kullanabiliriz. Bu tür, bir efekt grafiği oluşturmak ve efektin kullanıcılarının etkileşim kurabileceği genel özellikler aracılığıyla güncelleştirmek için gerekli tüm mantığı ayarlamak için basit bir yol sağlar. Uygulamamız gereken iki ana yöntem vardır:

  • BuildEffectGraph: Bu yöntem, çizmek istediğimiz efekt grafiğini oluşturmakla sorumludur. Başka bir ifadeyle ihtiyacımız olan tüm efektleri oluşturması ve çıkış düğümünü graf için kaydetmesi gerekir. Daha sonra güncelleştirilebilecek efektler için kayıt, gerektiğinde grafikten efektleri almak için arama anahtarı işlevi gören ilişkili CanvasEffectNode<T> bir değerle gerçekleştirilir.
  • ConfigureEffectGraph: Bu yöntem, kullanıcının yapılandırdığı ayarları uygulayarak efekt grafiğini yeniler. Bu yöntem gerektiğinde, efekti çizmeden hemen önce ve yalnızca efektin son kullanıldığından bu yana en az bir efekt özelliği değiştirilmişse otomatik olarak çağrılır.

Özel etkimiz aşağıdaki gibi tanımlanabilir:

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

Bu sınıfta dört bölüm olduğunu görebilirsiniz:

  • İlk olarak, güncellenebilecek efektlerin yanı sıra, etkinin kullanıcılarına göstermek istediğimiz tüm efekt özellikleri için destekleyici alanlar gibi tüm değiştirilebilir durumu izlemek için alanlarımız vardır.
  • Ardından, efekti yapılandıracak özelliklerimiz vardır. Her özelliğin ayarlayıcısı, SetAndInvalidateEffectGraph tarafından kullanıma sunulan CanvasEffect yöntemini kullanır, bu yöntem ayarlanan değer geçerli değerden farklıysa efekti otomatik olarak geçersiz kılacaktır. Bu, etkinin yalnızca gerçekten gerekli olduğunda yeniden yapılandırılmasını sağlar.
  • Son olarak, yukarıda bahsettiğimiz BuildEffectGraph ve ConfigureEffectGraph yöntemlerine sahibiz.

Uyarı

Gürültü etkisinden sonraki PremultiplyEffect düğümü çok önemlidir çünkü Win2D efektleri, çıkışın önceden çarpılmış olduğunu varsayar, oysa piksel gölgelendiricileri genellikle önceden çarpılmamış piksellerle çalışır. Bu nedenle, renklerin doğru şekilde korundığından emin olmak için özel gölgelendiricilerin önüne ve arkasına el ile premultiply/unpremultiply düğümleri eklemeyi unutmayın.

Çizmeye hazır!

Ve bununla, özel buzlu cam efektimiz hazır! Aşağıdaki gibi kolayca çizebiliriz:

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

    args.DrawingSession.DrawImage(effect);
}

Bu örnekte, daha önce kaynak olarak yüklediğimiz bir Draw kullanarak bir CanvasControlCanvasBitmap işleyicisinden efekti çiziyoruz. Bu, efekti test etmek için kullanacağımız giriş görüntüsüdür:

bulutlu bir gökyüzünün altındaki bazı dağların resmi

Sonuç şu şekildedir:

yukarıdaki resmin bulanık bir sürümü

Uyarı

Fotoğraf için Dominic Lange'e teşekkürler.

Ek kaynaklar