#include "fiko/render/viewport_hedef.hpp" #include "fiko/render/pencere_yuzeyi.hpp" // yenidenKurmaKapisi (ortak resize debounce) #include #include #include #include "fiko/core/gunluk.hpp" #include "fiko/render/vk_kontrol.hpp" #include "fiko/render/vulkan_baglam.hpp" #include "miniz.h" #include "vk_mem_alloc.h" namespace fiko { ViewportHedef::ViewportHedef(VulkanBaglam& vb, u32 g, u32 y) : vb_(vb) { olustur(g, y); } ViewportHedef::~ViewportHedef() { yik(); } void ViewportHedef::olustur(u32 g, u32 y) { boyut_ = {std::max(g, 1u), std::max(y, 1u)}; const VkDevice aygit = vb_.aygit(); // Kurulum anında sabitlenmiş, cihaz-desteğine kenetlenmiş etkin örnek sayısı // (VulkanAygit::viewportGecisOlustur zaten kenetleyip geri yazdı — burada yalnız okunur). const VkSampleCountFlagBits ornek = VulkanAygit::msaaOrnekSayisi(); const bool msaa = ornek != VK_SAMPLE_COUNT_1_BIT; VkImageCreateInfo gb{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; gb.imageType = VK_IMAGE_TYPE_2D; gb.format = kViewportRenkFormat; gb.extent = {boyut_.width, boyut_.height, 1}; gb.mipLevels = 1; gb.arrayLayers = 1; gb.samples = VK_SAMPLE_COUNT_1_BIT; // renk_: her zaman tek örnek (resolve HEDEFİ / ImGui okur) gb.tiling = VK_IMAGE_TILING_OPTIMAL; gb.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; VmaAllocationCreateInfo gt{}; gt.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; VK_KONTROL(vmaCreateImage(vb_.ayirici(), &gb, >, &renk_, &renkT_, nullptr)); if (VulkanAygit::adresIzleme()) gunluk::bilgi("[teshis] viewport RENK gorsel = 0x{:x}", (unsigned long long)(u64)renk_); VkImageViewCreateInfo gv{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; gv.image = renk_; gv.viewType = VK_IMAGE_VIEW_TYPE_2D; gv.format = kViewportRenkFormat; gv.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; VK_KONTROL(vkCreateImageView(aygit, &gv, nullptr, &renkGorunum_)); gb.format = kViewportDerinlikFormat; gb.samples = ornek; // derinlik_: MSAA örnek sayısında (asla resolve/sample edilmez) gb.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; VK_KONTROL(vmaCreateImage(vb_.ayirici(), &gb, >, &derinlik_, &derinlikT_, nullptr)); if (VulkanAygit::adresIzleme()) gunluk::bilgi("[teshis] viewport DERINLIK gorsel = 0x{:x}", (unsigned long long)(u64)derinlik_); gv.image = derinlik_; gv.format = kViewportDerinlikFormat; gv.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0, 1, 0, 1}; VK_KONTROL(vkCreateImageView(aygit, &gv, nullptr, &derinlikGorunum_)); if (msaa) { // Çok-örnekli renk hedefi (yalnız pass-içi; framebuffer dışında hiç okunmaz). gb.format = kViewportRenkFormat; gb.samples = ornek; gb.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; VK_KONTROL(vmaCreateImage(vb_.ayirici(), &gb, >, &renkMS_, &renkMST_, nullptr)); if (VulkanAygit::adresIzleme()) gunluk::bilgi("[teshis] viewport MSAA RENK gorsel = 0x{:x}", (unsigned long long)(u64)renkMS_); gv.image = renkMS_; gv.format = kViewportRenkFormat; gv.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; VK_KONTROL(vkCreateImageView(aygit, &gv, nullptr, &renkMSGorunum_)); } // Ek sırası render pass'inki ile BİREBİR eşleşmeli (bkz VulkanAygit::viewportGecisOlustur): // msaa kapalı → [renk, derinlik]; msaa açık → [msaaRenk, msaaDerinlik, çözünürlemeRenk]. const VkImageView gorunumlerKapali[2] = {renkGorunum_, derinlikGorunum_}; const VkImageView gorunumlerAcik[3] = {renkMSGorunum_, derinlikGorunum_, renkGorunum_}; VkFramebufferCreateInfo fb{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; fb.renderPass = vb_.viewportGecis(); fb.attachmentCount = msaa ? 3 : 2; fb.pAttachments = msaa ? gorunumlerAcik : gorunumlerKapali; fb.width = boyut_.width; fb.height = boyut_.height; fb.layers = 1; VK_KONTROL(vkCreateFramebuffer(aygit, &fb, nullptr, &kareTampon_)); } void ViewportHedef::yik() { const VkDevice aygit = vb_.aygit(); if (kareTampon_) vkDestroyFramebuffer(aygit, kareTampon_, nullptr); if (renkGorunum_) vkDestroyImageView(aygit, renkGorunum_, nullptr); if (renk_) vmaDestroyImage(vb_.ayirici(), renk_, renkT_); if (derinlikGorunum_) vkDestroyImageView(aygit, derinlikGorunum_, nullptr); if (derinlik_) vmaDestroyImage(vb_.ayirici(), derinlik_, derinlikT_); if (renkMSGorunum_) vkDestroyImageView(aygit, renkMSGorunum_, nullptr); if (renkMS_) vmaDestroyImage(vb_.ayirici(), renkMS_, renkMST_); kareTampon_ = VK_NULL_HANDLE; renkGorunum_ = VK_NULL_HANDLE; renk_ = VK_NULL_HANDLE; derinlikGorunum_ = VK_NULL_HANDLE; derinlik_ = VK_NULL_HANDLE; renkMSGorunum_ = VK_NULL_HANDLE; renkMS_ = VK_NULL_HANDLE; } void ViewportHedef::boyutla(u32 g, u32 y) { g = std::max(g, 1u); y = std::max(y, 1u); if (g == boyut_.width && y == boyut_.height) return; // Sürüklerken saniyede 40+ kez yık/kur = sürücüde aygıt kaybı (ölçüldü 07-21). // Takas zinciriyle AYNI kapı: aralık dolmadan yeniden kurulmaz (görüntü bir süre // eski çözünürlükte gerilir; sürükleme bitince tek kurulum olur). if (!PencereYuzeyi::yenidenKurmaKapisi(sonKurmaMs_)) return; // Boyut KARARLI olana dek yeniden kurma (render.hedef_kararlilik_ms; 0 = eski yol). static u32 sonG = 0, sonY = 0; static long long sonDegisim = 0; if (!PencereYuzeyi::boyutKararliMi(g, y, sonG, sonY, sonDegisim)) return; if (!vb_.bekle("ViewportHedef::boyutla")) return; // device kayıpsa yeniden boyutlandırma yok yik(); olustur(g, y); } void ViewportHedef::basla(VkCommandBuffer kb, float r, float g, float b) { // msaa açıkken ek sırası [msaaRenk, msaaDerinlik, çözünürlemeRenk] — 3. ek (resolve // hedefi) LOAD_OP_DONT_CARE olduğundan temizleme değeri kullanılmaz ama dizi // attachmentCount kadar eleman gerektirir (Vulkan zorunluluğu). const bool msaa = VulkanAygit::msaaOrnekSayisi() != VK_SAMPLE_COUNT_1_BIT; VkClearValue temizle[3]{}; // Alfa=0: sahne geometrisi opak (frag alfa=1) yazar, boş alan saydam kalır → // ImGui bu dokuyu arka plan görselinin (bulut+logo) üstüne kompoze eder. temizle[0].color = {{r, g, b, 0.0f}}; temizle[1].depthStencil = {0.0f, 0}; // reverse-Z: uzak = 0 VkRenderPassBeginInfo rp{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; rp.renderPass = vb_.viewportGecis(); rp.framebuffer = kareTampon_; rp.renderArea = {{0, 0}, boyut_}; rp.clearValueCount = msaa ? 3 : 2; rp.pClearValues = temizle; vkCmdBeginRenderPass(kb, &rp, VK_SUBPASS_CONTENTS_INLINE); const VkViewport gorunumAlan{0.0f, 0.0f, (float)boyut_.width, (float)boyut_.height, 0.0f, 1.0f}; const VkRect2D makas{{0, 0}, boyut_}; vkCmdSetViewport(kb, 0, 1, &gorunumAlan); vkCmdSetScissor(kb, 0, 1, &makas); } void ViewportHedef::bitir(VkCommandBuffer kb) { vkCmdEndRenderPass(kb); } bool ViewportHedef::pngKaydet(const char* yol) { std::vector png; bool basarili = false; if (pngBellek(png)) { if (FILE* f = std::fopen(yol, "wb")) { basarili = std::fwrite(png.data(), 1, png.size(), f) == png.size(); std::fclose(f); } } if (basarili) gunluk::bilgi("Viewport PNG yazildi: {} ({}x{})", yol, boyut_.width, boyut_.height); else gunluk::hata("Viewport PNG yazilamadi: {}", yol); return basarili; } bool ViewportHedef::pngBellek(std::vector& png) { png.clear(); if (vb_.aygitKayipMi() || !renk_) return false; const u32 g = boyut_.width, y = boyut_.height; const VkDeviceSize bayt = (VkDeviceSize)g * y * 4; VkBufferCreateInfo bb{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; bb.size = bayt; bb.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; bb.sharingMode = VK_SHARING_MODE_EXCLUSIVE; VmaAllocationCreateInfo bt{}; bt.usage = VMA_MEMORY_USAGE_AUTO; bt.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; VkBuffer tampon = VK_NULL_HANDLE; VmaAllocation tahsis = nullptr; VmaAllocationInfo bilgi{}; if (vmaCreateBuffer(vb_.ayirici(), &bb, &bt, &tampon, &tahsis, &bilgi) != VK_SUCCESS) return false; // Aynı kuyrukta önceki kare submit'ine karşı bariyer (queue-genel geçerli): // SHADER_READ_ONLY → TRANSFER_SRC, kopya, geri SHADER_READ_ONLY. vb_.anindaGonder([&](VkCommandBuffer kb) { VkImageMemoryBarrier b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; b.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; b.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; b.image = renk_; b.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; b.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; b.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; vkCmdPipelineBarrier(kb, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b); VkBufferImageCopy k{}; k.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; k.imageExtent = {g, y, 1}; vkCmdCopyImageToBuffer(kb, renk_, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, tampon, 1, &k); b.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; b.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; b.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; b.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; vkCmdPipelineBarrier(kb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b); }); bool basarili = false; if (!vb_.aygitKayipMi() && bilgi.pMappedData) { vmaInvalidateAllocation(vb_.ayirici(), tahsis, 0, bayt); // kViewportRenkFormat R8G8B8A8 → bayt sırası zaten RGBA (swizzle gerekmez). size_t pngBayt = 0; void* kodlanan = tdefl_write_image_to_png_file_in_memory_ex(bilgi.pMappedData, (int)g, (int)y, 4, &pngBayt, 6, MZ_FALSE); if (kodlanan) { png.assign((const u8*)kodlanan, (const u8*)kodlanan + pngBayt); basarili = true; mz_free(kodlanan); } } vmaDestroyBuffer(vb_.ayirici(), tampon, tahsis); return basarili; } } // namespace fiko