Vulkan occlusion query'leri ile fragment görünürlüğünü ölçme

Example: VulkanExamples/p44_occlusion

Serideki post 44 (post 11: depth attachment, post 30: timestamp query’leri).

Bir occlusion query GPU’da “bu draw’un kaç fragment’i depth test’ten sağ çıktı?” sorusunu yanıtlar — görünürlük culling’in, lens-flare fade’lerinin ve “bu kamera tile’ı ekranda mı” sorunsalının arkasındaki primitive. vkCmdBeginQuery/EndQuery bir draw’u kuşatır; vkGetQueryPoolResults query başına sample sayısı döndürür.

Kurulum

main.cpp (excerpt)
vk::QueryPoolCreateInfo qpi{};
qpi.setQueryType(vk::QueryType::eOcclusion).setQueryCount(2);
auto qpool = vk.device->createQueryPoolUnique(qpi);

Bir pool N bağımsız query slot’u tutar — frame başına izlemek istediğiniz her nesne/hücre için bir tane.

Command buffer’da

main.cpp (excerpt)
c.resetQueryPool(qpool.get(), 0, 2);     // cmd içinde reset ZORUNLU

quad(0, 0, 0.6f, 0.2f);                  // engelleyici (yakın, depth yazar)

c.beginQuery(qpool.get(), 0, {});
quad(0, 0, 0.6f, 0.8f);                  // aynı alan, arkada -> 0
c.endQuery(qpool.get(), 0);

c.beginQuery(qpool.get(), 1, {});
quad(-0.7f, -0.7f, 0.2f, 0.1f);          // köşe, görünür -> >0
c.endQuery(qpool.get(), 1);

Command buffer içindeki resetQueryPool zorunludur — query sonuçları frame’ler arasında örtük olarak temizlenmez.

Readback

main.cpp (excerpt)
uint64_t res[2];
vk.device->getQueryPoolResults(
    qpool.get(), 0, 2, sizeof(res), res, sizeof(uint64_t),
    vk::QueryResultFlagBits::e64);
example.sh
query[0] occluded quad: 0 samples passed
query[1] visible quad : 1521 samples passed

Occlude edilmiş quad sıfır fragment geçirdi — sayım kesin: küçük 0.2 ölçekli quad 39×39 = 1521 piksel kaplar ve hepsi geçti, yani 1521.

Kontrol

check.py
# SPDX-License-Identifier: CC0-1.0
import re, subprocess

o = subprocess.run(["./app", "--headless", "out.ppm"], check=True,
                   capture_output=True, text=True).stdout
m0 = re.search(r"query\[0\] occluded quad: (\d+) samples", o)
m1 = re.search(r"query\[1\] visible quad : (\d+) samples", o)
assert m0 and m1, o
assert int(m0.group(1)) == 0, o          # tamamen occlude -> sıfır
assert int(m1.group(1)) > 1000, o        # görünür quad ~1521
print(f"OK: occlusion counts 0 / {m1.group(1)}")

Nasıl KULLANILMAMALI

  • Query bitmemiş olabilirse eWithAvailability kontrolü olmadan sonuçları okumayın — draw’un bittiğini bilmiyorsanız (fence) veya availability flag’leri istemiyorsanız getQueryPoolResults eNotReady döndürür; bu demo oneTime ile submit + bekler, yani sonuç hazırdır.
  • Niyet ettiğinizden fazlasını kuşatmayın — begin/end arasında çizilen her şey sayılır, MSAA-resolve edilmiş fragment’ler dahil (sample sayıları eSampleCount4 vb. ile büyür; mutlak fragment sayıları gerekiyorsa aşama başına sayaçlar için ePipelineStatistics query’leri kullanın).
  • Aynı frame’de nesne başına query’lerde spin yapmayın — GPU query’leri güzel pipeline’lar ama aynı frame’de geri okumak stall’lar; üretim kodu pool’u double-buffer’lar ve sonuçları bir frame geç okur (readback ring ile aynı fikir).
  • eOcclusion vs VK_QUERY_CONTROL_PRECISE_BIT — ePrecise olmadan sayım bir üst sınır olabilir (bazı donanımlar early-z öncesi geçişleri sayar); “hiç fragment var mı” için precise modu kullanın.

Bu serideki tüm kaynak kod CC0 1.0 Universal (CC0-1.0) altında yayımlanmıştır — kamu malı tahsisi, atıf gerekmez.


Check out similar posts by category: C++ Vulkan