This is the Polish translation of Advanced-OpenGL/Advanced-Data article of learnopengl.com tutorial series.

Od dłuższego czasu używamy buforów w OpenGL do przechowywania danych. Istnieją bardziej interesujące sposoby manipulowania buforami, a także inne interesujące metody przekazywania dużych ilości danych do shaderów za pośrednictwem tekstur. W tym samouczku omówimy bardziej interesujące funkcje operujące na buforach i sposób wykorzystania obiektów tekstur do przechowywania dużych ilości danych (część samouczka dot. tekstur nie została jeszcze napisana).

Bufor OpenGL jest tylko obiektem, który zarządza częścią pamięci i niczym więcej. Ustawiamy znaczenie bufora po powiązaniu go z określonym przeznaczeniem bufora (ang. buffer target). Bufor to tylko bufor tablicy wierzchołków, gdy wiążemy go z GL_ARRAY_BUFFER, ale możemy go też łatwo połączyć z GL_ELEMENT_ARRAY_BUFFER. OpenGL wewnętrznie przechowuje bufor na każde jego przeznaczenie i bazując na tym przeznaczeniu przetwarza bufory w inny sposoby.

Do tej pory wypełniamy pamięć zarządzaną przez obiekty bufora, wywołując glBufferData, która przydziela kawałek pamięci i dodaje dane do tej pamięci. Gdybyśmy przekazali NULL jako argument danych, funkcja przydzieliłaby tylko pamięć i jej nie wypełniała. Jest to użyteczne, jeśli najpierw chcemy zarezerwować określoną ilość pamięci, a następnie powrócić do tego bufora, aby wypełnić go kawałek po kawałku.

Zamiast wypełniać cały bufor jednym wywołaniem funkcji, możemy również wypełnić określone obszary bufora, wywołując glBufferSubData. Ta funkcja przyjmuje jako argument przeznaczenie bufora, przesunięcie, rozmiar danych i rzeczywiste dane. Nowością w tej funkcji jest to, że możemy teraz podać przesunięcie, które określa od jakiego miejsca chcemy wypełnić bufor. To pozwala nam wstawiać/aktualizować tylko niektóre części pamięci bufora. Zwróć uwagę, że bufor powinien mieć wystarczającą ilość pamięci. Wynika z tego, że wywołanie glBufferData jest konieczne przed wywołaniem glBufferSubData na buforze.

    glBufferSubData(GL_ARRAY_BUFFER, 24, sizeof(data), &data); // Zakres: [24, 24 + sizeof(data)]

Jeszcze inną metodą zapisywania danych do bufora jest pobranie wskaźnika do pamięci bufora i bezpośrednie skopiowanie danych do bufora. wywołanie glMapBuffer OpenGL zwraca wskaźnik do pamięci aktualnie powiązanego bufora:

    float data[] = {
      0.5f, 1.0f, -0.35f
      ...
    };
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    // pobierz wskaźnik
    void *ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
    // teraz skopiuj dane do pamięci
    memcpy(ptr, data, sizeof(data));
    // pamiętaj, aby powiedzieć OpenGL, że skończyliśmy z operacjami na wskaźniku
    glUnmapBuffer(GL_ARRAY_BUFFER);

Mówiąc OpenGL, skończyliśmy operację na wskaźniku za pomocą glUnmapBuffer OpenGL wie, że ten wskaźnik będzie już niepotrzebny. Po odpięciu wskaźnika, staje się on niepoprawny.

Korzystanie z glMapBuffer jest przydatne do bezpośredniego mapowania danych bufora, bez wcześniejszego przechowywania ich w pamięci tymczasowej. Pomyśl o bezpośrednim odczytywaniu danych z pliku i kopiowaniu ich do pamięci bufora.

Grupowanie atrybutów wierzchołków

Używając glVertexAttribPointer byliśmy w stanie określić układ atrybutów w buforze wierzchołków. W buforze wierzchołków przeplataliśmy atrybuty; to jest, umieściliśmy współrzędne pozycji, normalne i/lub tekstury obok siebie dla każdego wierzchołka. Teraz, gdy wiemy nieco więcej o buforach, możemy przyjąć inne podejście.

Moglibyśmy również zgrupować wszystkie dane wektora w duże porcje danych według typu atrybutu zamiast ich przeplatania. Zamiast przeplatanego układu 123123123123 stosujemy podejście grupowania 111122223333.

Podczas ładowania danych wierzchołków z pliku zazwyczaj pobierasz tablicę pozycji, wektorów normalnych i/lub tablicę współrzędnych tekstury. Może to kosztować trochę wysiłku, aby połączyć te tablice w jedną dużą tablicę przeplatanych danych. Podejście grupowania jest wtedy prostszym rozwiązaniem, które możemy łatwo wdrożyć za pomocą glBufferSubData:

    float positions[] = { ... };
    float normals[] = { ... };
    float tex[] = { ... };
    // wypełnij bufor
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(positions), &positions);
    glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions), sizeof(normals), &normals);
    glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions) + sizeof(normals), sizeof(tex), &tex);

W ten sposób możemy bezpośrednio przenieść tablice atrybutów wierzchołków jako całość do bufora bez konieczności ich wcześniejszego przetwarzania. Mogliśmy również połączyć je w jedną dużą tablicę i natychmiast wypełnić bufor za pomocą glBufferData, ale funkcja glBufferSubData idealnie nadaje się do takich zadań.

Będziemy również musieli zaktualizować wskaźniki atrybutów wierzchołków, aby odzwierciedlić te zmiany:

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);  
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)(sizeof(positions)));  
    glVertexAttribPointer(
      2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)(sizeof(positions) + sizeof(normals)));  

Zauważ, że parametr stride jest równy rozmiarowi atrybutu wierzchołka, ponieważ następny wektor atrybutów wierzchołka można znaleźć bezpośrednio po jego 3 (lub 2) komponencie.

To daje nam jeszcze inne podejście do ustawiania i określania atrybutów wierzchołków. Korzystanie z obu metod nie przynosi natychmiastowej korzyści OpenGL, jest to w większości bardziej zorganizowany sposób ustawiania atrybutów wierzchołków. Podejście, które wybierzesz, jest oparte wyłącznie na twoich preferencjach i rodzaju aplikacji.

Kopiowanie buforów

Po wypełnieniu buforów danymi możesz chcieć udostępnić te dane innym buforom lub skopiować zawartość bufora do innego bufora. Funkcja glCopyBufferSubData umożliwia nam kopiowanie danych z jednego bufora do drugiego. Prototyp funkcji jest następujący:

    void glCopyBufferSubData(GLenum readtarget, GLenum writetarget, GLintptr readoffset,
                             GLintptr writeoffset, GLsizeiptr size);

Parametry readtarget i writetarget przewidują podanie przeznaczenia buforów, obu buforów uczestniczących w wymianie danych. Możemy na przykład skopiować z bufora VERTEX_ARRAY_BUFFER do bufora VERTEX_ELEMENT_ARRAY_BUFFER, określając odpowiednio te przeznaczenia buforów jako bufory do odczytu (readtarget) i do zapisu (writetarget). Bufory obecnie powiązane z tymi typami przeznaczenia zostaną użyte do operacji kopiowania.

Ale co by było, gdybyśmy chcieli odczytywać i zapisywać dane do dwóch różnych buforów, które są jednocześnie buforami tablicy wierzchołków? Nie możemy powiązać dwóch buforów w tym samym czasie z tym samym przeznaczeniem bufora. Z tego powodu, OpenGL daje nam dwa dodatkowe przeznaczenia buforów o nazwach GL_COPY_READ_BUFFER i GL_COPY_WRITE_BUFFER. Następnie wiążemy wybrane przez nas bufory do nowych typów przeznaczenia i ustawiamy je jako argumenty readtarget i writetarget.

Funkcja glCopyBufferSubData następnie odczytuje dane o rozmiarze size z danego przesunięcia readoffset i zapisuje je w buforze writetarget rozpoczynając od pozycji writeoffset. Przykład kopiowania zawartości dwóch buforów tablic wierzchołków pokazano poniżej:

    float vertexData[] = { ... };
    glBindBuffer(GL_COPY_READ_BUFFER, vbo1);
    glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2);
    glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));

Mogliśmy to również zrobić, poprzez powiązanie tylko bufora writetarget z jednym z nowych typów przeznaczenia buforów:

    float vertexData[] = { ... };
    glBindBuffer(GL_ARRAY_BUFFER, vbo1);
    glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2);
    glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));  

Dzięki dodatkowej wiedzy na temat sposobu manipulowania danymi buforów możemy już z nich korzystać w bardziej interesujący sposób. Im dalej wejdziesz w OpenGL, tym bardziej przydatne stają się te nowe metody zarządzania danymi. W następnym samouczku omówimy obiekty buforów uniformów (ang. uniform buffer objects) i zrobimy dobry użytek z glBufferSubData.