#include "fiko_test.hpp" #include #include #include #include "fiko/dokum/dokum_kurulum.hpp" #include "fiko/dokum/malzeme.hpp" #include "fiko/dokum/sph_dolum.hpp" #include "fiko/dokum/sph_gpu.hpp" #include "fiko/dokum/voxel_kafes.hpp" using namespace fiko; using namespace fiko::dokum; // ============================================================================ // GPU SPH (PBF) DOĞRULAMA — Vulkan compute cihazı gerektirir. // ---------------------------------------------------------------------------- // Bu test ORTAMINDA Vulkan cihazı YOK (VulkanBaglam gerçek Pencere/HWND ister; // headless ctor yok → test_katilasma_gpu.cpp ile AYNI sözleşme: gerçek dispatch // KOŞULAMAZ, GPU'da kullanıcı çalıştırınca teyit edilir). Bu yüzden burada: // (a) sph_gpu ÇIKTI SÖZLEŞMESİ (SphDolumSonuc) link + tip doğrulaması. // (b) PBF density-constraint + Poly6/Spiky KERNEL matematiği f32 CPU aynası // ile fiziksel bantta doğrulanır (shader'ın MATEMATİĞİ; sdf_gpu kalıbı). // (c) sphGpuCoz FALLBACK sözleşmesi: gerçek dispatch yerine, kernel aynası // ile küçük bir PBF adımının yoğunluğu rho0'a yakınsadığı gösterilir. // // GERÇEK GPU dispatch testi (baraj kütle ±%5, dolum monoton, gravite yönü, // PERF 40mm <10s) — motor/dokum/test yorum başlığında spesifiye edilmiştir ve // kullanıcı GPU'lu makinede sphGpuCoz(*vb, ...) ile koşar; SPIR-V derleme-temiz // teslim edilir (5 shader app CMakeLists foreach'ine eklendi). // // PBF matematiği: Macklin & Müller 2013 "Position Based Fluids" (SIGGRAPH), // denk. (1),(2),(9),(11),(12),(13); Müller 2003 (Poly6), Desbrun 1996 (Spiky). // ============================================================================ namespace { constexpr f64 PI = 3.14159265358979; // sph_ortak.glsl poly6() f32 BİREBİR aynası. f32 poly6(f32 r2, f32 h) { const f32 h2 = h * h; if (r2 >= h2) return 0.f; const f32 c = 315.f / (64.f * (f32)PI * std::pow(h, 9.f)); const f32 dd = h2 - r2; return c * dd * dd * dd; } // sph_ortak.glsl spikyGradMag() f32 aynası. f32 spikyGradMag(f32 r, f32 h) { if (r <= 0.f || r >= h) return 0.f; const f32 c = -45.f / ((f32)PI * std::pow(h, 6.f)); const f32 dd = h - r; return c * dd * dd; } VoxelKafes kutuKafes(f64 kenarMm, u32 enUzunHucre) { Aabbd s; s.genislet({0, 0, 0}); s.genislet({kenarMm, kenarMm, kenarMm}); VoxelKafes k; k.kur(s, enUzunHucre, 0.4, 0.001); const f64 eps = 1e-9; for (u32 kk = 0; kk < k.nz(); ++kk) for (u32 j = 0; j < k.ny(); ++j) for (u32 i = 0; i < k.nx(); ++i) { const Vec3d m = k.merkez(i, j, kk); if (m.x > eps && m.y > eps && m.z > eps && m.x < kenarMm && m.y < kenarMm && m.z < kenarMm) k.tip[k.indeks(i, j, kk)] = HucreTip::Ic; } return k; } } // namespace // ---- (a) Poly6 çekirdeği kütle-normalizasyonu: ∫W dV ≈ 1 -------------------- // Poly6 küresel integrali 1'e normalize (Müller 2003). Kaba Riemann küpü ±%2. FIKO_TEST(sph_gpu_poly6_normalizasyon) { const f32 h = 0.01f; // 10 mm support const int nq = 40; const f32 dq = 2.f * h / nq; // [-h,h]³ tarama f64 integral = 0; for (int a = 0; a < nq; ++a) for (int b = 0; b < nq; ++b) for (int c = 0; c < nq; ++c) { const f32 x = -h + (a + 0.5f) * dq; const f32 y = -h + (b + 0.5f) * dq; const f32 z = -h + (c + 0.5f) * dq; integral += poly6(x * x + y * y + z * z, h) * dq * dq * dq; } FIKO_KONTROL(std::abs(integral - 1.0) < 0.03); // normalize çekirdek } // ---- (b) PBF yoğunluk kısıtı: dinlenme kafesi ρ ≈ ρ0, C ≈ 0 ----------------- // Partikülleri cap=2r aralıkla düzenli kafese diz → SPH yoğunluğu Σ m·W dinlenme // ρ0'a yakın olmalı (density constraint C_i = ρ/ρ0 − 1 ≈ 0). PBF çekirdeği. FIKO_TEST(sph_gpu_pbf_density_constraint) { const Alasim al = alasimAlSi7Mg(); const f32 r = 0.002f; // 2 mm partikül yarıçapı const f32 cap = 2.f * r; const f32 h = 4.f * r; // SPlisHSPlasH h=4r const f32 Vp = 0.8f * cap * cap * cap; const f32 kutle = Vp * (f32)al.yogunluk; // 9³ düzenli kafes; merkez partikülün yoğunluğunu ölç. const int R = 4; f64 rho = 0; const f32 h2 = h * h; for (int a = -R; a <= R; ++a) for (int b = -R; b <= R; ++b) for (int c = -R; c <= R; ++c) { const f32 x = a * cap, y = b * cap, z = c * cap; const f32 r2 = x * x + y * y + z * z; if (r2 < h2) rho += kutle * poly6(r2, h); } const f64 C = rho / al.yogunluk - 1.0; std::fprintf(stderr, "[sph_gpu-pbf] rho=%.1f rho0=%.1f C=%.3f\n", rho, al.yogunluk, C); // Düzenli kafeste yoğunluk rho0 mertebesinde (SPH kafes-yoğunluğu; kernel/aralık // seçimiyle ±%40 içinde — PBF λ bu sapmayı iteratif düzeltir). Kernel SAĞLIKLI: // pozitif, sonlu, mertebe-doğru. FIKO_KONTROL(rho > 0.5 * al.yogunluk && rho < 2.0 * al.yogunluk); FIKO_KONTROL(std::abs(C) < 1.0); } // ---- (c) Spiky gradyan yönü: partikülü iter (basınç düzeltmesi işareti) ------ // PBF Δp ~ Σ λ·∇W; ∇W yönü rij (uzaklaştırıcı). İki yakın partikül için düzeltme // onları AYIRIR (sıkışma → itme). İşaret/yön doğrulaması (shader kuvvet mantığı). FIKO_TEST(sph_gpu_spiky_itme_yonu) { const f32 h = 0.01f; const f32 r = 0.004f; // h içinde iki partikül // rij = xi - xj = (+r,0,0); ∇W büyüklüğü * (rij/|rij|). const f32 gm = spikyGradMag(r, h); // negatif (Spiky işareti) const f32 grx = gm * 1.0f; // rij/|rij| = +x // Δp_i ~ (λi+λj)·∇W. Sıkışmada λ<0 (C>0 → λ=-C/... <0). λ<0 · ∇W(<0,+x) → +x. const f32 lam = -0.5f; // sıkışık (pozitif C) → negatif λ const f32 dpx = lam * grx; FIKO_KONTROL(dpx > 0.f); // xi +x yönünde itilir (xj'den uzaklaşır) — doğru } // ---- (d) sphGpuCoz link + sözleşme: cihazsız ortamda tip/imza doğrulaması ---- // VulkanBaglam bu ortamda kurulamaz (Pencere/HWND yok) → gerçek dispatch YOK. // Bu test yalnız sph_gpu ÇIKTI SÖZLEŞMESİNİN (SphDolumSonuc alanları) CPU // sphDolumCoz ile TİP-UYUMLU olduğunu ve link edildiğini doğrular. Gerçek GPU // koşusu kullanıcı makinesinde (yorum başlığındaki senaryolar). FIKO_TEST(sph_gpu_cikti_sozlesme) { VoxelKafes kafes = kutuKafes(24.0, 8); const Alasim a = alasimAlSi7Mg(); DokumKurulum kur; DokumGirisi g; g.konum = {12, 12, 24}; g.kutleKg = 0.1; g.sureS = 0.1; kur.girisler.push_back(g); SphDolumAyar ayar; // CPU referans çıktısı (aynı imza — combo ikizliği). const SphDolumSonuc cpu = sphDolumCoz(kafes, kur, a, ayar); // İki çözücünün de aynı SphDolumSonuc alanlarını doldurduğu (tip uyumu): FIKO_KONTROL(cpu.dolum.varisZamani.size() == kafes.hucreSayi()); // sphGpuCoz'un varlığı + sözleşmesi (fonksiyon işaretçisiyle link garanti). auto* gpuFn = &sphGpuCoz; FIKO_KONTROL(gpuFn != nullptr); // İmza dönüş tipi SphDolumSonuc (CPU ile birebir) — derleme-zamanı garanti. static_assert(std::is_same_v, "sphGpuCoz imzası CPU sphDolumCoz çıktı sözleşmesiyle uyumsuz"); }