// belge_ciz — Belge sahnesini ViewportHedef'e çizen kare-içi boru hattı. // Gövde main.cpp'deki sahne-çizim bloğundan BİREBİR taşındı (MDI Dalga 2); // yalnız fonksiyon-static önbellekler CizimOnbellek üyesine, tekil viewport // çağrıları hedef API'sine çevrildi. Davranış değişikliği yok. #include "belge_ciz.hpp" #include #include #include #include #include #include #include #include "fiko/core/gunluk.hpp" #include "fiko/core/mat/frustum.hpp" #include "fiko/render/referans_olcek.hpp" #include "fiko/render/vertex.hpp" #include "fiko/render/vulkan_baglam.hpp" #include "fiko/sahne/isin_bvh.hpp" // kalinlik per-fragment isin alani (GPU BVH) #include "fiko/sahne/sahne.hpp" #include "secim_gorunum.hpp" namespace fiko { // Dokunmatik/kalem: tek doğruluk kaynağı (tüm pencereler bunu okur) — bkz. belge_ciz.hpp. bool& dokunmatikAktif() { static bool b = true; return b; } // AyarSistemi — dokunmatik olmayan makinede etkisiz girdi::DokunmaEsleme& dokunmaEslemeGlobal() { static girdi::DokunmaEsleme e; return e; } girdi::DokunmaAyar& dokunmaAyarGlobal() { static girdi::DokunmaAyar a; return a; } int& orbitLodGecikmeKare() { static int k = 8; return k; } // AyarSistemi (bkz. belge_ciz.hpp) bool& bireyselOrbitLod() { static bool b = true; return b; } // AyarSistemi (bkz. belge_ciz.hpp) // Bireysel kaba LOD alt eşiği (indeks sayısı): altındaki gövdede etkileşim LOD'u atlanır // (küçük modelde ince mesh zaten ucuz; toggle görsel titreşim yapar). 150k indeks = 50k üçgen. int& bireyselLodMinIndeks() { static int k = 150'000; return k; } // AyarSistemi bool& etkilesimLodAktif() { static bool b = true; return b; } // AyarSistemi — ana anahtar bool& lodHoverTetik() { static bool b = false; return b; } // AyarSistemi — hover VARSAYILAN KAPALI bool& lodDondurAktif() { static bool b = true; return b; } // AyarSistemi — döndürmede LOD bool& lodPanAktif() { static bool b = true; return b; } // AyarSistemi — taşımada LOD bool& lodZoomAktif() { static bool b = true; return b; } // AyarSistemi — zoomda LOD // Çok-seçim vurgu eşiği: bundan FAZLA gövde seçiliyse (ör. ağaçtan büyük grup) seçili gövdeler // mega-partiden ÇIKARILMAZ → seçim değişiminde rebake (cook) + binlerce bireysel/overlay draw // OLMAZ. Karşılığı: eşik üstünde 3B per-gövde vurgu atlanır (ürün ağacı seçimi zaten gösterir). // 0 = kapalı (eski davranış: her seçili bireysel çizilir + vurgulanır, her seçimde cook). Vars 250. int& cokSecimVurguEsigi() { static int k = 3000; return k; } // Seçim cook'unu atla: seçili gövdeyi mega-partiden ÇIKARMA (hover gibi partide TUT) → seçim // değişiminde mega-parti YENİDEN PİŞMEZ (~18ms hitch gider, KÜÇÜK seçimde bile). Vurgu, partinin // üstüne z-bias'lı overlay ile basılır (kanıtlı hover kalıbı). VARSAYILAN KAPALI (eski davranış: // seçili bireysel + cook). Açınca RUNTIME TEST gerek (seçili yüzde z-fighting/speckle kontrolü). bool& secimCookAtla() { static bool b = true; return b; } // Kenar hover CPU pick (AyarSistemi "secim.cpu_kenar_pick", vars. AÇIK): kenar hover'ı // ekran-uzayı CPU testine taşır → GPU pick'te "her hover'da tüm kenarları çiz" yükü kalkar. // Kapalı → eski GPU kenar pass'ı (fallback). Model kenarları (datum HARİÇ, o GPU'da ucuz). bool& cpuKenarPick() { static bool b = true; return b; } // Tek-yüz vurgusunda (seçim/hover overlay) gövdenin TÜM mesh'ini değil YALNIZ o yüzün indeks // aralığını çiz. Ölçüldü (481k üçgen, tek yüz seçili, fare sabit): 60→25 FPS, GPU 24 ms/kare, // bir çekirdek %100 — maliyet shader'da discard edilen 480k üçgenin rasterizasyonu. // Kapalı VEYA düğümün aralık tablosu boşsa (süreksiz mesh) eski tam çizim (birebir davranış). bool& yuzAralikCiz() { static bool b = true; return b; } // Tek-kare takılma (hitch) teşhis eşiği, ms. Üstündeki her kare log'a tek satır yazar — // 600-karelik ortalamalar hitch'i gizliyor. 0 = kapalı. int& kareHitchEsigiMs() { static int m = 33; return m; } AdimOlcer::~AdimOlcer() { const f64 ms = std::chrono::duration(std::chrono::steady_clock::now() - t0).count(); if (kareHitchEsigiMs() > 0 && ms > (f64)kareHitchEsigiMs()) gunluk::uyari("[perf] ADIM YAVAS: {} — {:.0f} ms", ad, ms); } constexpr f32 kKenarPikselTol = 6.0f; // kenar hover 2B piksel yakalama yarıçapı namespace { constexpr u32 TUMU = 0xFFFFFFFFu; // Tek yüz vurgulanıyorsa o yüzün [ofset, sayı) indeks aralığı; yoksa false (tam mesh çiz). // Alt-aralık çizimi shader'ın discard'ıyla BİREBİR aynı pikseli üretir (kesin alt küme). bool yuzAralikBul(const Dugum& d, u32 yuz, u32& ofs, u32& sayi) { if (!yuzAralikCiz() || yuz == 0 || yuz == TUMU) return false; if ((std::size_t)yuz >= d.yuzIndeksAralik.size()) return false; const auto& a = d.yuzIndeksAralik[yuz]; if (a.second == 0 || a.first + a.second > d.indeksSayi) return false; ofs = a.first; sayi = a.second; return true; } u32 floatBits(f32 f) { u32 b; std::memcpy(&b, &f, 4); return b; } // Tersi: u32'yi f32 alanında TAŞI (analiz mod 10 izole yüz ordinali → cekimYonu.x; // shader floatBitsToUint ile geri okur). Sayısal dönüşüm DEĞİL, bit taşıma. f32 bitsFloat(u32 b) { f32 f; std::memcpy(&f, &b, 4); return f; } struct ModelItme { Mat4f mvp; Mat4f model; u32 vurgu[4]; // x: hoverYuz, y: izoleYuz, z: zamanBits, w: govdeAlfa bits }; // 144 bayt. Işıklar + seçim/hover rengi (112B) KARE-SABİT → kare_ubo.hpp // KareVeri'ye taşındı: her draw'da kopyalanmaları 256B tavanını doldurup yeni // alan eklenmesini engelliyordu (kesit clip'i model hattına bu yüzden konamadı // → gövde analiz hattına yönlendirildi → mod 10 hack'i). [[fikatia-kesit-pick-kirpma]] struct KenarItme { Mat4f mvp; f32 renk[4]; u32 vurgu[4]; // x: hoverAlt, y: izoleAlt, z: boyutBits (nokta), w: kesitSayi // Kesit clip (kenar.vert/frag): dunya donusumu + duzlemler. Varsayilan birim/0 // → clip'siz cizimler (datum/nokta/eksen-sistemi) alan doldurmadan gecer. Mat4f model = Mat4f::birim(); f32 kesit[4][4]{}; // xyz dunya normal, w d }; // Analiz shader push-constant (draft/undercut/slope + skaler-alan + kesit clip). // Layout analiz.vert/frag ile BIREBIR (mvp, model, cekimYonu, esik, rampa, kesit). struct AnalizItme { Mat4f mvp; Mat4f model; f32 cekimYonu[4]; // xyz yon, w mod (0 draft, 1 undercut, 2 kesit-only, 3 skaler, 4 slope, 8 signed-fark) f32 esik[4]; // x tersEsik/dikBant(°), y alanMax, z alanMin, w kesitSayi f32 rampa[4]; // x rampaTip(int), y kritikEsik, z/w rezerv f32 kesit[4][4]; // xyz normal, w d f32 ekstra[4]; // mod 8 (signed fark): xyz = AYNI/tolerans-içi rengi (panelden) }; // 256 bayt — push-constant üst sınır (tam dolu) // Selection Stack "peek" lens push-constant (peek.vert/frag ile BİREBİR). struct PeekItme { Mat4f mvp; Mat4f model; f32 merkez[4]; // xy: daire merkez (fb px), z: yarıçap (px), w: rezerv f32 renk[4]; // xyz: tint, w: karışım f32 hedef[4]; // x: aktif alt (yüz ordinali), y: tür, z/w: rezerv }; // Peek kenar/nokta lens push-constant (peek_kenar.vert/frag ile BİREBİR). struct PeekKenarItme { Mat4f mvp; f32 merkez[4]; // xy: daire merkez (fb px), z: yarıçap f32 renk[4]; // xyz: highlight f32 hedef[4]; // x: aktif kenar/nokta ordinali }; // GPU kesit kapağı dolgu push-constant (kesit_dolgu.vert/frag ile BİREBİR). struct KesitDolguItme { Mat4f mvp; // dünya → clip (viewProj) f32 merkez[4]; // xyz düzlem-üstü merkez, w yarı-kenar f32 uEks[4]; // xyz düzlem u ekseni f32 vEks[4]; // xyz düzlem v ekseni f32 kapakRenk[4]; // xyz renk, w alfa f32 kapakNrm[4]; // xyz kapak normali, w diğerKesitSayi f32 kesit[4][4]; // bu düzlem HARİÇ diğer aktif düzlemler }; } // namespace BoruHatlari::BoruHatlari(VulkanBaglam& vulkan, const std::string& shaderDizin) : golge(vulkan, analiz::golgeCozunurluk(), shaderDizin), isinAlani(vulkan), kareUbo(vulkan), malzemeGpu(vulkan) { const VkRenderPass vpGecis = vulkan.viewportGecis(); BoruHattiAyar modelAyar; modelAyar.vertexSpv = shaderDizin + "model.vert.spv"; modelAyar.fragmentSpv = shaderDizin + "model.frag.spv"; modelAyar.itmeSabitBayt = sizeof(ModelItme); modelAyar.vertexAdim = sizeof(Vertex); modelAyar.nitelikler = Vertex::nitelikler(); // Yuzeyleri hafif geriye it → kenarlar gercek derinlikte kalip on yuzeylerce // ortulur (arka kenarlar modelin icinden sizmaz, temiz gizli-cizgi). modelAyar.derinlikItme = 1.5f; // Çift-taraflı: ters/tutarsız winding'li STEP yüzleri (aynalı parça, ters kabuk, // önbellekten yüklenmiş eski mesh) ön yüzü ayıklanıp "şeffaf" görünmesin — derinlik // testi görünürlüğü belirler, winding değil. model.frag gl_FrontFacing ile ışığı düzeltir. modelAyar.ayiklama = VK_CULL_MODE_NONE; modelAyar.gecis = vpGecis; // Kare-sabit ışık/renk UBO'su (model.frag set=0). Türetilen model varyantları // (tek-yön, saydam) bu ayardan kopyalanır → yerleşimi de devralır. modelAyar.setYerlesim = kareUbo.setYerlesim(); // set=1: malzeme tablosu + yuz->malzeme + doku dizisi (model.frag statik referans // verir → HER model ciziminde bagli olmali; malzeme yoksa kukla tamponlar bagli // ve shader malzeme 0'da eski yola duser). modelAyar.setYerlesim2 = malzemeGpu.setYerlesim(); modelHat = BoruHatti(vulkan, modelAyar); // Çift-taraflı KAPALI varyant (Ayarlar): arka-yüz ayıklamalı — daha hızlı ama // ters-winding modelde "şeffaf yüz" görünebilir. Kullanıcı tercihine bağlı seçilir. BoruHattiAyar modelTekAyar = modelAyar; modelTekAyar.ayiklama = VK_CULL_MODE_BACK_BIT; modelHatTekYon = BoruHatti(vulkan, modelTekAyar); // Saydam gövde varyantı (voxel alanı/hayalet): alpha blend + derinlik-yazmasız // (test açık — opakların arkasında doğru örtülür, kendi içinde yaklaşık). Saydamda // arka-yüz ayıklama KALIR (çift-taraflı blend iç yüzü iki kez harmanlar → yanlış). BoruHattiAyar modelSeffafAyar = modelAyar; modelSeffafAyar.ayiklama = VK_CULL_MODE_BACK_BIT; modelSeffafAyar.harmanla = true; modelSeffafAyar.derinlikYaz = false; modelSeffafHat = BoruHatti(vulkan, modelSeffafAyar); // Analiz varyanti (draft/undercut renk + kesit clip). Model vertex girdisi; // ayri fragment/vertex + genis push-constant. Ayiklama NONE (DoubleSide analiz). BoruHattiAyar analizAyar; analizAyar.vertexSpv = shaderDizin + "analiz.vert.spv"; analizAyar.fragmentSpv = shaderDizin + "analiz.frag.spv"; analizAyar.itmeSabitBayt = sizeof(AnalizItme); analizAyar.vertexAdim = sizeof(Vertex); analizAyar.nitelikler = Vertex::nitelikler(); // binding=1: per-vertex analiz skaleri (radius/kalinlik) — location=4, tek float. analizAyar.ikinciAdim = sizeof(f32); analizAyar.nitelikler.push_back({4, 1, VK_FORMAT_R32_SFLOAT, 0}); analizAyar.ayiklama = VK_CULL_MODE_NONE; analizAyar.derinlikItme = 1.5f; analizAyar.gecis = vpGecis; // set 0: undercut gölge haritası (UBO{golgeVp,par} + sampler2D). analiz.frag mod 1 // dışında okumaz ama set HER analiz çiziminde bağlı olmalı (kapalıyken par.x=0). analizAyar.setYerlesim = golge.setYerlesim(); // set 1: et kalınlığı per-fragment ışın alanı (BVH + üçgen SSBO). Shader statik // referans verdiği için HER analiz çiziminde bağlı olmalı; veri yokken kukla // tamponlar bağlıdır ve ışın yolu rampa.z<=0 ile hiç çalışmaz. analizAyar.setYerlesim2 = isinAlani.setYerlesim(); analizHat = BoruHatti(vulkan, analizAyar); // Undercut EXACT gölge overlay'i (mod 9): analiz ikizi, derinlik biası KAMERAYA // doğru → altındaki yüzün önünde kalır (analizAyar +1.5 ile geriye itiyordu). BoruHattiAyar golgeOverlayAyar = analizAyar; golgeOverlayAyar.derinlikItme = -2.0f; golgeOverlayHat = BoruHatti(vulkan, golgeOverlayAyar); // Kesit kapagi saydam varyanti (yari-saydam/saydam kapak kipi): blend + // derinlik-yazmasiz — kapak arkasindaki kesilmis ic hayal meyal gorunur. BoruHattiAyar analizSeffafAyar = analizAyar; analizSeffafAyar.harmanla = true; analizSeffafAyar.derinlikYaz = false; analizSeffafHat = BoruHatti(vulkan, analizSeffafAyar); // Zebra / Reflect (Dalga B — CANLI-GPU yüzey kalite): analiz.vert + ayrı frag, // AYNI push-constant layout (AnalizItme). Yalnız fragment shader değişir. BoruHattiAyar zebraAyar = analizAyar; zebraAyar.fragmentSpv = shaderDizin + "zebra.frag.spv"; zebraHat = BoruHatti(vulkan, zebraAyar); BoruHattiAyar reflectAyar = analizAyar; reflectAyar.fragmentSpv = shaderDizin + "reflect.frag.spv"; reflectHat = BoruHatti(vulkan, reflectAyar); // Kenar (wireframe) — ince koyu, hover ici cyan BoruHattiAyar kenarAyar; kenarAyar.vertexSpv = shaderDizin + "kenar.vert.spv"; kenarAyar.fragmentSpv = shaderDizin + "kenar.frag.spv"; kenarAyar.itmeSabitBayt = sizeof(KenarItme); kenarAyar.vertexAdim = sizeof(KenarVertex); kenarAyar.nitelikler = KenarVertex::nitelikler(); kenarAyar.topoloji = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; kenarAyar.ayiklama = VK_CULL_MODE_NONE; kenarAyar.cizgiKalinlik = 1.6f; kenarAyar.harmanla = true; kenarAyar.gecis = vpGecis; kenarHat = BoruHatti(vulkan, kenarAyar); // Kenar vurgu — kalin, secili/hover kenarlar ustte BoruHattiAyar kenarVurguAyar = kenarAyar; kenarVurguAyar.cizgiKalinlik = 4.0f; kenarVurguAyar.derinlikYaz = false; kenarVurguHat = BoruHatti(vulkan, kenarVurguAyar); // Referans kenar (düzlem/eksen) — derinlik testi/yazımı KAPALI → katıların ve // diğer kenarların ÜSTÜNDE çizilir (CATIA datum davranışı: hep görünür). BoruHattiAyar refKenarAyar = kenarAyar; refKenarAyar.derinlikTest = false; refKenarAyar.derinlikYaz = false; refKenarHat = BoruHatti(vulkan, refKenarAyar); // Referans kenar seçim vurgusu — kalın + derinlik kapalı (seçili datum üstte turuncu). BoruHattiAyar refKenarVurguAyar = refKenarAyar; refKenarVurguAyar.cizgiKalinlik = 4.0f; refKenarVurguHat = BoruHatti(vulkan, refKenarVurguAyar); // Nokta (koseler) BoruHattiAyar noktaAyar; noktaAyar.vertexSpv = shaderDizin + "nokta.vert.spv"; noktaAyar.fragmentSpv = shaderDizin + "nokta.frag.spv"; noktaAyar.itmeSabitBayt = sizeof(KenarItme); noktaAyar.vertexAdim = sizeof(KenarVertex); noktaAyar.nitelikler = KenarVertex::nitelikler(); noktaAyar.topoloji = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; noktaAyar.ayiklama = VK_CULL_MODE_NONE; noktaAyar.harmanla = true; noktaAyar.gecis = vpGecis; noktaHat = BoruHatti(vulkan, noktaAyar); // --- GPU kesit kapağı (stencil capping) --- // Sayım: katı gövdeleri clip'li çiz, renk/derinlik YAZMA; ön yüz +1 / arka yüz -1 // (INCR/DECR wrap) → stencil ≠ 0 ⟺ o pikselde düzlem katıyı kesiyor. BoruHattiAyar sayimAyar; sayimAyar.vertexSpv = shaderDizin + "kesit_sayim.vert.spv"; sayimAyar.fragmentSpv = shaderDizin + "kesit_sayim.frag.spv"; sayimAyar.itmeSabitBayt = sizeof(AnalizItme); sayimAyar.vertexAdim = sizeof(Vertex); sayimAyar.nitelikler = Vertex::nitelikler(); sayimAyar.ayiklama = VK_CULL_MODE_NONE; sayimAyar.derinlikTest = false; sayimAyar.derinlikYaz = false; sayimAyar.renkYaz = false; sayimAyar.stencilTest = true; sayimAyar.stencilGecOp = VK_STENCIL_OP_INCREMENT_AND_WRAP; // ön yüz sayimAyar.stencilArkaGecOp = VK_STENCIL_OP_DECREMENT_AND_WRAP; // arka yüz sayimAyar.stencilKarsOp = VK_COMPARE_OP_ALWAYS; sayimAyar.stencilYazMask = 0xFF; sayimAyar.stencilRef = 0; sayimAyar.gecis = vpGecis; kesitSayimHat = BoruHatti(vulkan, sayimAyar); // Dolgu: kesit düzlemi quad'ı, stencil ≠ 0 maskesiyle → düz kapak (derinlik yaz). BoruHattiAyar dolguAyar; dolguAyar.vertexSpv = shaderDizin + "kesit_dolgu.vert.spv"; dolguAyar.fragmentSpv = shaderDizin + "kesit_dolgu.frag.spv"; dolguAyar.itmeSabitBayt = sizeof(KesitDolguItme); dolguAyar.vertexAdim = 0; // vertexsiz (gl_VertexIndex) dolguAyar.topoloji = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; dolguAyar.ayiklama = VK_CULL_MODE_NONE; dolguAyar.derinlikTest = true; dolguAyar.derinlikYaz = true; dolguAyar.harmanla = true; // opak (alfa=1) da doğru; yarı-saydam kapak kipi için hazır dolguAyar.stencilTest = true; dolguAyar.stencilGecOp = VK_STENCIL_OP_KEEP; dolguAyar.stencilArkaGecOp = VK_STENCIL_OP_KEEP; dolguAyar.stencilKarsOp = VK_COMPARE_OP_NOT_EQUAL; // stencil ≠ 0 dolguAyar.stencilYazMask = 0x00; // stencil yazma yok dolguAyar.stencilRef = 0; dolguAyar.gecis = vpGecis; kesitDolguHat = BoruHatti(vulkan, dolguAyar); // Selection Stack "peek" lens: model vertex, derinlik-testsiz (occluder'sız), // fragment daire-dışını atar. Highlight gövde imleç dairesinde görünür. BoruHattiAyar peekAyar; peekAyar.vertexSpv = shaderDizin + "peek.vert.spv"; peekAyar.fragmentSpv = shaderDizin + "peek.frag.spv"; peekAyar.itmeSabitBayt = sizeof(PeekItme); peekAyar.vertexAdim = sizeof(Vertex); peekAyar.nitelikler = Vertex::nitelikler(); peekAyar.ayiklama = VK_CULL_MODE_NONE; peekAyar.derinlikTest = false; peekAyar.derinlikYaz = false; peekAyar.harmanla = true; // daire kenarı yumuşak (frag alfa) peekAyar.gecis = vpGecis; peekHat = BoruHatti(vulkan, peekAyar); // Peek kenar/nokta: aktif kenar/nokta lens içinde parlak (gövde hayalet üstünde). BoruHattiAyar peekKenarAyar; peekKenarAyar.vertexSpv = shaderDizin + "peek_kenar.vert.spv"; peekKenarAyar.fragmentSpv = shaderDizin + "peek_kenar.frag.spv"; peekKenarAyar.itmeSabitBayt = sizeof(PeekKenarItme); peekKenarAyar.vertexAdim = sizeof(KenarVertex); peekKenarAyar.nitelikler = KenarVertex::nitelikler(); peekKenarAyar.topoloji = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; peekKenarAyar.ayiklama = VK_CULL_MODE_NONE; peekKenarAyar.derinlikTest = false; peekKenarAyar.derinlikYaz = false; peekKenarAyar.harmanla = true; peekKenarAyar.cizgiKalinlik = 3.2f; peekKenarAyar.gecis = vpGecis; peekKenarHat = BoruHatti(vulkan, peekKenarAyar); BoruHattiAyar peekNoktaAyar = peekKenarAyar; peekNoktaAyar.topoloji = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; peekNoktaHat = BoruHatti(vulkan, peekNoktaAyar); } // ---- Et kalınlığı per-fragment ışın alanı: kare güncelleme fazı ------------ // Kalınlık analizi olan İLK gövdenin mesh'ini DÜNYA uzayında BVH'leyip SSBO'ya // yükler (imza aynıysa iş yapmaz). Kayıt DIŞINDA çağrılır — descriptor yazımı ve // device-idle burada güvenli. namespace { // Teşhis: per-fragment ışın gerçekten aktif mi? "Hâlâ üçgensel görünüyor" // şikâyetini TAHMİNE bırakmamak için panelde canlı gösterilir. const char* g_isinDurum = "hesaplanmadı"; u32 g_isinUcgen = 0; } // namespace const char* kalinlikIsinDurumu(u32* ucgenCikti) { if (ucgenCikti) *ucgenCikti = g_isinUcgen; return g_isinDurum; } void kalinlikIsinAlaniGuncelle(Sahne& sahne, analiz::AnalizSistemi& analizSis, BoruHatlari& hatlar) { if (!hatlar.isinAlani.gecerli()) { g_isinDurum = "GPU descriptor kurulamadı"; g_isinUcgen = 0; return; } if (!analiz::kalinlikPerFragment()) { // ayar kapalı → eski per-vertex yol hatlar.isinAlani.bosalt(); g_isinDurum = "ayar kapalı (analiz.kalinlik_perfragment)"; g_isinUcgen = 0; return; } // Hiç analiz yoksa kare başına harita kurma (sıcak yol: gereksiz tahsis yasak). if (analizSis.hepsi().empty()) { hatlar.isinAlani.bosalt(); g_isinDurum = "analiz yok"; g_isinUcgen = 0; return; } const auto harita = analizSis.tumRenderBilgi(); Dugum* hedef = nullptr; for (auto& d : sahne.dugumler()) { const auto it = harita.find(d.kimlik); if (it == harita.end() || !it->second.var) continue; if (it->second.tip != analiz::AnalizTip::Kalinlik) continue; if (d.cpuKonum.empty() || d.cpuIndeks.size() < 3) continue; hedef = &d; break; // tek set = tek gövde (ötekiler per-vertex alana düşer) } if (!hedef) { hatlar.isinAlani.bosalt(); g_isinDurum = "kalınlık analizi yok / gövde mesh'i boş"; g_isinUcgen = 0; return; } // İmza: gövde + mesh boyu + dönüşüm. Değişmedikçe BVH/SSBO yeniden kurulmaz. u64 imza = 1469598103934665603ull; const auto kar = [&imza](u64 v) { imza = (imza ^ v) * 1099511628211ull; }; kar(hedef->kimlik); kar(hedef->cpuKonum.size()); kar(hedef->cpuIndeks.size()); const Mat4f model = hedef->matris(); for (int i = 0; i < 16; ++i) kar((u64)std::bit_cast(model.veri()[i])); // Kullanıcı kalınlık sınıfları da içeriğin parçası (renk/sınır değişince yenilenir). static const std::vector kBos; const auto hit = harita.find(hedef->kimlik); const std::vector& bantlar = (hit != harita.end() && hit->second.bantlar) ? *hit->second.bantlar : kBos; for (const analiz::AnalizAralik& b : bantlar) { kar((u64)std::bit_cast(b.min)); kar((u64)std::bit_cast(b.max)); kar((u64)std::bit_cast(b.nominal)); kar((u64)std::bit_cast(b.tolerans)); kar((u64)std::bit_cast(b.renk.x)); kar((u64)std::bit_cast(b.renk.y)); kar((u64)std::bit_cast(b.renk.z)); } if (imza == hatlar.isinAlani.imza() && hatlar.isinAlani.veriVar()) { g_isinDurum = "AKTİF"; g_isinUcgen = hatlar.isinAlani.ucgenSayi(); return; } // BVH senkron kurulur (kalınlık analizi başına BİR kez; hover BVH'sinden ayrı — // o gövdeye bağlı yaşam döngüsü ve iptal kuralları var, ödünç almak riskli). const auto bvh = isinBvhKur(hedef->cpuKonum, hedef->cpuIndeks); if (!bvh) { hatlar.isinAlani.bosalt(); g_isinDurum = "BVH kurulamadı (mesh dejenere?)"; g_isinUcgen = 0; return; } if (!hatlar.isinAlani.yukle(imza, hedef->kimlik, *bvh, hedef->cpuKonum, hedef->cpuIndeks, model, bantlar)) { hatlar.isinAlani.bosalt(); g_isinDurum = "SSBO yüklenemedi (bellek?)"; g_isinUcgen = 0; return; } g_isinDurum = "AKTİF"; g_isinUcgen = hatlar.isinAlani.ucgenSayi(); } void belgeCiz(Belge& b, ViewportHedef& hedef, VulkanBaglam& vulkan, BoruHatlari& hatlar, CizimOnbellek& onbellek, const IsikDurum& isikDurum, VkCommandBuffer kb, const BelgeCizimGirdi& girdi, f32& sonEnBoy, f32& sonG, f32& sonY) { // Belge sistemleri + boru hatları + önbellek: eski yerel isimlerle takma-ad // (Dalga 1b kalıbı — gövde metni main.cpp'den değişmeden taşındı). Sahne& sahne = b.sahne; SecimSistemi& secimSis = b.secimSis; analiz::AnalizSistemi& analizSis = b.analizSis; OrbitKamera& kamera = b.kamera; BoruHatti& modelHat = secimGorunum().ciftTarafli ? hatlar.modelHat : hatlar.modelHatTekYon; BoruHatti& modelSeffafHat = hatlar.modelSeffafHat; BoruHatti& analizHat = hatlar.analizHat; BoruHatti& analizSeffafHat = hatlar.analizSeffafHat; BoruHatti& zebraHat = hatlar.zebraHat; BoruHatti& reflectHat = hatlar.reflectHat; BoruHatti& kenarHat = hatlar.kenarHat; BoruHatti& kenarVurguHat = hatlar.kenarVurguHat; BoruHatti& refKenarHat = hatlar.refKenarHat; BoruHatti& refKenarVurguHat = hatlar.refKenarVurguHat; BoruHatti& noktaHat = hatlar.noktaHat; BoruHatti& kesitSayimHat = hatlar.kesitSayimHat; BoruHatti& kesitDolguHat = hatlar.kesitDolguHat; BoruHatti& peekHat = hatlar.peekHat; BoruHatti& peekKenarHat = hatlar.peekKenarHat; BoruHatti& peekNoktaHat = hatlar.peekNoktaHat; auto& skalerOnbellek = onbellek.skalerOnbellek; auto& bantOnbellek = onbellek.bantOnbellek; GpuTampon& sifirTampon = onbellek.sifirTampon; u32& sifirBoyut = onbellek.sifirBoyut; std::vector& mergeImza = onbellek.mergeImza; GpuTampon& mergeVB = onbellek.mergeVB; GpuTampon& mergeIB = onbellek.mergeIB; u32& mergeIndeksSayi = onbellek.mergeIndeksSayi; std::vector& mergeImzaKaba = onbellek.mergeImzaKaba; GpuTampon& mergeVBKaba = onbellek.mergeVBKaba; GpuTampon& mergeIBKaba = onbellek.mergeIBKaba; u32& mergeIndeksSayiKaba = onbellek.mergeIndeksSayiKaba; std::vector& kenarMergeImza = onbellek.kenarMergeImza; GpuTampon& kenarMergeVB = onbellek.kenarMergeVB; u32& kenarMergeSayi = onbellek.kenarMergeSayi; const f32 zaman = girdi.zaman; const int kare = girdi.kare; const SecimKip kip = sahne.kip(); // CAD Fark: eski (referans) gövdeyi bu kare için GİZLE (overlay kapalıysa) → // sapma ısı-haritası yalnız yeni gövdede, iki opak mesh üst üste gelmez → Z-FIGHTING // YOK (profesyonel tek-mesh yaklaşımı). gorunurBit_ geçici kapatılır (gorunurlukTazele // ÖNCESİ → cache de yansıtır), fonksiyon sonunda GERİ yüklenir. std::vector farkGizlenen; // PERF: hiç gizleyen Fark yoksa (yaygın durum) per-düğüm farkGizliGovde O(analiz) taramasını // atla → devasa modelde her-kare O(N) boşuna çağrı gitmez. Kapı O(analiz sayısı) (genelde 0). if (analizSis.farkGizliAktifMi()) for (auto& d : sahne.dugumler()) if (d.gorunurBit_ && analizSis.farkGizliGovde(d.kimlik)) { d.gorunurBit_ = false; farkGizlenen.push_back(&d); } // Kare-basi TEK gecis: etkin gorunurlugu memoize et. Asagidaki ~20 loop (+ pick // gecisi) dugum-basi agacGorunur (ust-zincir yuruyusu) yerine agacGorunurHizli // O(1) okur. Cache her karede bastan kurulur → dis mutasyon (transform/gorunurluk) // dahil daima guncel. sahne.gorunurlukTazele(); // Hover çözümü vp/vpb kurulduktan SONRA (aşağıda, CPU BVH yolu vp'ye ihtiyaç duyar). const VkExtent2D vpb = hedef.boyut(); const f32 enBoy = (f32)vpb.width / (f32)vpb.height; sonEnBoy = enBoy; sonG = (f32)vpb.width; sonY = (f32)vpb.height; // Adaptif derinlik ipucu: kameradan en yakın/en uzak görünür geometri // (sınır küresi bazlı) → izdusum yakın düzlemi yalnız küçültür (büyük // parçada yüzeye yaklaşınca ön kesilme yok), uzak düzlemi yalnız büyütür // (yakınlaşmışken sahnenin uzak ucu kırpılmaz). { const Vec3f kmp = kamera.konum(); // Kamera SABİT (hover) + yakın zamanda hesaplandıysa cache'i kullan → her-kare O(N) atla. // Kamera hareketi VEYA 30 kare (~0.5s; geometri değişimi güvencesi) → yeniden hesap. const bool kamSabit = (kmp - onbellek.depthKamKonum).uzunlukKare() < 1e-10f; if (kamSabit && (kare - onbellek.depthKare) < 30 && onbellek.depthEnYakin != -2.0f) { kamera.derinlikIpucuAta(onbellek.depthEnYakin, onbellek.depthEnUzak); } else { f32 enYakin = -1.0f, enUzak = -1.0f; for (auto& d : sahne.dugumler()) { if (!d.yerelSinir.gecerliMi() || !d.gorunurBit_) continue; if (d.tip == DugumTip::Duzlem || d.tip == DugumTip::Eksen || d.tip == DugumTip::EksenSistemi || d.tip == DugumTip::Nokta) continue; // ekran-sabit ölçekli (Cizgi dunya-olcekli → derinlik ipucuna girer) const f32 r = d.dunyaYaricap(); if (!(r > 0.0f) || !std::isfinite(r)) continue; const f32 m = (d.dunyaMerkez() - kmp).uzunluk(); if (!std::isfinite(m)) continue; const f32 yk = std::max(m - r, 0.0f); enYakin = enYakin < 0.0f ? yk : std::min(enYakin, yk); enUzak = std::max(enUzak, m + r); } kamera.derinlikIpucuAta(enYakin, enUzak); onbellek.depthKamKonum = kmp; onbellek.depthEnYakin = enYakin < 0.0f ? -2.0f : enYakin; // -2 = boş sahne işareti onbellek.depthEnUzak = enUzak; onbellek.depthKare = kare; } } const Mat4f vp = kamera.izdusum(enBoy) * kamera.gorunum(); // ---- HOVER çözümü (SPLIT: yüz/gövde CPU BVH, kenar/nokta GPU) ---- // YÜZ hover'ı HER ZAMAN CPU BVH raycast (isinVur, O(log n)) ile çöz → GPU pick'in pahalı // YÜZ vertex-pass'ı ELENİR. Milyon-üçgen rough mesh'te GPU pick her hover'da TÜM (kaba) // mesh'in verteksini işliyordu (scissor fragment'i kırpar ama VERTEX shader'ı kırpmaz) → // asıl kasma buydu. BVH ray query üçgen sayısından bağımsız. Kenar/nokta GPU pick'te kalır // (az primitif → ucuz). hoverCozumle GPU nokta/kenar > CPU yüz > CPU gövde önceliğiyle birleştirir. // Kesit düzlemlerini PICK'e de bildir (aksi halde kesitle kırpılan — ekranda // GÖRÜNMEYEN — yüzey tıklanabilir kalır; ölçüm oraya tutunur). Ayar kapalıysa // liste boş → eski davranış birebir. ≤4 düzlem, değişmezse Sahne yazmaz. { std::vector kd; if (pickAyar().kesitKirp) for (const auto& k : analizSis.aktifKesitler()) kd.push_back({k.normal, k.d}); sahne.kirpDuzlemleriAyarla(kd); } const bool hoverAktif = girdi.vpHover && !girdi.navigasyonAktif && !girdi.kameraHareketli; // Dev mesh'in BVH'si henüz arka planda kuruluyorsa true olur (import sonrası ilk // birkaç saniye): o pencere boyunca yüz hover'ı GEÇİCİ olarak GPU pick'e döner // (aşağıdaki pick kaydına yüz-pass eklenir) — ana-thread donması ASLA olmaz. bool bvhEksik = false; if (hoverAktif) { const f32 W = (f32)vpb.width, H = (f32)vpb.height; const f32 ndcX = 2.0f * (f32)girdi.imlecX / W - 1.0f; const f32 ndcY = 2.0f * (f32)girdi.imlecY / H - 1.0f; const Mat4f invVP = vp.ters(); const Vec4f yH = invVP * Vec4f{ndcX, ndcY, 1.0f, 1.0f}; // reverse-Z: yakın düzlem = 1 const Vec4f uH = invVP * Vec4f{ndcX, ndcY, 0.0f, 1.0f}; // uzak düzlem = 0 const Vec3f yakin = yH.xyz() * (1.0f / yH.w); const Vec3f uzak = uH.xyz() * (1.0f / uH.w); const Vec3f yon = (uzak - yakin).birim(); secimSis.hoverIsin(yakin, yon, true); // derinlik-örtme testi (arkadaki kenar bastır) // PERF: CPU yüz pick (isinVur) YALNIZ hover girdisi değişince koşar — fare/kamera/düğüm // sayısı sabitken (kullanıcı hover kartını okurken) her-kare BVH traversal + O(N) düğüm // taraması BOŞUNAYDI. Sonuç secimSis'te retained → hoverCozumle onu okur. GPU pick zaten // benzer kapıda (pickGerekli); bu CPU yüz pick'ini de aynı disipline sokar. u64 hVp = 1469598103934665603ull; for (int i = 0; i < 16; ++i) { u32 b; std::memcpy(&b, &vp.e[0][0] + i, 4); hVp = (hVp ^ b) * 1099511628211ull; } const std::size_t dSay = sahne.dugumler().size(); const bool hoverDegisti = girdi.imlecX != onbellek.hoverSonImlecX || girdi.imlecY != onbellek.hoverSonImlecY || hVp != onbellek.hoverSonVpImza || dSay != onbellek.hoverSonDugumSayi || onbellek.hoverSonBvhEksik; // BVH kuruluyorsa sonuç kısmi → tam olana dek her kare dene if (hoverDegisti) { FIKO_ADIM("hover CPU pick (isinVur+cpuKenarVur)"); Sahne::IsinVurus vr; const bool hit = sahne.isinVur(yakin, yon, vr, &bvhEksik); // Kurulum penceresinde CPU sonucu KISMİ (yalnız hazır düğümler) → yanlış öne-alma // olmasın: tamamen GPU yüz-pass fallback'ine bırak (hoverCozumle GPU yüz dalı). if (bvhEksik) secimSis.cpuYuzAyarla(0u, 0u, 3.0e30f, false); else secimSis.cpuYuzAyarla(hit ? vr.govde : 0u, hit ? vr.alt : 0u, hit ? vr.t : 3.0e30f, hit); // CPU KENAR pick (ekran-uzayı): GPU kenar pass'ının "her hover'da tüm kenarları çiz" // yükünü kaldırır (yüz gibi CPU'ya taşındı). ~6px tolerans. cpuKenar boşsa (mesh'siz // datum vb.) bulundu=false → GPU kenar fallback devrede kalır (hoverCozumle sırası). if (cpuKenarPick()) { Sahne::KenarVurus kvr; const bool kHit = sahne.cpuKenarVur(yakin, yon, vp, (f32)girdi.imlecX, (f32)girdi.imlecY, (f32)vpb.width, (f32)vpb.height, kKenarPikselTol, kvr); secimSis.cpuKenarAyarla(kHit ? kvr.govde : 0u, kHit ? kvr.alt : 0u, kHit ? kvr.t : 3.0e30f, kHit); } else { secimSis.cpuKenarAyarla(0u, 0u, 3.0e30f, false); // GPU kenar pass'ı devrede (eski yol) } onbellek.hoverSonImlecX = girdi.imlecX; onbellek.hoverSonImlecY = girdi.imlecY; onbellek.hoverSonVpImza = hVp; onbellek.hoverSonDugumSayi = dSay; onbellek.hoverSonBvhEksik = bvhEksik; } } else { secimSis.hoverIsin({}, {}, false); secimSis.cpuYuzAyarla(0u, 0u, 3.0e30f, false); secimSis.cpuKenarAyarla(0u, 0u, 3.0e30f, false); onbellek.hoverSonImlecX = -1e30f; // hover pasif → tekrar girişte hemen pick et } secimSis.hoverCozumle(vulkan.kareIndeks(), girdi.vpHover); // Kare-başı BİR kez: frustum düzlemleri (görünürlük kültü) + analiz render // haritası (O(N·M) linear-tarama yerine O(1) sorgu). Tüm render geçişleri // (model/analiz/kenar/refKenar) bunları paylaşır; pick geçişi kendi içinde // aynı frustum'u kurar (davranış birebir). Datum/wireframe kült dışıdır. const Frustum frustum = Frustum::kur(vp); const auto analizHarita = analizSis.tumRenderBilgi(); const auto analizVarMi = [&](u32 kimlik) -> bool { return analizHarita.find(kimlik) != analizHarita.end(); }; // Ekran-sabit referans (origin eksen/düzlem): zoom'dan bağımsız aynı boyut. // Düzlem/Eksen düğümleri kameraya-mesafe-orantılı ölçeklenir; diğer tüm düğümler // (katı, CAM yolu, GSD tel) dünya-ölçekli kalır. Pick geçişi de aynı ölçeği // paylaşır → seçim görsel boyutla eşleşir. (Aynı politika secim_sistemi.cpp'de.) const f32 kamMesafe = kamera.mesafe(); // Tip bazlı görsel çarpan: eksen %30 küçük, düzlem %60 küçük (kullanıcı isteği). const auto refTipOran = [](DugumTip t) -> f32 { return t == DugumTip::Duzlem ? REFERANS_ORAN_DUZLEM : REFERANS_ORAN_EKSEN; }; const auto dugumMvp = [&](const Dugum& d) -> Mat4f { // Ekran-sabit datum grubu (Cizgi HARIC → dunya-olcekli vp*matris'e duser). if (d.tip == DugumTip::Duzlem || d.tip == DugumTip::Eksen || d.tip == DugumTip::EksenSistemi || d.tip == DugumTip::Nokta) { const Vec3f pivot{(f32)d.donusum.konum.x, (f32)d.donusum.konum.y, (f32)d.donusum.konum.z}; return referansGorselMvp(vp, d.matris(), pivot, kamMesafe, refTipOran(d.tip), d.gorselOran); } return vp * d.matris(); }; // ---- GPU pick geçişi (imlec pikseli, SecimSistemi filtreye göre kaydeder) ---- // PERF (P1): Pick geçişi tüm sahnenin İKİNCİ tam raster'ı (yüz+kenar+nokta, // düğüm-başı bind+draw). İmleç altındaki eleman ancak şu durumlarda değişir: // fare hareketi, kamera değişimi (vp), sahne değişimi (ekle/sil/transform/mesh), // filtre değişimi veya viewport'a giriş. Hiçbiri yoksa pick'i ATLA — önceki // pick sonucu (okuma tamponları) korunur, hover değişmez. hoverCozumle yukarıda // her kare çalışır (retained tamponu okur) → hover takılması yok. Şüphede-çalıştır. { // vp matris hash (kamera): tüm kamera hareketleri (orbit/pan/zoom + yumuşatma // + kamMesafe) vp'ye yansır → tek hash yeter. u64 vpImza = 1469598103934665603ull; const auto katU = [](u64& h, u64 v) { h = (h ^ v) * 1099511628211ull; }; for (int i = 0; i < 16; ++i) { u32 b; std::memcpy(&b, &vp.e[0][0] + i, 4); katU(vpImza, b); } // Sahne hash: pick'e giren her görünür düğümün kimlik + dünya-matris + eleman // sayıları + tampon tanıtıcıları (rebuild/döküm scrubber'da handle değişir → // yakalanır). Ekle/sil düğüm kümesini, transform matrisi, mesh yeniden-inşa // tanıtıcıyı değiştirir. O(N) skaler hash. // ⭐ B#1 (agent): bu O(N) hash SADECE pick mümkünken hesaplanır — navigasyon // sürerken (pick zaten atlanır) ve fare viewport DIŞINDAyken tamamen boşuna. // pickOlasi=false → sahneImza önceki değerde kalır; pickGerekli o dalda zaten // false (nav VEYA !vpHover) → davranış birebir korunur, kare-başı O(N) yükü gider. const bool pickOlasi = !girdi.navigasyonAktif && !girdi.kameraHareketli && girdi.vpHover; u64 sahneImza = onbellek.pickSonSahneImza; if (pickOlasi) { sahneImza = 1469598103934665603ull; for (auto& d : sahne.dugumler()) { if (!d.gorunurBit_) continue; katU(sahneImza, d.kimlik); const Mat4f dm = d.matris(); for (int i = 0; i < 16; ++i) { u32 b; std::memcpy(&b, &dm.e[0][0] + i, 4); katU(sahneImza, b); } katU(sahneImza, ((u64)d.indeksSayi << 40) ^ ((u64)d.kenarVertexSayi << 20) ^ (u64)d.noktaSayi); katU(sahneImza, (u64)reinterpret_cast(d.vertexTampon.tampon())); katU(sahneImza, (u64)reinterpret_cast(d.kenarTampon.tampon())); katU(sahneImza, (u64)reinterpret_cast(d.noktaTampon.tampon())); } } const u32 filtre = (secimSis.fYuz ? 1u : 0u) | (secimSis.fKenar ? 2u : 0u) | (secimSis.fNokta ? 4u : 0u) | (secimSis.fGovde ? 8u : 0u); const bool fareHareket = girdi.imlecX != onbellek.pickSonImlecX || girdi.imlecY != onbellek.pickSonImlecY; // Etkileşim LOD debounce: LOD'u tetikleyen kareyi kaydet (mega parti + bireysel yol okur). // Kip-farkındalıklı kapı: hangi navigasyon kipinin kaliteyi düşüreceği ayrı ayrı ayarlanır // (döndür/pan/zoom). Hover (yalnız fare gezme) VARSAYILAN kapalı → boşta kalite düşmez. const bool navLod = (girdi.navKip == 0 && lodDondurAktif()) || // döndür (girdi.navKip == 2 && lodPanAktif()) || // pan ((girdi.navKip == 1 || girdi.tekerlekZoom) && lodZoomAktif()); // zoom if ((lodHoverTetik() && fareHareket) || navLod) onbellek.sonHareketKare = kare; const bool hoverGiris = girdi.vpHover && !onbellek.pickSonHover; // viewport'a giriş // Pick sonucu yalnız hover'lıyken tüketilir (hoverCozumle vpHover'sız erken // döner; tık yalnız hover'da). Bu yüzden değişim sinyalleri hover'a koşullu → // fare viewport dışındayken boşuna raster yok. İlk kare + giriş her zaman koşar. // Navigasyon (döndür/pan/zoom) sürerken pick'i ATLA — nav bitince vp farkı // yeniden tetikler (trackerlar aşağıda her kare güncellenir → kesintisiz devam). // Hover pick throttle: fare gezerken pick'i HER frame değil, 2 karede bir koştur (~30 Hz) // → rough modelde pick GPU render yükü yarıya iner, hover algısı bozulmaz. Sahne/vp/filtre // değişimi ve viewport'a giriş anında (hoverGiris) HEMEN pick (gecikmesiz doğruluk). const bool fareThrottle = fareHareket && (kare & 1) == 0; const bool pickGerekli = !girdi.navigasyonAktif && !girdi.kameraHareketli && (!onbellek.pickGecerli || hoverGiris || (girdi.vpHover && (fareThrottle || vpImza != onbellek.pickSonVpImza || sahneImza != onbellek.pickSonSahneImza || filtre != onbellek.pickSonFiltre))); onbellek.pickSonImlecX = girdi.imlecX; onbellek.pickSonImlecY = girdi.imlecY; onbellek.pickSonHover = girdi.vpHover; onbellek.pickSonVpImza = vpImza; onbellek.pickSonSahneImza = sahneImza; onbellek.pickSonFiltre = filtre; onbellek.pickGecerli = true; ++onbellek.pickToplam; // GPU pick YALNIZ kenar/nokta gerektiğinde koşar (yüz/gövde CPU BVH'den) + YÜZ pass'ı // atlanır → milyon-üçgen vertex işleme her hover'da KALKAR (asıl rough-mesh hızlanması). // Kenar/nokta filtresi kapalıysa GPU pick tümüyle atlanır (yüz+gövde zaten CPU'dan). // İSTİSNA — bvhEksik (dev mesh BVH'si arka planda kuruluyor, birkaç sn): yüz-pass // GEÇİCİ çizilir ki yüz hover'ı kurulum penceresinde de çalışsın (kaba mesh'le, ucuz). if (pickGerekli && (secimSis.fKenar || secimSis.fNokta || bvhEksik)) { secimSis.pickGecisiKaydet(kb, vp, (i32)girdi.imlecX, (i32)girdi.imlecY, kamMesafe, /*yuzPassAtla=*/!bvhEksik, /*kenarPassAtla=*/cpuKenarPick()); // model kenarları CPU pick'te ++onbellek.pickCalisan; } if (kare == 0 || (kare % 600 == 0)) { gunluk::bilgi("[perf] pick-gating: {}/{} karede pick koştu ({:.1f}%) — sabit sahne+fare+kamera'da atlanır", onbellek.pickCalisan, onbellek.pickToplam, onbellek.pickToplam ? 100.0 * (f64)onbellek.pickCalisan / (f64)onbellek.pickToplam : 0.0); } } // ---- Undercut görünürlük gölge haritası (viewport geçişinin DIŞINDA) ---- // Render pass'ler iç içe geçemez → pick geçişinin yanına, hedef.basla'dan ÖNCE. // Çekim yönünde ortografik derinlik: her texel'de o ışın boyunca EN YAKIN yüzey. // analiz.frag her fragment için "önümde yüzey var mı?" diye sorar → undercut. // Web (fikoyt-ui undercut.component.ts) mimarisinin birebir karşılığı. { analiz::AnalizSistemi::RenderBilgi ucRb; u32 ucGovde = 0; for (const auto& [govdeK, rb] : analizHarita) if (rb.var && rb.tip == analiz::AnalizTip::Undercut) { ucRb = rb; ucGovde = govdeK; break; } // Gölge geçişi YALNIZ gerektiğinde: Yüz kipinde karar tümüyle BREP'ten gelir → // haritayı hiç render etme (her kare tüm gövdeleri ikinci kez raster'lamak pahalı). const bool ucAcik = ucRb.var && analiz::undercutGorunurluk() && analiz::undercutKip() != analiz::UndercutKip::Yuz && hatlar.golge.gecerli(); GolgeKamera gkam{}; if (ucAcik) { hatlar.golge.cozunurlukAyarla(analiz::golgeCozunurluk()); // İKİ sınır (bkz. golge_kamera.hpp): hedef = analiz edilen gövde (XY → texel // yoğunluğu), engel = TÜM görünür gövdeler (Z → örten parçalar haritaya girsin). // Gövde-sınırı KÜRE tabanlı (tumunuSigdir kalıbı) — dünya AABB'si doğrudan yok. Aabbf hedefS, engelS; for (auto& d : sahne.dugumler()) { if (!d.gorunurBit_ || !d.yerelSinir.gecerliMi() || d.indeksSayi == 0) continue; if (d.tip == DugumTip::Duzlem || d.tip == DugumTip::Eksen || d.tip == DugumTip::EksenSistemi || d.tip == DugumTip::Nokta) continue; const Vec3f m = d.dunyaMerkez(); const f32 r = d.dunyaYaricap(); engelS.genislet(m - Vec3f{r, r, r}); engelS.genislet(m + Vec3f{r, r, r}); if (d.kimlik == ucGovde) { hedefS.genislet(m - Vec3f{r, r, r}); hedefS.genislet(m + Vec3f{r, r, r}); } } // Hedef gövde bulunamazsa (hedefGovde=0 → "tüm görünür") tüm sahneye düş. gkam = golgeKameraKur(hedefS.gecerliMi() ? hedefS : engelS, engelS, ucRb.cekimYonu); } // EXACT overlay varsa kısmi yüzler gölge haritasına sorulmaz → gölge geçişini // hiç render etme (pahalı: tüm gövdeler ikinci kez raster). const bool overlayVar = ucRb.golgeIndeks && !ucRb.golgeIndeks->empty(); const bool ciz = ucAcik && gkam.gecerli && !overlayVar; // Render pass DIŞINDA (vkCmdUpdateBuffer kuralı). kip shader'a burada gider. hatlar.golge.uboYaz(kb, gkam.vp, ciz, analiz::golgeEsik(), (f32)(u8)analiz::undercutKip(), overlayVar); if (ciz) { hatlar.golge.basla(kb); for (auto& d : sahne.dugumler()) { if (!d.gorunurBit_ || d.indeksSayi == 0) continue; if (d.tip == DugumTip::Duzlem || d.tip == DugumTip::Eksen || d.tip == DugumTip::EksenSistemi || d.tip == DugumTip::Nokta) continue; if (!d.vertexTampon.tampon() || !d.indeksTampon.tampon()) continue; // NULL bind = DEVICE_LOST hatlar.golge.ciz(kb, gkam.vp * d.matris(), d.vertexTampon.tampon(), d.indeksTampon.tampon(), d.indeksSayi); } hatlar.golge.bitir(kb); } // TEŞHİS: undercut yanlış boyanınca ilk bakılacak satır. Gölge geçişi gerçekten // koştu mu, kip ne, hedef gövde bulundu mu — hepsi burada (600 karede bir). if (ucRb.var && (kare == 0 || kare % 600 == 0)) gunluk::bilgi("[analiz] undercut render: kip={} ucAcik={} golgeCiz={} hedefGovde={} " "yuzAlani={} golgeHatGecerli={}", (int)analiz::undercutKip(), ucAcik ? 1 : 0, ciz ? 1 : 0, ucGovde, ucRb.undercutYuzAlani ? 1 : 0, hatlar.golge.gecerli() ? 1 : 0); } // Işık + seçim/hover rengi: KARE-SABİT → tek UBO'ya bir kez yazılır (eskiden her // draw'da ModelItme'ye kopyalanıyordu). vkCmdUpdateBuffer render pass DIŞINDA // olmalı → hedef.basla'dan ÖNCE. kameraTakip için kamera bakış yönü gerekir. const Vec3f kamBakis = kamera.kameraYonu(); const SecimGorunum& sg = secimGorunum(); const f32 lHover[4] = {sg.hoverRenk.x, sg.hoverRenk.y, sg.hoverRenk.z, sg.nabizSiddet}; { KareVeri kv{}; const auto yaz = [](f32* dyon, f32* drenk, const YonselIsik& y, const Vec3f& bak) { const Vec3f yon = isikEtkinYon(y, bak); dyon[0] = yon.x; dyon[1] = yon.y; dyon[2] = yon.z; dyon[3] = y.siddet; drenk[0] = y.renk.x; drenk[1] = y.renk.y; drenk[2] = y.renk.z; drenk[3] = y.aktif ? 1.0f : 0.0f; }; kv.ortam[0] = isikDurum.ortamRenk.x; kv.ortam[1] = isikDurum.ortamRenk.y; kv.ortam[2] = isikDurum.ortamRenk.z; kv.ortam[3] = isikDurum.ortamSiddet; yaz(kv.isik1, kv.isik1Renk, isikDurum.isik1, kamBakis); yaz(kv.isik2, kv.isik2Renk, isikDurum.isik2, kamBakis); kv.secimRenk[0] = sg.secimRenk.x; kv.secimRenk[1] = sg.secimRenk.y; kv.secimRenk[2] = sg.secimRenk.z; kv.secimRenk[3] = sg.nabizHiz; std::memcpy(kv.hoverRenk, lHover, 16); const Vec3f kamKonum = kamera.konum(); kv.kamera[0] = kamKonum.x; kv.kamera[1] = kamKonum.y; kv.kamera[2] = kamKonum.z; hatlar.kareUbo.yaz(kb, kv); } // ---- 3D sahne geçişi (offscreen viewport dokusu) ---- hedef.basla(kb, 0.075f, 0.082f, 0.11f); const u32 zamanBits = floatBits(zaman); // 1) Model yüzleri (taban). Analiz aktif (draft/undercut) veya kesit clip // varsa gövde ANALİZ pipeline'ından (renk + fragment discard) çizilir; // aksi halde standart model pipeline'ından. Analiz-boyalı gövdeler kesit // push-constant'ıyla da kesilir (tek shader iki iş). const auto kesitClip = analizSis.aktifKesitler(); // en fazla 6 → ilk 4 shader'a // Analiz push-constant'ın ortak alanlarını doldur (clip + mod + rampa). // dikBant: draft/undercut/slope sarı bant yarı-genişliği (°). alanMin/Max + // rampaTip: mod-3 skaler alan (radius/kalınlık) renk-rampa remap (GPU, anlık). // yon: draft çekim yönü VEYA (zebra/reflect) kamera göz konumu; dikBant: sarı-bant // yarı-gen. VEYA (zebra/reflect) şerit/hat sayısı; faz: zebra/reflect rampa.z fazı. struct AItmeP { int mod; Vec3f yon; f32 dikBant; f32 alanMin; f32 alanMax; int rampaTip; f32 kritikEsik; f32 faz = 0; Vec3f ekstra{}; f32 ekstraW = 0; f32 rampaW = 0; }; const auto analizItmeKur = [&](Dugum& d, const AItmeP& p) { AnalizItme it; it.mvp = vp * d.matris(); it.model = d.matris(); it.cekimYonu[0] = p.yon.x; it.cekimYonu[1] = p.yon.y; it.cekimYonu[2] = p.yon.z; it.cekimYonu[3] = (f32)p.mod; it.esik[0] = p.dikBant; it.esik[1] = p.alanMax; it.esik[2] = p.alanMin; const int ks = (int)std::min(kesitClip.size(), 4); it.esik[3] = (f32)ks; it.rampa[0] = (f32)p.rampaTip; it.rampa[1] = p.kritikEsik; it.rampa[2] = p.faz; it.rampa[3] = p.rampaW; it.ekstra[0] = p.ekstra.x; it.ekstra[1] = p.ekstra.y; it.ekstra[2] = p.ekstra.z; it.ekstra[3] = p.ekstraW; for (int i = 0; i < ks; ++i) { it.kesit[i][0] = kesitClip[i].normal.x; it.kesit[i][1] = kesitClip[i].normal.y; it.kesit[i][2] = kesitClip[i].normal.z; it.kesit[i][3] = kesitClip[i].d; } for (int i = ks; i < 4; ++i) { it.kesit[i][0] = it.kesit[i][1] = it.kesit[i][2] = it.kesit[i][3] = 0; } return it; }; // Per-vertex analiz skaleri (binding=1). Mod-3 (radius/kalınlık): PerYüz alanı // cpuAlt (yüz ordinali) ile per-vertex'e yay → analiz.frag rampa boyar. Alan // hazır olduğunda BİR kez kurulur (bayat sürüm), eşik slider'ı YALNIZ uniform // (rampa/alanMin/Max) → yeniden yükleme YOK. Analitik modlar (0/1/4) sıfır tampon. const auto sifirTamponAl = [&](u32 vSayi) -> VkBuffer { if (vSayi > sifirBoyut) { std::vector z(vSayi, 0.0f); sifirTampon = GpuTampon(vulkan, z.data(), (u64)vSayi * sizeof(f32), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); sifirBoyut = vSayi; } return sifirTampon.tampon(); }; const auto skalerTamponAl = [&](Dugum& d, const analiz::AnalizSistemi::RenderBilgi& rb) -> VkBuffer { const u32 vSayi = (u32)d.cpuAlt.size(); const bool perVertex = rb.alan && rb.alan->yerlesim == analiz::SonucAlan::Yerlesim::PerVertex; if (!rb.alan || vSayi == 0 || (rb.alan->yerlesim != analiz::SonucAlan::Yerlesim::PerYuz && !perVertex)) return sifirTamponAl(std::max(vSayi, 1u)); auto& [ver, buf] = skalerOnbellek[rb.analizKimlik]; // İçerik imzası: PerYuz → FNV (yüz sayısı küçük, ucuz). PerVertex (Fark, milyon // eleman) → data() pointer + boyut (hesapla() her seferinde yeni vektör move // eder → pointer değişir; kare-başı milyon-eleman hash sıcak yolda YASAK). u32 yeniVer = 2166136261u ^ vSayi; if (perVertex) { const u64 pt = (u64)reinterpret_cast(rb.alan->deger.data()); yeniVer = (yeniVer ^ (u32)pt) * 16777619u; yeniVer = (yeniVer ^ (u32)(pt >> 32)) * 16777619u; yeniVer = (yeniVer ^ (u32)rb.alan->deger.size()) * 16777619u; } else { for (f32 v : rb.alan->deger) { u32 bb; std::memcpy(&bb, &v, 4); yeniVer = (yeniVer ^ bb) * 16777619u; } } if (buf.tampon() == VK_NULL_HANDLE || ver != yeniVer) { std::vector pv(vSayi, -1.0f); if (perVertex) { // PerVertex (CAD Fark): deger sırası = cpuKonum vertex sırası — doğrudan. const size_t n = std::min((size_t)vSayi, rb.alan->deger.size()); std::copy_n(rb.alan->deger.begin(), n, pv.begin()); } else { for (u32 i = 0; i < vSayi; ++i) { const u32 ord = d.cpuAlt[i]; // yüz ordinali (1-tabanlı) if (ord < rb.alan->deger.size()) pv[i] = rb.alan->deger[ord]; } } buf = GpuTampon(vulkan, pv.data(), (u64)vSayi * sizeof(f32), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); ver = yeniVer; } return buf.tampon(); }; // Radius ITEM-BAZLI BANT (mod 7): her yüzün yarıçapına düşen bandın rengini bul, // RGB'yi TEK float'a paketle (8-8-8 < 2^24 → tam temsil) → shader unpack. Vertex // layout/pipeline DEĞİŞMEZ (mevcut binding=1 float buffer'ı yeniden kullanır). const auto bantTamponAl = [&](Dugum& d, const analiz::AnalizSistemi::RenderBilgi& rb) -> VkBuffer { const u32 vSayi = (u32)d.cpuAlt.size(); if (!rb.alan || !rb.bantlar || rb.alan->yerlesim != analiz::SonucAlan::Yerlesim::PerYuz || vSayi == 0) return sifirTamponAl(std::max(vSayi, 1u)); auto& [ver, buf] = bantOnbellek[rb.analizKimlik]; // İmza: alan (yarıçaplar) + bantlar (min/max/renk) → biri değişince yeniden bak. u32 yeniVer = 2166136261u ^ vSayi; for (f32 v : rb.alan->deger) { u32 bb; std::memcpy(&bb, &v, 4); yeniVer = (yeniVer ^ bb) * 16777619u; } for (const auto& b : *rb.bantlar) for (f32 v : {b.min, b.max, b.renk.x, b.renk.y, b.renk.z}) { u32 bb; std::memcpy(&bb, &v, 4); yeniVer = (yeniVer ^ bb) * 16777619u; } if (buf.tampon() == VK_NULL_HANDLE || ver != yeniVer) { const auto paketle = [](Vec3f c) -> f32 { auto k = [](f32 x) { return (u32)(std::clamp(x, 0.f, 1.f) * 255.f + 0.5f); }; return (f32)((k(c.x) << 16) | (k(c.y) << 8) | k(c.z)); }; const Vec3f notr{0.62f, 0.64f, 0.68f}; const f32 notrPak = paketle(notr); std::vector pv(vSayi, notrPak); for (u32 i = 0; i < vSayi; ++i) { const u32 ord = d.cpuAlt[i]; if (ord >= rb.alan->deger.size()) continue; const f32 r = rb.alan->deger[ord]; if (r < 0) continue; // nötr (düzlem/örneksiz) for (const auto& b : *rb.bantlar) if (r >= b.min && r <= b.max) { pv[i] = paketle(b.renk); break; } } buf = GpuTampon(vulkan, pv.data(), (u64)vSayi * sizeof(f32), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); ver = yeniVer; } return buf.tampon(); }; // CAD Fark AYRIK kip (mod 7): tolerans-üstü vertexler düz renk — yeni gövde YEŞİL // (eklenen), eski gövde KIRMIZI (silinen), kalan nötr. PerVertex paketli float // (mod-7 unpack). İmza ptr+boyut+tolerans (milyon-eleman hash sıcak yolda yasak). // Önbellek anahtarı gövdeye göre ayrışır (aynı analiz iki gövde boyar). const auto farkAyrikTamponAl = [&](Dugum& d, const analiz::AnalizSistemi::RenderBilgi& rb) -> VkBuffer { const u32 vSayi = (u32)d.cpuAlt.size(); if (!rb.alan || rb.alan->yerlesim != analiz::SonucAlan::Yerlesim::PerVertex || vSayi == 0) return sifirTamponAl(std::max(vSayi, 1u)); auto& [ver, buf] = bantOnbellek[rb.analizKimlik ^ (rb.farkSilinen ? 0x40000000u : 0x20000000u)]; u32 yeniVer = 2166136261u ^ vSayi ^ (rb.farkGradyan ? 0x1000u : 0u); const u64 pt = (u64)reinterpret_cast(rb.alan->deger.data()); yeniVer = (yeniVer ^ (u32)pt) * 16777619u; yeniVer = (yeniVer ^ (u32)(pt >> 32)) * 16777619u; for (f32 v : {rb.farkTolerans, rb.farkRenkDegisim.x, rb.farkRenkDegisim.y, rb.farkRenkDegisim.z, rb.farkRenkAyni.x, rb.farkRenkAyni.y, rb.farkRenkAyni.z}) { u32 bb; std::memcpy(&bb, &v, 4); yeniVer = (yeniVer ^ bb) * 16777619u; } if (buf.tampon() == VK_NULL_HANDLE || ver != yeniVer) { const auto paketle = [](Vec3f c) -> f32 { auto k = [](f32 x) { return (u32)(std::clamp(x, 0.f, 1.f) * 255.f + 0.5f); }; return (f32)((k(c.x) << 16) | (k(c.y) << 8) | k(c.z)); }; const auto lerp3 = [](Vec3f a, Vec3f b, f32 t) -> Vec3f { return {a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t}; }; const f32 notrPak = paketle(rb.farkRenkAyni); const f32 degisimPak = paketle(rb.farkRenkDegisim); std::vector pv(vSayi, notrPak); const size_t n = std::min((size_t)vSayi, rb.alan->deger.size()); const f32 tol = std::max(rb.farkTolerans, 1e-6f); constexpr Vec3f kTuruncu{1.0f, 0.55f, 0.0f}; // web gradyan ara rengi for (size_t i = 0; i < n; ++i) { const f32 d = rb.alan->deger[i]; if (rb.farkGradyan) { // Web CadDiff heatmap rampasi BIREBIR: 0→SAME; (0,tol]→SAME→turuncu; // >tol→turuncu→degisim rengi, 4×tol'de doyar (maxGradientDist=tol*5). if (d <= 1e-9f) continue; // SAME (notrPak zaten) const Vec3f c = d <= tol ? lerp3(rb.farkRenkAyni, kTuruncu, d / tol) : lerp3(kTuruncu, rb.farkRenkDegisim, std::min((d - tol) / (4.0f * tol), 1.0f)); pv[i] = paketle(c); } else if (d > tol) { pv[i] = degisimPak; // binary: tolerans-ustu duz degisim rengi } } buf = GpuTampon(vulkan, pv.data(), (u64)vSayi * sizeof(f32), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); ver = yeniVer; } return buf.tampon(); }; // Analiz tipi → shader modu (0 draft, 1 undercut, 3 skaler radius/kalınlık/kurvatür, // 4 slope, 5 zebra, 6 reflect). 5/6 AYRI pipeline (zebraHat/reflectHat). const auto analizModu = [](analiz::AnalizTip t) -> int { switch (t) { case analiz::AnalizTip::Undercut: return 1; case analiz::AnalizTip::Radius: case analiz::AnalizTip::Kalinlik: case analiz::AnalizTip::Kurvatur: case analiz::AnalizTip::Fark: return 3; // CAD Fark: PerVertex sapma → rampa case analiz::AnalizTip::Zebra: return 5; case analiz::AnalizTip::Reflect: return 6; default: return 0; // Draft } }; const bool kesitVar = !kesitClip.empty(); // Model pipeline: analiz/kesit YOK olan gövdeler. // // PERF (dalga 2 — draw-call azaltma / statik-katı toplu çizim): // Ayrı mesh'li N gövde = N bind+bind+draw. Statik (hover/seçili DEĞİL, analiz/ // kesit YOK) katı gövdeleri CPU'da tek "mega" VB/IB'ye DÜNYA-uzayında pişirip // TEK bind + TEK drawIndexed ile çizeriz. Model matrisi vertex'e pişirildiği // için push-constant'ta mvp=vp, model=birim → ışık dünya-uzayı korunur. // - vurgu (hover/izole yüz) statik gövdede zaten 0 → toplu çizimde 0. // - hover/seçili gövde toplu partiden HARİÇ, aşağıdaki bireysel yolla çizilir // (vurgu mantığı BİREBİR korunur). altKimlik çakışması zararsız (highlight yok). // - Seçim/pick AYRI geçiş (secim_gpu) → hiç dokunulmadı, doğruluk tam korunur. // Mega tampon YALNIZ imza (kimlik/geometri-sürüm/dünya-matris/renk) değişince // yeniden pişirilir (anındaGönder bloklar; kare-başı DEĞİL). Frustum kültü // toplu partide düğüm-başı yapılamaz (tek draw) — bireysel yol kültü korur. modelHat.bagla(kb); modelHat.setBagla(kb, hatlar.kareUbo.set()); // kare-sabit isik/renk (model.frag set=0) modelHat.setBagla(kb, hatlar.malzemeGpu.set(), 1); // malzeme/doku (model.frag set=1) // Bu kare toplu-parti adayları: statik + görünür + mesh'li + analiz/kesit yok. // Seçili gövde hariç (bireysel çizilir → highlight korunur). HOVER PARTİDE // KALIR: hover değişimi mega-parti rebuild'i TETİKLEMEZ (yoğun sahnede her // gövde-geçişinde tam CPU pişirme + GPU upload = büyük kasma kaynağıydı). // Hover vurgusu partinin ÜSTÜNE bireysel overlay geçişiyle çizilir (aşağıda). const u32 hoverGovde = sahne.vurgu.govde; // Secili govde kimlikleri O(1) sorgu. PERF: YALNIZ seçim değişince yeniden kur (imza kapısı) — // binlerce seçilide (ağaçtan grup) her kare binlerce insert boşunaydı (cok_secim'de küme // neredeyse yalnız hover için okunuyor). İmza aynıysa önceki kare kümesi geçerli (onbellek üyesi). std::unordered_set& seciliGovdeSet = onbellek.seciliGovdeScratch; // A#6: yeniden kullan { u64 secImza = 1469598103934665603ull; // FNV-1a (govde dizisi; sıra kare-başı kararlı) for (const SecimOge& o : sahne.secililer()) secImza = (secImza ^ o.govde) * 1099511628211ull; if (secImza != onbellek.seciliGovdeImza) { onbellek.seciliGovdeImza = secImza; seciliGovdeSet.clear(); for (const SecimOge& o : sahne.secililer()) seciliGovdeSet.insert(o.govde); } } const auto seciliMiGovde = [&](u32 k) { return seciliGovdeSet.count(k) > 0; }; // Çok-seçim: eşik üstünde (ör. ağaçtan binlerce gövde) seçili gövdeler mega-partide KALIR // (aşağıda topluda + imza) → seçim değişiminde cook YOK, bireysel/overlay draw YOK. 3B per-gövde // vurgu atlanır (ağaç seçimi gösterir). Eşik altı → cokSecili=false → BİREBİR eski davranış. const bool cokSecili = cokSecimVurguEsigi() > 0 && (int)sahne.secililer().size() > cokSecimVurguEsigi(); // Seçili gövde mega-partide mi TUTULsun? cok_secili (eşik üstü) VEYA secim_cook_atla bayrağı. // true → seçili gövdeler partide kalır (cook yok); vurgu overlay'i (varsa) z-bias'lı basılır. const bool secimPartide = cokSecili || secimCookAtla(); // Etkileşim LOD: orbit VEYA hover-gezme (fare son ~8 kare hareket etti) → kaba mesh çiz. // Fare durunca (8 kare ≈130ms hareketsiz) ince mesh'e döner. onbellek.sonHareketKare pick // bloğunda güncellenir (fareHareket orada); burada okunur (debounce → gezme/durma titremesi yok). // Not: aktif navigasyon karesinde debounce zaten güncellenir (sonHareketKare=kare, yukarıda // kip-kapılı) → ayrı navigasyonAktif dalı gereksiz; saf debounce hem doğru hem kip-anahtarlarına // uyumlu (kapalı kipte sonHareketKare güncellenmez → o navigasyonda kalite düşmez). const bool etkilesimLod = etkilesimLodAktif() && (kare - onbellek.sonHareketKare < orbitLodGecikmeKare()); std::vector& yeniImza = onbellek.imzaScratch; // A#2: yeniden kullan (tahsis churn'ü yok) yeniImza.clear(); for (auto& d : sahne.dugumler()) { if (!d.gorunurBit_ || d.indeksSayi == 0) continue; if (analizVarMi(d.kimlik) || kesitVar) continue; if (!secimPartide && seciliMiGovde(d.kimlik)) continue; // seçili çıkar (partide-tut kapalıysa) if (d.cpuKonum.empty() || d.cpuIndeks.empty()) continue; // CPU verisi yok → bireysel if (d.saydamlik < 1.0f) continue; // saydam geçişte çizilir // NOT: per-yüz kısmi alfalı gövde (yuzSaydamVar) opak partide KALIR — opak yüzleri // derinlik yazsın (frag kısmi yüzleri discard eder); kısmi yüzler ayrıca saydam geçişte. if (d.dinamikMesh) continue; // canlı mesh (döküm scrubber) → bireysel, parti pişirmez const Mat4f m = d.matris(); u64 h = 1469598103934665603ull; // FNV-1a const auto kat = [&](u64 v) { h = (h ^ v) * 1099511628211ull; }; kat(d.kimlik); kat((u64)d.cpuKonum.size()); kat((u64)reinterpret_cast(d.cpuKonum.data())); // rebuild → yeni data ptr kat((u64)reinterpret_cast(d.cpuRenk.data())); // ogeRenkYenile → yeni ptr kat((u64)reinterpret_cast(d.cpuAlfa.data())); // alfa/görünürlük değişimi → yeni ptr kat((u64)reinterpret_cast(d.kabaRenk.data())); // LOD renk düzenleme → yeni ptr kat((u64)reinterpret_cast(d.kabaAlfa.data())); kat((u64)d.indeksSayi); for (int i = 0; i < 16; ++i) { u32 bb; std::memcpy(&bb, &m.e[0][0] + i, 4); kat(bb); } u32 rb; std::memcpy(&rb, &d.renk.x, 4); kat(rb); yeniImza.push_back(h); } // Orbit-anı LOD: İki AYRI kalıcı buffer (ince/kaba). Toggle SADECE hangi buffer'ın // çizileceğini seçer → geçişte YENİDEN PİŞİRME YOK (agent A #1: orbit/hover stall kökü). // İmza LOD bayrağı TAŞIMAZ; her seviye geometri/renk/transform değişince bir kez pişer. const bool lodModu = etkilesimLod; GpuTampon& aktifVB = lodModu ? mergeVBKaba : mergeVB; GpuTampon& aktifIB = lodModu ? mergeIBKaba : mergeIB; u32& aktifSayi = lodModu ? mergeIndeksSayiKaba : mergeIndeksSayi; std::vector& aktifImza = lodModu ? mergeImzaKaba : mergeImza; // AZ GÖVDELİ sahnede mega-parti GEREKSİZ + ZARARLI: dünya-uzayı bake cook'u (354k vertex ~70ms) // her seçim/LOD değişiminde stutter yapıyordu (teşhis: cook'suz 60 FPS, cook'lu 5-29 FPS). Az // gövdede bireysel çizim (GPU transform, cook YOK) daha hızlı; mega yalnız ÇOK gövdede draw-call // kültü için kazandırır. yeniImza.size() = batch aday gövde sayısı. constexpr size_t kMegaPartiEsik = 48; // > bu → mega-parti; ≤ bu → bireysel (cook yok) const bool megaKullan = yeniImza.size() > kMegaPartiEsik; // Yalnız SEÇİLİ seviyenin imzası değiştiyse o seviyeyi yeniden pişir (diğeri dokunulmaz). if (megaKullan && yeniImza != aktifImza) { FIKO_ADIM("mega-parti cook"); const auto megaT0 = std::chrono::steady_clock::now(); // KALICI scratch (kapasite korunur) → her cook'ta reserve'süz push_back realloc'u BİTER // (70ms→~18ms; teşhis: cook her seçim/LOD değişiminde tekrarlanıyordu, realloc asıl maliyet). static std::vector mv; static std::vector mi; mv.clear(); mi.clear(); for (auto& d : sahne.dugumler()) { if (!d.gorunurBit_ || d.indeksSayi == 0) continue; if (analizVarMi(d.kimlik) || kesitVar) continue; // KRİTİK: pişirme filtresi İMZA döngüsüyle (yukarıda) BİREBİR aynı olmalı — aksi halde // seçili gövde imzaya girer (topluda sayılır) ama pişmez → ne partide ne bireysel çizilir // = GÖRÜNMEZ olur. secimPartide'de seçili gövdeler de partiye pişirilir. if (!secimPartide && seciliMiGovde(d.kimlik)) continue; if (d.cpuKonum.empty() || d.cpuIndeks.empty()) continue; if (d.saydamlik < 1.0f) continue; // saydam geçişte çizilir (per-yüz kısmi opak partide kalır) if (d.dinamikMesh) continue; // canlı mesh → bireysel yol const Mat4f m = d.matris(); // sütun-major: e[sütun][satır] const auto nokUygula = [&](const Vec3f& p) -> Vec3f { return {m.e[0][0]*p.x + m.e[1][0]*p.y + m.e[2][0]*p.z + m.e[3][0], m.e[0][1]*p.x + m.e[1][1]*p.y + m.e[2][1]*p.z + m.e[3][1], m.e[0][2]*p.x + m.e[1][2]*p.y + m.e[2][2]*p.z + m.e[3][2]}; }; const auto norUygula = [&](const Vec3f& n) -> Vec3f { // mat3(model)·n (ölçek dahil) return {m.e[0][0]*n.x + m.e[1][0]*n.y + m.e[2][0]*n.z, m.e[0][1]*n.x + m.e[1][1]*n.y + m.e[2][1]*n.z, m.e[0][2]*n.x + m.e[1][2]*n.y + m.e[2][2]*n.z}; }; // Bu buffer'ın seviyesi (lodModu): kaba varsa kaba, yoksa ince. Her iki seviye de // per-yüz renk/alt/alfa taşır → orbit/hover'da STEP renkleri KAYBOLMAZ. const bool lod = lodModu && !d.kabaKonum.empty() && !d.kabaIndeks.empty(); const std::vector& konumlar = lod ? d.kabaKonum : d.cpuKonum; const std::vector& normaller = lod ? d.kabaNormal : d.cpuNormal; const std::vector& indeksler = lod ? d.kabaIndeks : d.cpuIndeks; const std::vector& renkler = lod ? d.kabaRenk : d.cpuRenk; const std::vector& altlar = lod ? d.kabaAlt : d.cpuAlt; const std::vector& alfalar = lod ? d.kabaAlfa : d.cpuAlfa; const u32 taban = (u32)mv.size(); const size_t vs = konumlar.size(); for (size_t i = 0; i < vs; ++i) { Vertex v{}; const Vec3f pw = nokUygula(konumlar[i]); const Vec3f nw = i < normaller.size() ? norUygula(normaller[i]) : Vec3f{0,0,1}; v.konum[0]=pw.x; v.konum[1]=pw.y; v.konum[2]=pw.z; v.normal[0]=nw.x; v.normal[1]=nw.y; v.normal[2]=nw.z; // Per-vertex renk/alt/alfa (STEP per-yüz renkleri, yuzOz override) — ince VE kaba. const Vec3f vr = i < renkler.size() ? renkler[i] : d.renk; v.renk[0]=vr.x; v.renk[1]=vr.y; v.renk[2]=vr.z; v.altKimlik = i < altlar.size() ? altlar[i] : 0; v.alfa = i < alfalar.size() ? alfalar[i] : 1.0f; // per-yüz görünürlük v.globalYuzIndeks = v.altKimlik > 0 ? d.yuzBaz_ + v.altKimlik - 1 : 0; // Aşama 0d (SSBO global) mv.push_back(v); } for (u32 idx : indeksler) mi.push_back(taban + idx); } // Dinamik guncelle(): kapasite yeterse salt memcpy → ctor submit+fence STALL'i yok. if (!mv.empty()) { aktifVB.guncelle(vulkan, mv.data(), mv.size()*sizeof(Vertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); aktifIB.guncelle(vulkan, mi.data(), mi.size()*sizeof(u32), VK_BUFFER_USAGE_INDEX_BUFFER_BIT); aktifSayi = (u32)mi.size(); } else { aktifVB = GpuTampon(); aktifIB = GpuTampon(); aktifSayi = 0; } aktifImza = yeniImza; // kopya YALNIZ pişmede (nadir); scratch kapasitesi korunur const f64 megaCookMs = std::chrono::duration( std::chrono::steady_clock::now() - megaT0).count(); perfGosterge().modelCookMs = megaCookMs; // HUD: VAR OLAN ölçümü yaz (davranış-nötr) static gunluk::Seyrek megaSeyrek{1000}; // sıcak yol: her-kare pişmede log disk I/O'ya dönüşür if (megaSeyrek.gecer()) gunluk::bilgi( "[perf] model mega-parti pişirme ({}): {:.1f} ms ({} vertex) — LOD toggle artık " "tetiklemez (bastirilan {})", lodModu ? "kaba" : "ince", megaCookMs, mv.size(), megaSeyrek.bastirilanAl()); } u32 modelDrawCagri = 0; // Teşhis (davranış-nötr): vurgu overlay'inin GERÇEK indeks yükü — tek yüz aralığı mı, // yoksa tam mesh mi gönderiliyor. "aralik" 0 ise düğümün yüz→indeks tablosu yok (fallback). u32 vurguSecimIndeks = 0, vurguHoverIndeks = 0, vurguAralikTablo = 0; // Toplu statik parti: TEK bind + TEK draw (model=birim, dünya-uzayı pişmiş). Yalnız megaKullan. if (megaKullan && aktifSayi > 0) { ModelItme it{}; it.mvp = vp; // vertex zaten dünya-uzayında it.model = Mat4f::birim(); // normal transform birim (dünya normali pişik) it.vurgu[0]=0; it.vurgu[1]=0; it.vurgu[2]=zamanBits; it.vurgu[3]=0; modelHat.itmeSabitYaz(kb, &it, sizeof(it)); const VkBuffer vt = aktifVB.tampon(); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdBindIndexBuffer(kb, aktifIB.tampon(), 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(kb, aktifSayi, 1, 0, 0, 0); ++modelDrawCagri; } // Bireysel yol: hover/seçili gövde + CPU verisi olmayan/frustum-görünür statikler. // (Toplu partiye giren düğümler burada TEKRAR çizilmez — imza eşleşmesiyle atlanır.) for (auto& d : sahne.dugumler()) { if (!d.gorunurBit_ || d.indeksSayi == 0) continue; // mesh'siz (düzlem/eksen) atla // GPU buffer'ı henüz yüklenmemiş (async mesh worker) → indeksSayi>0 olsa da tampon NULL // olabilir. NULL vertex/index buffer bind + drawIndexed = geçersiz GPU işlemi (VUID) → // gerçek GPU'da (özellikle resize stres'inde) DEVICE_LOST riski. Yüklenene dek atla. if (!d.vertexTampon.tampon() || !d.indeksTampon.tampon()) continue; if (analizVarMi(d.kimlik) || kesitVar) continue; // analiz/kesit pipeline'ında çizilecek if (d.saydamlik < 1.0f) continue; // saydam geçişte çizilir (per-yüz kısmi opak yolda da çizilir) const bool topluda = megaKullan && (secimPartide || !seciliMiGovde(d.kimlik)) && !d.dinamikMesh && !d.cpuKonum.empty() && !d.cpuIndeks.empty(); if (topluda) continue; // mega partide çizildi (partide-tut: seçili de) if (!dugumFrustumGorunur(d, frustum)) continue; ModelItme it; it.mvp = vp * d.matris(); it.model = d.matris(); u32 hoverYuz = 0; if (d.kimlik == sahne.vurgu.govde) { hoverYuz = (secimSis.vurguTur() == 1 && sahne.vurgu.alt) ? sahne.vurgu.alt // yüz : (secimSis.vurguTur() == 0) ? TUMU // tüm gövde : 0; } it.vurgu[0] = hoverYuz; it.vurgu[1] = 0; it.vurgu[2] = zamanBits; it.vurgu[3] = 0; modelHat.itmeSabitYaz(kb, &it, sizeof(it)); // Orbit/hover-gezme LOD BİREYSEL yolda da: mega-parti eşiği ALTINDAKİ az-gövdeli // sahne (ör. 11 yüzeyli yüz-milyon-vertex model) tam burada yaşar — etkileşim // sürerken kaba tampon çizilir (üçgen ~10× az), fare durunca ince döner (mega // ile aynı zamanlama). Kaba da per-yüz renk/alt taşır → highlight/renk korunur. // Eşik: kaba LOD yalnız GERÇEKTEN ağır gövdede (indeks >= eşik). Küçük/orta modelde // ince mesh zaten ucuz; fare her gezişinde kaba↔ince toggle görünür titreşim // (silüet/gölge farkı) yapıyordu — "hover'da renkler gidip geliyor" şikayetinin // davranışsal yarısı. Dev modelde (asıl hedef) LOD aynen sürer. const bool kaba = bireyselOrbitLod() && etkilesimLod && d.kabaIndeksSayi > 0 && (i32)d.indeksSayi >= bireyselLodMinIndeks() && d.kabaVertexTampon.tampon() && d.kabaIndeksTampon.tampon(); const VkBuffer vt = (kaba ? d.kabaVertexTampon : d.vertexTampon).tampon(); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdBindIndexBuffer(kb, (kaba ? d.kabaIndeksTampon : d.indeksTampon).tampon(), 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(kb, kaba ? d.kabaIndeksSayi : d.indeksSayi, 1, 0, 0, 0); ++modelDrawCagri; } // Hover vurgu overlay: hover'lı gövde mega-partide KALIR (rebuild yok) — // vurgu partinin üstüne İKİNCİ bireysel çizimle basılır. Depth LEQUAL var ama // CPU'da dünya-uzayına pişmiş vertex ile shader'daki (vp·model)·p çarpımı // float yuvarlamada birebir eşit değil → clip-z'yi ~5e-5 NDC öne çekerek // (mvp satır2 -= eps·satır3) derinlik kazanımı garanti edilir (speckle yok). if (hoverGovde && (secimSis.vurguTur() == 0 || (secimSis.vurguTur() == 1 && sahne.vurgu.alt))) { Dugum* hd2 = sahne.bul(hoverGovde); const bool partide = hd2 && sahne.agacGorunurHizli(hd2->kimlik) && hd2->indeksSayi > 0 && !analizVarMi(hd2->kimlik) && !kesitVar && hd2->saydamlik >= 1.0f && !seciliMiGovde(hd2->kimlik) && !hd2->cpuKonum.empty() && !hd2->cpuIndeks.empty(); if (partide && dugumFrustumGorunur(*hd2, frustum)) { ModelItme it; it.mvp = vp * hd2->matris(); // Hover tint mesh'i taban modelin önünde ama KENARLARIN (kenar.vert −2e-5) ARKASINDA // kalmalı; aksi halde overlay kenarları depth-test'te örter (boş-seçim+hover flicker). for (int c = 0; c < 4; ++c) it.mvp.e[c][2] -= 1e-5f * it.mvp.e[c][3]; it.model = hd2->matris(); const u32 hoverYuz = (secimSis.vurguTur() == 1) ? sahne.vurgu.alt : TUMU; it.vurgu[0] = hoverYuz; it.vurgu[1] = 0; it.vurgu[2] = zamanBits; it.vurgu[3] = 0; modelHat.itmeSabitYaz(kb, &it, sizeof(it)); // Overlay, mega-partinin O ANKİ seviyesini AYNEN izler (parti kaba çizdiyse // overlay de kaba) → derinlik/silüet birebir örtüşür + dev gövdede etkileşim // sırasında tam-ince ikinci çizim yapılmaz. const bool kaba = lodModu && !hd2->kabaKonum.empty() && !hd2->kabaIndeks.empty() && hd2->kabaIndeksSayi > 0 && hd2->kabaVertexTampon.tampon() && hd2->kabaIndeksTampon.tampon(); const VkBuffer vt = (kaba ? hd2->kabaVertexTampon : hd2->vertexTampon).tampon(); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdBindIndexBuffer(kb, (kaba ? hd2->kabaIndeksTampon : hd2->indeksTampon).tampon(), 0, VK_INDEX_TYPE_UINT32); // Tek yüz hover'ında yalnız o yüzün indeks aralığı (kaba LOD'da tablo yok → tam mesh). u32 hOfs = 0, hSayi = kaba ? hd2->kabaIndeksSayi : hd2->indeksSayi; if (!kaba) { u32 o, s; if (yuzAralikBul(*hd2, hoverYuz, o, s)) { hOfs = o; hSayi = s; } } vurguHoverIndeks += hSayi; vurguAralikTablo = (u32)hd2->yuzIndeksAralik.size(); vkCmdDrawIndexed(kb, hSayi, 1, hOfs, 0, 0); ++modelDrawCagri; } } // Saydam gövde geçişi (voxel alanı/hayalet): opaklar bittikten sonra blend + // derinlik-yazmasız çiz; kaba arka→öne sıralama (kamera mesafesi, doğru harman). { std::vector>& saydamlar = onbellek.saydamScratch; // kare-başı yeniden kullan saydamlar.clear(); // kapasite korunur → per-frame heap alloc churn yok for (auto& d : sahne.dugumler()) { if (!d.gorunurBit_ || d.indeksSayi == 0) continue; // Gövde VEYA per-yüz VEYA MALZEME saydamlığı (cam/sıvı atanmış gövde de // blend geçişine girmeli — aksi halde opak geçişte discard edilir, kaybolur). if (d.saydamlik >= 1.0f && !d.yuzSaydamVar() && !hatlar.malzemeGpu.saydamMi(d.kimlik)) continue; if (analizVarMi(d.kimlik) || kesitVar) continue; if (!dugumFrustumGorunur(d, frustum)) continue; const Vec3f fark = d.dunyaMerkez() - kamera.konum(); saydamlar.emplace_back(-fark.uzunlukKare(), &d); // uzak önce } if (!saydamlar.empty()) { std::sort(saydamlar.begin(), saydamlar.end(), [](const auto& a, const auto& b2) { return a.first < b2.first; }); modelSeffafHat.bagla(kb); modelSeffafHat.setBagla(kb, hatlar.kareUbo.set()); modelSeffafHat.setBagla(kb, hatlar.malzemeGpu.set(), 1); for (auto& [mesafe, dp] : saydamlar) { Dugum& d = *dp; ModelItme it; it.mvp = vp * d.matris(); it.model = d.matris(); u32 alfaBits; // Gövde alfası: gövde saydamsa clamp'li değer; yalnız per-yüz saydamsa 1.0 // (frag'da vAlfa ile çarpılır → yüz alfası tek başına belirler). const f32 alfa = d.saydamlik < 1.0f ? std::clamp(d.saydamlik, 0.02f, 0.999f) : 1.0f; std::memcpy(&alfaBits, &alfa, 4); it.vurgu[0] = 0; it.vurgu[1] = 0; it.vurgu[2] = zamanBits; it.vurgu[3] = alfaBits; modelSeffafHat.itmeSabitYaz(kb, &it, sizeof(it)); const VkBuffer vt = d.vertexTampon.tampon(); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdBindIndexBuffer(kb, d.indeksTampon.tampon(), 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(kb, d.indeksSayi, 1, 0, 0, 0); ++modelDrawCagri; } } } // HUD: kare-başı draw-call + toplu durumu (VAR OLAN sayılar, davranış-nötr). perfGosterge().modelDrawCagri = modelDrawCagri; perfGosterge().megaIndeks = mergeIndeksSayi; // Draw-call ölçümü: ilk kare + her 600 karede bir (log spam yok). if (kare == 0 || (kare % 600 == 0)) { gunluk::bilgi("[perf] model geçişi draw-call: {} (toplu={}, mega-indeks={})", modelDrawCagri, mergeIndeksSayi > 0 ? 1 : 0, mergeIndeksSayi); } // Analiz pipeline: analiz-boyalı VEYA (kesit aktifken) tüm brep gövdeler. // Zebra/Reflect (mod 5/6) AYRI pipeline (zebraHat/reflectHat) + göz konumu + // şerit sayısı push-constant; diğerleri analizHat. Pipeline BİR kez bağlanır // (aktifHat izleme → mod değişince yeniden bağla). if (kesitVar || !analizHarita.empty()) { const Vec3f gozKonum = kamera.konum(); BoruHatti* aktifHat = nullptr; // Kesit kapakları: kendi düzleminin clip'inden MUAF (tam düzlem üstünde — // float hassasiyet titremesi olmasın) + saydamlık kipi (blend hat) → opak // gövdelerden SONRA çizilmek üzere biriktirilir. struct KapakCizim { Dugum* d; u32 sahipKesit; }; std::vector kapakCizimler; for (auto& d : sahne.dugumler()) { if (!d.gorunurBit_ || d.indeksSayi == 0) continue; if (!dugumFrustumGorunur(d, frustum)) continue; if (kesitVar) { if (const u32 sahip = analizSis.kesitKapakSahibi(d.kimlik)) { kapakCizimler.push_back({&d, sahip}); continue; } } const auto hit = analizHarita.find(d.kimlik); const analiz::AnalizSistemi::RenderBilgi rb = hit != analizHarita.end() ? hit->second : analiz::AnalizSistemi::RenderBilgi{}; if (!rb.var && !kesitVar) continue; // ne analiz ne kesit → model'de çizildi AItmeP p{}; VkBuffer skalerBuf; BoruHatti* hedefHat = &analizHat; if (!rb.var) { p = {2, {0, 0, 1}, 0, -90, 90, 0, 0}; // yalnız kesit clip (doğal renk) // Kesit aktifken gövde model hattında çizilmez → model.frag'ın hover tint'i // devre dışı kalıyordu (kesit açıkken imleç-altı yüz renklenmiyordu). // Hover'lı gövde mod 10'dan çizilir: izole=0 (hiçbir yüz atılmaz) + hover yüz. // Diğer gövdeler mod 2'de kalır → davranış birebir, ek maliyet yok. const bool hoverBurada = hoverGovde == d.kimlik && !seciliMiGovde(d.kimlik) && (secimSis.vurguTur() == 0 || secimSis.vurguTur() == 1); if (hoverBurada) { p.mod = 10; p.yon = {bitsFloat(0u), bitsFloat(secimSis.vurguTur() == 1 && sahne.vurgu.alt ? sahne.vurgu.alt : TUMU), 0}; // mod 10'da esik.xyz = hover rengi (analizItmeKur: dikBant/alanMax/alanMin // → esik[0..2]; bu modda o anlamları taşımaz). p.dikBant = lHover[0]; p.alanMax = lHover[1]; p.alanMin = lHover[2]; // ekstra (seçim rengi) boş: izole=0 → shader seçim dalına hiç girmez. } skalerBuf = sifirTamponAl(std::max((u32)d.cpuAlt.size(), 1u)); } else { int mod = analizModu(rb.tip); // Radius + item-bazlı bant tanımlı → mod 7 (discrete bant rengi, paketli float). if (rb.tip == analiz::AnalizTip::Radius && rb.bantlar && !rb.bantlar->empty()) mod = 7; // CAD Fark: gradyan → mod 8 (ham sapma per-vertex → fragment rampa, PÜRÜZSÜZ // web görünümü); ayrık → mod 7 (flat paketli, keskin binary). if (rb.tip == analiz::AnalizTip::Fark) mod = rb.farkGradyan ? 8 : 7; if (mod == 5 || mod == 6) { // Zebra/Reflect: yon=göz konumu, dikBant=şerit/hat sayısı, faz=rampa.z. hedefHat = mod == 5 ? &zebraHat : &reflectHat; p = {mod, gozKonum, std::max(rb.zebraSerit, 1.f), 0, 0, 0, 0, rb.zebraAci * 0.0174533f}; skalerBuf = sifirTamponAl(std::max((u32)d.cpuAlt.size(), 1u)); } else if (mod == 8) { // CAD Fark PROFESYONEL signed diverging (GOM/PolyWorks): İŞARETLİ sapma // (skalerTamponAl PerVertex, per-fragment interpolasyon → pürüzsüz). Renk: // |d|≤tol → YEŞİL (tolerans içi, shader sabit), d>0 → yeşil→MAVI (dışa/fazla), // d<0 → yeşil→KIRMIZI (içe/eksik). Push: yon=ARTI(mavi), esik=(tol, maksAralık, // EKSI.r), rampa=(_, EKSI.g, EKSI.b). // esik.x = ghostOp (<0 → ghost kapalı), rampaTip = tek-yön bayrağı, rampa.w = sınır keskinliği. p = {8, rb.farkRenkArti, rb.farkGhostKip ? rb.farkGhostOp : -1.0f, rb.farkRenkEksi.x, rb.farkMaksAralik, rb.farkTekYon, rb.farkRenkEksi.y, rb.farkRenkEksi.z, rb.farkRenkAyni, rb.farkGurultuTaban, rb.farkSinirKeskin}; skalerBuf = skalerTamponAl(d, rb); // Ghost kip: değişmeyen bölge saydam → TRANSPARAN analiz geçişi (derinlik yazmaz). if (rb.farkGhostKip) hedefHat = &analizSeffafHat; } else { // dikBant: draft/undercut/slope sarı-bant yarı-genişliği (ters eşiğinden ~2°). const f32 dikBant = std::abs(rb.tersEsik) < 0.05f ? 1.5f : std::abs(rb.tersEsik) * 5.f; // Skaler modda alan min/max + kritik eşik; analitik modda kullanılmaz. const f32 aMin = rb.alan ? rb.alan->min : -90.f; const f32 aMax = rb.alan ? rb.alan->max : 90.f; const int rampaTip = rb.rampaTip; // panelden seçilir (jet/turbo/tolerans) // ET KALINLIĞI PER-FRAGMENT: bu gövdenin ışın alanı yüklüyse faz(rampa.z) // = ışın menzili → shader her piksel için kendi kalınlığını ÖLÇER // (üçgenlemeden bağımsız gerçek ısı haritası). Yüklü değilse 0 → eski // per-vertex alan yolu birebir. f32 isinMenzil = 0.f; if (mod == 3 && rb.tip == analiz::AnalizTip::Kalinlik && hatlar.isinAlani.veriVar() && hatlar.isinAlani.dugum() == d.kimlik) isinMenzil = d.yerelSinir.gecerliMi() ? d.yerelSinir.kosegen() * d.donusum.olcek.x * 1.5f : 1e5f; // ekstra.w = kalınlık SINIF sayısı (0 = sürekli rampa). Sınıf varsa // shader her pikselin ölçülen kalınlığını sınıfa düşürüp AYRIK renk verir. const f32 bantSay = isinMenzil > 0.f ? (f32)hatlar.isinAlani.bantSayi() : 0.f; p = {mod, rb.cekimYonu, dikBant, aMin, aMax, rampaTip, mod == 3 ? rb.esik : 0.f, isinMenzil, {}, bantSay}; // Undercut: per-yüz EXACT durum kodları (alanDuz/FLAT → keskin yüz sınırı). const bool ucYuz = mod == 1 && rb.undercutYuzAlani; skalerBuf = (mod == 3 || ucYuz) ? skalerTamponAl(d, rb) : (mod == 7) ? (rb.tip == analiz::AnalizTip::Fark ? farkAyrikTamponAl(d, rb) : bantTamponAl(d, rb)) : sifirTamponAl(std::max((u32)d.cpuAlt.size(), 1u)); } } if (hedefHat != aktifHat) { hedefHat->bagla(kb); // Gölge set'i (UBO+sampler) analiz hatlarında ZORUNLU — mod 1 dışında // okunmaz ama bağlı olmalı (aksi halde validation hatası/UB). hedefHat->setBagla(kb, hatlar.golge.set()); // set 1 = ışın alanı. Zebra/Reflect de analizAyar'dan türediği için aynı // yerleşime sahip → hepsinde bağlanır (shader statik referans = zorunlu). hedefHat->setBagla(kb, hatlar.isinAlani.set(), 1); aktifHat = hedefHat; } AnalizItme it = analizItmeKur(d, p); aktifHat->itmeSabitYaz(kb, &it, sizeof(it)); const VkBuffer vtler[2] = {d.vertexTampon.tampon(), skalerBuf}; const VkDeviceSize ofsler[2] = {0, 0}; vkCmdBindVertexBuffers(kb, 0, 2, vtler, ofsler); vkCmdBindIndexBuffer(kb, d.indeksTampon.tampon(), 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(kb, d.indeksSayi, 1, 0, 0, 0); // ---- Undercut EXACT gölge overlay (mod 9) ---- // Kısmi örtülü yüzlerin gölgeli alt-bölgesi AYRI geometri (OCCT boolean) → // sınır gerçek EĞRİ, çözünürlük/merdiven YOK. Gövde topolojisi değişmez. if (rb.golgeIndeks && !rb.golgeIndeks->empty() && rb.golgeKonum) { auto& [ver, vbuf, ibuf] = onbellek.golgeOnbellek[rb.analizKimlik]; if (ver != rb.golgeSurum || vbuf.tampon() == VK_NULL_HANDLE) { const size_t n = rb.golgeKonum->size(); std::vector vs(n); const bool nOk = rb.golgeNormal && rb.golgeNormal->size() == n; for (size_t i = 0; i < n; ++i) { const Vec3f& p = (*rb.golgeKonum)[i]; const Vec3f nv = nOk ? (*rb.golgeNormal)[i] : Vec3f{0, 0, 1}; vs[i].konum[0] = p.x; vs[i].konum[1] = p.y; vs[i].konum[2] = p.z; vs[i].normal[0] = nv.x; vs[i].normal[1] = nv.y; vs[i].normal[2] = nv.z; vs[i].renk[0] = 0.90f; vs[i].renk[1] = 0.20f; vs[i].renk[2] = 0.18f; vs[i].altKimlik = 0; } vbuf = GpuTampon(vulkan, vs.data(), (u64)n * sizeof(Vertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); ibuf = GpuTampon(vulkan, rb.golgeIndeks->data(), (u64)rb.golgeIndeks->size() * sizeof(u32), VK_BUFFER_USAGE_INDEX_BUFFER_BIT); ver = rb.golgeSurum; } if (vbuf.tampon() && ibuf.tampon()) { // NULL bind = DEVICE_LOST hatlar.golgeOverlayHat.bagla(kb); hatlar.golgeOverlayHat.setBagla(kb, hatlar.golge.set()); hatlar.golgeOverlayHat.setBagla(kb, hatlar.isinAlani.set(), 1); AItmeP op{}; op.mod = 9; op.ekstra = {0.90f, 0.20f, 0.18f}; // undercut kırmızısı (mod 1 ile aynı) AnalizItme oit = analizItmeKur(d, op); hatlar.golgeOverlayHat.itmeSabitYaz(kb, &oit, sizeof(oit)); const VkBuffer ovt[2] = {vbuf.tampon(), sifirTamponAl((u32)rb.golgeKonum->size())}; const VkDeviceSize oofs[2] = {0, 0}; vkCmdBindVertexBuffers(kb, 0, 2, ovt, oofs); vkCmdBindIndexBuffer(kb, ibuf.tampon(), 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(kb, (u32)rb.golgeIndeks->size(), 1, 0, 0, 0); aktifHat = nullptr; // hat değişti → sonraki döngü yeniden bağlasın } } } // ---- Kesit kapakları (capping): mod 2 (doğal/veri-renkli vertex renk) ---- // Kendi düzlemi clip listesinden çıkarılır; saydamlık kipinde blend hat. const f32 kapakAlfa = analizSis.kapakAlfa(); for (auto& [dp, sahipKesit] : kapakCizimler) { Dugum& d = *dp; const AItmeP p{2, {0, 0, 1}, 0, -90, 90, 0, 0}; const VkBuffer skalerBuf = sifirTamponAl(std::max((u32)d.cpuAlt.size(), 1u)); BoruHatti* hedefHat = kapakAlfa < 1.0f ? &analizSeffafHat : &analizHat; if (hedefHat != aktifHat) { hedefHat->bagla(kb); aktifHat = hedefHat; } AnalizItme it = analizItmeKur(d, p); it.rampa[3] = kapakAlfa; // analiz.frag mod-2 alfa (saydam kapak kipi) int ks = 0; for (size_t i = 0; i < kesitClip.size() && ks < 4; ++i) { if (kesitClip[i].kimlik == sahipKesit) continue; // kendi düzlemi muaf it.kesit[ks][0] = kesitClip[i].normal.x; it.kesit[ks][1] = kesitClip[i].normal.y; it.kesit[ks][2] = kesitClip[i].normal.z; it.kesit[ks][3] = kesitClip[i].d; ++ks; } for (int i = ks; i < 4; ++i) it.kesit[i][0] = it.kesit[i][1] = it.kesit[i][2] = it.kesit[i][3] = 0; it.esik[3] = (f32)ks; aktifHat->itmeSabitYaz(kb, &it, sizeof(it)); const VkBuffer vtler[2] = {d.vertexTampon.tampon(), skalerBuf}; const VkDeviceSize ofsler[2] = {0, 0}; vkCmdBindVertexBuffers(kb, 0, 2, vtler, ofsler); vkCmdBindIndexBuffer(kb, d.indeksTampon.tampon(), 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(kb, d.indeksSayi, 1, 0, 0, 0); } // ---- GPU kesit kapağı (stencil capping): OCCT kapağı OLMAYAN düzlemler ---- // Sayım geçişi (stencil parite) + düzlem quad dolgusu → OCCT boolean/remesh // olmadan anlık düz kapak. Analiz-renkli kapaklar yukarıda OCCT ile çizilir. if (kesitVar) { std::unordered_set occtKapak; for (auto& [dp, sahip] : kapakCizimler) occtKapak.insert(sahip); // Sahne kapsamı (dünya merkez + yarıçap) → dolgu quad boyutu. Vec3f sMerkez{}; f32 sR = 0; u32 say = 0; for (Dugum& d : sahne.dugumler()) { if (!d.brep || !d.brep->gecerliMi() || d.indeksSayi == 0) continue; if (!d.gorunurBit_ || !d.yerelSinir.gecerliMi()) continue; sMerkez = sMerkez + d.dunyaMerkez(); ++say; } if (say) sMerkez = sMerkez * (1.0f / (f32)say); for (Dugum& d : sahne.dugumler()) { if (!d.brep || !d.brep->gecerliMi() || d.indeksSayi == 0) continue; if (!d.gorunurBit_ || !d.yerelSinir.gecerliMi()) continue; sR = std::max(sR, (d.dunyaMerkez() - sMerkez).uzunluk() + d.dunyaYaricap()); } // GPU kapak istenen düzlem var mı? (OCCT kapağı olmayanlar) bool gpuGerek = false; for (const auto& kc : kesitClip) if (!occtKapak.count(kc.kimlik)) { gpuGerek = true; break; } if (gpuGerek && sR > 0) { // 1) Sayım geçişi: tüm görünür katıları clip'li çiz (renk/derinlik yazma yok). kesitSayimHat.bagla(kb); const AItmeP sp{2, {0, 0, 1}, 0, -90, 90, 0, 0}; for (Dugum& d : sahne.dugumler()) { if (!d.brep || !d.brep->gecerliMi() || d.indeksSayi == 0) continue; if (!d.gorunurBit_) continue; if (!dugumFrustumGorunur(d, frustum)) continue; const AnalizItme it = analizItmeKur(d, sp); kesitSayimHat.itmeSabitYaz(kb, &it, sizeof(it)); const VkBuffer vt = d.vertexTampon.tampon(); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdBindIndexBuffer(kb, d.indeksTampon.tampon(), 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(kb, d.indeksSayi, 1, 0, 0, 0); } // 2) Dolgu geçişi: her GPU düzlemi için stencil≠0 maskeli quad. kesitDolguHat.bagla(kb); const f32 kapakAlfaG = analizSis.kapakAlfa(); const f32 hKen = sR * 1.12f; for (const auto& kc : kesitClip) { if (occtKapak.count(kc.kimlik)) continue; // analiz-renkli → OCCT çizdi const Vec3f n = kc.normal.uzunlukKare() > 1e-8f ? kc.normal.birim() : Vec3f{0, 0, 1}; const Vec3f m = sMerkez - n * (sMerkez.nokta(n) + kc.d); // merkez → düzlem izdüşümü const Vec3f ref = std::abs(n.z) < 0.9f ? Vec3f{0, 0, 1} : Vec3f{1, 0, 0}; const Vec3f u = n.capraz(ref).birim(); const Vec3f v = n.capraz(u); KesitDolguItme it{}; it.mvp = vp; it.merkez[0] = m.x; it.merkez[1] = m.y; it.merkez[2] = m.z; it.merkez[3] = hKen; it.uEks[0] = u.x; it.uEks[1] = u.y; it.uEks[2] = u.z; it.vEks[0] = v.x; it.vEks[1] = v.y; it.vEks[2] = v.z; it.kapakRenk[0] = 0.72f; it.kapakRenk[1] = 0.73f; it.kapakRenk[2] = 0.77f; it.kapakRenk[3] = kapakAlfaG > 0.0f ? kapakAlfaG : 1.0f; it.kapakNrm[0] = -n.x; it.kapakNrm[1] = -n.y; it.kapakNrm[2] = -n.z; // kesik yüzü kameraya bakar int ks = 0; for (const auto& o : kesitClip) { if (o.kimlik == kc.kimlik || ks >= 4) continue; // kendi düzlemi hariç it.kesit[ks][0] = o.normal.x; it.kesit[ks][1] = o.normal.y; it.kesit[ks][2] = o.normal.z; it.kesit[ks][3] = o.d; ++ks; } it.kapakNrm[3] = (f32)ks; kesitDolguHat.itmeSabitYaz(kb, &it, sizeof(it)); vkCmdDraw(kb, 4, 1, 0, 0); // triangle strip quad } } } } // 2) Kenar wireframe (ince koyu; Kenar kipinde hover cyan) // Düzlem/Eksen (referans datum) İKİNCİ geçişte, derinlik testi kapalı pipeline // ile katıların ÜSTÜNE çizilir → arkada kalmaz (CATIA davranışı). Diğer tüm // kenarlar (katı/CAM yolu/GSD tel) normal derinlikli ilk geçişte. // Kenar push-constant'ına aktif kesit düzlemlerini doldur (kenar.vert/frag // clip). Datum/profil/tel çizgileri MUAF (çağıran doldurtmaz → vurgu.w=0). // muafKesit: bu kesit düzlemi clip listesine GİRMEZ (kesit profili kendi düzleminin // TAM üstünde — kenar.frag toleranssız, kendi düzlemiyle kırpılsa float hatasında // profil yok olurdu). Kapak çiziminin ikizi. const auto kenarKesitDoldur = [&](KenarItme& it, u32 muafKesit = 0) { int ks = 0; for (size_t i = 0; i < kesitClip.size() && ks < 4; ++i) { if (kesitClip[i].kimlik == muafKesit) continue; it.kesit[ks][0] = kesitClip[i].normal.x; it.kesit[ks][1] = kesitClip[i].normal.y; it.kesit[ks][2] = kesitClip[i].normal.z; it.kesit[ks][3] = kesitClip[i].d; ++ks; } it.vurgu[3] = (u32)ks; }; const auto kenarCiz = [&](Dugum& d) { KenarItme it; it.mvp = dugumMvp(d); // düzlem/eksen ekran-sabit; diğerleri dünya-ölçekli // Referans geometri (düzlem/eksen/takım yolu/GSD tel) kendi rengiyle çizilir; katı gövde kenarları koyu. const bool referans = d.tip == DugumTip::Duzlem || d.tip == DugumTip::Eksen || d.tip == DugumTip::EksenSistemi || d.tip == DugumTip::Nokta || d.tip == DugumTip::Cizgi || d.tip == DugumTip::TakimYolu || d.tip == DugumTip::Tel; // DATUM muafiyeti (ekran-sabit, hep görünür olmalı) ile DÜNYA geometrisi ayrı: // takım yolu ve GSD teli gerçek geometridir → kesitin attığı yarıda havada // asılı kalmasınlar, katı gövde gibi kırpılsınlar. Düzlem/eksen/nokta/çizgi muaf. const bool datumMuaf = d.tip == DugumTip::Duzlem || d.tip == DugumTip::Eksen || d.tip == DugumTip::EksenSistemi || d.tip == DugumTip::Nokta || d.tip == DugumTip::Cizgi; if (referans) { it.renk[0] = d.renk.x; it.renk[1] = d.renk.y; it.renk[2] = d.renk.z; it.renk[3] = 0.7f; } else { it.renk[0] = 0.05f; it.renk[1] = 0.06f; it.renk[2] = 0.09f; it.renk[3] = 0.85f; } const u32 hoverKenar = (secimSis.vurguTur() == 2 && d.kimlik == sahne.vurgu.govde) ? sahne.vurgu.alt : 0; it.vurgu[0] = hoverKenar; it.vurgu[1] = 0; it.vurgu[2] = 0; it.vurgu[3] = 0; // Katı gövde kenarları kesit clip'iyle kesilir (yüzeylerle aynı düzlemler). // Kesit PROFİL eğrisi de kırpılır (TakimYolu tipinde doğar → referans sayılıp // muaf kalıyordu: çoklu kesitte A'nın profili B'nin attığı yarıda sürüyordu), // ama KENDİ düzleminden muaf tutulur (tam onun üstünde). const u32 profilSahip = kesitVar ? analizSis.kesitProfilSahibi(d.kimlik) : 0; if (kesitVar && !datumMuaf) { it.model = d.matris(); kenarKesitDoldur(it, profilSahip); // profil kendi düzleminden muaf (0 = muafiyet yok) } const VkBuffer vt = d.kenarTampon.tampon(); // Pipeline bağlama + itmeSabitYaz çağıranın sorumluluğu (kenarHat / refKenarHat). return std::pair{it, vt}; }; // Kenar mega-parti (model mega-partinin ikizi): katı (referans-olmayan, // hover/seçili-olmayan) gövde kenarları dünya-uzayında TEK tampona pişer → // N bind+draw yerine 1 draw. LINE_LIST non-indexed → concat basit (remap yok), // tümü aynı koyu renk + mvp=vp. Datum/TakımYolu/Tel kendi renginde per-düğüm kalır. // HOVER PARTİDE KALIR (model mega-partiyle aynı gerekçe: rebuild kasması yok); // hover'lı kenar vurgusu aşağıda z-bias'lı overlay ile üstüne çizilir. // Fark analizli gövde: kenarları per-düğüm yolda çizilir (panel "Kenarları Göster" // gizleme + "Kenar" renk özelleştirmesi mega-partide uygulanamaz; 1-2 gövde ucuz). const auto farkKenarBilgi = [&](const Dugum& d) -> const analiz::AnalizSistemi::RenderBilgi* { const auto it = analizHarita.find(d.kimlik); return (it != analizHarita.end() && it->second.farkGovdesi) ? &it->second : nullptr; }; // secimPartide: seçili gövdenin kenarları da mega-partide KALIR (mesh ile aynı) → seçim // değişiminde kenar partisi de YENİDEN PİŞMEZ. Tek yüklem → imza/pişirme/bireysel 3 nokta // OTOMATİK tutarlı (mesh'teki gibi ayrı-nokta tutarsızlık = vanish riski YOK). const auto kenarToplanabilir = [&](const Dugum& d) { return d.kenarVertexSayi > 0 && !d.cpuKenar.empty() && (secimPartide || !seciliMiGovde(d.kimlik)) && !farkKenarBilgi(d) && d.tip != DugumTip::Duzlem && d.tip != DugumTip::Eksen && d.tip != DugumTip::EksenSistemi && d.tip != DugumTip::TakimYolu && d.tip != DugumTip::Tel && d.tip != DugumTip::Sketch && d.tip != DugumTip::Nokta && d.tip != DugumTip::Cizgi; // referans datum → per-dugum }; { std::vector& kenarYeniImza = onbellek.kenarImzaScratch; // A#2: yeniden kullan std::vector& kenarYeniKimlik = onbellek.kenarImzaKimlik; kenarYeniImza.clear(); kenarYeniKimlik.clear(); for (auto& d : sahne.dugumler()) { if (!d.gorunurBit_ || !kenarToplanabilir(d)) continue; const Mat4f m = d.matris(); u64 h = 1469598103934665603ull; const auto kat = [&](u64 v) { h = (h ^ v) * 1099511628211ull; }; kat(d.kimlik); kat((u64)d.cpuKenar.size()); kat((u64)reinterpret_cast(d.cpuKenar.data())); for (int i = 0; i < 16; ++i) { u32 bb; std::memcpy(&bb, &m.e[0][0] + i, 4); kat(bb); } kenarYeniImza.push_back(h); kenarYeniKimlik.push_back(d.kimlik); } if (kenarYeniImza != kenarMergeImza) { const auto kenarT0 = std::chrono::steady_clock::now(); // TEŞHİS: imzayı BOZAN düğümü isimlendir. Bu blok her karede pişerse (ölçüldü // 07-21: 16 ms aralıkla 9210 vertex) kök neden burada görünür; tahmin gerekmez. u32 sucluKimlik = 0; const char* sucluNeden = "set"; if (kenarYeniImza.size() == kenarMergeImza.size()) { for (std::size_t i = 0; i < kenarYeniImza.size(); ++i) if (kenarYeniImza[i] != kenarMergeImza[i]) { sucluKimlik = i < kenarYeniKimlik.size() ? kenarYeniKimlik[i] : 0; sucluNeden = (i < onbellek.kenarImzaKimlikSon.size() && onbellek.kenarImzaKimlikSon[i] == sucluKimlik) ? "matris/veri" : "kimlik"; break; } } onbellek.kenarImzaKimlikSon = kenarYeniKimlik; std::vector& mv = onbellek.kenarPisirScratch; // kare-başı tahsis yok mv.clear(); for (auto& d : sahne.dugumler()) { if (!d.gorunurBit_ || !kenarToplanabilir(d)) continue; const Mat4f m = d.matris(); // sütun-major const auto nokUygula = [&](const Vec3f& p) -> Vec3f { return {m.e[0][0]*p.x + m.e[1][0]*p.y + m.e[2][0]*p.z + m.e[3][0], m.e[0][1]*p.x + m.e[1][1]*p.y + m.e[2][1]*p.z + m.e[3][1], m.e[0][2]*p.x + m.e[1][2]*p.y + m.e[2][2]*p.z + m.e[3][2]}; }; for (const Vec3f& p : d.cpuKenar) { const Vec3f w = nokUygula(p); mv.push_back(KenarVertex{{w.x, w.y, w.z}, 0}); } } if (!mv.empty()) // dinamik guncelle(): rebuild'de submit+fence STALL yerine memcpy kenarMergeVB.guncelle(vulkan, mv.data(), mv.size() * sizeof(KenarVertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); else kenarMergeVB = GpuTampon(); kenarMergeSayi = (u32)mv.size(); kenarMergeImza = kenarYeniImza; // kopya YALNIZ pişmede (nadir) const f64 kenarCookMs = std::chrono::duration( std::chrono::steady_clock::now() - kenarT0).count(); perfGosterge().kenarCookMs = kenarCookMs; // HUD: VAR OLAN ölçümü yaz (davranış-nötr) static gunluk::Seyrek kenarSeyrek{1000}; // sıcak yol: her-kare log = her-kare disk yazımı if (kenarSeyrek.gecer()) gunluk::bilgi( "[perf] kenar mega-parti pişirme: {:.1f} ms ({} vertex) — bastirilan {} · imzayı " "bozan dugum {} ({})", kenarCookMs, mv.size(), kenarSeyrek.bastirilanAl(), sucluKimlik, sucluNeden); } } kenarHat.bagla(kb); if (kenarMergeSayi > 0) { KenarItme it{}; it.mvp = vp; // kenarlar dünya-uzayında pişik it.model = Mat4f::birim(); // dünya-uzayı pişik → clip doğrudan konumdan it.renk[0] = 0.05f; it.renk[1] = 0.06f; it.renk[2] = 0.09f; it.renk[3] = 0.85f; it.vurgu[0] = 0; it.vurgu[1] = 0; it.vurgu[2] = 0; it.vurgu[3] = 0; if (kesitVar) kenarKesitDoldur(it); kenarHat.itmeSabitYaz(kb, &it, sizeof(it)); const VkBuffer vt = kenarMergeVB.tampon(); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdDraw(kb, kenarMergeSayi, 1, 0, 0); } // kenarKalin (analiz kenarları): kenarVurguHat (4px) ile satır-içi çizilir; // pipeline yalnız geçişte rebind edilir (kare-başı tahsis YOK — sıcak yol). bool kalinBagli = false; for (auto& d : sahne.dugumler()) { if (!d.gorunurBit_ || d.kenarVertexSayi == 0) continue; if (d.tip == DugumTip::Duzlem || d.tip == DugumTip::Eksen || d.tip == DugumTip::EksenSistemi || d.tip == DugumTip::Nokta || d.tip == DugumTip::Cizgi) continue; // 2. geçiş (datum üstte) if (d.tip == DugumTip::Sketch) continue; // 2B overlay çizer; kenarTampon YALNIZ pick if (d.kenarUstte) continue; // derinliksiz istek → datum geçişi (2b) if (kenarToplanabilir(d)) continue; // mega partide çizildi if (!dugumFrustumGorunur(d, frustum)) continue; // Çizim aralığı (web setDrawRange eşleniği — CAM sim geçilen-yol reveal): // varsayılan bas=0, adet=~0u → tam tampon (mevcut düğümler değişmez). const u32 cizBas = d.kenarCizBas < d.kenarVertexSayi ? d.kenarCizBas : d.kenarVertexSayi; const u32 cizAdet = d.kenarCizSayi < d.kenarVertexSayi - cizBas ? d.kenarCizSayi : d.kenarVertexSayi - cizBas; if (cizAdet == 0) continue; // reveal henüz başlamadı // Fark gövdesi: kenar gizleme + özel kenar rengi (panel Görüntüleme Ayarları). const analiz::AnalizSistemi::RenderBilgi* fkb = farkKenarBilgi(d); if (fkb && !fkb->farkKenarGoster) continue; if (d.kenarKalin != kalinBagli) { (d.kenarKalin ? kenarVurguHat : kenarHat).bagla(kb); kalinBagli = d.kenarKalin; } auto [it, vt] = kenarCiz(d); if (fkb) { it.renk[0] = fkb->farkRenkKenar.x; it.renk[1] = fkb->farkRenkKenar.y; it.renk[2] = fkb->farkRenkKenar.z; } // Kalın çizgi yüzeyin tam üstünde — z-fight etmesin (hover vurgusuyla aynı bias). if (d.kenarKalin) for (int c = 0; c < 4; ++c) it.mvp.e[c][2] -= 5e-5f * it.mvp.e[c][3]; (kalinBagli ? kenarVurguHat : kenarHat).itmeSabitYaz(kb, &it, sizeof(it)); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdDraw(kb, cizAdet, 1, cizBas, 0); } if (kalinBagli) kenarHat.bagla(kb); // sonraki çizimler ince pipeline bekler // Hover kenar vurgu overlay: hover'lı gövde kenar mega-partisinde kaldı (koyu) // → cyan vurgulu kenar, partinin üstüne z-bias'lı ikinci çizimle basılır // (model overlay ile aynı float-yuvarlama gerekçesi). if (secimSis.vurguTur() == 2 && hoverGovde && sahne.vurgu.alt) { Dugum* hd2 = sahne.bul(hoverGovde); if (hd2 && sahne.agacGorunurHizli(hd2->kimlik) && kenarToplanabilir(*hd2) && dugumFrustumGorunur(*hd2, frustum)) { auto [it, vt] = kenarCiz(*hd2); // Yalnız hover'lı kenarı çiz (izole) — kalın + derinlik-yazmasız üstte, cyan. // Tüm gövde kenarlarını yeniden basmaz (perf) → mega-parti ile z-fight yok. it.vurgu[0] = 0; it.vurgu[1] = sahne.vurgu.alt; it.renk[0] = 0.25f; it.renk[1] = 0.85f; it.renk[2] = 1.0f; it.renk[3] = 1.0f; for (int c = 0; c < 4; ++c) it.mvp.e[c][2] -= 5e-5f * it.mvp.e[c][3]; kenarVurguHat.bagla(kb); kenarVurguHat.itmeSabitYaz(kb, &it, sizeof(it)); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdDraw(kb, hd2->kenarVertexSayi, 1, 0, 0); } } // 2b) Referans datum kenarları — derinlik kapalı, üstte refKenarHat.bagla(kb); for (auto& d : sahne.dugumler()) { if (!d.gorunurBit_ || d.kenarVertexSayi == 0) continue; // EksenSistemi ayrı boyanır (X kırmızı/Y yeşil/Z mavi + L-köşe gri); aşağıda. // Referans Nokta (hac) + Cizgi de bu derinlik-kapalı datum geçişinde (üstte). // kenarUstte (web simCizgiOnde): TakimYolu da bu geçişe yönlenebilir. const bool datum = d.tip == DugumTip::Duzlem || d.tip == DugumTip::Eksen || d.tip == DugumTip::Nokta || d.tip == DugumTip::Cizgi; if (!datum && !(d.kenarUstte && d.tip == DugumTip::TakimYolu)) continue; // Çizim aralığı burada da uygulanır (sim geçilen-yol "önde" kipinde reveal korunur). const u32 cizBas = d.kenarCizBas < d.kenarVertexSayi ? d.kenarCizBas : d.kenarVertexSayi; const u32 cizAdet = d.kenarCizSayi < d.kenarVertexSayi - cizBas ? d.kenarCizSayi : d.kenarVertexSayi - cizBas; if (cizAdet == 0) continue; auto [it, vt] = kenarCiz(d); refKenarHat.itmeSabitYaz(kb, &it, sizeof(it)); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdDraw(kb, cizAdet, 1, cizBas, 0); } // 2c) CATIA Eksen Sistemi — TEK düğüm, alt-ordinal başına renk (izole çizim). // X kırmızı / Y yeşil / Z mavi (alt 1-3), L-köşe düzlem gri (alt 4-6). // kenar frag: izole(vurgu.y) != vAlt → discard → yalnız o alt-öğe çizilir. // Hover (vurguTur==2) veya seçili alt-öğe cyan/turuncu parlar (parça parça). for (auto& d : sahne.dugumler()) { if (d.tip != DugumTip::EksenSistemi || !d.gorunurBit_ || d.kenarVertexSayi == 0) continue; const Mat4f mvp = dugumMvp(d); const VkBuffer vt = d.kenarTampon.tampon(); const VkDeviceSize ofs = 0; const u32 hoverAlt = (secimSis.vurguTur() == 2 && d.kimlik == sahne.vurgu.govde) ? sahne.vurgu.alt : 0; auto altSecili = [&](u32 a) { return sahne.seciliMi({d.kimlik, a}); }; // alt-ordinal → renk (X/Y/Z eksen belirgin; L-köşe nötr gri). struct AR { u32 alt; float r, g, b; }; const AR renkler[6] = { {eksensis::EKSEN_X, 0.92f, 0.26f, 0.28f}, {eksensis::EKSEN_Y, 0.30f, 0.82f, 0.35f}, {eksensis::EKSEN_Z, 0.32f, 0.52f, 0.98f}, {eksensis::DUZLEM_XY, 0.78f, 0.80f, 0.85f}, {eksensis::DUZLEM_YZ, 0.78f, 0.80f, 0.85f}, {eksensis::DUZLEM_ZX, 0.78f, 0.80f, 0.85f}}; refKenarHat.bagla(kb); for (const AR& a : renkler) { KenarItme it; it.mvp = mvp; float r = a.r, g = a.g, b2 = a.b; if (altSecili(a.alt)) { r = 1.0f; g = 0.55f; b2 = 0.10f; } // seçili → turuncu else if (hoverAlt == a.alt) { r = 0.25f; g = 0.85f; b2 = 1.0f; } // hover → cyan it.renk[0] = r; it.renk[1] = g; it.renk[2] = b2; it.renk[3] = 1.0f; it.vurgu[0] = 0; it.vurgu[1] = a.alt; // izole: yalnız bu altKimlik çizilir it.vurgu[2] = 0; it.vurgu[3] = 0; refKenarHat.itmeSabitYaz(kb, &it, sizeof(it)); vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdDraw(kb, d.kenarVertexSayi, 1, 0, 0); } } // 3) Nokta filtresi açıksa yalnız imleç ALTINDAKI köşe (snap göstergesi; tüm // köşeleri basmaz — temiz görünüm). Seçilebilirlik pick geçişinde tüm // köşeler için korunur, bu yalnız görsel geri bildirim. if (secimSis.fNokta && secimSis.vurguTur() == 3 && sahne.vurgu.alt) { if (Dugum* d = sahne.bul(sahne.vurgu.govde); d && sahne.agacGorunurHizli(d->kimlik) && d->noktaSayi > 0) { noktaHat.bagla(kb); KenarItme it; it.mvp = vp * d->matris(); it.renk[0] = 0.25f; it.renk[1] = 0.85f; it.renk[2] = 1.0f; it.renk[3] = 1.0f; // izole (vurgu[1]): shader yalnız bu altKimlik'e sahip köşeyi çizer. it.vurgu[0] = 0; it.vurgu[1] = sahne.vurgu.alt; it.vurgu[2] = floatBits(11.0f); it.vurgu[3] = 0; noktaHat.itmeSabitYaz(kb, &it, sizeof(it)); const VkBuffer vt = d->noktaTampon.tampon(); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdDraw(kb, d->noktaSayi, 1, 0, 0); } } // 4) Seçim vurgusu (overlay). Nabız + renk: kenar/nokta/datum vurgusu model // yüz-seçimiyle AYNI (Ayarlar'dan sg.secimRenk + nabizHiz/nabizSiddet). Formül // model.frag ile birebir: nabiz = (1-şiddet) + şiddet·sin(t·hız). const f32 secimNabiz = (1.0f - sg.nabizSiddet) + sg.nabizSiddet * std::sin(zaman * sg.nabizHiz); const Vec3f secR = sg.secimRenk; // Çok-seçim eşiği üstünde per-gövde vurgu overlay ATLANIR (binlerce seçilide N draw = kasma; // seçili gövdeler mega-partide base olarak zaten çizildi, ürün ağacı seçimi gösterir). Eşik // altı → tüm döngü eskisi gibi çalışır (birebir davranış). for (const SecimOge& oge : sahne.secililer()) { if (cokSecili) break; // eşik üstü: 3B per-gövde vurgu yok Dugum* d = sahne.bul(oge.govde); if (!d || !sahne.agacGorunurHizli(d->kimlik)) continue; // PERF: frustum DIŞI seçili gövdenin vurgusu zaten hiçbir fragment üretmez → tam-mesh // draw-call'ı ATLA (ana mesh geçişi de böyle kültler; datum/sketch/tel tipleri cull-exempt // döner → davranış birebir/piksel-özdeş). Ağaçtan grup seçince binlerce gövde seçiliyken // ekran-dışı olanlar artık kare-başı draw harcamaz (GPU-bound kasma azalır). if (!dugumFrustumGorunur(*d, frustum)) continue; // CAM takım yolu: seçim overlay'i TÜM kenar tamponunu vurguyla çizer (alt=0) → // 200k+ vertexli yol komple parlıyordu ("hepsi seçili" görünümü). Yol bir // görselleştirme; seçim vurgusu almaz (ayar: secim.yol_secilemez). if (pickAyar().yolSecilemez && d->tip == DugumTip::TakimYolu) continue; const Mat4f mvp = dugumMvp(*d); // Eksen Sistemi: alt-öğe seçim vurgusu 2c bloğunda (parça parça turuncu) + // Nokta kip origin için aşağıdaki Nokta dalı. Buradaki tüm-kenar overlay'i atla. if (d->tip == DugumTip::EksenSistemi) { if (kip != SecimKip::Nokta) continue; // eksen/L → 2c çizdi; nokta → aşağı düş } // Referans datum (düzlem/eksen/nokta/çizgi): mesh yok → tüm kenarları kalın turuncu + // derinlik kapalı (üstte). Govde kipinde seçilir; her kip için aynı overlay. else if (d->tip == DugumTip::Duzlem || d->tip == DugumTip::Eksen || d->tip == DugumTip::Nokta || d->tip == DugumTip::Cizgi) { if (d->kenarVertexSayi == 0) continue; refKenarVurguHat.bagla(kb); KenarItme it; it.mvp = mvp; it.renk[0] = secR.x * secimNabiz; it.renk[1] = secR.y * secimNabiz; it.renk[2] = secR.z * secimNabiz; it.renk[3] = 1.0f; it.vurgu[0] = 0; it.vurgu[1] = 0; it.vurgu[2] = 0; it.vurgu[3] = 0; // alt=0 → tüm kenarlar refKenarVurguHat.itmeSabitYaz(kb, &it, sizeof(it)); const VkBuffer vt = d->kenarTampon.tampon(); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdDraw(kb, d->kenarVertexSayi, 1, 0, 0); continue; } if (kip == SecimKip::Kenar) { kenarVurguHat.bagla(kb); KenarItme it; it.mvp = mvp; it.renk[0] = secR.x * secimNabiz; it.renk[1] = secR.y * secimNabiz; it.renk[2] = secR.z * secimNabiz; it.renk[3] = 1.0f; it.vurgu[0] = 0; it.vurgu[1] = oge.alt; it.vurgu[2] = 0; it.vurgu[3] = 0; if (kesitVar) { it.model = d->matris(); kenarKesitDoldur(it); } kenarVurguHat.itmeSabitYaz(kb, &it, sizeof(it)); const VkBuffer vt = d->kenarTampon.tampon(); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdDraw(kb, d->kenarVertexSayi, 1, 0, 0); } else if (kip == SecimKip::Nokta) { noktaHat.bagla(kb); KenarItme it; it.mvp = mvp; it.renk[0] = secR.x * secimNabiz; it.renk[1] = secR.y * secimNabiz; it.renk[2] = secR.z * secimNabiz; it.renk[3] = 1.0f; it.vurgu[0] = 0; it.vurgu[1] = oge.alt; it.vurgu[2] = floatBits(13.0f); it.vurgu[3] = 0; noktaHat.itmeSabitYaz(kb, &it, sizeof(it)); const VkBuffer vt = d->noktaTampon.tampon(); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdDraw(kb, d->noktaSayi, 1, 0, 0); } else if (kesitVar && !analizVarMi(d->kimlik) && d->indeksSayi > 0) { // KESİT AKTİF → gövde model hattında çizilmedi (analiz mod 2'den geldi). // Vurgu da analiz hattından (mod 10) çizilmeli; model hattı clip'siz olduğu // için kesilen yarım yüzü de boyuyordu ("highlight kesik değil bütün"). analizHat.bagla(kb); analizHat.setBagla(kb, hatlar.golge.set()); // analiz hatlarında ZORUNLU analizHat.setBagla(kb, hatlar.isinAlani.set(), 1); AItmeP p{}; p.mod = 10; // yon.x = izole yüz (seçim overlay'i), yon.y = hover yüz (burada yok — izole // varken shader zaten hover'a bakmaz). ekstra = seçim rengi + nabız. p.yon = {bitsFloat((kip == SecimKip::Yuz && oge.alt) ? oge.alt : TUMU), bitsFloat(0u), 0}; p.ekstra = secR; p.ekstraW = secimNabiz; AnalizItme it = analizItmeKur(*d, p); it.mvp = mvp; // datum/ekran-sabit ölçek dahil çağıranın MVP'si analizHat.itmeSabitYaz(kb, &it, sizeof(it)); const VkBuffer vtler[2] = {d->vertexTampon.tampon(), sifirTamponAl(std::max((u32)d->cpuAlt.size(), 1u))}; const VkDeviceSize ofsler[2] = {0, 0}; vkCmdBindVertexBuffers(kb, 0, 2, vtler, ofsler); vkCmdBindIndexBuffer(kb, d->indeksTampon.tampon(), 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(kb, d->indeksSayi, 1, 0, 0, 0); } else { modelHat.bagla(kb); modelHat.setBagla(kb, hatlar.kareUbo.set()); // kare-sabit isik/renk (model.frag set=0) modelHat.setBagla(kb, hatlar.malzemeGpu.set(), 1); // malzeme/doku (model.frag set=1) ModelItme it; it.mvp = mvp; // secim_cook_atla: seçili gövde mega-partide base olarak çizildiyse (bake filtresiyle BİREBİR // eşleşen koşul) vurgu overlay'ini z-bias'la öne çek — batch dünya-uzay pişik (vp·p) ile bu // overlay'in vp·model·p float farkı speckle yapar (kanıtlı hover z-nudge kalıbı, 1e-5 NDC). const bool bpartide = secimPartide && megaKullan && !d->dinamikMesh && !kesitVar && !analizVarMi(d->kimlik) && d->saydamlik >= 1.0f && !d->cpuKonum.empty() && !d->cpuIndeks.empty(); if (bpartide) for (int c = 0; c < 4; ++c) it.mvp.e[c][2] -= 1e-5f * it.mvp.e[c][3]; it.model = d->matris(); it.vurgu[0] = 0; it.vurgu[1] = (kip == SecimKip::Yuz && oge.alt) ? oge.alt : TUMU; it.vurgu[2] = zamanBits; it.vurgu[3] = 0; modelHat.itmeSabitYaz(kb, &it, sizeof(it)); const VkBuffer vt = d->vertexTampon.tampon(); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdBindIndexBuffer(kb, d->indeksTampon.tampon(), 0, VK_INDEX_TYPE_UINT32); // Tek yüz seçiliyse yalnız o yüzün indeks aralığı (taban gövde zaten partide/bireysel çizildi). u32 sOfs = 0, sSayi = d->indeksSayi; { u32 o, s; if (yuzAralikBul(*d, it.vurgu[1], o, s)) { sOfs = o; sSayi = s; } } vurguSecimIndeks += sSayi; vurguAralikTablo = (u32)d->yuzIndeksAralik.size(); vkCmdDrawIndexed(kb, sSayi, 1, sOfs, 0, 0); } } if ((kare == 0 || (kare % 600 == 0)) && (vurguSecimIndeks || vurguHoverIndeks)) { gunluk::bilgi("[perf] vurgu overlay: secim {} indeks · hover {} indeks (yuz tablosu {} giris)", vurguSecimIndeks, vurguHoverIndeks, vurguAralikTablo); } // ---- Selection Stack "peek" lens: highlight gövde, imleç dairesinde derinlik-testsiz ---- // Occluder'sız (önündeki elemanlar olmadan) → gizli aday daire içinde görünür. En son // çizilir (her şeyin üstü). Scissor daire bbox'ına daraltılır (fragment daireyi keser). if (girdi.peekGovde) { Dugum* pd = sahne.bul(girdi.peekGovde); if (pd && pd->indeksSayi > 0 && pd->vertexTampon.tampon()) { const VkExtent2D fb = hedef.boyut(); const i32 r = (i32)girdi.peekYari + 2; const i32 x0 = std::clamp((i32)girdi.peekMerkezX - r, 0, (i32)fb.width); const i32 y0 = std::clamp((i32)girdi.peekMerkezY - r, 0, (i32)fb.height); const i32 x1 = std::clamp((i32)girdi.peekMerkezX + r, 0, (i32)fb.width); const i32 y1 = std::clamp((i32)girdi.peekMerkezY + r, 0, (i32)fb.height); if (x1 > x0 && y1 > y0) { const VkRect2D sc{{x0, y0}, {(u32)(x1 - x0), (u32)(y1 - y0)}}; vkCmdSetScissor(kb, 0, 1, &sc); peekHat.bagla(kb); PeekItme it{}; it.mvp = vp * pd->matris(); it.model = pd->matris(); it.merkez[0] = girdi.peekMerkezX; it.merkez[1] = girdi.peekMerkezY; it.merkez[2] = girdi.peekYari; it.merkez[3] = 0; it.renk[0] = secR.x; it.renk[1] = secR.y; it.renk[2] = secR.z; it.renk[3] = 0.55f; it.hedef[0] = (f32)girdi.peekAlt; it.hedef[1] = (f32)girdi.peekTur; it.hedef[2] = 0; it.hedef[3] = 0; peekHat.itmeSabitYaz(kb, &it, sizeof(it)); const VkBuffer vt = pd->vertexTampon.tampon(); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdBindIndexBuffer(kb, pd->indeksTampon.tampon(), 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(kb, pd->indeksSayi, 1, 0, 0, 0); // Aktif KENAR (tür 2) / NOKTA (tür 3) → lens içinde parlak (gövde hayaletin üstüne). if ((girdi.peekTur == 2 && pd->kenarVertexSayi > 0) || (girdi.peekTur == 3 && pd->noktaSayi > 0)) { PeekKenarItme ki{}; ki.mvp = vp * pd->matris(); ki.merkez[0] = girdi.peekMerkezX; ki.merkez[1] = girdi.peekMerkezY; ki.merkez[2] = girdi.peekYari; ki.renk[0] = secR.x; ki.renk[1] = secR.y; ki.renk[2] = secR.z; ki.hedef[0] = (f32)girdi.peekAlt; BoruHatti& kh = girdi.peekTur == 2 ? peekKenarHat : peekNoktaHat; const GpuTampon& kt = girdi.peekTur == 2 ? pd->kenarTampon : pd->noktaTampon; const u32 say = girdi.peekTur == 2 ? pd->kenarVertexSayi : pd->noktaSayi; kh.bagla(kb); kh.itmeSabitYaz(kb, &ki, sizeof(ki)); const VkBuffer kvt = kt.tampon(); vkCmdBindVertexBuffers(kb, 0, 1, &kvt, &ofs); vkCmdDraw(kb, say, 1, 0, 0); } } } } hedef.bitir(kb); // Fark: geçici gizlenen eski gövdelerin görünürlüğünü GERİ yükle (dış durum korunur). for (Dugum* d : farkGizlenen) d->gorunurBit_ = true; } } // namespace fiko