Manuals
Manuals




This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this manual in English

Programy compute

Obsługa shaderów obliczeniowych (ang. compute shaders) w Defold jest obecnie w fazie technical preview. Oznacza to, że pewnych funkcji jeszcze brakuje i że API może w przyszłości ulec zmianie.

Shadery obliczeniowe są potężnym narzędziem do wykonywania obliczeń ogólnego przeznaczenia na GPU. Pozwalają wykorzystać równoległą moc obliczeniową GPU do zadań takich jak symulacje fizyki, przetwarzanie obrazów i wiele innych. Shader obliczeniowy działa na danych przechowywanych w buforach lub teksturach, wykonując operacje równolegle w wielu wątkach GPU. Właśnie ta równoległość sprawia, że shadery obliczeniowe są tak skuteczne przy wymagających obliczeniach.

Do czego można używać shaderów obliczeniowych?

Ponieważ shadery obliczeniowe służą do ogólnych obliczeń, praktycznie nie ma ograniczeń co do tego, do czego możesz ich użyć. Oto kilka przykładów typowych zastosowań shaderów obliczeniowych:

Przetwarzanie obrazów

  • Filtrowanie obrazów: stosowanie rozmycia, wykrywania krawędzi, wyostrzania i podobnych efektów.
  • Korekcja barw: dostosowywanie przestrzeni kolorów obrazu.

Fizyka

  • Systemy cząsteczek: symulacja dużej liczby cząsteczek dla efektów takich jak dym, ogień i dynamika płynów.
  • Fizyka ciał miękkich: symulacja odkształcalnych obiektów, takich jak tkanina i galaretka.
  • Odrzucanie: occlusion culling, frustum culling

Generowanie proceduralne

  • Generowanie terenu: tworzenie szczegółowego terenu przy użyciu funkcji szumu.
  • Roślinność i zarośla: tworzenie proceduralnie generowanych roślin i drzew.

Efekty renderowania

  • Oświetlenie globalne (global illumination): symulowanie realistycznego oświetlenia przez przybliżenie sposobu, w jaki światło odbija się w scenie.
  • Wokselizacja (voxelization): tworzenie trójwymiarowej siatki wokseli z danych siatki.

Jak działają shadery obliczeniowe?

Na wysokim poziomie shadery obliczeniowe działają przez podział zadania na wiele mniejszych zadań, które mogą zostać wykonane jednocześnie. Odbywa się to dzięki pojęciom work groups i invocations:

Grupy robocze
Shader obliczeniowy działa na siatce work groups. Każda grupa robocza zawiera stałą liczbę wywołań (wątków). Rozmiar grup roboczych i liczba wywołań są definiowane w kodzie shadera.
Wywołania
Każde wywołanie (wątek) wykonuje program shadera obliczeniowego. Wywołania w obrębie jednej grupy roboczej mogą współdzielić dane przez pamięć współdzieloną, co pozwala na wydajną komunikację i synchronizację między nimi.

GPU wykonuje shader obliczeniowy, uruchamiając równolegle wiele wywołań w wielu grupach roboczych, co zapewnia znaczną moc obliczeniową dla odpowiednich zadań.

Tworzenie programu compute

Aby utworzyć program compute, right click docelowy folder w widoku Assets i wybierz New... ▸ Compute. Możesz też wybrać z menu File ▸ New..., a następnie Compute. Nadaj nazwę nowemu plikowi compute i naciśnij Ok.

Plik compute

Nowy plik compute otworzy się w Compute Editor.

Edytor compute

Plik compute zawiera następujące informacje:

Compute Program
Plik programu shadera compute (.cp), którego należy użyć. Shader działa na „abstrakcyjnych elementach roboczych”, co oznacza, że nie ma stałej definicji typów danych wejściowych i wyjściowych. To programista definiuje, co shader obliczeniowy ma wygenerować.
Constants
Uniformy, które zostaną przekazane do programu shadera compute. Poniżej znajdziesz listę dostępnych stałych.
Samplers
W pliku materiału możesz opcjonalnie skonfigurować konkretne samplery. Dodaj sampler, nazwij go zgodnie z nazwą używaną w programie shadera i ustaw parametry wrap oraz filter według potrzeb.

Używanie programu compute w Defold

W odróżnieniu od materiałów programy compute nie są przypisywane do żadnych komponentów i nie są częścią normalnego przebiegu renderowania. Aby wykonały jakąkolwiek pracę, trzeba je wywołać (dispatch) w skrypcie do renderowania. Zanim jednak to zrobisz, musisz upewnić się, że skrypt do renderowania ma odwołanie do programu compute. Obecnie jedynym sposobem, aby skrypt do renderowania wiedział o programie compute, jest dodanie go do pliku .render, który przechowuje odwołanie do twojego skryptu do renderowania:

Plik render z compute

Aby użyć programu compute, trzeba go najpierw powiązać z kontekstem renderowania. Robi się to tak samo jak w przypadku materiałów:

render.set_compute("my_compute")
-- Tutaj wykonuj pracę compute; wywołaj render.set_compute(), aby odwiązać program
render.set_compute()

Choć stałe compute zostaną automatycznie zastosowane podczas dispatchowania programu, z poziomu edytora nie da się powiązać z programem compute żadnych zasobów wejściowych ani wyjściowych (tekstur, buforów itd.). Zamiast tego trzeba to zrobić w skryptach do renderowania:

render.enable_texture("blur_render_target", "tex_blur")
render.enable_texture(self.storage_texture, "tex_storage")

Aby uruchomić program w wybranej przez siebie przestrzeni roboczej, musisz go wywołać:

render.dispatch_compute(128, 128, 1)
-- dispatch_compute przyjmuje też tabelę opcji jako ostatni argument
-- tej tabeli możesz użyć do przekazania stałych renderowania do wywołania dispatch
local constants = render.constant_buffer()
constants.tint = vmath.vector4(1, 1, 1, 1)
render.dispatch_compute(32, 32, 32, {constants = constants})

Zapisywanie danych z programów compute

Obecnie generowanie jakiegokolwiek rodzaju danych wyjściowych z programu compute jest możliwe tylko za pomocą storage textures. Tekstura storage jest podobna do „zwykłej tekstury”, ale oferuje więcej funkcji i możliwości konfiguracji. Jak sugeruje nazwa, tekstury storage mogą służyć jako ogólny bufor, z którego program compute może odczytywać i do którego może zapisywać dane. Ten sam bufor możesz potem powiązać z innym programem shadera do odczytu.

Aby utworzyć teksturę storage w Defold, musisz zrobić to ze zwykłego pliku .script. Skrypty do renderowania nie mają tej funkcjonalności, ponieważ tekstury dynamiczne trzeba tworzyć przez API zasobów, które jest dostępne tylko w zwykłych plikach .script.

-- W pliku .script:
function init(self)
    -- Utwórz zasób tekstury jak zwykle, ale dodaj flagę "storage",
    -- aby można go było użyć jako zaplecza dla programów compute
    local t_backing = resource.create_texture("/my_backing_texture.texturec", {
        type   = resource.TEXTURE_TYPE_IMAGE_2D,
        width  = 128,
        height = 128,
        format = resource.TEXTURE_FORMAT_RGBA32F,
        flags  = resource.TEXTURE_USAGE_FLAG_STORAGE + resource.TEXTURE_USAGE_FLAG_SAMPLE,
    })

    -- pobierz uchwyt tekstury z zasobu
    local t_backing_handle = resource.get_texture_info(t_backing).handle

    -- powiadom renderer o teksturze zaplecza, aby można było ją powiązać przez render.enable_texture
    msg.post("@render:", "set_backing_texture", { handle = t_backing_handle })
end

Łącząc wszystko w całość

Program shadera

// compute.cp
#version 450

layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;

// określ zasoby wejściowe
uniform vec4 color;
uniform sampler2D texture_in;

// określ obraz wyjściowy
layout(rgba32f) uniform image2D texture_out;

void main()
{
    // To nie jest szczególnie ciekawy shader, ale pokazuje,
    // jak czytać z tekstury i bufora stałych oraz zapisywać do tekstury storage

    ivec2 tex_coord   = ivec2(gl_GlobalInvocationID.xy);
    vec4 output_value = vec4(0.0, 0.0, 0.0, 1.0);
    vec2 tex_coord_uv = vec2(float(tex_coord.x)/(gl_NumWorkGroups.x), float(tex_coord.y)/(gl_NumWorkGroups.y));
    vec4 input_value = texture(texture_in, tex_coord_uv);
    output_value.rgb = input_value.rgb * color.rgb;

    // Zapisz wartość wyjściową do tekstury storage
    imageStore(texture_out, tex_coord, output_value);
}

Komponent skryptowy

-- W pliku .script:

-- Tutaj określamy teksturę wejściową, którą później powiążemy
-- z programem compute. Możemy przypisać tę teksturę do komponentu modelu
-- albo włączyć ją w kontekście renderowania w skrypcie do renderowania.
go.property("texture_in", resource.texture())

function init(self)
    -- Utwórz zasób tekstury jak zwykle, ale dodaj flagę "storage",
    -- aby można go było użyć jako zaplecza dla programów compute
    local t_backing = resource.create_texture("/my_backing_texture.texturec", {
        type   = resource.TEXTURE_TYPE_IMAGE_2D,
        width  = 128,
        height = 128,
        format = resource.TEXTURE_FORMAT_RGBA32F,
        flags  = resource.TEXTURE_USAGE_FLAG_STORAGE + resource.TEXTURE_USAGE_FLAG_SAMPLE,
    })

    local textures = {
        texture_in = resource.get_texture_info(self.texture_in).handle,
        texture_out = resource.get_texture_info(t_backing).handle
    }

    -- powiadom renderer o teksturach wejściowej i wyjściowej
    msg.post("@render:", "set_backing_texture", textures)
end

Skrypt do renderowania

-- reaguj na wiadomość "set_backing_texture",
-- aby ustawić teksturę zaplecza dla programu compute
function on_message(self, message_id, message)
    if message_id == hash("set_backing_texture") then
        self.texture_in = message.texture_in
        self.texture_out = message.texture_out
    end
end

function update(self)
    render.set_compute("compute")
    -- Możemy powiązać tekstury z konkretnymi nazwanymi stałymi
    render.enable_texture(self.texture_in, "texture_in")
    render.enable_texture(self.texture_out, "texture_out")
    render.set_constant("color", vmath.vector4(0.5, 0.5, 0.5, 1.0))
    -- Uruchom program compute tyle razy, ile mamy pikseli.
    -- To stanowi naszą "grupę roboczą". Shader zostanie wywołany
    -- 128 x 128 x 1 razy, czyli raz na piksel.
    render.dispatch_compute(128, 128, 1)
    -- Gdy skończymy pracę z programem compute, musimy go odwiązać
    render.set_compute()
end

Zgodność

Defold obsługuje obecnie shadery obliczeniowe na następujących adapterach graficznych:

  • Vulkan
  • Metal (przez MoltenVK)
  • OpenGL 4.3+
  • OpenGL ES 3.1+

Obecnie nie ma sposobu, aby sprawdzić, czy uruchomiony klient obsługuje shadery obliczeniowe. Oznacza to, że jeśli adapter graficzny jest oparty na OpenGL lub OpenGL ES, nie ma gwarancji, że klient obsłuży uruchamianie shaderów obliczeniowych. Vulkan i Metal obsługują shadery obliczeniowe od wersji 1.0. Aby użyć Vulkan, musisz utworzyć własny manifest i wybrać ten backend.