Vulkan'da vkCmdBlitImage ile mipmap nasıl üretilir

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.

Sol: zorlanan LOD 4 (çeyrekler görünür). Sağ: zorlanan LOD 8 = 1x1 mip = ortalama renk

Üç malzeme

  1. Image, baştan mipLevels = log2(max(w,h))+1 seviyeyle oluşturulmalı — zinciri sonra büyütemezsin. Usage, eSampled‘a ek olarak eTransferSrc | eTransferDst ister ve view tüm seviyeleri kapsamalı.
  2. Üretim döngüsü: her l seviyesi için eTransferDstOptimal‘a geç, l-1 seviyesinden blit (eTransferSrcOptimal‘da bırakılmış), sonra bir sonraki iterasyon için l‘yi eTransferSrcOptimal‘a geç. Sonda bir barrier tüm zinciri eShaderReadOnlyOptimal‘a taşır.
  3. Sampler: mipmapMode = eLinear ve zinciri kapsayan maxLod — klasik bug, OpenGL’in varsayılan maxLod=0‘ını kopyalamak (veya unutmak, aynı etki); bu her sample’ı sessizce seviye 0’a kenetler.
example.cpp
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

build.sh
g++ -std=c++23 -O2 main.cpp -o app -lvulkan -lglfw -lshaderc
./app                    # pencereli
./app --headless out.ppm # offscreen -> PPM
python3 check.py out.ppm

Kontrol

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.
check.py
#!/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()
output.txt
$ ./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 = average

Tam kaynak

main.cpp
// 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.


Check out similar posts by category: C++ Vulkan