Vulkan'da pointer-tabanlı shader erişimi için VK_KHR_buffer_device_address nasıl kullanılır

Example: VulkanExamples/p32-bda

Şimdiye kadarki her buffer erişimi descriptor’lar üzerinden geçti — set bağla, binding’i indeksle. VK_KHR_buffer_device_address (Vulkan 1.2’de core) zıt ucu sunar: VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT ile allocate edilen buffer’ın kararlı bir uint64 “GPU pointer"ı vardır ve GLSL’nin buffer_reference‘ı onu shader’ın doğrudan dereference ettiği tip’li bir pointer’a çevirir. Descriptor set yok, pool yok, binding tablosu yok.

Üç parça

example.cpp
// 1. özelliği etkinleştir (varsayılan kapalı)
vk::PhysicalDeviceBufferDeviceAddressFeatures bda{};
bda.setBufferDeviceAddress(true);
vk.createDevice({}, &bda);            // vkmini'nin pNextFeatures zinciri

// 2. buffer'ı işaretle ve adresini al
auto buf = vk.createBuffer(size,
    eTransferDst | eStorageBuffer | eShaderDeviceAddress, eDeviceLocal);
uint64_t addr = device->getBufferAddress({{}, buf.buf.get()});

// 3. teslim et — push constant, UBO alanı, her neyse
c.pushConstants(layout, eVertex, 0, sizeof(uint64_t), &addr);
example.txt
#extension GL_EXT_buffer_reference : require
#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require
struct Vert { float x, y; float r, g, b; };
layout(buffer_reference, std430) buffer Verts { Vert v[]; };
layout(push_constant) uniform Push { uint64_t addr; } pc;

Verts v = Verts(pc.addr);            // adres -> pointer'a cast
Vert vert = v.v[gl_VertexIndex];

Demo vertex-pulling yazısıyla (gl_VertexIndex ile bağlı SSBO’dan vertex çeken) aynı 2×2 grid’i render eder — ama sıfır descriptor ile: tüm kaynak arayüzü tek bir 64-bit push constant.

Neden var

  • GPU veri yapıları — buffer’lar diğer buffer’ların adreslerini içerebilir → linked list’ler, ağaçlar, pointer’la bindless sahne grafikleri. Ray tracing’in acceleration structure’ları bunun üstüne kurulu.
  • Pointer-zengin compute — CUDA/OpenCL kodu gibi kernel’e Node* dizisi geç.
  • Daha az descriptor — hot-path veri set/binding kayıt işi yerine push constant/SSBO’larda ham pointer olarak taşınır.

Nasıl KULLANILMAMALI

  • Adres bir refcount değildir — shader yaşam bilgisi tutmaz; GPU hâlâ dereference ederken buffer’ı free et → device loss. Diğer her şeyle aynı fence kuralları.
  • buffer_reference_align önemli — layout’un bildirdiği alignment buffer’ın gerçek alignment’ını aşmamalı (4-byte misalign tabanda getBufferAddress + yanlış align = çöp okuma).
  • Adresler process başınadır — BDA’yı diske serialize etme veya process’ler arası paylaşma; senin device’ının page table’larında bir virtual adres.
  • Yine de eStorageBuffer semantiği gerekir — özellik usage flag’lerini ikame etmez; eShaderDeviceAddress ekleyicidir, buffer yine shader’ların ne yapabileceğini bildirir.
  • readonly/writeonly buffer_reference bloklarına descriptor SSBO’lar gibi uygulanmaz — erişimi kendi disiplininle kontrol et (veya restrict-tarzı akıl yürütmeyi dikkatli kullan).

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
#!/usr/bin/env python3
# SPDX-License-Identifier: CC0-1.0
# check.py — buffer device address'in çalıştığını doğrula:
# vertex shader ham bir uint64 GPU pointer'ı dereference etti,
# ZERO vertex input state ile SSBO'dan tüm pozisyon+renkler —
# koyu arka planda renkli quad'ların (R/G/B/beyaz) 2x2 grid'i.
import sys


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


def px(data, w, x, y):
    i = (y * w + x) * 3
    return data[i], data[i + 1], data[i + 2]


def main():
    path = sys.argv[1] if len(sys.argv) > 1 else "out.ppm"
    w, h, data = load_ppm(path)
    assert (w, h) == (384, 384)
    BG = (13, 13, 20)

    def near(a, b, tol=40):
        return all(abs(x - y) <= tol for x, y in zip(a, b))

    # quad merkezleri: NDC +-0.45 -> px = (1+x)*w/2 (Y-aşağı: NDC -1 = üst)
    lo, hi = int(0.275 * w), int(0.725 * w)
    tlo, thi = int(0.275 * h), int(0.725 * h)
    q_tl = px(data, w, lo, tlo)   # kırmızı (i0: -0.45,-0.45)
    q_tr = px(data, w, hi, tlo)   # yeşil   (i1: +0.45,-0.45)
    q_bl = px(data, w, lo, thi)   # mavi    (i2: -0.45,+0.45)
    q_br = px(data, w, hi, thi)   # beyaz   (i3: +0.45,+0.45)
    print("quads:", q_tl, q_tr, q_bl, q_br)
    assert q_tl[0] > 150 and q_tl[1] < 120, f"tl {q_tl}"
    assert q_tr[1] > 150 and q_tr[0] < 120, f"tr {q_tr}"
    assert q_bl[2] > 150 and q_bl[0] < 120, f"bl {q_bl}"
    assert all(c > 200 for c in q_br), f"br {q_br}"
    assert near(px(data, w, w // 2, h // 2), BG), \
        px(data, w, w // 2, h // 2)
    print("OK: all 4 quads drawn with no descriptors at all (BDA pull)")


if __name__ == "__main__":
    main()
output.txt
buffer device address: 0xffff800100080000
quads: (255, 51, 51) (51, 255, 51) (51, 51, 255) (255, 255, 255)
OK: all 4 quads drawn with no descriptors at all (BDA pull)

Tam kaynak

main.cpp
// SPDX-License-Identifier: CC0-1.0
//
// Buffer Device Address (VK_KHR_buffer_device_address, core 1.2):
// eShaderDeviceAddress ile allocate edilen VkBuffer'ın kararlı
// bir uint64 "GPU pointer"ı vardır. Push constant ile geçir ve
// GLSL'nin buffer_reference'ı doğrudan okur — descriptor set
// yok, binding yok. GPU-taraflı bağlı yapılar / pointer-ağırlıklı
// pull'lar mümkün olur. Vertex-pulling yazısının fikri üstüne
// kurulu.
//
//   ./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* kVert = R"GLSL(
#version 460
#extension GL_EXT_buffer_reference : require
#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require
struct Vert { float x, y; float r, g, b; };
// ÖZELLİK: device belleğine tip'li pointer
layout(buffer_reference, std430) buffer Verts { Vert v[]; };
layout(push_constant) uniform Push { uint64_t addr; } pc;
layout(location = 0) out vec3 vColor;
void main() {
    Verts v = Verts(pc.addr);        // adres -> pointer'a cast
    Vert vert = v.v[gl_VertexIndex];
    gl_Position = vec4(vert.x, vert.y, 0, 1);
    vColor = vec3(vert.r, vert.g, vert.b);
}
)GLSL";

static const char* kFrag = R"GLSL(
#version 460
layout(location = 0) in vec3 vColor;
layout(location = 0) out vec4 outColor;
void main() { outColor = vec4(vColor, 1.0); }
)GLSL";

struct Vert {
    float pos[2];
    float col[3];
};

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();
    // BDA bir device özelliği + buffer usage flag'i
    vk::PhysicalDeviceBufferDeviceAddressFeatures bda{};
    bda.setBufferDeviceAddress(true);
    vk.createDevice({}, &bda);

    // ---- vertex'ler (vertex-pulling yazısıyla aynı 2x2 grid) -----
    auto quad = [](float cx, float cy, float s, float r, float g,
                   float b) {
        std::array<Vert, 6> q{};
        std::array p{std::array{-0.5f, -0.5f}, std::array{0.5f, -0.5f},
                     std::array{0.5f, 0.5f},   std::array{-0.5f, -0.5f},
                     std::array{0.5f, 0.5f},   std::array{-0.5f, 0.5f}};
        for (int i = 0; i < 6; ++i) {
            q[i].pos[0] = cx + p[i][0] * s;
            q[i].pos[1] = cy + p[i][1] * s;
            q[i].col[0] = r;
            q[i].col[1] = g;
            q[i].col[2] = b;
        }
        return q;
    };
    std::vector<Vert> verts;
    float cols[4][3] = {{1, .2f, .2f}, {.2f, 1, .2f},
                        {.2f, .2f, 1}, {1, 1, 1}};
    for (int i = 0; i < 4; ++i) {
        float cx = (i % 2 == 0) ? -0.45f : 0.45f;
        float cy = (i < 2) ? -0.45f : 0.45f;
        auto q = quad(cx, cy, 0.6f, cols[i][0], cols[i][1],
                      cols[i][2]);
        verts.insert(verts.end(), q.begin(), q.end());
    }

    auto stage = vk.createBuffer(
        verts.size() * sizeof(Vert),
        vk::BufferUsageFlagBits::eTransferSrc,
        vk::MemoryPropertyFlagBits::eHostVisible |
            vk::MemoryPropertyFlagBits::eHostCoherent,
        true);
    memcpy(stage.mapped, verts.data(), verts.size() * sizeof(Vert));
    // eShaderDeviceAddress getBufferDeviceAddress'i yasal kılar
    auto vbuf = vk.createBuffer(
        verts.size() * sizeof(Vert),
        vk::BufferUsageFlagBits::eTransferDst |
            vk::BufferUsageFlagBits::eStorageBuffer |
            vk::BufferUsageFlagBits::eShaderDeviceAddress,
        vk::MemoryPropertyFlagBits::eDeviceLocal);
    vk.oneTime([&](vk::CommandBuffer c) {
        vk::BufferCopy cp{0, 0, verts.size() * sizeof(Vert)};
        c.copyBuffer(stage.buf.get(), vbuf.buf.get(), cp);
    });

    vk::BufferDeviceAddressInfo bai{};
    bai.setBuffer(vbuf.buf.get());
    uint64_t addr = vk.device->getBufferAddress(bai);
    printf("buffer device address: 0x%llx\n",
           (unsigned long long)addr);
    if (addr == 0)
        throw std::runtime_error("no device address");

    // ---- pipeline — hiç descriptor yok ----------------------------
    auto vs = vk.shader(kVert, shaderc_vertex_shader, "s.vert");
    auto fs = vk.shader(kFrag, shaderc_fragment_shader, "s.frag");
    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::PushConstantRange pcr{};
    pcr.setStageFlags(vk::ShaderStageFlagBits::eVertex)
        .setSize(sizeof(uint64_t));
    vk::PipelineLayoutCreateInfo plci{};
    plci.setPushConstantRanges(pcr);
    auto layout = vk.device->createPipelineLayoutUnique(plci);
    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(layout.get())
        .setPNext(&rendering);
    auto pres = vk.device->createGraphicsPipelineUnique({}, gi);
    if (pres.result != vk::Result::eSuccess)
        throw std::runtime_error("pipeline failed");
    auto pipe = std::move(pres.value);

    vkmini::Headless hl;
    hl.init(vk, 384, 384);
    hl.render(vk::ImageLayout::eColorAttachmentOptimal,
              [&](vk::CommandBuffer c) {
                  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.05f, 0.05f, 0.08f,
                                         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,
                                 pipe.get());
                  // TÜM kaynak arayüzü = tek bir uint64
                  c.pushConstants(
                      layout.get(),
                      vk::ShaderStageFlagBits::eVertex, 0,
                      sizeof(uint64_t), &addr);
                  c.draw(24, 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