Example: VulkanExamples/p15-mips
Serideki 15. yazı (5: texture’lar,
14: MSAA).
Vulkan’da — OpenGL’in aksine — glGenerateMipmap yok. Eşdeğeri bir
vkCmdBlitImage çağrı zinciri: her mip seviyesi öncekinden linear
filtreli bir blit ile altörneklenir ve her seviye yol boyunca kendi
layout geçişlerini gerektirir.
Üç malzeme
- Image, baştan
mipLevels = log2(max(w,h))+1seviyeyle oluşturulmalı — zinciri sonra büyütemezsin. Usage,eSampled‘a ek olarakeTransferSrc | eTransferDstister ve view tüm seviyeleri kapsamalı. - Üretim döngüsü: her
lseviyesi içineTransferDstOptimal‘a geç,l-1seviyesinden blit (eTransferSrcOptimal‘da bırakılmış), sonra bir sonraki iterasyon içinl‘yieTransferSrcOptimal‘a geç. Sonda bir barrier tüm zincirieShaderReadOnlyOptimal‘a taşır. - Sampler:
mipmapMode = eLinearve zinciri kapsayanmaxLod— klasik bug, OpenGL’in varsayılanmaxLod=0‘ını kopyalamak (veya unutmak, aynı etki); bu her sample’ı sessizce seviye 0’a kenetler.
c.blitImage(img, eTransferSrcOptimal, // seviye l-1
img, eTransferDstOptimal, // seviye l
blit, vk::Filter::eLinear);eLinear‘a dikkat — eNearest de çalışır ama mipmapping’in
amacını boşa çıkarır. Bazı formatlar linear blit edilemez;
production kodu VkFormatProperties::optimalTilingFeatures & eSampledImageFilterLinear‘ı kontrol etmeli.
Build & çalıştırma
g++ -std=c++23 -O2 main.cpp -o app -lvulkan -lglfw -lshaderc
./app # pencereli
./app --headless out.ppm # offscreen -> PPM
python3 check.py out.ppmKontrol
Zincirin doğru olduğunu kanıtlamak (sadece “bir texture sample
ediyor” değil) fragment shader’da textureLod() ile kesin
seviyeleri zorlar:
- Sol quad LOD 4 (16×16 mip) sample eder: seviye 0’ın dört çeyrek rengi hâlâ ayırt edilebilir olmalı.
- Sağ quad LOD 8 (1×1 mip) sample eder: tek bir üniform renk
olmalı — ve analitik olarak dört çeyreğin ortalaması
(128,128,64)olmalı. Blit zinciri çöp yazdıysa bu ortalama başarısız olur.
#!/usr/bin/env python3
# SPDX-License-Identifier: CC0-1.0
# check.py — mip zincirinin doğru altörneklenmiş veri tuttuğunu doğrula.
#
# Texture'ın seviye 0'ı dört düz çeyrek:
# SÜ=kırmızı SÜsag=mavi SAlt=yeşil SAsag=sarı
# (main.cpp'teki uv->pixel eşlemesine bak: left->r, top->no g,
# bottom-left->g, bottom-right->r+b=sarı? aşağıda doğrulanıyor)
#
# İki quad aynı texture'ı sample eder:
# SOL quad -> textureLod(tex, uv, 4): 16x16 mip — çeyrekler
# hâlâ ayırt edilebilir olmalı
# SAĞ quad -> textureLod(tex, uv, 8): 1x1 mip — TEK üniform
# ortalama renk
#
# Blit zinciri bozuk olsaydı, lod 8 seviye 0 verisi ya da çöp
# gösterirdi — ortalama değil.
import sys
def load_ppm(path):
d = open(path, "rb").read()
hdr, data = d.split(b"255\n", 1)
parts = hdr.split()
assert parts[0] == b"P6"
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) == (512, 256)
def near(a, b, tol=30):
return all(abs(x - y) <= tol for x, y in zip(a, b))
# ---- sol quad (lod 4): çeyrek renkler ayırt edilebilir --------
# sol quad merkezi (133,128); NDC yarıboyut 0.45 -> x +/-115px,
# y +/-57px (viewport 2:1, quad'lar geniş)
tl = px(data, w, 70, 90)
bl = px(data, w, 70, 165)
tr = px(data, w, 200, 90)
br = px(data, w, 200, 165)
print("lod4 quadrants:", tl, tr, bl, br)
# dördü de birbirinden (kabaca) FARKLI olmalı
def dist(a, b):
return max(abs(x - y) for x, y in zip(a, b))
assert dist(tl, bl) > 80, "lod4 top/bottom-left identical"
assert dist(tr, br) > 80, "lod4 top/bottom-right identical"
assert dist(tl, tr) > 80, "lod4 left/right-top identical"
# ---- sağ quad (lod 8): tek üniform renk -----------------------
samples = [px(data, w, x, y)
for x in (330, 384, 440)
for y in (90, 128, 165)]
print("lod8 samples:", samples)
for s in samples[1:]:
assert near(s, samples[0], 10), \
f"lod8 not uniform: {samples[0]} vs {s}"
# ve 4 çeyreğin ortalaması olmalı:
# (255,0,0),(0,0,255),(0,255,0),(255,255,0) ort = (128,128,64)
avg = samples[0]
assert near(avg, (128, 128, 64), 25), \
f"lod8 color {avg} != quadrant average ~(128,128,64)"
print("OK: mip chain verified — lod4 quadrants, lod8 = average")
if __name__ == "__main__":
main()$ ./app --headless out.ppm && python3 check.py out.ppm
device: AMD Radeon RX 7800 XT (RADV NAVI32)
wrote out.ppm
lod4 quadrants: (255, 0, 0) (0, 0, 0) (255, 255, 0) (0, 255, 255)
lod8 samples: [(128, 128, 64), (128, 128, 64), ...]
OK: mip chain verified — lod4 quadrants, lod8 = averageTam kaynak
// SPDX-License-Identifier: CC0-1.0
//
// Post 15: vkCmdBlitImage ile mipmap'ler. Seviye 0 bir staging
// buffer'dan yüklenir; diğer her seviye öncekinden blit'leme
// (linear filtreli altörnekleme) ile GPU'DA üretilir, her biri
// kendi layout geçişleriyle. Gerçek bir mipLodRange'li sampler
// sonra seviyeleri seçer — iki quad aynı texture'ı zorlanan LOD 4
// ve LOD 8'de sample ederek zincirin doğru altörneklenmiş veri
// içerdiğini kanıtlar.
//
// ./app pencereli
// ./app --headless o.ppm bir frame -> check.py için PPM
//
// 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
layout(location = 0) out vec2 vUV;
layout(push_constant) uniform Push { vec2 ofs; float lod; } pc;
void main() {
vec2 p[6] = vec2[](vec2(-0.5,-0.5), vec2(0.5,-0.5), vec2(0.5,0.5),
vec2(-0.5,-0.5), vec2(0.5,0.5), vec2(-0.5,0.5));
vec2 q = p[gl_VertexIndex];
gl_Position = vec4(q * 0.9 + pc.ofs, 0.0, 1.0);
vUV = q + 0.5;
}
)GLSL";
static const char* kFrag = R"GLSL(
#version 460
layout(binding = 0) uniform sampler2D tex;
layout(location = 0) in vec2 vUV;
layout(location = 0) out vec4 outColor;
layout(push_constant) uniform Push { vec2 ofs; float lod; } pc;
void main() {
// textureLod kesin mip seviyesini zorlar — bu testi
// ekran-uzayı türevlerinden bağımsız kılar.
outColor = textureLod(tex, vUV, pc.lod);
}
)GLSL";
struct Push {
float ofs[2];
float lod;
float _pad;
};
static_assert(sizeof(Push) == 16);
/// Tam mip zincirli texture — 256x256 -> 9 seviye. View TÜM
/// seviyeleri kapsamalı (vkmini'nin varsayılanı bu), sampler o
/// aralığa izin vermeli ve her seviye üretim sırasında
/// TRANSFER_SRC/DST layout'ları ister.
static vkmini::Vk::Image makeTexture(vkmini::Vk& vk, uint32_t w,
uint32_t h) {
vkmini::Vk::Image o;
uint32_t levels = 0;
for (uint32_t s = w; s; s >>= 1) ++levels;
vk::ImageCreateInfo ii{};
ii.setImageType(vk::ImageType::e2D)
.setFormat(vk::Format::eR8G8B8A8Unorm)
.setExtent({w, h, 1})
.setMipLevels(levels) // <- zincir
.setArrayLayers(1)
.setSamples(vk::SampleCountFlagBits::e1)
.setTiling(vk::ImageTiling::eOptimal)
.setUsage(vk::ImageUsageFlagBits::eSampled |
vk::ImageUsageFlagBits::eTransferDst |
vk::ImageUsageFlagBits::eTransferSrc)
.setSharingMode(vk::SharingMode::eExclusive)
.setInitialLayout(vk::ImageLayout::eUndefined);
o.img = vk.device->createImageUnique(ii);
auto req = vk.device->getImageMemoryRequirements(o.img.get());
o.mem = vk.device->allocateMemoryUnique(
{req.size, vk.memoryType(
req.memoryTypeBits,
vk::MemoryPropertyFlagBits::eDeviceLocal)});
vk.device->bindImageMemory(o.img.get(), o.mem.get(), 0);
vk::ImageViewCreateInfo vi{};
vi.setImage(o.img.get())
.setViewType(vk::ImageViewType::e2D)
.setFormat(vk::Format::eR8G8B8A8Unorm)
.setSubresourceRange({vk::ImageAspectFlagBits::eColor, 0,
levels, 0, 1}); // TÜM seviyeler
o.view = vk.device->createImageViewUnique(vi);
return o;
}
/// Seviye 0'ı yükle + seviye 1..N'yi blit'lerle üret.
static void uploadAndGenerateMips(vkmini::Vk& vk,
const vkmini::Vk::Image& tex,
uint32_t w, uint32_t h,
const void* pixels) {
auto staging = vk.createBuffer(
vk::DeviceSize(w) * h * 4, vk::BufferUsageFlagBits::eTransferSrc,
vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent,
true);
memcpy(staging.mapped, pixels, vk::DeviceSize(w) * h * 4);
vk.oneTime([&](vk::CommandBuffer c) {
// seviye 0: undefined -> transfer dst, kopyala
auto barrier = [&](uint32_t base, vk::ImageLayout ol,
vk::ImageLayout nl, vk::AccessFlags src,
vk::AccessFlags dst,
vk::PipelineStageFlags ss,
vk::PipelineStageFlags ds) {
vk::ImageMemoryBarrier b{};
b.setOldLayout(ol)
.setNewLayout(nl)
.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setImage(tex.img.get())
.setSubresourceRange(
{vk::ImageAspectFlagBits::eColor, base, 1, 0, 1})
.setSrcAccessMask(src)
.setDstAccessMask(dst);
c.pipelineBarrier(ss, ds, {}, {}, {}, b);
};
barrier(0, vk::ImageLayout::eUndefined,
vk::ImageLayout::eTransferDstOptimal, {},
vk::AccessFlagBits::eTransferWrite,
vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eTransfer);
vk::BufferImageCopy r{};
r.setImageSubresource(
{vk::ImageAspectFlagBits::eColor, 0, 0, 1});
r.setImageExtent({w, h, 1});
c.copyBufferToImage(staging.buf.get(), tex.img.get(),
vk::ImageLayout::eTransferDstOptimal, r);
// seviye 0 artık blit KAYNAĞI
barrier(0, vk::ImageLayout::eTransferDstOptimal,
vk::ImageLayout::eTransferSrcOptimal,
vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eTransferRead,
vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eTransfer);
// her seviyeyi öncekinden üret
uint32_t levels = 0;
for (uint32_t s = w; s; s >>= 1) ++levels;
int32_t mw = int32_t(w), mh = int32_t(h);
for (uint32_t l = 1; l < levels; ++l) {
barrier(l, vk::ImageLayout::eUndefined,
vk::ImageLayout::eTransferDstOptimal, {},
vk::AccessFlagBits::eTransferWrite,
vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eTransfer);
vk::ImageBlit bl{};
bl.setSrcSubresource(
{vk::ImageAspectFlagBits::eColor, l - 1, 0, 1});
bl.setSrcOffsets({vk::Offset3D{0, 0, 0},
vk::Offset3D{mw, mh, 1}});
int32_t nw = std::max(1, mw / 2), nh = std::max(1, mh / 2);
bl.setDstSubresource(
{vk::ImageAspectFlagBits::eColor, l, 0, 1});
bl.setDstOffsets({vk::Offset3D{0, 0, 0},
vk::Offset3D{nw, nh, 1}});
c.blitImage(tex.img.get(),
vk::ImageLayout::eTransferSrcOptimal,
tex.img.get(),
vk::ImageLayout::eTransferDstOptimal, bl,
vk::Filter::eLinear);
// bu seviye sonraki kaynak + son okuma layout'u olur
barrier(l, vk::ImageLayout::eTransferDstOptimal,
vk::ImageLayout::eTransferSrcOptimal,
vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eTransferRead,
vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eTransfer);
mw = nw;
mh = nh;
}
// tüm zincir -> shader read
barrier(0, vk::ImageLayout::eTransferSrcOptimal,
vk::ImageLayout::eShaderReadOnlyOptimal,
vk::AccessFlagBits::eTransferRead,
vk::AccessFlagBits::eShaderRead,
vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eFragmentShader);
// seviye 1..N hâlâ TransferSrc — tek barrier ile düzelt
vk::ImageMemoryBarrier b{};
b.setOldLayout(vk::ImageLayout::eTransferSrcOptimal)
.setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal)
.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setImage(tex.img.get())
.setSubresourceRange({vk::ImageAspectFlagBits::eColor, 1,
levels - 1, 0, 1})
.setSrcAccessMask(vk::AccessFlagBits::eTransferRead)
.setDstAccessMask(vk::AccessFlagBits::eShaderRead);
c.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eFragmentShader,
{}, {}, {}, b);
});
}
struct Pipe {
vk::UniquePipelineLayout layout;
vk::UniquePipeline pipeline;
vk::UniqueDescriptorSetLayout dsLayout;
vk::UniqueDescriptorPool pool;
vk::UniqueDescriptorSet ds;
vk::UniqueSampler sampler;
};
static Pipe makePipeline(vkmini::Vk& vk, vk::Format fmt,
vk::ImageView view) {
Pipe o;
vk::DescriptorSetLayoutBinding bnd{};
bnd.setBinding(0)
.setDescriptorType(vk::DescriptorType::eCombinedImageSampler)
.setDescriptorCount(1)
.setStageFlags(vk::ShaderStageFlagBits::eFragment);
o.dsLayout = vk.device->createDescriptorSetLayoutUnique(
{{}, bnd});
vk::PushConstantRange pcr{};
pcr.setStageFlags(vk::ShaderStageFlagBits::eVertex |
vk::ShaderStageFlagBits::eFragment)
.setOffset(0)
.setSize(sizeof(Push));
o.layout = vk.device->createPipelineLayoutUnique(
{{}, o.dsLayout.get(), pcr});
auto vs = vk.shader(kVert, shaderc_vertex_shader, "m.vert");
auto fs = vk.shader(kFrag, shaderc_fragment_shader, "m.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::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);
// sampler + descriptor — mipmap yetenekli sampler hikayenin
// öteki yarısı: mipmapMode eLinear, lod aralığı 0..1000.
vk::SamplerCreateInfo sci{};
sci.setMagFilter(vk::Filter::eLinear)
.setMinFilter(vk::Filter::eLinear)
.setMipmapMode(vk::SamplerMipmapMode::eLinear)
.setMinLod(0.f)
.setMaxLod(1000.f); // tüm zincire izin ver
o.sampler = vk.device->createSamplerUnique(sci);
vk::DescriptorPoolSize ps{vk::DescriptorType::eCombinedImageSampler,
1};
o.pool = vk.device->createDescriptorPoolUnique(
{vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, 1,
ps});
auto sets = vk.device->allocateDescriptorSetsUnique(
{o.pool.get(), o.dsLayout.get()});
o.ds = std::move(sets[0]);
vk::DescriptorImageInfo di{};
di.setSampler(o.sampler.get())
.setImageView(view)
.setImageLayout(vk::ImageLayout::eShaderReadOnlyOptimal);
vk::WriteDescriptorSet wr{};
wr.setDstSet(o.ds.get())
.setDstBinding(0)
.setDescriptorType(
vk::DescriptorType::eCombinedImageSampler)
.setImageInfo(di);
vk.device->updateDescriptorSets(wr, {});
return o;
}
static void record(vk::CommandBuffer c, vk::ImageView view,
vk::Extent2D extent, const Pipe& p) {
vk::RenderingAttachmentInfo att{};
att.setImageView(view)
.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}, extent})
.setLayerCount(1)
.setColorAttachments(att);
c.beginRendering(ri);
c.bindPipeline(vk::PipelineBindPoint::eGraphics,
p.pipeline.get());
c.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
p.layout.get(), 0, p.ds.get(), {});
vk::Viewport vp{0, 0, float(extent.width), float(extent.height),
0.f, 1.f};
c.setViewport(0, vp);
vk::Rect2D sc{{0, 0}, extent};
c.setScissor(0, sc);
// SOL quad: zorlanan LOD 4 (16x16 mip — çeyrekler hâlâ ayrı)
Push lo{{-0.48f, 0.f}, 4.f, 0.f};
c.pushConstants(p.layout.get(),
vk::ShaderStageFlagBits::eVertex |
vk::ShaderStageFlagBits::eFragment,
0, sizeof(Push), &lo);
c.draw(6, 1, 0, 0);
// SAĞ quad: zorlanan LOD 8 (1x1 mip — tek ortalama renk)
Push hi{{0.48f, 0.f}, 8.f, 0.f};
c.pushConstants(p.layout.get(),
vk::ShaderStageFlagBits::eVertex |
vk::ShaderStageFlagBits::eFragment,
0, sizeof(Push), &hi);
c.draw(6, 1, 0, 0);
c.endRendering();
}
static void imgBarrier(vk::CommandBuffer c, vk::Image img,
vk::ImageAspectFlags aspect,
vk::ImageLayout ol, vk::ImageLayout nl,
vk::AccessFlags src, vk::AccessFlags dst,
vk::PipelineStageFlags ss,
vk::PipelineStageFlags ds) {
vk::ImageMemoryBarrier b{};
b.setOldLayout(ol)
.setNewLayout(nl)
.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setImage(img)
.setSubresourceRange({aspect, 0, 1, 0, 1})
.setSrcAccessMask(src)
.setDstAccessMask(dst);
c.pipelineBarrier(ss, ds, {}, {}, {}, b);
}
int main(int argc, char** argv) {
bool headless = argc > 1 && std::string(argv[1]) == "--headless";
const char* ppmOut = argc > 2 ? argv[2] : "out.ppm";
vkmini::Vk vk;
GLFWwindow* win = nullptr;
vk::UniqueSurfaceKHR surface;
if (!headless) {
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
win = glfwCreateWindow(512, 256, "vkmini p15", nullptr, nullptr);
}
std::vector<const char*> instExts;
if (!headless) {
uint32_t n = 0;
const char** e = glfwGetRequiredInstanceExtensions(&n);
instExts.assign(e, e + n);
}
vk.createInstance(instExts);
if (!headless) {
VkSurfaceKHR raw;
glfwCreateWindowSurface(vk.instance.get(), win, nullptr, &raw);
surface = vk::UniqueSurfaceKHR(raw, vk.instance.get());
}
vk.pickPhysicalDevice();
vk.createDevice(headless ? std::vector<const char*>{}
: std::vector<const char*>{
VK_KHR_SWAPCHAIN_EXTENSION_NAME});
// seviye-0 texture: dört çeyrek renk
constexpr uint32_t TW = 256, TH = 256;
std::vector<uint8_t> px(TW * TH * 4);
for (uint32_t y = 0; y < TH; ++y)
for (uint32_t x = 0; x < TW; ++x) {
uint8_t* p = &px[(y * TW + x) * 4];
bool left = x < TW / 2, top = y < TH / 2;
p[0] = left ? 255 : 0; // r
p[1] = top ? 0 : 255; // g
p[2] = !left && !top ? 255 : 0; // b
p[3] = 255;
}
auto tex = makeTexture(vk, TW, TH);
uploadAndGenerateMips(vk, tex, TW, TH, px.data());
if (headless) {
vkmini::Headless hl;
hl.init(vk, 512, 256);
Pipe p = makePipeline(vk, vk::Format::eR8G8B8A8Unorm,
tex.view.get());
hl.render(vk::ImageLayout::eColorAttachmentOptimal,
[&](vk::CommandBuffer c) {
record(c, hl.color.view.get(), hl.extent, p);
});
hl.savePpm(ppmOut);
printf("wrote %s\n", ppmOut);
return 0;
}
vkmini::Swapchain sc;
sc.create(vk, vk.phys, surface.get(), 512, 256);
Pipe p = makePipeline(vk, sc.format, tex.view.get());
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 f = 0; f < 240 && !glfwWindowShouldClose(win); ++f) {
glfwPollEvents();
vk.device->resetFences(fence.get());
auto acq = vk.device->acquireNextImageKHR(
sc.sc.get(), UINT64_MAX, acqSem.get(), {});
vk::Image img = sc.images[acq.value];
cmd[0]->reset();
cmd[0]->begin(
{vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
imgBarrier(cmd[0].get(), img, vk::ImageAspectFlagBits::eColor,
vk::ImageLayout::eUndefined,
vk::ImageLayout::eColorAttachmentOptimal,
vk::AccessFlags{},
vk::AccessFlagBits::eColorAttachmentWrite,
vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eColorAttachmentOutput);
record(cmd[0].get(), sc.views[acq.value].get(), sc.extent, p);
imgBarrier(cmd[0].get(), img, vk::ImageAspectFlagBits::eColor,
vk::ImageLayout::eColorAttachmentOptimal,
vk::ImageLayout::ePresentSrcKHR,
vk::AccessFlagBits::eColorAttachmentWrite,
vk::AccessFlags{},
vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eBottomOfPipe);
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);
(void)vk.queue.presentKHR(pi);
(void)vk.device->waitForFences(fence.get(), VK_TRUE,
UINT64_MAX);
}
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ı: iki-geçişli post-processing pipeline’ı — sahneyi offscreen render et, sonra bir ekran geçişinde sample et.
