Wydaje się, że właściwą odpowiedzią na to jest pominięcie ContentPipeline i użycie Texture2D.FromStream do załadowania tekstur w czasie wykonywania. Ta metoda działa dobrze na PC i choć będzie niewielki wzrost wydajności, mogę to zoptymalizować, gdy będę bliżej daty premiery. Na razie potrzebuję możliwości dynamicznego modyfikowania zawartości zarówno edytora, jak i gry. Po zablokowaniu zawartości mogę to zoptymalizować, wracając do ContentPipeline.
Ponieważ wybrałeś tę trasę, muszę cię ostrzec, że tak naprawdę nie jest tak prosta, jak skorzystanie Texture2D.FromStream
z dwóch powodów:
Problem nr 1 - Brak wstępnie pomnożonej obsługi alfa
XNA4 domyślnie obsługuje teraz tekstury z kolorami we wstępnie zmultiplikowanym formacie alfa. Gdy ładujesz teksturę przez potok zawartości, przetwarzanie jest wykonywane automatycznie. Niestety Texture2D.FromStream
nie robi tego samego, więc wszelkie tekstury wymagające pewnego stopnia przejrzystości zostaną załadowane i renderowane niepoprawnie. Poniżej znajduje się zrzut ekranu ilustrujący problem:
Aby więc uzyskać prawidłowe wyniki, musisz sam wykonać przetwarzanie. Metoda, którą pokażę, wykorzystuje procesor graficzny do przetworzenia, więc jest dość szybka. Został oparty na tym świetnym artykule . Oczywiście możesz również poinstruować, SpriteBatch
aby renderować w starym trybie NonPremultiplyAlpha, ale tak naprawdę nie polecam tego robić.
Problem nr 2 - Nieobsługiwane formaty
Potok treści obsługuje więcej formatów niż Texture2D.FromStream
. W szczególności Texture2D.FromStream
obsługuje tylko png, jpg i gif. Z drugiej strony, potok treści obsługuje bmp, dds, dib, hdr, jpg, pfm, png, ppm i tga. Jeśli spróbujesz załadować obsługiwany format Texture2D.FromStream
, otrzymasz InvalidOperationException
niewiele dodatkowych informacji.
Naprawdę potrzebowałem wsparcia bmp w moim silniku, więc w tym konkretnym przypadku znalazłem obejście, które wydaje się działać dobrze. Nie znam jednak żadnego z innych formatów. Złap z moją metodą polega na tym, że musisz dodać odwołanie do System.Drawing
zestawu do swojego projektu, ponieważ korzysta on z GDI, Image.FromStream
które obsługują więcej formatów niż Texture2D.FromStream
.
Jeśli nie zależy ci na obsłudze bmp, możesz łatwo upuścić tę część mojego rozwiązania i po prostu wykonać wstępnie pomnożone przetwarzanie alfa.
Rozwiązanie - prosta wersja (wolniejsza)
Po pierwsze, oto najprostsze rozwiązanie, jeśli nie zależy ci na obsłudze bmps. W tym przykładzie etap przetwarzania odbywa się całkowicie na CPU. Jest nieco wolniejszy niż alternatywa, którą pokażę poniżej (przeprowadziłem testy porównawcze obu rozwiązań), ale łatwiej zrozumieć:
public static Texture2D FromStream(GraphicsDevice graphicsDevice, Stream stream)
{
Texture2D texture = Texture2D.FromStream(graphicsDevice, stream);
Color[] data = new Color[texture.Width * texture.Height];
texture.GetData(data);
for (int i = 0; i != data.Length; ++i)
data[i] = Color.FromNonPremultiplied(data[i].ToVector4());
texture.SetData(data);
return texture;
}
Jeśli zależy Ci na bmps, musisz najpierw załadować obraz za pomocą GDI, a następnie przekonwertować go do formatu PNG przed przekazaniem go do Texture2D.FromStream
. Oto kod, który to robi:
// Load image using GDI because Texture2D.FromStream doesn't support BMP
using (Image image = Image.FromStream(stream))
{
// Now create a MemoryStream which will be passed to Texture2D after converting to PNG internally
using (MemoryStream ms = new MemoryStream())
{
image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
ms.Seek(0, SeekOrigin.Begin);
texture = Texture2D.FromStream(_graphicsDevice, ms);
}
}
Rozwiązanie - wersja złożona (szybsza)
Wreszcie podejście, które stosuję w moich projektach, polega na wykorzystaniu procesora graficznego do przetwarzania zamiast tego. W tej metodzie musisz utworzyć cel renderowania, poprawnie ustawić niektóre stany mieszania i narysować obraz dwukrotnie za pomocą SpriteBatch. Na koniec przeglądam cały RenderTarget2D i klonuję zawartość do osobnego obiektu Texture2D, ponieważ RenderTarget2D jest niestabilny i nie przetrwa takich rzeczy, jak zmiana rozmiaru bufora, więc bezpieczniej jest wykonać kopię.
Zabawne jest to, że pomimo tego wszystkiego, w moich testach to podejście działało około 3 razy szybciej niż procesor. Jest to więc zdecydowanie szybsze niż przesuwanie każdego piksela i samodzielne obliczanie koloru. Kod jest nieco długi, więc umieściłem go w koszu z kodami:
http://pastie.org/3651642
Po prostu dodaj tę klasę do swojego projektu i użyj jej tak prosto, jak:
TextureLoader textureLoader = new TextureLoader(GraphicsDevice);
Texture2D texture = textureLoader.FromFile("Content/texture.png");
Uwaga: Musisz utworzyć tylko jedną TextureLoader
instancję dla całej gry. Używam również poprawki BMP, ale możesz ją usunąć, jeśli nie potrzebujesz i zyskać sporo wydajności, lub po prostu zostaw needsBmp
parametr jako fałszywy.