Example: VulkanExamples/p12-resize
Serideki 12. yazı (1: surface, 2: üçgen, 8: frames in flight, 9: index buffer’lar, 10: UBO’lar + MVP, 11: derinlik). Her Vulkan yeni başlayanın yakaladığı ilk pencereli-program bug’ı: pencereyi boyutlandır ve program çöker ya da çöp render eder, çünkü swapchain sabit bir surface state’ine aittir — boyutlandırma onu geçersiz kılar.
İki sinyal — ve kimsenin söylemediği üçüncüsü
| sinyal | nerede | anlamı |
|---|---|---|
VK_ERROR_OUT_OF_DATE_KHR | acquireNextImageKHR, presentKHR | swapchain kullanılamaz — çizimden önce yeniden oluşturulmalı |
VK_SUBOPTIMAL_KHR | acquireNextImageKHR, presentKHR | hâlâ kullanılabilir — bu frame’i çiz, sonraki acquire’dan önce yeniden oluştur |
framebuffer boyutu ≠ swapchain.extent | glfwGetFramebufferSize vs sakladığın extent | sürücü hiç sinyal vermese bile chain eskidir — bazı X11 WSI yollarında olur |
Üçü de ele alınmalı. Demo acquire/present’ın eSuboptimalKHR
sonucunu artı frame başına framebuffer boyutu karşılaştırmasını
kullanır, böylece sinyal veren ve vermeyen sürücülerde çalışır.
Yeniden oluşturma kuralları
- acquire‘dan
OUT_OF_DATE→ bu frame’de hiçbir şey çizme. SUBOPTIMAL(acquire veya present) → frame’i normal bitir, sonraki acquire’dan önce yeniden oluştur.- Yeni chain’i oluşturmadan önce:
queue.waitIdle()(veya en azından eski image’ları hâlâ kullanabilecek her frame’in fence’i), sonra image view’ları ve eski swapchain’i yok et — bu sırayla. - 0×0 framebuffer (simge durumuna küçültülmüş pencere) → hiç
swapchain oluşturma; boyut sıfır olmayana dek
glfwWaitEvents(). - Extent’e veya swapchain image sayısı/formatına bağlı her şey yeniden kurulmalı: image view’lar, depth attachment’lar, image başına sync nesneleri, belirli bir format için kurulmuş pipeline’lar. Bu örnek pipeline’ı koşulsuz yeniden kurar — ucuz ve surface formatı da değiştiyse doğru.
Build & çalıştırma
g++ -std=c++23 -O2 main.cpp -o app -lvulkan -lglfw -lshaderc
./app # windowed — resizes itself at frame 120
python3 check.py # drives ./app under Xvfb and asserts the logKontrol
Bu yönün assert edecek pikseli yok — gözlenebilir davranış log’da.
check.py demoyu xvfb-run altında çalıştırır, sonra üç şeyi
doğrular: temiz çıkış, gerçekten bir yeniden oluşturma olduğu ve
yeniden oluşturulan swapchain’in extent’inin yeni pencere boyutuyla
(800×600) eşleştiği.
#!/usr/bin/env python3
# SPDX-License-Identifier: CC0-1.0
# check.py — Xvfb altında boyutlandırma demosunu sür ve doğrula:
# 1. pencereli çalışma 0 ile çıkar
# 2. swapchain gerçekten yeni extent'te yeniden oluşturuldu
# 3. log beklenen yeni boyutu raporlar (800x600 framebuffer
# veya bir HiDPI katı)
import re
import subprocess
import sys
def main():
exe = sys.argv[1] if len(sys.argv) > 1 else "./app"
r = subprocess.run(["xvfb-run", "-a", exe],
capture_output=True, text=True, timeout=180)
out = r.stdout + r.stderr
print(out, end="")
assert r.returncode == 0, f"exit {r.returncode}"
assert "[demo] resized window to 800x600" in out, \
"resize was never triggered"
m = re.findall(r"\[swapchain\] recreated: (\d+)x(\d+)", out)
assert m, "no swapchain recreation happened"
for w, h in m:
# burada X11 framebuffer == pencere boyutu
# (Xvfb altında HiDPI yok)
assert (int(w), int(h)) == (800, 600), f"bad extent {w}x{h}"
print(f"OK: clean exit after {len(m)} recreation(s) to 800x600")
if __name__ == "__main__":
main()$ python3 check.py
device: AMD Radeon RX 7800 XT (RADV NAVI32)
[demo] resized window to 800x600
[swapchain] recreated: 800x600 (#1)
[demo] done: 1 recreation(s)
OK: clean exit after 1 recreation(s) to 800x600Tam kaynak
// SPDX-License-Identifier: CC0-1.0
//
// Post 12: boyutlandırmada swapchain yeniden oluşturma. Sadece-
// pencere konusu — boyutlandırılmış pencere swapchain'i geçersiz
// kılar. acquireNextImageKHR sonra VK_ERROR_OUT_OF_DATE_KHR döner
// ve presentKHR VK_SUBOPTIMAL_KHR dönebilir. Yanlış ele alınır:
// çökme veya bozuk frame'ler. Doğru ele alınır: yeni surface
// cap'lerine karşı swapchain'i yeniden oluştur.
//
// Demo kendi penceresini çalışma ortasında boyutlandırır
// (frame 120, 640x480 -> 800x600) ki tüm döngü otomatik
// işletilsin.
//
// ./app pencereli, kendini boyutlandırır, ~240 frame, 0 çıkar
// check.py Xvfb altında sürer; log + çıkış kodunu assert eder
//
// Build: g++ -std=c++23 -O2 main.cpp -o app -lvulkan -lglfw -lshaderc
#include "vkmini.hpp"
#include <GLFW/glfw3.h>
#include <cmath>
static const char* kVert = R"GLSL(
#version 460
layout(location = 0) out vec3 vColor;
void main() {
vec2 p[3] = vec2[](vec2(-0.6, 0.6), vec2(0.6, 0.6),
vec2(0.0, -0.6));
vec3 c[3] = vec3[](vec3(1, 0, 0), vec3(0, 1, 0),
vec3(0, 0, 1));
gl_Position = vec4(p[gl_VertexIndex], 0.0, 1.0);
vColor = c[gl_VertexIndex];
}
)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 Pipe {
vk::UniquePipelineLayout layout;
vk::UniquePipeline pipeline;
};
/// Swapchain başına kurulur çünkü color attachment formatı
/// (prensipte) surface yeniden müzakereleri arasında değişebilir.
static Pipe makePipeline(vkmini::Vk& vk, vk::Format fmt) {
Pipe o;
auto vs = vk.shader(kVert, shaderc_vertex_shader, "t.vert");
auto fs = vk.shader(kFrag, shaderc_fragment_shader, "t.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);
o.layout = vk.device->createPipelineLayoutUnique({});
vk::PipelineRenderingCreateInfo rendering{};
rendering.setColorAttachmentFormats(fmt);
vk::GraphicsPipelineCreateInfo gi{};
gi.setStages(stages)
.setPVertexInputState(&vin)
.setPInputAssemblyState(&ia)
.setPViewportState(&vp)
.setPDynamicState(&dyn)
.setPRasterizationState(&rs)
.setPMultisampleState(&ms)
.setPColorBlendState(&blend)
.setLayout(o.layout.get())
.setPNext(&rendering);
auto res = vk.device->createGraphicsPipelineUnique({}, gi);
if (res.result != vk::Result::eSuccess)
throw std::runtime_error("pipeline failed");
o.pipeline = std::move(res.value);
return o;
}
int main() {
vkmini::Vk vk;
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
GLFWwindow* win =
glfwCreateWindow(640, 480, "vkmini p12", nullptr, nullptr);
uint32_t n = 0;
const char** e = glfwGetRequiredInstanceExtensions(&n);
vk.createInstance({e, e + n});
VkSurfaceKHR raw;
glfwCreateWindowSurface(vk.instance.get(), win, nullptr, &raw);
auto surface = vk::UniqueSurfaceKHR(raw, vk.instance.get());
vk.pickPhysicalDevice();
vk.createDevice({VK_KHR_SWAPCHAIN_EXTENSION_NAME});
vkmini::Swapchain sc;
auto createSwapchain = [&] {
int fw = 0, fh = 0;
// Simge durumundaki pencere 0x0 framebuffer raporlar —
// 0-boyut chain kurmak yerine gerçek boyuta dönene dek bekle.
while (fw == 0 || fh == 0) {
glfwGetFramebufferSize(win, &fw, &fh);
glfwWaitEvents();
}
sc.create(vk, vk.phys, surface.get(), uint32_t(fw),
uint32_t(fh));
};
createSwapchain();
Pipe p = makePipeline(vk, sc.format);
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({});
bool needRecreate = false;
int recreations = 0;
for (int f = 0; f < 480 && !glfwWindowShouldClose(win); ++f) {
glfwPollEvents();
// Frame 120'de kendi kendine boyutlandır — gerçek uygulama
// bunu OS/pencere callback'inden yapar; tetik noktası önemli
// değil.
if (f == 120) {
glfwSetWindowSize(win, 800, 600);
printf("[demo] resized window to 800x600\n");
}
// Sürücü sinyalinden bağımsız ikinci tetik: gerçek framebuffer
// boyutu swapchain extent'inden ayrışırsa, acquire/present
// hiç hata vermemiş olsa bile chain eskidir — bazı X11 WSI
// yollarında olur.
{
int fw = 0, fh = 0;
glfwGetFramebufferSize(win, &fw, &fh);
if (fw && fh &&
(uint32_t(fw) != sc.extent.width ||
uint32_t(fh) != sc.extent.height))
needRecreate = true;
}
if (needRecreate) {
// Ancak GPU eski swapchain ile işini bitirdikten sonra.
vk.queue.waitIdle();
sc.views.clear();
sc.sc.reset();
createSwapchain();
// Müzakere edilen format farklı olabilirse pipeline'ı da
// yeniden oluştur — burada koşulsuz yapmak yeterince ucuz.
p = makePipeline(vk, sc.format);
needRecreate = false;
++recreations;
printf("[swapchain] recreated: %ux%u (#%d)\n",
sc.extent.width, sc.extent.height, recreations);
}
vk.device->resetFences(fence.get());
// ---- acquire with resize handling -------------------------
auto acq = vk.device->acquireNextImageKHR(
sc.sc.get(), UINT64_MAX, acqSem.get(), {});
if (acq.result == vk::Result::eErrorOutOfDateKHR) {
needRecreate = true;
continue; // bu frame'e çizilecek bir şey yok
}
// SUBOPTIMAL: hâlâ kullanılabilir — bu frame'i çiz, sonraki
// acquire'da yeniden oluştur
if (acq.result == vk::Result::eSuboptimalKHR)
needRecreate = true;
vk::Image img = sc.images[acq.value];
cmd[0]->reset();
cmd[0]->begin(
{vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
vk::ImageSubresourceRange sr{vk::ImageAspectFlagBits::eColor,
0, 1, 0, 1};
vk::ImageMemoryBarrier toAtt{};
toAtt.setOldLayout(vk::ImageLayout::eUndefined)
.setNewLayout(vk::ImageLayout::eColorAttachmentOptimal)
.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setImage(img)
.setSubresourceRange(sr)
.setSrcAccessMask({})
.setDstAccessMask(
vk::AccessFlagBits::eColorAttachmentWrite);
cmd[0]->pipelineBarrier(
vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eColorAttachmentOutput, {}, {},
{}, toAtt);
vk::RenderingAttachmentInfo att{};
att.setImageView(sc.views[acq.value].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}, sc.extent})
.setLayerCount(1)
.setColorAttachments(att);
cmd[0]->beginRendering(ri);
cmd[0]->bindPipeline(vk::PipelineBindPoint::eGraphics,
p.pipeline.get());
vk::Viewport vp{0, 0, float(sc.extent.width),
float(sc.extent.height), 0.f, 1.f};
cmd[0]->setViewport(0, vp);
vk::Rect2D sci{{0, 0}, sc.extent};
cmd[0]->setScissor(0, sci);
cmd[0]->draw(3, 1, 0, 0);
cmd[0]->endRendering();
vk::ImageMemoryBarrier toPresent{};
toPresent.setOldLayout(
vk::ImageLayout::eColorAttachmentOptimal)
.setNewLayout(vk::ImageLayout::ePresentSrcKHR)
.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setImage(img)
.setSubresourceRange(sr)
.setSrcAccessMask(
vk::AccessFlagBits::eColorAttachmentWrite)
.setDstAccessMask({});
cmd[0]->pipelineBarrier(
vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eBottomOfPipe, {}, {}, {},
toPresent);
cmd[0]->end();
vk::PipelineStageFlags waitStage =
vk::PipelineStageFlagBits::eColorAttachmentOutput;
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(sc.sc.get())
.setImageIndices(acq.value);
vk::Result pr = vk.queue.presentKHR(pi);
if (pr == vk::Result::eErrorOutOfDateKHR ||
pr == vk::Result::eSuboptimalKHR)
needRecreate = true;
(void)vk.device->waitForFences(fence.get(), VK_TRUE,
UINT64_MAX);
}
printf("[demo] done: %d recreation(s)\n", recreations);
vk.queue.waitIdle();
sc.views.clear();
sc.sc.reset();
surface.reset();
glfwDestroyWindow(win);
glfwTerminate();
return 0;
}Bu serideki tüm kaynak kodu CC0 1.0 Universal (CC0-1.0) altında yayınlandı — kamu malı adaması, atıf gerekmez.
Serinin devamı: instanced rendering — gl_InstanceIndex ve
instance-rate vertex attribute’larıyla tek draw call’da yüzlerce
kopya.