Jak buforować zasoby w moim systemie renderowania homebrew


10

Tło:

Projektuję prosty system renderowania 3D dla architektury typu elementu systemu elementów za pomocą C ++ i OpenGL. System składa się z renderera i wykresu sceny. Kiedy skończę pierwszą iterację mechanizmu renderującego, mogę rozpowszechnić wykres sceny w architekturze ECS. Na razie jest to symbol zastępczy w taki czy inny sposób. Jeśli to możliwe, następujące cele są dla renderera:

  1. Prostota . To jest projekt badawczy i chcę mieć możliwość łatwej zmiany i rozszerzenia moich systemów (stąd podejście ECS).
  2. Wydajność . Moja scena może mieć wiele małych modeli, a także duże objętości o dużej geometrii. Niedopuszczalne jest pozyskiwanie obiektów z kontekstu OGL i geometrii bufora dla każdej ramki renderowania. Dążę do lokalizacji danych, aby uniknąć błędów w pamięci podręcznej.
  3. Elastyczność . Musi być w stanie renderować duszki, modele i objętości (woksele).
  4. Oddzielone . Graf scen może zostać ponownie przekształcony w podstawową architekturę ECS po napisaniu mojego renderera.
  5. Modułowy . Byłoby miło móc wymieniać różne renderery bez zmiany mojego wykresu sceny.
  6. Referencyjna przezroczystość , co oznacza, że ​​w dowolnym momencie mogę nadać jej dowolną prawidłową scenę i zawsze będzie renderować ten sam obraz dla tej sceny. W szczególności ten cel nie jest koniecznie wymagany. Pomyślałem, że pomoże to uprościć serializację scen (będę musiał być w stanie zapisywać i ładować sceny) i dać mi elastyczność w zamianie różnych scen podczas działania w celach testowych / eksperymentalnych.

Problem i pomysły:

Wymyśliłem kilka różnych podejść do wypróbowania, ale mam problemy z buforowaniem zasobów OGL (VAO, VBO, shadery itp.) Dla każdego węzła renderowania. Oto różne koncepcje buforowania, które do tej pory wymyśliłem:

  1. Scentralizowana pamięć podręczna. Każdy węzeł sceny ma identyfikator, a moduł renderujący ma pamięć podręczną, która odwzorowuje identyfikatory w celu renderowania węzłów. Każdy węzeł renderowania zawiera VAO i VBO związane z geometrią. Brak pamięci podręcznej nabywa zasoby i mapuje geometrię do węzła renderowania w pamięci podręcznej. Po zmianie geometrii ustawiana jest brudna flaga. Jeśli moduł renderujący widzi brudną flagę geometrii podczas iteracji przez węzły sceny, ponownie buforuje dane za pomocą węzła renderowania. Po usunięciu węzła sceny zdarzenie jest rozgłaszane, a moduł renderujący usuwa powiązany węzeł renderowania z pamięci podręcznej, zwalniając zasoby. Alternatywnie węzeł jest oznaczony do usunięcia, a moduł renderujący jest odpowiedzialny za jego usunięcie. Myślę, że to podejście najbardziej zbliża się do osiągnięcia celu 6, jednocześnie biorąc pod uwagę 4 i 5. 2 cierpi z powodu dodatkowej złożoności i utraty lokalizacji danych przy wyszukiwaniu mapy zamiast dostępu do tablicy.
  2. Rozproszona pamięć podręczna . Podobnie powyżej, ale każdy węzeł sceny ma węzeł renderowania. Pomija to wyszukiwanie mapy. Aby zająć się lokalizacją danych, węzły renderujące mogą być przechowywane w module renderującym. Następnie węzły sceny mogłyby zamiast tego mieć wskaźniki do renderowania węzłów, a moduł renderujący ustawia wskaźnik na brak pamięci podręcznej. Myślę, że ten rodzaj naśladuje podejście komponentu bytu, więc byłoby spójne z resztą architektury. Problem polega na tym, że teraz węzły scen przechowują dane specyficzne dla implementacji mechanizmu renderującego. Jeśli zmienię sposób renderowania rzeczy w rendererze (np. Renderowanie duszków względem woluminów), muszę teraz zmienić węzeł renderowania lub dodać więcej „komponentów” do węzła sceny (co oznacza również zmianę wykresu sceny). Na plus wydaje się, że jest to najprostszy sposób na uruchomienie mojego renderera pierwszej iteracji.
  3. Rozproszone metadane . Komponent metadanych pamięci podręcznej mechanizmu renderującego jest przechowywany w każdym węźle sceny. Te dane nie są specyficzne dla implementacji, ale raczej zawierają identyfikator, typ i wszelkie inne istotne dane potrzebne pamięci podręcznej. Następnie wyszukiwanie pamięci podręcznej można wykonać bezpośrednio w tablicy przy użyciu identyfikatora, a typ może wskazywać, jakiego rodzaju metody renderowania należy użyć (jak duszki kontra woluminy).
  4. Gość + mapowanie rozproszone . Mechanizm renderujący jest gościem, a węzły sceny są elementami we wzorze gościa. Każdy węzeł sceny zawiera klucz pamięci podręcznej (taki jak metadane, ale tylko identyfikator), którym manipuluje tylko moduł renderujący. Identyfikatora można użyć dla tablicy zamiast uogólnionego wyszukiwania mapy. Mechanizm renderujący może zezwolić węzłowi sceny na wywołanie innej funkcji renderowania w zależności od typu węzła sceny, a identyfikator może być używany przez dowolną pamięć podręczną. Domyślny lub spoza zakresu identyfikator wskazuje na brak pamięci podręcznej.

Jak rozwiązałbyś ten problem? Czy masz jakieś sugestie? Dzięki za przeczytanie mojej ściany tekstu!


1
Czy zrobiłeś jakieś postępy?
Andreas

To niezwykle złożone pytanie i prawdopodobnie powinno zostać podzielone na kilka osobnych pytań. To w zasadzie pytanie „jak zaprojektować silnik?” Moją radą byłoby zaprojektowanie czegoś, co w pierwszej kolejności będzie obsługiwać prostsze komponenty, a następnie dodawanie i refaktoryzowanie funkcji. Poszukaj także książek o projektowaniu silników gier 3D, które powinny obejmować wiele informacji, których szukasz.
Ian Young,

Odpowiedzi:


2

Po ponownym przeczytaniu pytania zdecydowanie czuję, że nadmiernie komplikujesz problem. Dlatego:

  1. Istnieją tylko dwa rodzaje systemu renderowania: Do przodu i Odroczony, z których żaden nie jest zależny od wykresu sceny.

  2. Jedyne problemy z wydajnością, które naprawdę powinieneś mieć przy każdym systemie renderującym, powinny wynikać z dużej liczby poli oraz nieefektywnego modułu cieniującego i klienta.

  3. Chybienie w pamięci podręcznej rzeczywiście zmniejszają wydajność, ale nie są to potwory, które mogą ci się wydawać. 80% ulepszeń wydajności będzie pochodzić z bardziej wydajnego algorytmu. Nie popełniaj błędu podczas wstępnej optymalizacji kodu.

To mówi:

Jeśli używasz scenopisu homebrew, powinieneś już używać interfejsu „Renderer” (lub klasy bazowej) do projektowania części renderującej kodu scenegraph. Wzorzec gościa korzystający z podwójnej wysyłki jest dobrym podejściem, ponieważ możesz używać wielu rodzajów węzłów graficznych, takich jak kolor, tekstura, siatka, transformacja itp. W ten sposób podczas cyklu renderowania wszystko, co renderer musi zrobić, to przejdź najpierw strukturę drzewa sceny, przechodząc jako argument. W ten sposób renderer jest w zasadzie tylko zbiorem shaderów i być może buforem klatek lub dwoma. Powoduje to, że kod wyszukiwania / usuwania nie jest już konieczny dla systemu renderującego, a jedynie sam scenegraph.

Z pewnością istnieją inne sposoby rozwiązania problemów, które napotykasz, ale nie chcę udzielać zbyt długiej odpowiedzi. Tak więc, moja najlepsza rada to najpierw znaleźć coś prostego w działaniu, a następnie rozwinąć go, aby znaleźć jego słabości, a następnie eksperymentować z innymi podejściami i zobaczyć, gdzie ich mocne / słabe strony leżą w praktyce.

Będziesz wtedy dobrze przygotowany do podjęcia świadomej decyzji.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.