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
// 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);#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 tabandagetBufferAddress+ 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
eStorageBuffersemantiği gerekir — özellik usage flag’lerini ikame etmez;eShaderDeviceAddressekleyicidir, buffer yine shader’ların ne yapabileceğini bildirir. readonly/writeonlybuffer_reference bloklarına descriptor SSBO’lar gibi uygulanmaz — erişimi kendi disiplininle kontrol et (veyarestrict-tarzı akıl yürütmeyi dikkatli kullan).
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.ppm#!/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()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
// 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.