Hassas barrier'lar için Vulkan synchronization2 (vkCmdPipelineBarrier2) kullanımı

Example: VulkanExamples/p27-sync2

vkCmdPipelineBarrier çalışır — ama stage mask’leri eski (eTopOfPipe, eTransfer, …) ve barrier kafa karıştıran bölünmüş semantikle tek çağrıya sıkıştırılmış stage mask + access mask + layout değişikliğidir. Synchronization2 (Vulkan 1.3’te core, önceden VK_KHR_synchronization2) onu yeniden yapılandırır: tek bir VkDependencyInfo yeni VkMemoryBarrier2/VkBufferMemoryBarrier2/ VkImageMemoryBarrier2 listelerini taşır — her biri çok daha granüler (eComputeShader, eColorAttachmentOutput, …) PipelineStageFlagBits2/AccessFlagBits2 alanlarına sahip ve doğal olarak tek bir kaynağa kapsamlanır.

Eski vs yeni şekil

example.cpp
// eski: pipelineBarrier(srcStage, dstStage, ..., imageBarriers)
c.pipelineBarrier(eTopOfPipe, eTransfer, {}, {}, {}, imgBarrier);

// sync2: dependency info barrier listesini taşır
vk::ImageMemoryBarrier2 b{};
b.setSrcStageMask(eNone).setSrcAccessMask(eNone)
 .setDstStageMask(eComputeShader)
 .setDstAccessMask(eShaderStorageWrite)
 .setOldLayout(eUndefined).setNewLayout(eGeneral)
 .setImage(img).setSubresourceRange(sr);
vk::DependencyInfo dep{};
dep.setImageMemoryBarriers(b);
c.pipelineBarrier2(dep);

Bu bir feature — VkPhysicalDeviceSynchronization2Features:: synchronization2 device oluştururken etkinleştirilmeli (1.3’te core ama varsayılan açık değil) ve vkmini‘nin createDevice‘i tam bunun için bir pNextFeatures zinciri alır.

Demonun iki kazancı

  1. Layout’suz execution dependency. compute→sample barrier iki tarafta da eGeneral tutar — sync2 execution sıralamasını (stage’ler) layout transition’larından ayırır; aynı eski/yeni layout’lu barrier saf bir “bu yazımları bekle” işaretidir:

    example.cpp
    c2f.setSrcStageMask(eComputeShader)
       .setSrcAccessMask(eShaderStorageWrite)
       .setDstStageMask(eFragmentShader)
       .setDstAccessMask(eShaderRead)
       .setOldLayout(eGeneral).setNewLayout(eGeneral);   // layout yok
    

    Eski API’de dürüst eşdeğer bir memory barrier’dı; insanlar sık sık eAllCommands→eAllCommands kullandı — doğru ama tiled/GPU-paralel donanımda gerekenden çok daha fazlasını serileştirir.

  2. Hassas stage mask’leri bedava performans bilgisi. Driver’a “sadece compute shader yazar, sadece fragment shader okur” demek, tüm pipeline’ı boşaltmak yerine başka işi overlap etmesine izin verir.

sync2 Nasıl KULLANILMAMALI

  • Frame ortasında zihinsel modelleri dikkatsizce karıştırma — eski ve sync2 barrier’lar birlikte çalışır (pipelineBarrier bir dependency’dir) ama kaynak zinciri başına bir stil seç, yoksa denetimler karışır.
  • eAllCommands→eAllCommands hâlâ kör çekiç — sync2 aşırı-senkronizasyonu durdurmaz, sadece hassas versiyonu yazmayı keyifli kılar.
  • Stage mask’leri access mask’i ima etmez — eShaderStorageWrite→eShaderRead olmadan iki uçta eComputeShader execution’ı sıralar ama görünürlüğü garanti etmez. İkisi de gerek.
  • eNone geçerli bir stage — eUndefined→herhangi için src eNone (beklenecek önceki iş yok); alışkanlıktan eTopOfPipe bırakmak çalışır ama sync2’nin eNone‘u daha net.

Derleme ve çalıştırma

build.sh
g++ -std=c++23 -O2 main.cpp -o app -lvulkan -lglfw -lshaderc
./app --headless out.ppm && python3 check.py out.ppm

check.py compute-yazılan plazmanın fragment shader’a tam vardığını doğrular — 49 sample piksel analitik formülle eşleşir.

check.py
#!/usr/bin/env python3
# SPDX-License-Identifier: CC0-1.0
# check.py — sync2 zincirinin uçtan uca çalıştığını doğrula:
# compute plazmayı yazdı (r=sinüzoidal, g=u, b=v), barrier2
# onu fragment shader'a görünür kıldı ve tam-ekran pass onu
# NEAREST ile sample etti.
import sys
import math


def read_ppm(path):
    d = open(path, "rb").read()
    hdr, data = d.split(b"255\n", 1)
    toks = hdr.split()
    assert toks[0] == b"P6"
    w, h = int(toks[1]), int(toks[2])
    assert len(data) == w * h * 3
    return w, h, data


def px(d, w, x, y):
    i = (y * w + x) * 3
    return tuple(d[i:i + 3])


def main():
    path = sys.argv[1] if len(sys.argv) > 1 else "out.ppm"
    w, h, d = read_ppm(path)
    assert (w, h) == (384, 384)

    checked = 0
    # ekran px (x,y) texel floor((x+.5)*256/384)'ü sample eder
    for y in range(20, h, 60):
        for x in range(20, w, 60):
            tx = int((x + 0.5) * 256 / 384)
            ty = int((y + 0.5) * 256 / 384)
            u, v = tx / 256.0, ty / 256.0
            r = int((0.5 + 0.5 * math.sin(u * 24.0)
                     * math.cos(v * 24.0)) * 255 + 0.5)
            want = (r, tx, ty)
            got = px(d, w, x, y)
            assert all(abs(a - b) <= 2 for a, b in zip(got, want)), \
                f"px({x},{y})={got} want {want}"
            checked += 1
    print(f"OK: {checked} pixels — compute->barrier2->sample chain "
          "exact")


if __name__ == "__main__":
    main()
output.txt
OK: 49 pixels — compute->barrier2->sample chain exact

Tam kaynak

main.cpp
// SPDX-License-Identifier: CC0-1.0
//
// Synchronization2 (Vulkan 1.3'te core, VK_KHR_synchronization2):
// vkCmdPipelineBarrier2 eski vkCmdPipelineBarrier'ı
// VkDependencyInfo içinde
// VkImageMemoryBarrier2/VkBufferMemoryBarrier2 ile değiştirir —
// kazanç *hassas* stage+access mask'leridir:
// ALL_COMMANDS -> ALL_COMMANDS yerine "COMPUTE_SHADER yazdı ->
// FRAGMENT_SHADER sample etti".
//
// Demo: compute storage image'a plazma yazar -> TEK barrier2
// (compute write -> fragment sampled read) -> graphics pass
// sample eder -> readback. Storage-image yazısıyla aynı iş
// yükü, her transition barrier2 ile.
//
//   ./app --headless o.ppm     tek kare -> check.py için PPM
//   ./app                      pencereli
//
// Build:  g++ -std=c++23 -O2 main.cpp -o app -lvulkan -lglfw -lshaderc

#include "vkmini.hpp"

#include <GLFW/glfw3.h>

static const char* kComp = R"GLSL(
#version 460
layout(local_size_x = 16, local_size_y = 16) in;
layout(binding = 0, rgba8) uniform writeonly image2D img;
void main() {
    ivec2 p = ivec2(gl_GlobalInvocationID.xy);
    vec2 uv = vec2(p) / 256.0;
    float v = 0.5 + 0.5 * sin(uv.x * 24.0) * cos(uv.y * 24.0);
    imageStore(img, p, vec4(v, uv.x, uv.y, 1.0));
}
)GLSL";

static const char* kVert = R"GLSL(
#version 460
layout(location = 0) out vec2 vUV;
void main() {
    vec2 p = vec2((gl_VertexIndex << 1 & 2), gl_VertexIndex & 2);
    vUV = p;
    gl_Position = vec4(p * 2.0 - 1.0, 0, 1);
}
)GLSL";

static const char* kFrag = R"GLSL(
#version 460
layout(binding = 0) uniform sampler2D tex;
layout(location = 0) in vec2 vUV;
layout(location = 0) out vec4 outColor;
void main() { outColor = texture(tex, vUV); }
)GLSL";

int main(int argc, char** argv) {
    bool headless = argc > 1 && std::string(argv[1]) == "--headless";
    const char* ppmOut = argc > 2 ? argv[2] : "out.ppm";
    (void)headless;

    vkmini::Vk vk;
    vk.createInstance({});
    vk.pickPhysicalDevice();
    // synchronization2 bir FEATURE: diğerleri gibi etkinleştirilir
    vk::PhysicalDeviceSynchronization2Features sync2{};
    sync2.setSynchronization2(true);
    vk.createDevice({}, &sync2);

    // ---- storage image (compute yazar, graphics sample eder) -----
    auto img = vk.createImage(
        256, 256, vk::Format::eR8G8B8A8Unorm,
        vk::ImageUsageFlagBits::eStorage |
            vk::ImageUsageFlagBits::eSampled);
    vk::ImageSubresourceRange sr{vk::ImageAspectFlagBits::eColor,
                                 0, 1, 0, 1};

    // ---- compute pipeline -----------------------------------------
    auto cs = vk.shader(kComp, shaderc_compute_shader, "s.comp");
    vk::DescriptorSetLayoutBinding cBind{
        0, vk::DescriptorType::eStorageImage, 1,
        vk::ShaderStageFlagBits::eCompute};
    vk::DescriptorSetLayoutCreateInfo cdslci{};
    cdslci.setBindings(cBind);
    auto cdsl = vk.device->createDescriptorSetLayoutUnique(cdslci);
    vk::PipelineLayoutCreateInfo cplci{};
    vk::DescriptorSetLayout cdslH = cdsl.get();
    cplci.setSetLayouts(cdslH);
    auto clayout = vk.device->createPipelineLayoutUnique(cplci);
    vk::ComputePipelineCreateInfo cpi{};
    cpi.stage.setStage(vk::ShaderStageFlagBits::eCompute)
        .setModule(cs.get())
        .setPName("main");
    cpi.setLayout(clayout.get());
    auto cres = vk.device->createComputePipelineUnique({}, cpi);
    if (cres.result != vk::Result::eSuccess)
        throw std::runtime_error("compute pipeline failed");
    auto cpipe = std::move(cres.value);

    // ---- graphics pipeline (image'ı sample eder) ------------------
    auto vs = vk.shader(kVert, shaderc_vertex_shader, "s.vert");
    auto fs = vk.shader(kFrag, shaderc_fragment_shader, "s.frag");
    vk::DescriptorSetLayoutBinding gBind{
        0, vk::DescriptorType::eCombinedImageSampler, 1,
        vk::ShaderStageFlagBits::eFragment};
    vk::DescriptorSetLayoutCreateInfo gdslci{};
    gdslci.setBindings(gBind);
    auto gdsl = vk.device->createDescriptorSetLayoutUnique(gdslci);
    vk::PipelineLayoutCreateInfo gplci{};
    vk::DescriptorSetLayout gdslH = gdsl.get();
    gplci.setSetLayouts(gdslH);
    auto glayout = vk.device->createPipelineLayoutUnique(gplci);

    vk::PipelineShaderStageCreateInfo stages[2];
    stages[0].setStage(vk::ShaderStageFlagBits::eVertex)
        .setModule(vs.get())
        .setPName("main");
    stages[1].setStage(vk::ShaderStageFlagBits::eFragment)
        .setModule(fs.get())
        .setPName("main");
    vk::PipelineVertexInputStateCreateInfo vin{};
    vk::PipelineInputAssemblyStateCreateInfo ia{};
    ia.setTopology(vk::PrimitiveTopology::eTriangleList);
    vk::PipelineViewportStateCreateInfo vp{};
    vp.setViewportCount(1).setScissorCount(1);
    std::array dynStates{vk::DynamicState::eViewport,
                         vk::DynamicState::eScissor};
    vk::PipelineDynamicStateCreateInfo dyn{};
    dyn.setDynamicStates(dynStates);
    vk::PipelineRasterizationStateCreateInfo rs{};
    rs.setPolygonMode(vk::PolygonMode::eFill)
        .setCullMode(vk::CullModeFlagBits::eNone)
        .setFrontFace(vk::FrontFace::eCounterClockwise)
        .setLineWidth(1.0f);
    vk::PipelineMultisampleStateCreateInfo ms{};
    ms.setRasterizationSamples(vk::SampleCountFlagBits::e1);
    vk::PipelineColorBlendAttachmentState cb{};
    cb.setColorWriteMask(
        vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
        vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA);
    vk::PipelineColorBlendStateCreateInfo blend{};
    blend.setAttachments(cb);
    vk::Format cfmt = vk::Format::eR8G8B8A8Unorm;
    vk::PipelineRenderingCreateInfo rendering{};
    rendering.setColorAttachmentFormats(cfmt);
    vk::GraphicsPipelineCreateInfo gi{};
    gi.setStages(stages)
        .setPVertexInputState(&vin)
        .setPInputAssemblyState(&ia)
        .setPViewportState(&vp)
        .setPDynamicState(&dyn)
        .setPRasterizationState(&rs)
        .setPMultisampleState(&ms)
        .setPColorBlendState(&blend)
        .setLayout(glayout.get())
        .setPNext(&rendering);
    auto pres = vk.device->createGraphicsPipelineUnique({}, gi);
    if (pres.result != vk::Result::eSuccess)
        throw std::runtime_error("graphics pipeline failed");
    auto gpipe = std::move(pres.value);

    // ---- descriptor'lar --------------------------------------------
    vk::DescriptorPoolSize ps[2]{
        {vk::DescriptorType::eStorageImage, 1},
        {vk::DescriptorType::eCombinedImageSampler, 1}};
    auto dpool = vk.device->createDescriptorPoolUnique(
        {{vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet}, 2,
         ps});
    vk::DescriptorSetLayout layouts[2]{cdsl.get(), gdsl.get()};
    vk::DescriptorSetAllocateInfo dsai{};
    dsai.setDescriptorPool(dpool.get()).setSetLayouts(layouts);
    auto dsets = vk.device->allocateDescriptorSetsUnique(dsai);
    auto sampler = vk.device->createSamplerUnique(
        {{},
         vk::Filter::eNearest,
         vk::Filter::eNearest,
         vk::SamplerMipmapMode::eNearest});
    vk::DescriptorImageInfo cimg{VK_NULL_HANDLE, img.view.get(),
                                 vk::ImageLayout::eGeneral};
    vk::DescriptorImageInfo gimg{sampler.get(), img.view.get(),
                                 vk::ImageLayout::eGeneral};
    vk::WriteDescriptorSet w[2];
    w[0].setDstSet(dsets[0].get())
        .setDstBinding(0)
        .setDescriptorType(vk::DescriptorType::eStorageImage)
        .setImageInfo(cimg);
    w[1].setDstSet(dsets[1].get())
        .setDstBinding(0)
        .setDescriptorType(vk::DescriptorType::eCombinedImageSampler)
        .setImageInfo(gimg);
    vk.device->updateDescriptorSets(w, {});

    // ---- tek command buffer: compute -> barrier2 -> draw -----------
    vkmini::Headless hl;
    hl.init(vk, 384, 384);
    hl.render(
        vk::ImageLayout::eColorAttachmentOptimal,
        [&](vk::CommandBuffer c) {
            // 1) storage write için UNDEFINED -> GENERAL
            //    (barrier2: her şey VkDependencyInfo)
            vk::ImageMemoryBarrier2 toGen{};
            toGen.setSrcStageMask(vk::PipelineStageFlagBits2::eNone)
                .setSrcAccessMask(vk::AccessFlagBits2::eNone)
                .setDstStageMask(
                    vk::PipelineStageFlagBits2::eComputeShader)
                .setDstAccessMask(
                    vk::AccessFlagBits2::eShaderStorageWrite)
                .setOldLayout(vk::ImageLayout::eUndefined)
                .setNewLayout(vk::ImageLayout::eGeneral)
                .setImage(img.img.get())
                .setSubresourceRange(sr);
            vk::DependencyInfo dep{};
            dep.setImageMemoryBarriers(toGen);
            c.pipelineBarrier2(dep);

            c.bindPipeline(vk::PipelineBindPoint::eCompute,
                           cpipe.get());
            c.bindDescriptorSets(vk::PipelineBindPoint::eCompute,
                                 clayout.get(), 0, dsets[0].get(),
                                 {});
            c.dispatch(256 / 16, 256 / 16, 1);

            // 2) HASSAS barrier: COMPUTE yazımları bitti,
            //    FRAGMENT_SHADER sample edebilir. Hâlâ eGeneral
            //    layout — barrier2 layout'suz olabilir (saf
            //    execution dep).
            vk::ImageMemoryBarrier2 c2f{};
            c2f.setSrcStageMask(
                    vk::PipelineStageFlagBits2::eComputeShader)
                .setSrcAccessMask(
                    vk::AccessFlagBits2::eShaderStorageWrite)
                .setDstStageMask(
                    vk::PipelineStageFlagBits2::eFragmentShader)
                .setDstAccessMask(vk::AccessFlagBits2::eShaderRead)
                .setOldLayout(vk::ImageLayout::eGeneral)
                .setNewLayout(vk::ImageLayout::eGeneral)
                .setImage(img.img.get())
                .setSubresourceRange(sr);
            vk::DependencyInfo dep2{};
            dep2.setImageMemoryBarriers(c2f);
            c.pipelineBarrier2(dep2);

            // 3) image'ı sample eden tam-ekran üçgen çiz
            vk::RenderingAttachmentInfo att{};
            att.setImageView(hl.color.view.get())
                .setImageLayout(
                    vk::ImageLayout::eColorAttachmentOptimal)
                .setLoadOp(vk::AttachmentLoadOp::eClear)
                .setStoreOp(vk::AttachmentStoreOp::eStore)
                .setClearValue(vk::ClearValue{vk::ClearColorValue{
                    std::array{0.f, 0.f, 0.f, 1.f}}});
            vk::RenderingInfo ri{};
            ri.setRenderArea({{0, 0}, hl.extent})
                .setLayerCount(1)
                .setColorAttachments(att);
            c.beginRendering(ri);
            vk::Viewport v{0, 0, 384, 384, 0.f, 1.f};
            c.setViewport(0, v);
            vk::Rect2D sc{{0, 0}, hl.extent};
            c.setScissor(0, sc);
            c.bindPipeline(vk::PipelineBindPoint::eGraphics,
                           gpipe.get());
            c.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
                                 glayout.get(), 0, dsets[1].get(),
                                 {});
            c.draw(3, 1, 0, 0);
            c.endRendering();
        });
    hl.savePpm(ppmOut);
    printf("wrote %s\n", ppmOut);
    return 0;
}

Bu serideki tüm kaynak kod CC0 1.0 Universal (CC0-1.0) altında yayınlanmıştır — kamu malı tahsisi, atıf gerekmez.


Check out similar posts by category: C++ Vulkan