#include "fiko/render/doku_dizisi.hpp" #include #include #include "fiko/core/gunluk.hpp" #include "fiko/platform/gorsel.hpp" #include "fiko/render/vk_kontrol.hpp" #include "fiko/render/vulkan_baglam.hpp" #include "vk_mem_alloc.h" namespace fiko { namespace { const std::string kBosYol; u32 mipSayisi(int g, int y) { u32 n = 1; int b = g > y ? g : y; while (b > 1) { b >>= 1; ++n; } return n; } } // namespace DokuDizisi::DokuDizisi(VulkanBaglam& vb, int enBuyukKenar, f32 anizotropi) : vb_(vb), enBuyukKenar_(enBuyukKenar > 63 ? enBuyukKenar : 4096) { VkSamplerCreateInfo sb{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; sb.magFilter = VK_FILTER_LINEAR; sb.minFilter = VK_FILTER_LINEAR; sb.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; // Tekrar (REPEAT): prosedürel izdüşümde doku alanı sınırsızdır — CLAMP kenarda // tek pikseli uzatır (çirkin şerit). Sarma tipi malzeme başına UV'ye uygulanır // (shader `fract/mirror`), örnekleyici tek kalır → sampler patlaması yok. sb.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; sb.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; sb.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; sb.maxLod = VK_LOD_CLAMP_NONE; sb.anisotropyEnable = vb_.anizotropiVar() ? VK_TRUE : VK_FALSE; sb.maxAnisotropy = vb_.anizotropiVar() ? std::min(anizotropi, vb_.enBuyukAnizotropi()) : 1.0f; VK_KONTROL(vkCreateSampler(vb_.aygit(), &sb, nullptr, &ornekleyici_)); // Slot 0 = 1×1 beyaz kukla: doku atanmamış kanal bunu örnekler (opak beyaz → // çarpımda nötr). Descriptor dizisinin TAMAMI geçerli görüntüyle dolu olmalı. const unsigned char beyaz[4] = {255, 255, 255, 255}; gorseliKur(slotlar_[0], beyaz, 1, 1, false); slotlar_[0].anahtar = "#kukla"; slotlar_[0].sayac = 1; bilgiler_.resize(KAPASITE); for (u32 i = 0; i < KAPASITE; ++i) bilgiTazele(i); } DokuDizisi::~DokuDizisi() { vb_.bekle(); for (Slot& s : slotlar_) slotYikil(s); if (ornekleyici_) vkDestroySampler(vb_.aygit(), ornekleyici_, nullptr); } void DokuDizisi::bilgiTazele(u32 slot) { // Boş slot kukla görüntüyü gösterir → shader tanımsız descriptor okumaz. const Slot& kaynak = slotlar_[slot].gorunum ? slotlar_[slot] : slotlar_[0]; bilgiler_[slot] = {ornekleyici_, kaynak.gorunum, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}; } const std::string& DokuDizisi::slotYolu(u32 slot) const { return slot < KAPASITE ? slotlar_[slot].yol : kBosYol; } u32 DokuDizisi::slotAl(const std::string& yol, bool srgb) { if (yol.empty()) return 0; const std::string anahtar = yol + (srgb ? "|s" : "|l"); if (auto it = dizin_.find(anahtar); it != dizin_.end()) { ++slotlar_[it->second].sayac; return it->second; } u32 slot = 0; for (u32 i = 1; i < KAPASITE; ++i) if (!slotlar_[i].gorunum) { slot = i; break; } if (!slot) { // Kapasite dolu: referanssız (sayac==0) en eski slotu geri al. for (u32 i = 1; i < KAPASITE; ++i) if (!slotlar_[i].sayac) { slot = i; break; } if (!slot) { gunluk::uyari("Doku dizisi dolu ({} slot) — yeni doku yuklenmedi: {}", KAPASITE, yol); return 0; } dizin_.erase(slotlar_[slot].anahtar); vb_.bekle(); // slot kullanımdaydı olamaz (sayac 0) ama görüntü uçuşta olabilir slotYikil(slotlar_[slot]); } // Tavan (ayar): 4K doku = 64 MB RGBA + mip; üstü VRAM'i sessizce yer. const platform::Gorsel gr = platform::gorselOku(yol, enBuyukKenar_); if (!gr.gecerli()) { gunluk::uyari("Doku okunamadi: {}", yol); return 0; } Slot& s = slotlar_[slot]; if (!gorseliKur(s, gr.rgba.data(), gr.genislik, gr.yukseklik, srgb)) return 0; s.anahtar = anahtar; s.yol = yol; s.sayac = 1; dizin_[anahtar] = slot; bilgiTazele(slot); ++surum_; ++dolu_; vramBayt_ += s.bayt; return slot; } void DokuDizisi::slotBirak(u32 slot) { if (!slot || slot >= KAPASITE || !slotlar_[slot].sayac) return; --slotlar_[slot].sayac; // Görüntü hemen YIKILMAZ: aynı doku yeniden atanabilir (malzeme penceresinde // kanal açıp kapamak sık). Slot ancak kapasite dolunca geri alınır. } bool DokuDizisi::gorseliKur(Slot& s, const unsigned char* rgba, int g, int y, bool srgb) { if (g <= 0 || y <= 0) return false; const u32 mip = mipSayisi(g, y); const VkFormat format = srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM; VkImageCreateInfo gb{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; gb.imageType = VK_IMAGE_TYPE_2D; gb.format = format; gb.extent = {(u32)g, (u32)y, 1}; gb.mipLevels = mip; gb.arrayLayers = 1; gb.samples = VK_SAMPLE_COUNT_1_BIT; gb.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; // SRC: mip zinciri blit'i VmaAllocationCreateInfo gt{}; gt.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; if (vmaCreateImage(vb_.ayirici(), &gb, >, &s.gorsel, &s.tahsis, nullptr) != VK_SUCCESS) return false; const u64 bayt = (u64)g * (u64)y * 4; VkBufferCreateInfo tb{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; tb.size = bayt; tb.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; VmaAllocationCreateInfo tt{}; tt.usage = VMA_MEMORY_USAGE_AUTO; tt.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; VkBuffer kaynak = VK_NULL_HANDLE; VmaAllocation kaynakT = nullptr; VmaAllocationInfo bilgi{}; if (vmaCreateBuffer(vb_.ayirici(), &tb, &tt, &kaynak, &kaynakT, &bilgi) != VK_SUCCESS) { vmaDestroyImage(vb_.ayirici(), s.gorsel, s.tahsis); s.gorsel = VK_NULL_HANDLE; return false; } std::memcpy(bilgi.pMappedData, rgba, bayt); vb_.anindaGonder([&](VkCommandBuffer kb) { VkImageMemoryBarrier b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; b.image = s.gorsel; b.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, mip, 0, 1}; b.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; b.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; b.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; vkCmdPipelineBarrier(kb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b); VkBufferImageCopy kopya{}; kopya.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; kopya.imageExtent = {(u32)g, (u32)y, 1}; vkCmdCopyBufferToImage(kb, kaynak, s.gorsel, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &kopya); // Mip zinciri: her seviye bir öncekinden yarıya blit'lenir. Kaynak seviye // TRANSFER_SRC'ye çevrilir, sonunda hepsi SHADER_READ'e alınır. int mg = g, my = y; b.subresourceRange.levelCount = 1; for (u32 i = 1; i < mip; ++i) { b.subresourceRange.baseMipLevel = i - 1; b.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; b.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; b.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; b.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; vkCmdPipelineBarrier(kb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b); const int hg = mg > 1 ? mg / 2 : 1, hy = my > 1 ? my / 2 : 1; VkImageBlit blit{}; blit.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, i - 1, 0, 1}; blit.srcOffsets[1] = {mg, my, 1}; blit.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, i, 0, 1}; blit.dstOffsets[1] = {hg, hy, 1}; vkCmdBlitImage(kb, s.gorsel, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, s.gorsel, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_LINEAR); 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); mg = hg; my = hy; } b.subresourceRange.baseMipLevel = mip - 1; b.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; b.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; b.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_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); }); vmaDestroyBuffer(vb_.ayirici(), kaynak, kaynakT); VkImageViewCreateInfo gv{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; gv.image = s.gorsel; gv.viewType = VK_IMAGE_VIEW_TYPE_2D; gv.format = format; gv.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, mip, 0, 1}; if (vkCreateImageView(vb_.aygit(), &gv, nullptr, &s.gorunum) != VK_SUCCESS) { vmaDestroyImage(vb_.ayirici(), s.gorsel, s.tahsis); s.gorsel = VK_NULL_HANDLE; return false; } s.bayt = bayt + bayt / 3; // mip zinciri ≈ %33 ek return true; } void DokuDizisi::slotYikil(Slot& s) { if (s.gorunum) vkDestroyImageView(vb_.aygit(), s.gorunum, nullptr); if (s.gorsel) vmaDestroyImage(vb_.ayirici(), s.gorsel, s.tahsis); if (vramBayt_ >= s.bayt) vramBayt_ -= s.bayt; if (s.gorunum && dolu_) --dolu_; s = {}; } } // namespace fiko