Najszybszy sposób renderowania linii z AA, o różnej grubości w DirectX

14

Zajmuję się więc tworzeniem DirectX, a dokładniej korzystam z SharpDX w .NET (ale rozwiązania API DirectX / C ++ mają zastosowanie). Szukam najszybszego sposobu renderowania linii w rzucie ortogonalnym (np. Symulowanie rysowania linii 2D dla aplikacji naukowych) przy użyciu DirectX.

Zrzut ekranu z rodzajami wykresów, które próbuję wyrenderować: wprowadź opis zdjęcia tutaj

Często zdarza się, że tego rodzaju wykresy mają linie z milionami segmentów, o zmiennej grubości, z lub bez antyaliasingu na linię (lub włączanie / wyłączanie pełnoekranowego AA). Muszę bardzo często aktualizować wierzchołki dla linii (np. 20 razy / sekundę) i odciążyć jak najwięcej do GPU.

Do tej pory próbowałem:

  1. Renderowanie oprogramowania, np. GDI +, faktycznie nie jest złą wydajnością, ale oczywiście ma duży wpływ na procesor
  2. Direct2D API - wolniejszy niż GDI, szczególnie przy włączonym wygładzaniu krawędzi
  3. Direct3D10 przy użyciu tej metody do emulacji AA przy użyciu kolorów wierzchołków i teselacji po stronie procesora. Również powoli (profilowałem to i 80% czasu spędzam na obliczaniu pozycji wierzchołków)

W przypadku trzeciej metody używam buforów wierzchołków do wysyłania paska trójkąta do GPU i aktualizuję co 200 ms nowymi wierzchołkami. Otrzymuję częstotliwość odświeżania około 5 klatek na sekundę dla 100 000 segmentów linii. Idealnie potrzebuję milionów!

Teraz myślę, że najszybszym sposobem byłoby wykonanie teselacji na GPU, np. W module Geometry Shader. Mógłbym wysłać wierzchołki jako listę linii lub spakować teksturę i rozpakować w module cieniującym geometrię, aby utworzyć quady. Lub po prostu wyślij nieprzetworzone punkty do modułu cieniującego piksele i zaimplementuj rysowanie linii Bresenhama całkowicie w module cieniującym piksele. Mój HLSL jest zardzewiały, moduł cieniujący 2 z 2006 roku, więc nie wiem o szalonych rzeczach, które mogą zrobić współczesne procesory graficzne.

Pytanie brzmi zatem: - czy ktoś zrobił to wcześniej i czy masz jakieś sugestie do wypróbowania? - Czy masz jakieś sugestie dotyczące poprawy wydajności dzięki szybkiej aktualizacji geometrii (np. Nowa lista wierzchołków co 20 ms)?

AKTUALIZACJA 21 stycznia

Od tamtej pory zaimplementowałem metodę (3) powyżej za pomocą shaderów geometrii za pomocą LineStrip i dynamicznych buforów wierzchołków. Teraz dostaję 100 FPS przy 100 000 punktów i 10 FPS przy 1 000 000 punktów. To ogromna poprawa, ale teraz mam ograniczony współczynnik wypełnienia i ograniczoną moc obliczeniową, więc pomyślałem o innych technikach / pomysłach.

  • Co z wystąpieniem sprzętowym geometrii segmentu linii?
  • Co z Sprite Batch?
  • Co z innymi metodami (Pixel Shader)?
  • Czy mogę efektywnie zrezygnować z GPU lub procesora?

Twoje uwagi i sugestie są mile widziane!

Dr. ABT
źródło
1
Co z natywnym AA w Direct3D?
API-Beast
5
Czy możesz pokazać kilka przykładowych krzywych, które chcesz narysować? Wspominasz o milionach wierzchołków, ale twój ekran prawdopodobnie nie ma więcej niż milion pikseli , czy ta ilość jest naprawdę potrzebna? Wydaje mi się, że wszędzie nie będziesz potrzebować tak dużej gęstości danych. Czy zastanawiałeś się nad LOD?
sam hocevar
1
Drugie podejście, które połączyłeś, wydaje się dobre: ​​używasz dynamicznego bufora wierzchołków, który aktualizujesz, czy tworzysz nowy za każdym razem?
1
Prawdopodobnie powinieneś użyć dynamicznego bufora wierzchołków (niewiele pracy, po prostu powiedz, że bufor wierzchołków jest dynamiczny, mapuj bufor, skopiuj dane, usuń mapę), ale jeśli wąskim gardłem jest generowanie wierzchołków, to prawdopodobnie wygrał niewiele teraz pomaga. Nie myśl, że jest coś szalonego w używaniu GS do zmniejszenia obciążenia procesora
1
Jeśli liczba wierzchołków jest zbyt duża dla GPU, nadal możesz próbować zmniejszać próbkowanie danych wejściowych - tak naprawdę nie potrzebujesz setek milionów segmentów linii dla pojedynczej krzywej, gdy ekran ma szerokość kilku tysięcy pikseli najbardziej.

Odpowiedzi:

16

Jeśli zamierzasz renderować Y = f(X)tylko wykresy, sugeruję wypróbowanie następującej metody.

Dane krzywej są przekazywane jako dane tekstury , dzięki czemu są trwałe i umożliwiają na przykład częściowe aktualizacje glTexSubImage2D. Jeśli potrzebujesz przewijania, możesz nawet zaimplementować bufor okrągły i zaktualizować tylko kilka wartości na ramkę. Każda krzywa jest renderowana jako quad pełnoekranowy i cała praca jest wykonywana przez moduł cieniujący piksele.

Zawartość tekstury jednoskładnikowej może wyglądać następująco:

+----+----+----+----+
| 12 | 10 |  5 | .. | values for curve #1
+----+----+----+----+
| 55 | 83 | 87 | .. | values for curve #2
+----+----+----+----+

Działanie modułu cieniującego piksele jest następujące:

  • znajdź współrzędną X bieżącego fragmentu w przestrzeni zestawu danych
  • weź np. 4 najbliższe punkty danych, które mają dane; Na przykład, jeżeli wartość X jest 41.3to wybrać 40, 41, 42i 43.
  • zapytanie tekstury o wartości 4 Y (upewnij się, że próbnik nie interpoluje żadnego rodzaju)
  • zamień X,Ypary na miejsce na ekranie
  • obliczyć odległość od bieżącego fragmentu do każdego z trzech segmentów i czterech punktów
  • użyj odległości jako wartości alfa dla bieżącego fragmentu

Możesz zastąpić 4 większymi wartościami w zależności od potencjalnego poziomu powiększenia.

Napisałem bardzo szybki i brudny moduł cieniujący GLSL implementujący tę funkcję . Mogę dodać wersję HLSL później, ale powinieneś być w stanie przekonwertować ją bez większego wysiłku. Wynik można zobaczyć poniżej, przy różnych rozmiarach linii i gęstości danych:

Krzywe

Jedną wyraźną zaletą jest to, że ilość przesyłanych danych jest bardzo mała, a liczba wezwań jest tylko jedna.

sam hocevar
źródło
To całkiem fajna technika - robiłem już GPGPU, więc pakowanie tekstur z danymi to coś, o czym wiem, ale nie jest to coś, co rozważałem. Jaki jest największy rozmiar (np. Największy zestaw danych, jaki można załadować?). Pobieram kod, jeśli nie masz nic przeciwko i bawię się nim - chętnie zobaczę występ.
Dr ABT
@ Dr.ABT Rozmiar zestawu danych jest ograniczony tylko maksymalnym rozmiarem tekstury. Nie rozumiem, dlaczego miliony punktów nie działałyby, biorąc pod uwagę odpowiedni układ danych. Pamiętaj, że mój kod z pewnością nie jest jakością produkcyjną, ale w razie problemów skontaktuj się ze mną prywatnie.
sam hocevar
Pozdrawiam @SamHocevar - spróbuję. Minęło trochę czasu, odkąd skompilowałem przykładowy umysł OpenGL! :-)
Dr. ABT
Oznaczając jako odpowiedź, ponieważ chociaż nie używam ładowania tekstur, przedstawiłeś prawdziwy przykład OpenGL, który mógłby narysować linie na module cieniującym piksele i skierować nas we właściwym kierunku !! :)
Dr. ABT
4

Był rozdział o Klejnotach GPU na temat renderowania linii antyaliasingowych: Szybkie wstępnie filtrowane linie . Podstawową ideą jest renderowanie każdego segmentu linii jako kwadratu i obliczanie, przy każdym pikselu, funkcji Gaussa odległości środka piksela od segmentu linii.

Oznacza to narysowanie każdego segmentu linii na wykresie jako osobnego kwadratu, ale w D3D11 można z pewnością użyć modułu cieniującego geometrię i / lub instancji do wygenerowania kwadratów, zmniejszając ilość danych do przesłania do GPU do samych punktów danych sami. Prawdopodobnie ustawiłbym punkty danych jako StructuredBuffer do odczytu przez moduł cieniujący wierzchołek / geometrię, a następnie wykonałem wywołanie rysujące określające liczbę segmentów do narysowania. Nie byłoby żadnych rzeczywistych buforów wierzchołków; moduł cieniujący wierzchołek użyłby po prostu SV_VertexID lub SV_InstanceID, aby określić, które punkty danych należy obejrzeć.

Nathan Reed
źródło
Hej, dziękuję - tak, znam ten artykuł, niestety nie ma kodu źródłowego :-( Wydaje się, że GeoShader + FragShader jest zwycięzcą w tego typu pracach. Wydajne przesyłanie danych do GPU będzie podstępna część!
Dr ABT
Hej @NathanReed wracając do tego - jeśli użyłem Geometru Shadera lub instancji do narysowania quadów, to Point1 / Point2 są przeciwnymi wierzchołkami kwadratu, jak w Pixel Shaderze mogę obliczyć odległość do linii między Pt1 i Pt2? Czy moduł cieniujący pikseli ma wiedzę na temat geometrii wejściowej?
Dr ABT
@ Dr.ABT Parametry linii matematycznej należy przesłać do modułu cieniującego za pomocą interpolatorów. Moduł cieniujący pikseli nie ma bezpośredniego dostępu do geometrii.
Nathan Reed
Rozumiem, czy można tego dokonać, wysyłając dane wyjściowe z GeometryShadera lub instancję? Aby uzyskać dodatkowy kredyt (jeśli jesteś zainteresowany), dodałem Q tutaj: gamedev.stackexchange.com/questions/47831/...
Dr ABT
@ Dr.ABT Dokładnie w ten sam sposób współrzędne tekstury, normalne wektory i inne tego typu rzeczy są normalnie wysyłane do modułu cieniującego piksele - poprzez zapisywanie danych wyjściowych z modułu cieniującego wierzchołek / geometria, które są interpolowane i wprowadzane do modułu cieniującego piksele.
Nathan Reed
0

@ Dr.ABT - Przepraszamy za to pytanie, a nie odpowiedź. Nie znalazłem sposobu, aby o to zapytać w odpowiedzi Sama Hocevara powyżej.

Czy mógłbyś podzielić się szczegółowymi informacjami na temat tego, jak w końcu zaimplementowałeś linie na swoim wykresie? Mam taką samą potrzebę aplikacji WPF. Z góry dziękuję za wszelkie szczegóły lub kod, który możesz udostępnić na ten temat.

ErcGeek
źródło
jak wiesz, że to nie jest odpowiedź, edytuj i umieść go w komentarzu
MephistonX
Próbowałem to zrobić, ale w oryginalnym poście nie ma przycisku „dodaj komentarz”.
ErcGeek
Cześć ErcGeek, Niestety nie mogę udostępnić ostatecznego rozwiązania, ponieważ informacje te są zastrzeżone dla mojej firmy. Chociaż powyższe przykłady i sugestie wprowadzają nas na właściwą drogę. Wszystkiego najlepszego!
Dr ABT
Nie możesz publikować komentarzy, zanim osiągniesz określoną reputację. A wszystkie te opinie z pewnością nie zapewnią ci takiej możliwości.
Mathias Lykkegaard Lorenzen
Facet nie powinien być karany za to, że chce poznać końcowy wynik i poprosić o to w jedyny dostępny mu sposób. Poparł odpowiedź.
David Ching