#include "fiko/dokum/katilasma_gpu.hpp" #include #include #include #include #include "fiko/core/gunluk.hpp" #include "fiko/render/compute_baglam.hpp" #include "fiko/render/vulkan_baglam.hpp" // ImGui/pencere bağlantısı YOK — saf motor (sdf_gpu_motor kalıbı). Shader SPIR-V // yolu çalışma dizinine göre çözülür (app CMakeLists foreach'ine eklenir). namespace fiko::dokum { namespace { constexpr const char* kFdmSpv = "shader/katilasma_fdm.comp.spv"; // Bir gönderim (calis) içinde kaç FDM adımı zincirlenir. Submit/fence gecikmesi // adım başına değil batch başına ödenir → GPU boru hattı doygun kalır. Durma // koşulu (tüm hücreler katılaştı mı) batch sonunda readback ile kontrol edilir. constexpr u32 kBatchAdim = 32; // katilasma_fdm.comp push constant ile BIREBIR (std430 skaler sıralama). struct FdmItme { u32 nx, ny, nz; f32 h2; f32 invH; f32 k; f32 rho, cp; f32 gizliIsi; f32 tLik, tSol; f32 kPart; u32 fsModel; f32 U; f32 tKalip; f32 dt; f32 t; f32 hMm2; f32 niyamaFs; u32 niyamaTara; }; u32 fsModelKod(FsModel m) { return m == FsModel::Scheil ? 2u : (m == FsModel::Lever ? 1u : 0u); } } // namespace bool katilasmaGpuVarMi(VulkanBaglam& vb) { if (vb.aygitKayipMi()) return false; ComputeBoruHatti test(vb, kFdmSpv, 10, static_cast(sizeof(FdmItme))); return test.gecerli(); } // --------------------------------------------------------------------------- // katilasmaGpuCoz — CPU katilasmaCoz'un GPU ikizi (aynı çıktı). basari=false → // çağıran CPU'ya düşer. GPU hazır değilse hemen false + boş sonuç. // --------------------------------------------------------------------------- KatilasmaSonuc katilasmaGpuCoz(VulkanBaglam& vb, VoxelKafes& kafes, const Alasim& alasim, const Kalip& kalip, const FdmAyar& ayar, bool& basari, f64 niyamaEsik, IlerlemeCB ilerleme, const std::vector* baslangicT, const std::vector* hucreH) { basari = false; KatilasmaSonuc r; if (!kafes.gecerliMi()) return r; if (vb.aygitKayipMi()) return r; const auto gpuT0 = std::chrono::steady_clock::now(); const u32 nx = kafes.nx(), ny = kafes.ny(), nz = kafes.nz(); const u32 N = kafes.hucreSayi(); const f64 h = kafes.h(); const f64 h2 = h * h; const f64 rho = alasim.yogunluk, c = alasim.c, k = alasim.k; const f64 alpha = alasim.yayinim(); auto metalMi = [&](u32 idx) { return kafes.tip[idx] == HucreTip::Ic || kafes.tip[idx] == HucreTip::Sinir; }; // ---- Başlangıç koşulu (CPU katilasmaCoz ile birebir) ---- const bool kopru = baslangicT && baslangicT->size() == N; u32 metalSayi = 0; for (u32 i = 0; i < N; ++i) { if (metalMi(i)) { const f64 T0 = kopru ? std::max((*baslangicT)[i], alasim.tLikidus) : ayar.dokumSicaklik; kafes.T[i] = T0; kafes.H[i] = entalpi(alasim, T0); kafes.fs[i] = alasim.katiOran(T0); ++metalSayi; } else { kafes.T[i] = kalip.tBaslangic; kafes.H[i] = 0.0; kafes.fs[i] = 0.0; } } if (metalSayi == 0) return r; // ---- Kararlı zaman adımı (CPU ile birebir) ---- const f64 rc = rho * c; const f64* hH = (hucreH && hucreH->size() == N) ? hucreH->data() : nullptr; f64 Umaks = kalip.arayuzH; if (hH) for (u32 i = 0; i < N; ++i) Umaks = std::max(Umaks, hH[i]); const f64 difOran = 6.0 * alpha / h2; const f64 sinirOran = 6.0 * Umaks / (rc * h); f64 dt = ayar.cflGuvenlik / std::max(difOran + sinirOran, 1e-12); dt = std::min(dt, ayar.maksZaman); if (!(dt > 0) || !std::isfinite(dt)) return r; // ---- Compute pipeline + SSBO'lar ---- ComputeBaglam comp(vb); // İki boru hattı: A (T/H → yeniT/yeniH) ve B (rolleri ters) — tek komut // tamponunda ping-pong (batch içi çift adım zincirleme, re-bind yok). ComputeBoruHatti boruA(vb, kFdmSpv, 10, static_cast(sizeof(FdmItme))); ComputeBoruHatti boruB(vb, kFdmSpv, 10, static_cast(sizeof(FdmItme))); if (!boruA.gecerli() || !boruB.gecerli()) { gunluk::uyari("Katılaşma GPU: compute pipeline yok (SPIR-V?) — CPU'ya düşülüyor"); return r; } const u64 fBayt = static_cast(N) * sizeof(f32); const u64 uBayt = static_cast(N) * sizeof(u32); // Çift T/H tamponu (ping-pong). fs/tip/maske/niyama tek. SsboTampon T0s(vb, fBayt), T1s(vb, fBayt); SsboTampon H0s(vb, fBayt), H1s(vb, fBayt); SsboTampon fss(vb, fBayt); SsboTampon tips(vb, uBayt); SsboTampon maskes(vb, fBayt); SsboTampon niyGs(vb, fBayt), niyRs(vb, fBayt), niyAs(vb, uBayt); if (!T0s.gecerli() || !T1s.gecerli() || !H0s.gecerli() || !H1s.gecerli() || !fss.gecerli() || !tips.gecerli() || !maskes.gecerli() || !niyGs.gecerli() || !niyRs.gecerli() || !niyAs.gecerli()) { gunluk::uyari("Katılaşma GPU: SSBO ayrılamadı — CPU'ya düşülüyor"); return r; } // f64 kafes alanları → f32 SSBO (host yükleme). std::vector Tf(N), Hf(N), fsf(N), maskef(N, 0.0f); std::vector tipf(N), niyAf(N, 0u); for (u32 i = 0; i < N; ++i) { Tf[i] = (f32)kafes.T[i]; Hf[i] = (f32)kafes.H[i]; fsf[i] = (f32)kafes.fs[i]; tipf[i] = kafes.tip[i] == HucreTip::Ic ? 1u : (kafes.tip[i] == HucreTip::Sinir ? 2u : 0u); if (hH && hH[i] > 0.0) maskef[i] = (f32)hH[i]; } T0s.yaz(Tf.data(), fBayt); T1s.yaz(Tf.data(), fBayt); // kalıp hücreleri her iki tamponda sabit kalsın H0s.yaz(Hf.data(), fBayt); H1s.yaz(Hf.data(), fBayt); fss.yaz(fsf.data(), fBayt); tips.yaz(tipf.data(), uBayt); maskes.yaz(maskef.data(), fBayt); std::vector sifir(N, 0.0f); niyGs.yaz(sifir.data(), fBayt); niyRs.yaz(sifir.data(), fBayt); niyAs.yaz(niyAf.data(), uBayt); // Binding sırası (shader): 0=T,1=H,2=fs,3=tip,4=maske,5=yeniH,6=niyG,7=niyR, // 8=niyAlindi,9=yeniT. boruA okur T0/H0 yazar H1/T1; boruB tersi. const std::vector bagA = {T0s.tampon(), H0s.tampon(), fss.tampon(), tips.tampon(), maskes.tampon(), H1s.tampon(), niyGs.tampon(), niyRs.tampon(), niyAs.tampon(), T1s.tampon()}; const std::vector bagB = {T1s.tampon(), H1s.tampon(), fss.tampon(), tips.tampon(), maskes.tampon(), H0s.tampon(), niyGs.tampon(), niyRs.tampon(), niyAs.tampon(), T0s.tampon()}; boruA.bagla(bagA); boruB.bagla(bagB); const f32 hMm = (f32)(h * 1000.0); const f64 hedefFs = std::min(ayar.durmaKatiOran, 0.99); const f64 niyamaFs = 0.7; const u32 gx = (nx + 3) / 4, gy = (ny + 3) / 4, gz = (nz + 3) / 4; FdmItme it{}; it.nx = nx; it.ny = ny; it.nz = nz; it.h2 = (f32)h2; it.invH = (f32)(1.0 / h); it.k = (f32)k; it.rho = (f32)rho; it.cp = (f32)c; it.gizliIsi = (f32)alasim.gizliIsi; it.tLik = (f32)alasim.tLikidus; it.tSol = (f32)alasim.tSolidus; it.kPart = (f32)alasim.kPartisyon; it.fsModel = fsModelKod(alasim.fsModel); it.tKalip = (f32)kalip.tBaslangic; it.dt = (f32)dt; it.hMm2 = 2.0f * hMm; it.niyamaFs = (f32)niyamaFs; // ---- Ana döngü (CPU ile birebir: dt/adım/durma host tarafında) ---- f64 t = 0.0; u32 adim = 0; bool niyamaKalanVar = true; // en az bir hücre örneklenmedi std::vector niyAoku(N); std::vector Toku(N), fsoku(N); bool cift = false; // hangi tampon güncel (false: T0/H0 girdi) while (adim < ayar.maksAdim && t < ayar.maksZaman) { // Bir batch: kBatchAdim FDM adımı tek komut tamponunda zincirlenir. const u32 buBatch = std::min(kBatchAdim, ayar.maksAdim - adim); const bool ok = comp.calis([&](VkCommandBuffer kb) { for (u32 s = 0; s < buBatch; ++s) { FdmItme adimIt = it; const f64 ts = t + (f64)s * dt; adimIt.U = (f32)(ayar.yariSonsuzKalip ? kalip.etkinH(ts) : kalip.arayuzH); adimIt.t = (f32)ts; adimIt.niyamaTara = niyamaKalanVar ? 1u : 0u; // cift XOR s: batch içi her adımda tampon rolü değişir. const bool girdiA = ((s & 1u) == 0u) ? !cift : cift; (girdiA ? boruA : boruB).dispatchKaydet(kb, &adimIt, sizeof(adimIt), gx, gy, gz); ComputeBoruHatti::ssboBariyeri(kb); } }); if (!ok) { gunluk::uyari("Katılaşma GPU: dispatch device-lost/timeout — CPU'ya düşülüyor"); return r; // basari=false → çağıran CPU katilasmaCoz'a düşer } t += (f64)buBatch * dt; adim += buBatch; // Batch sonunda güncel tampon: buBatch tek/çift. cift = (buBatch & 1u) ? !cift : cift; // Durma koşulu + Niyama-kalan kontrolü: güncel T + fs + niyAlindi readback. SsboTampon& Tguncel = cift ? T1s : T0s; Tguncel.oku(Toku.data(), fBayt); fss.oku(fsoku.data(), fBayt); // Katılaşan (fs≥hedefFs) + katılaşma anı: r.katilasmaSuresi ilk-ulaşma. if (r.katilasmaSuresi.empty()) r.katilasmaSuresi.assign(N, -1.0); u32 katilasan = 0; for (u32 i = 0; i < N; ++i) { if (!metalMi(i)) continue; if (fsoku[i] >= hedefFs) { ++katilasan; if (r.katilasmaSuresi[i] < 0) r.katilasmaSuresi[i] = t; // batch çözünürlüğü } } if (niyamaKalanVar) { niyAs.oku(niyAoku.data(), uBayt); u32 kalan = 0; for (u32 i = 0; i < N; ++i) if (metalMi(i) && niyAoku[i] == 0u) { ++kalan; break; } niyamaKalanVar = (kalan > 0); } if (ilerleme) { if (!ilerleme(std::clamp((f64)katilasan / (f64)metalSayi, 0.0, 1.0), "Katılaşma çözülüyor (GPU)")) break; } if (katilasan >= metalSayi) break; } // ---- Son alanları oku + sonuç doldur (CPU post-process ile birebir) ---- SsboTampon& Tson = cift ? T1s : T0s; Tson.oku(Toku.data(), fBayt); std::vector niyGoku(N), niyRoku(N); niyGs.oku(niyGoku.data(), fBayt); niyRs.oku(niyRoku.data(), fBayt); niyAs.oku(niyAoku.data(), uBayt); // Kafes alanlarını güncelle (görselleştirme sonucu okur). for (u32 i = 0; i < N; ++i) kafes.T[i] = Toku[i]; if (r.katilasmaSuresi.empty()) r.katilasmaSuresi.assign(N, -1.0); r.sonSicaklik.assign(N, 0.0); r.niyama.assign(N, 0.0); r.sdas.assign(N, 0.0); r.niyamaMin = 1e30; r.niyamaMax = 0.0; r.toplamSure = t; r.hotSpotSure = -1.0; bool niyamaVar = false; for (u32 idx = 0; idx < N; ++idx) { r.sonSicaklik[idx] = kafes.T[idx]; if (!metalMi(idx)) continue; const f64 ts = r.katilasmaSuresi[idx]; if (ts > r.hotSpotSure) { r.hotSpotSure = ts; r.hotSpotIndeks = idx; } if (ts > r.toplamSure) r.toplamSure = ts; if (niyAoku[idx]) { const f64 Gv = niyGoku[idx]; const f64 Rv = std::max((f64)niyRoku[idx], 1e-9); const f64 Ny = Gv / std::sqrt(Rv); r.niyama[idx] = Ny; r.niyamaMin = std::min(r.niyamaMin, Ny); r.niyamaMax = std::max(r.niyamaMax, Ny); if (Ny < niyamaEsik) ++r.niyamaKritik; niyamaVar = true; const f64 lambda = alasim.sdasK * std::pow(Rv, -1.0 / 3.0); r.sdas[idx] = lambda * 1e6; } } if (!niyamaVar) { r.niyamaMin = 0; r.niyamaMax = 0; } r.hazir = true; basari = true; const f64 gpuMs = std::chrono::duration( std::chrono::steady_clock::now() - gpuT0).count(); gunluk::bilgi("Katilasma GPU dispatch OK: {} hucre ({} metal) x {} adim = {} ms", N, metalSayi, adim, (long long)gpuMs); return r; } } // namespace fiko::dokum