Example: VulkanExamples/p01-surface
Bu, küçük ve ayrı ayrı doğrulanabilir adımlarla karmaşıklık zincirinde
yükselen bir Vulkan serisinin ilk postu. Aşağıdaki her örnek tam,
derlenebilir bir program — ve her biri tek bir kareyi ekran dışında bir
PPM dosyasına render eden --headless modu ile gerçek piksel
değerlerini assert eden küçük bir Python kontrolü içerir.
Burada hâlâ piksel üreten mutlak minimumu yapıyoruz: bir GLFW penceresi,
ona bağlı bir VkSurfaceKHR, bir swapchain ve kare başına bir
vkCmdClearColorImage — bilinçli olarak graphics pipeline yok,
böylece odaktaki tek şey surface/swapchain mekanizması.
Nasıl çalışır
Bootstrap zincirinin katı bir sırası var ve her adım bir sonrakinin ihtiyaç duyduğu soruyu yanıtlar:
- API context’siz GLFW penceresi.
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API)çıplak bir pencere oluşturur — GL/GLES context yok. Vulkan yalnızca platform pencere handle’ına ihtiyaç duyar. - GLFW’nin gerekli uzantılarıyla instance. Vulkan instance’ı
pencere sisteminden bağımsızdır; WSI tutkalı uzantılarda yaşar.
Hangileri (örn.
VK_KHR_surface+VK_KHR_xcb_surface) platforma bağlı —glfwGetRequiredInstanceExtensions()vk::createInstance‘a geçilecek listeyi tam olarak döndürür. glfwCreateWindowSurface→VkSurfaceKHR. Postun konusu bu adım. Surface, “o pencerenin çizilebilir bölgesi” için opak bir handle. Surface’e asla doğrudan render edilmez — bir swapchain’in ona bağlanabilmesi için var olur.VK_KHR_swapchain‘li device. Present etmek device-seviyesinde bir özellik, bu yüzden logical device swapchain uzantısını etkinleştirmeli. AyrıcagetSurfaceSupportKHR(queueFamily, surface)‘i doğrula — her queue family her surface’e present edemez.- Swapchain müzakeresi.
getSurfaceCapabilitiesKHR+getSurfaceFormatsKHRneyin yasal olduğunu söyler. Aşağıdaki koda gömülü iki gerçek-dünya tuzağı:currentExtent == 0xFFFFFFFF“surface bir boyut dikte etmiyor” demektir —min/maxImageExtentiçine kısıtlanmış GLFW framebuffer boyutunu sağlamalısın. Sentinel’i olduğu gibi geçmek ~4 milyar piksellik bir swapchain yaratır → anındaVK_ERROR_OUT_OF_HOST_MEMORY.maxImageCount == 0“sınırsız” demektir, “sıfır” değil.
- Pipeline’sız render.
vkCmdClearColorImagebir image’ı doğrudan doldurur — yalnızcaeTransferDstusage gerekir, bu yüzden swapchaineColorAttachment | eTransferDstile oluşturulur. Kare başına: image acquire →eTransferDstOptimal‘a geçiş → clear →ePresentSrcKHR‘ye geçiş → present. Tek bir shader olmadan ekranda gerçek bir mavi/kırmızı değişen kare titremesi elde edersin.
Örnekler küçük bir helper header’ı paylaşır, vkmini.hpp (instance/
device/bellek/buffer/image/tek-seferlik-komutlar/shaderc tutkalı artı
kontrollerin kullandığı Headless readback hedefi). Altta tam olarak
dahil; serinin sonraki postları onu değiştirmeden yeniden kullanır.
Derle ve çalıştır
g++ -std=c++23 -O2 main.cpp -o app -lvulkan -lglfw -lshaderc
./app # windowed: alternating blue/red for ~2s
./app --headless out.ppm # offscreen: one blue frame -> out.ppm
python3 check.py out.ppm # assert the pixels are exactly #3366CCvkmini.hpp helper’ı (aşağıda dahil) sonraki postlar GLSL’i runtime’da
SPIR-V’ye derlediği için shaderc’yi bağlar — bu post henüz shader
kullanmıyor. Debian/Ubuntu paketleri:
apt install libvulkan-dev libglfw3-dev libshaderc-dev.
Kontrol
#!/usr/bin/env python3
# SPDX-License-Identifier: CC0-1.0
# Headless render'ın düz (51,102,204) mavi bir 640x480 image
# ürettiğini assert eder — 4x4 grid artı tam merkez satırı örnekler.
import sys
def read_ppm(path):
d = open(path, "rb").read()
# "255\n" ile böl: whitespace-tokenize etmek \r/\n/boşluk olan
# önde gelen piksel baytlarını yerdi.
hdr, data = d.split(b"255\n", 1)
toks = hdr.split()
assert toks[0] == b"P6", "not a binary PPM"
w, h = int(toks[1]), int(toks[2])
assert len(data) == w * h * 3, "truncated pixel data"
return w, h, data
def px(data, w, x, y):
i = (y * w + x) * 3
return tuple(data[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) == (640, 480), f"unexpected size {w}x{h}"
expected = (51, 102, 204) # main.cpp'de temizlenen mavi
for gy in range(4):
for gx in range(4):
p = px(d, w, 80 + gx * 160, 60 + gy * 120)
assert p == expected, f"pixel({gx},{gy})={p}"
row = {px(d, w, x, h // 2) for x in range(w)}
assert row == {expected}, f"centre row not uniform: {row}"
print(f"OK: {w}x{h} uniform {expected}")
if __name__ == "__main__":
main()Çalıştırma:
$ ./app --headless out.ppm && python3 check.py out.ppm
device: AMD Radeon RX 7800 XT (RADV NAVI32)
wrote out.ppm
OK: 640x480 uniform (51, 102, 204)Tam kaynak
// SPDX-License-Identifier: CC0-1.0
//
// Post 1: GLFW üzerinden Vulkan surface, üzerinde swapchain oluştur
// ve her kareyi düz renge temizleyerek çalıştığını göster.
//
// ./app windowed: clears the window, alternating color
// ./app --headless o.ppm offscreen: same clear, dumped to a PPM
//
// Derle: g++ -std=c++23 -O2 main.cpp -o app -lvulkan -lglfw -lshaderc
#include "vkmini.hpp"
#include <GLFW/glfw3.h>
int main(int argc, char** argv) {
bool headless = argc > 1 && std::string(argv[1]) == "--headless";
const char* ppmOut = argc > 2 ? argv[2] : "out.ppm";
// ---- 1. GLFW penceresi ----------------------------------------------
// GLFW_NO_API: GLFW bir OpenGL context'i oluşturmamalı — pencere
// Vulkan'a verdiğimiz sadece bir platform handle'ı.
if (!headless) {
if (!glfwInit()) throw std::runtime_error("glfwInit failed");
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
}
// ---- 2. Instance -----------------------------------------------------
// Surface uzantısı (VK_KHR_surface + VK_KHR_xcb_surface gibi bir
// platform WSI uzantısı) pencere sistemi tarafından dikte edilir —
// GLFW hangi instance uzantılarına ihtiyaç duyduğunu tam olarak
// söyler.
vkmini::Vk vk;
std::vector<const char*> instExts;
if (!headless) {
uint32_t n = 0;
const char** glfwExt = glfwGetRequiredInstanceExtensions(&n);
instExts.assign(glfwExt, glfwExt + n);
}
vk.createInstance(instExts);
// ---- 3. Surface'in kendisi -------------------------------------------
// glfwCreateWindowSurface arka planda doğru VK_KHR_*_surface
// uzantısını seçer ve bir VkSurfaceKHR döndürür — "bu pencerenin
// çizilebilir alanı" için opak bir handle. Surface'e doğrudan hiçbir
// şey render edilmez; yalnızca bir *swapchain* ona bağlanabilsin
// diye var olur.
GLFWwindow* win = nullptr;
vk::UniqueSurfaceKHR surface;
if (!headless) {
win = glfwCreateWindow(640, 480, "vkmini p01", nullptr, nullptr);
VkSurfaceKHR raw;
if (glfwCreateWindowSurface(vk.instance.get(), win, nullptr,
&raw) != VK_SUCCESS)
throw std::runtime_error("surface creation failed");
surface = vk::UniqueSurfaceKHR(raw, vk.instance.get());
}
// ---- 4. Physical + logical device ------------------------------------
vk.pickPhysicalDevice();
// *Present eden* bir device swapchain uzantısına ihtiyaç duyar.
vk.createDevice(headless
? std::vector<const char*>{}
: std::vector<const char*>{VK_KHR_SWAPCHAIN_EXTENSION_NAME});
// ---- 5. Present-desteği kontrolü -------------------------------------
// Her queue family her surface'e present edemez. Vulkan'a sor.
if (!headless) {
vk::Bool32 canPresent =
vk.phys.getSurfaceSupportKHR(vk.qfam, surface.get());
if (!canPresent)
throw std::runtime_error("queue family cannot present");
}
// ---- 6. Swapchain -----------------------------------------------------
// Swapchain, compositor'un arasında geçiş yaptığı bir image halkasına
// sahip. Çift tamponlu, FIFO (vsync), BGRA8 istiyoruz.
vk::UniqueSwapchainKHR swapchain;
std::vector<vk::Image> scImages;
vk::Format scFormat = vk::Format::eB8G8R8A8Unorm;
vk::Extent2D scExtent{640, 480};
if (!headless) {
auto caps = vk.phys.getSurfaceCapabilitiesKHR(surface.get());
// currentExtent == 0xFFFFFFFF "surface boyut dikte etmiyor"
// demektir — surface'in izin verilen extent aralığına kısıtlanmış
// GLFW framebuffer boyutunu seç. Sentinel'i olduğu gibi kullanmak
// ~4G x ~4G swapchain ister (anında OUT_OF_HOST_MEMORY —
// klasik tuzak).
if (caps.currentExtent.width == 0xFFFFFFFFu) {
int fw = 0, fh = 0;
glfwGetFramebufferSize(win, &fw, &fh);
scExtent.width = std::clamp<uint32_t>(
fw, caps.minImageExtent.width,
caps.maxImageExtent.width);
scExtent.height = std::clamp<uint32_t>(
fh, caps.minImageExtent.height,
caps.maxImageExtent.height);
} else {
scExtent = caps.currentExtent;
}
// BGRA ummak yerine surface-destekli bir format seç
auto fmts = vk.phys.getSurfaceFormatsKHR(surface.get());
scFormat = fmts.front().format;
uint32_t nImages = caps.minImageCount + 1;
if (caps.maxImageCount) // 0 "sınırsız" demektir
nImages = std::min(nImages, caps.maxImageCount);
vk::SwapchainCreateInfoKHR si{};
si.setSurface(surface.get())
.setMinImageCount(nImages)
.setImageFormat(scFormat)
.setImageColorSpace(vk::ColorSpaceKHR::eSrgbNonlinear)
.setImageExtent(scExtent)
.setImageArrayLayers(1)
.setImageUsage(vk::ImageUsageFlagBits::eColorAttachment |
vk::ImageUsageFlagBits::eTransferDst)
.setImageSharingMode(vk::SharingMode::eExclusive)
.setPreTransform(caps.currentTransform)
.setCompositeAlpha(vk::CompositeAlphaFlagBitsKHR::eOpaque)
.setPresentMode(vk::PresentModeKHR::eFifo)
.setClipped(VK_TRUE);
swapchain = vk.device->createSwapchainKHRUnique(si);
scImages = vk.device->getSwapchainImagesKHR(swapchain.get());
}
// ---- 7. Render --------------------------------------------------------
// Henüz pipeline yok: vkCmdClearColorImage bir image'ı doğrudan
// doldurur. eTransferDst usage gerektirir (yukarıda swapchain'de
// ayarlı) ve tüm zincirin çalıştığını kanıtlamanın en ucuz yolu.
const vk::ClearColorValue colors[] = {
std::array{0.2f, 0.4f, 0.8f, 1.0f}, // mavi
std::array{0.8f, 0.3f, 0.2f, 1.0f}, // kırmızı
};
if (headless) {
vkmini::Headless hl;
hl.init(vk, 640, 480);
// headless.render() komutlarımızı clear'ın ihtiyaç duyduğu
// layout geçişleriyle sarar.
hl.render(vk::ImageLayout::eTransferDstOptimal,
[&](vk::CommandBuffer c) {
vk::ImageSubresourceRange sr{
vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
c.clearColorImage(hl.color.img.get(),
vk::ImageLayout::eTransferDstOptimal,
colors[0], sr);
});
hl.savePpm(ppmOut);
printf("wrote %s\n", ppmOut);
return 0;
}
// Pencereli: acquire → clear → present, ~2sn değişen renk.
auto cmd = vk.device->allocateCommandBuffersUnique(
{vk.pool.get(), vk::CommandBufferLevel::ePrimary, 1});
auto acqSem = vk.device->createSemaphoreUnique({});
auto doneSem = vk.device->createSemaphoreUnique({});
auto fence = vk.device->createFenceUnique({});
for (int frame = 0; frame < 120 && !glfwWindowShouldClose(win);
++frame) {
glfwPollEvents();
vk.device->resetFences(fence.get());
auto acq = vk.device->acquireNextImageKHR(
swapchain.get(), UINT64_MAX, acqSem.get(), {});
vk::Image img = scImages[acq.value];
cmd[0]->reset();
cmd[0]->begin(
{vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
// swapchain image'ları UNDEFINED-benzeri durumda gelir:
// TRANSFER_DST'ye geçiş, clear, PRESENT'e geçiş.
vk::ImageSubresourceRange sr{
vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
vk::ImageMemoryBarrier toDst{};
toDst.setOldLayout(vk::ImageLayout::eUndefined)
.setNewLayout(vk::ImageLayout::eTransferDstOptimal)
.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setImage(img)
.setSubresourceRange(sr)
.setSrcAccessMask({})
.setDstAccessMask(vk::AccessFlagBits::eTransferWrite);
cmd[0]->pipelineBarrier(
vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, toDst);
cmd[0]->clearColorImage(
img, vk::ImageLayout::eTransferDstOptimal,
colors[frame & 1], sr);
vk::ImageMemoryBarrier toPresent{};
toPresent.setOldLayout(vk::ImageLayout::eTransferDstOptimal)
.setNewLayout(vk::ImageLayout::ePresentSrcKHR)
.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setImage(img)
.setSubresourceRange(sr)
.setSrcAccessMask(vk::AccessFlagBits::eTransferWrite)
.setDstAccessMask({});
cmd[0]->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eBottomOfPipe,
{}, {}, {}, toPresent);
cmd[0]->end();
vk::PipelineStageFlags waitStage =
vk::PipelineStageFlagBits::eTransfer;
vk::SubmitInfo si{};
si.setWaitSemaphores(acqSem.get())
.setWaitDstStageMask(waitStage)
.setCommandBuffers(cmd[0].get())
.setSignalSemaphores(doneSem.get());
vk.queue.submit(si, fence.get());
vk::PresentInfoKHR pi{};
pi.setWaitSemaphores(doneSem.get())
.setSwapchains(swapchain.get())
.setImageIndices(acq.value);
(void)vk.queue.presentKHR(pi);
(void)vk.device->waitForFences(fence.get(), VK_TRUE, UINT64_MAX);
}
// Yok etme sırası önemli: swapchain ve surface platform penceresini
// referans alır — glfwDestroyWindow'dan önce serbest bırak, yoksa
// sürücünün WSI yıkımı ölü bir wl_surface/xcb penceresine dokunur.
vk.queue.waitIdle();
swapchain.reset();
surface.reset();
glfwDestroyWindow(win);
glfwTerminate();
return 0;
}// SPDX-License-Identifier: CC0-1.0
//
// vkmini.hpp — TechOverflow Vulkan öğretici serisi için paylaşılan
// yardımcılar. Kasıtlı olarak küçük: örneklerin paylaştığı her şey
// (instance, device, bellek, buffer'lar, image'lar, tek-seferlik
// komutlar, shaderc, PPM readback'li headless render hedefi) burada
// yaşar, böylece her post tam olarak bir yeni unsura odaklanabilir.
// vulkan-hpp C++ API'sini kullanır.
//
// Every example supports two modes:
// ./app opens a GLFW window and renders live (where the
// aspect applies — compute-only examples stay
// headless)
// ./app --headless out.ppm
// renders exactly one frame offscreen and writes a
// binary PPM, so a small python script can assert on
// actual pixel values.
#pragma once
#include <vulkan/vulkan.hpp>
#include <shaderc/shaderc.hpp>
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <stdexcept>
#include <string>
#include <vector>
namespace vkmini {
/// Instance + physical device + logical device + tek queue.
/// Her şey için bir queue family (graphics, burada ilgili tüm
/// gerçek-dünya donanımında compute+transfer'i ima eder — ve öğretici
/// queue-transfer karmaşasından arı kalır).
struct Vk {
vk::UniqueInstance instance;
vk::PhysicalDevice phys;
vk::UniqueDevice device;
vk::Queue queue;
uint32_t qfam = 0;
vk::UniqueCommandPool pool;
/// instance için layer+extension listesi; GLFW'nin gerektirdiği
/// surface uzantıları gerektiğinde çağıran tarafından eklenir.
void createInstance(std::vector<const char*> exts,
const char* appName = "vkmini") {
vk::ApplicationInfo ai{};
ai.setPApplicationName(appName)
.setApiVersion(VK_API_VERSION_1_3);
vk::InstanceCreateInfo ici{};
ici.setPApplicationInfo(&ai)
.setPEnabledExtensionNames(exts);
instance = vk::createInstanceUnique(ici);
}
void pickPhysicalDevice() {
auto devs = instance->enumeratePhysicalDevices();
if (devs.empty())
throw std::runtime_error("no Vulkan device");
phys = devs.front();
auto p = phys.getProperties();
printf("device: %s\n", p.deviceName.data());
}
/// Dynamic rendering etkin logical device (tüm seri VkRenderPass
/// nesneleri olmadan render eder).
void createDevice(std::vector<const char*> exts = {}) {
auto props = phys.getQueueFamilyProperties();
for (uint32_t i = 0; i < props.size(); ++i) {
if (props[i].queueFlags & vk::QueueFlagBits::eGraphics) {
qfam = i;
break;
}
}
float prio = 1.0f;
vk::DeviceQueueCreateInfo qi{};
qi.setQueueFamilyIndex(qfam)
.setQueueCount(1)
.setPQueuePriorities(&prio);
vk::PhysicalDeviceDynamicRenderingFeatures dyn{};
dyn.setDynamicRendering(true);
vk::DeviceCreateInfo di{};
di.setQueueCreateInfos(qi)
.setPEnabledExtensionNames(exts)
.setPNext(&dyn);
device = phys.createDeviceUnique(di);
queue = device->getQueue(qfam, 0);
pool = device->createCommandPoolUnique(
{vk::CommandPoolCreateFlagBits::eResetCommandBuffer, qfam});
}
// ---------- bellek --------------------------------------------------
uint32_t memoryType(uint32_t bits,
vk::MemoryPropertyFlags want) const {
auto mem = phys.getMemoryProperties();
for (uint32_t i = 0; i < mem.memoryTypeCount; ++i)
if ((bits & (1u << i)) &&
(mem.memoryTypes[i].propertyFlags & want) == want)
return i;
throw std::runtime_error("no suitable memory type");
}
struct Buffer {
vk::UniqueBuffer buf;
vk::UniqueDeviceMemory mem;
void* mapped = nullptr;
vk::DeviceSize size = 0;
};
Buffer createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
vk::MemoryPropertyFlags props,
bool map = false) const {
Buffer o;
o.size = size;
o.buf = device->createBufferUnique(
{{}, size, usage, vk::SharingMode::eExclusive});
auto req = device->getBufferMemoryRequirements(o.buf.get());
o.mem = device->allocateMemoryUnique(
{req.size, memoryType(req.memoryTypeBits, props)});
device->bindBufferMemory(o.buf.get(), o.mem.get(), 0);
if (map)
o.mapped = device->mapMemory(o.mem.get(), 0, size);
return o;
}
struct Image {
vk::UniqueImage img;
vk::UniqueDeviceMemory mem;
vk::UniqueImageView view;
};
Image createImage(uint32_t w, uint32_t h, vk::Format fmt,
vk::ImageUsageFlags usage,
vk::ImageTiling tiling =
vk::ImageTiling::eOptimal) const {
Image o;
vk::ImageCreateInfo ii{};
ii.setImageType(vk::ImageType::e2D)
.setFormat(fmt)
.setExtent({w, h, 1})
.setMipLevels(1)
.setArrayLayers(1)
.setSamples(vk::SampleCountFlagBits::e1)
.setTiling(tiling)
.setUsage(usage)
.setSharingMode(vk::SharingMode::eExclusive)
.setInitialLayout(vk::ImageLayout::eUndefined);
o.img = device->createImageUnique(ii);
auto req = device->getImageMemoryRequirements(o.img.get());
o.mem = device->allocateMemoryUnique(
{req.size,
memoryType(req.memoryTypeBits,
vk::MemoryPropertyFlagBits::eDeviceLocal)});
device->bindImageMemory(o.img.get(), o.mem.get(), 0);
vk::ImageViewCreateInfo vi{};
vi.setImage(o.img.get())
.setViewType(vk::ImageViewType::e2D)
.setFormat(fmt)
.setSubresourceRange(
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1});
o.view = device->createImageViewUnique(vi);
return o;
}
// ---------- komutlar ------------------------------------------------
/// `fn(cmd)`'i yeni bir command buffer'a kaydet, submit et, idle bekle.
template <typename F> void oneTime(F&& fn) const {
auto cmds = device->allocateCommandBuffersUnique(
{pool.get(), vk::CommandBufferLevel::ePrimary, 1});
cmds[0]->begin(
{vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
fn(cmds[0].get());
cmds[0]->end();
vk::SubmitInfo si{};
si.setCommandBuffers(cmds[0].get());
queue.submit(si);
queue.waitIdle();
}
// ---------- shader'lar ------------------------------------------------
/// shaderc ile GLSL'i runtime'da SPIR-V'ye derle — her örneği tek
/// komutla derlenebilir tutar (çevrimdışı glslc adımı yok).
vk::UniqueShaderModule shader(const char* glsl,
shaderc_shader_kind kind,
const char* name) const {
shaderc::Compiler cc;
auto res = cc.CompileGlslToSpv(glsl, kind, name);
if (res.GetCompilationStatus() !=
shaderc_compilation_status_success)
throw std::runtime_error(name + std::string(": ") +
res.GetErrorMessage());
std::vector<uint32_t> spv(res.cbegin(), res.cend());
vk::ShaderModuleCreateInfo si{};
si.setCode(spv);
return device->createShaderModuleUnique(si);
}
};
/// --headless modu için ekran dışı RGBA8 render hedefi + host-görünür
/// readback. Image kareler arasında eShaderReadOnlyOptimal'de durur;
/// yardımcılar onu örneğin ihtiyacına göre geçirir.
struct Headless {
Vk* vm;
Vk::Image color; // eColorAttachment|eTransferSrc|eSampled|eStorage
Vk::Buffer readback;
vk::Extent2D extent;
void init(Vk& v, uint32_t w, uint32_t h) {
vm = &v;
extent = vk::Extent2D{w, h};
color = v.createImage(
w, h, vk::Format::eR8G8B8A8Unorm,
vk::ImageUsageFlagBits::eColorAttachment |
vk::ImageUsageFlagBits::eTransferSrc |
vk::ImageUsageFlagBits::eSampled |
vk::ImageUsageFlagBits::eStorage);
readback = v.createBuffer(
vk::DeviceSize(w) * h * 4,
vk::BufferUsageFlagBits::eTransferDst,
vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent,
true);
}
/// `render(cmd)` UNDEFINED -> renderLayout -> TRANSFER_SRC
/// geçişleriyle sarılmış tek-seferlik command buffer içinde çalışır,
/// ardından image `readback`'e kopyalanır. Tek atış: headless mod
/// piksel kontrolü için tam bir kare render eder.
template <typename F>
void render(vk::ImageLayout renderLayout, F&& render) {
vm->oneTime([&](vk::CommandBuffer c) {
barrier(c, vk::ImageLayout::eUndefined, renderLayout,
vk::AccessFlags{},
vk::AccessFlagBits::eColorAttachmentWrite |
vk::AccessFlagBits::eShaderWrite |
vk::AccessFlagBits::eTransferWrite);
render(c);
barrier(c, renderLayout,
vk::ImageLayout::eTransferSrcOptimal,
vk::AccessFlagBits::eColorAttachmentWrite |
vk::AccessFlagBits::eShaderWrite |
vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eTransferRead);
vk::BufferImageCopy r{};
r.setImageSubresource(
{vk::ImageAspectFlagBits::eColor, 0, 0, 1})
.setImageExtent({extent.width, extent.height, 1});
c.copyImageToBuffer(color.img.get(),
vk::ImageLayout::eTransferSrcOptimal,
readback.buf.get(), r);
});
}
void barrier(vk::CommandBuffer c, vk::ImageLayout ol,
vk::ImageLayout nl, vk::AccessFlags src,
vk::AccessFlags dst) {
vk::ImageMemoryBarrier b{};
b.setOldLayout(ol)
.setNewLayout(nl)
.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setImage(color.img.get())
.setSubresourceRange(
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1})
.setSrcAccessMask(src)
.setDstAccessMask(dst);
c.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
vk::PipelineStageFlagBits::eAllCommands, {},
{}, {}, b);
}
/// Readback buffer'ı ikili PPM (P6, alfa yok) olarak yaz.
void savePpm(const char* path) const {
std::ofstream f(path, std::ios::binary);
f << "P6\n" << extent.width << " " << extent.height << "\n255\n";
auto* px = static_cast<const uint8_t*>(readback.mapped);
for (uint32_t y = 0; y < extent.height; ++y)
for (uint32_t x = 0; x < extent.width; ++x) {
const uint8_t* p = px + (y * extent.width + x) * 4;
char rgb[3] = {char(p[0]), char(p[1]), char(p[2])};
f.write(rgb, 3);
}
}
};
/// Surface capability'lerine karşı müzakere edilen swapchain + image
/// başına view'lar. Post 2'den itibaren pencereli modda kullanılır
/// (post 1 uzun formu gösterir).
struct Swapchain {
vk::UniqueSwapchainKHR sc;
std::vector<vk::Image> images;
std::vector<vk::UniqueImageView> views;
vk::Format format = vk::Format::eB8G8R8A8Unorm;
vk::Extent2D extent{};
void create(Vk& v, vk::PhysicalDevice phys, vk::SurfaceKHR surface,
uint32_t wantW, uint32_t wantH) {
auto caps = phys.getSurfaceCapabilitiesKHR(surface);
extent = caps.currentExtent;
if (extent.width == 0xFFFFFFFFu) { // "surface umursamıyor"
extent.width = std::clamp(
wantW, caps.minImageExtent.width,
caps.maxImageExtent.width);
extent.height = std::clamp(
wantH, caps.minImageExtent.height,
caps.maxImageExtent.height);
}
format = phys.getSurfaceFormatsKHR(surface).front().format;
uint32_t n = caps.minImageCount + 1;
if (caps.maxImageCount) n = std::min(n, caps.maxImageCount);
vk::SwapchainCreateInfoKHR si{};
si.setSurface(surface)
.setMinImageCount(n)
.setImageFormat(format)
.setImageColorSpace(vk::ColorSpaceKHR::eSrgbNonlinear)
.setImageExtent(extent)
.setImageArrayLayers(1)
.setImageUsage(vk::ImageUsageFlagBits::eColorAttachment)
.setImageSharingMode(vk::SharingMode::eExclusive)
.setPreTransform(caps.currentTransform)
.setCompositeAlpha(vk::CompositeAlphaFlagBitsKHR::eOpaque)
.setPresentMode(vk::PresentModeKHR::eFifo)
.setClipped(VK_TRUE);
sc = v.device->createSwapchainKHRUnique(si);
images = v.device->getSwapchainImagesKHR(sc.get());
views.clear();
for (auto img : images) {
vk::ImageViewCreateInfo vi{};
vi.setImage(img)
.setViewType(vk::ImageViewType::e2D)
.setFormat(format)
.setSubresourceRange(
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1});
views.push_back(v.device->createImageViewUnique(vi));
}
}
};
} // namespace vkmini
Bu serideki tüm kaynak kodu CC0 1.0 Universal (CC0-1.0) altında yayınlanır — kamu malı adaması, atıf gerekmez.
Seride sıradaki: gerçek bir üçgen çizmek
— bir graphics pipeline, SPIR-V shader’ları ve VkRenderPass
nesneleri yerine VK_KHR_dynamic_rendering.
