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
// 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ı
Layout’suz execution dependency. compute→sample barrier iki tarafta da
eGeneraltutar — 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.cppc2f.setSrcStageMask(eComputeShader) .setSrcAccessMask(eShaderStorageWrite) .setDstStageMask(eFragmentShader) .setDstAccessMask(eShaderRead) .setOldLayout(eGeneral).setNewLayout(eGeneral); // layout yokEski API’de dürüst eşdeğer bir memory barrier’dı; insanlar sık sık
eAllCommands→eAllCommandskullandı — doğru ama tiled/GPU-paralel donanımda gerekenden çok daha fazlasını serileştirir.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 (
pipelineBarrierbir dependency’dir) ama kaynak zinciri başına bir stil seç, yoksa denetimler karışır. eAllCommands→eAllCommandshâ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→eShaderReadolmadan iki uçtaeComputeShaderexecution’ı sıralar ama görünürlüğü garanti etmez. İkisi de gerek. eNonegeçerli bir stage —eUndefined→herhangi için srceNone(beklenecek önceki iş yok); alışkanlıktaneTopOfPipebırakmak çalışır ama sync2’nineNone‘u daha net.
Derleme ve çalıştırma
g++ -std=c++23 -O2 main.cpp -o app -lvulkan -lglfw -lshaderc
./app --headless out.ppm && python3 check.py out.ppmcheck.py compute-yazılan plazmanın fragment shader’a tam
vardığını doğrular — 49 sample piksel analitik formülle eşleşir.
#!/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()OK: 49 pixels — compute->barrier2->sample chain exactTam kaynak
// 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.