|
SDLPainter 1.2.0
SDL3 + OpenGL/Vulkan 2D çizim kütüphanesi
|
Her demo bir öncekinin üzerine inşa edilir. Örnekler fazlandırma usulü geliştirildi. Her biri, bir diğerinin üzerine bir ekleme yaparak gider. Bu doküman her örneğin neyi doğruladığını, hangi API yeteneklerini kullandığını ve mühendislik açısından neden önemli olduğunu açıklar.
Amaç: Derleme, SDL3 başlatma ve spdlog entegrasyonunun çalıştığını doğrular. Herhangi bir çizim yapmaz.
| Yetenek | Detay |
|---|---|
| SDL3 başlatma | SDL_Init(SDL_INIT_VIDEO) |
| OpenGL context | 3.3 Core Profile nitelik ayarları |
| spdlog | ANSI renkli çıktı, [SS:DD:SS][Seviye] formatı |
| Event loop | SDL_PollEvent + ESC / SDL_EVENT_QUIT |
| Windows uyumluluğu | ENABLE_VIRTUAL_TERMINAL_PROCESSING ile ANSI escape etkinleştirme |
Phase 0 demosunun çizim yapmaması bilinçlidir: altyapı (CMake, Conan, CI/CD pipeline, IRenderer arayüzü) çalışmadan üst fazlara geçmek anlam taşımaz. Demo bu altyapının entegrasyon testi işlevi görür.
Amaç: OpenGL backend'in tüm temel çizim primitiflerini doğrular. Tek bir pencerede stroke/fill çiftleri, kalın çizgiler, polyline ve konkav poligon bir arada gösterilir.
| Yetenek | Gösterim |
|---|---|
| FillRect / DrawRect | Dolu ve çerçeve dikdörtgenler |
| FillCircle / DrawCircle | Farklı renk ve kalınlıkta |
| FillEllipse / DrawEllipse | rx ≠ ry ile asimetrik elips |
| DrawLine | İki kesişen kalın çizgi (5px) |
| DrawPolyline | 5 noktalı kırık çizgi |
| FillPolygon | L şeklinde konkav poligon — ear clipping |
| DrawPolygon | Aynı poligonun çerçevesi |
| Save / Rotate / Scale / Restore | Döndürülmüş ve ölçeklenmiş dikdörtgen/daire |
| Düzenli çokgen üretimi | Döngüyle hesaplanan pentagon köşeleri |
| MSAA | SDL_GL_MULTISAMPLESAMPLES = 4 |
L şeklindeki konkav poligon, ear clipping algoritmasının doğrudan testidir; triangle fan bu geometriyi yanlış render ederdi. Pentagon köşeleri trigonometrik hesapla üretilir — sabit koordinat listesi değil. Demo her frame'de tamamen yeniden çizilir; stateful render state birikimi olmaz.
Amaç: Transform stack'in tüm operasyonlarını animasyonlu olarak doğrular. Yedi bağımsız bölüm her operasyonu izole eder.
| Bölüm | Test edilen yetenek |
|---|---|
| 1 | Zincirleme Translate — iç içe kaydırma |
| 2 | Rotate animasyonu — dönen dikdörtgen |
| 3 | Scale animasyonu — sin ile nefes efekti |
| 4 | 3 katlı iç içe Save / Restore — bağımsız hız ve yön |
| 5 | SetClipRect / ClearClip — scissor içine kırpılan daire ve dönen dikdörtgen |
| 6 | ResetTransform — kasıtlı yanlış transform sonrası sıfırlama |
| 7 | Bağımsız X/Y Scale animasyonu — elips üzerinde |
Bölüm 6'da Translate(9999, 9999) ile ekran dışına çıkıldıktan sonra ResetTransform() çağrılır ve daire ekranın beklenen konumunda görünür. Bu, matrisin sıfırlanmasının üst stack katmanlarını bozmadığını doğrulayan regresyon testidir. Bölüm 4'teki 3 katlı iç içe stack, push/pop sırasının doğruluğunu gösterir: en içteki Rotate dışarıdakilerden bağımsız hareket eder.
Amaç: Translate → Rotate sırasının doğru uygulandığını izole biçimde doğrular. Pencere yeniden boyutlandırılsa bile dikdörtgen daima ekranın tam ortasında kendi merkezi etrafında döner.
| Yetenek | Detay |
|---|---|
| Dinamik pencere boyutu | SDL_GetWindowSize her frame'de çağrılır |
| Translate + Rotate | (-w/2, -h/2) ofseti ile merkez döndürme |
| Referans arti işareti | İnce çizgilerle ekran merkezi işaretlenir |
Bu örnek, transforms içinde gösterilip geçilebilecek bir senaryonun ayrı bir demo olarak çıkarılmasının neden gerekli olduğunu gösterir: döndürme pivotunun doğru noktada olması, transform matrisinin sıralamasına son derece duyarlıdır. Rotate → Translate yerine Translate → Rotate yazıldığında pivot kayar ve daire merkezinin köşeyi işaret etmesi bu hatayı anında ortaya çıkarır. Demo, regresyon kapsamının neden geniş tutulması gerektiğinin somut örneğidir.
Amaç: Image yükleme, ölçekleme, atlas dilimleme ve alpha blending pipeline'ını doğrular. Harici dosyaya bağımlı olmamak için tüm dokular prosedürel olarak üretilir.
| Fonksiyon | Boyut | Kanal | İçerik |
|---|---|---|---|
| MakeCheckerboard | 128×128 | RGBA | Damalı desen |
| MakeGradient | 256×64 | RGB | Yatay renk geçişi |
| MakeAlphaCircle | 128×128 | RGBA | Merkeze doğru yükselen alfa |
| MakeColorAtlas | 256×256 | RGBA | 2×2 renk atlası |
| Bölüm | Test edilen yetenek |
|---|---|
| 1 | DrawImage(image, x, y) — orijinal piksel boyutu |
| 2 | DrawImage(image, dest_rect) — ölçeklendirilmiş çizim |
| 3 | DrawImage(image, src_rect, dest_rect) — src crop + dst scale |
| 4 | Texture atlas dilimleme — 4 karenin ayrı src_rect ile çizimi |
| 5 | Alpha blending — üst üste bindirilen yarı saydam dokular |
| 6 | Transform + DrawImage — dönen texture animasyonu |
| 7 | Scale animasyonu — texture'ın kendi merkezinden büyüyüp küçülmesi |
Image::CreateFromData API'si, test senaryolarını harici dosyadan bağımsız kılar — CI ortamında asset yokken bile doku pipeline'ı test edilebilir. Atlas dilimleme (Bölüm 4) sprite sheet kullanım senaryosunu simüle eder. Alpha circle dokusu (Bölüm 5) GPU tarafındaki blending sırasını doğrular: arkaplan dikdörtgen önce, saydam doku sonra çizilir.
Amaç: SDL_ttf üzerinden font yükleme, ölçüm, hizalama ve transform entegrasyonunu doğrular.
| Bölüm | Test edilen yetenek |
|---|---|
| 1 | DrawText(rect, text, kCenter) — 56pt başlık, ortada |
| 2 | DrawText(rect, text, kRight) — sağa hizalı alt başlık |
| 3 | Üç hizalama yan yana — kLeft / kCenter / kRight karşılaştırması |
| 4 | 4 farklı punto boyutu — 14 / 22 / 36pt |
| 5 | Pen rengi ile metin rengi — 5 farklı renk |
| 6 | SetOpacity — 4 farklı saydamlık seviyesi (100% → 15%) |
| 7 | Transform + DrawText — MeasureText ile pivot hesabı, dönen metin |
| 8 | DrawText(x, y, text) — konum bazlı çizim |
FindSystemFont fonksiyonu olası yolları sırayla dener; hiçbiri bulunamazsa demo hata vererek çıkar. Bu, harici asset bağımlılığını açık biçimde yönetmenin örneğidir.
text, SDL_ttf'in static veya shared linklenmiş olmasına göre iki farklı target_link_libraries dalından birini seçer. Opsiyonel bağımlılıkların build sistemi seviyesinde ele alınması burada görülür.
Bölüm 7'deki dönen metin, MeasureText sonucunun yarısı kadar negatif ofset uygulanarak pivot'u metin merkezine taşır. Bu, transform stack'in sadece geometriyle değil, text rendering pipeline'ıyla da doğru çalıştığını doğrular.
Bu beş demo, Vulkan backend'in OpenGL ile aynı davranışı sergilediğini kademeli olarak doğrular. Hepsi SDLPAINTER_WITH_VULKAN=ON ile derlenir.
| Demo | Doğrulanan yetenek |
|---|---|
| vulkan_clear | Vulkan context başlatma, swapchain, Clear |
| vulkan_triangles | Untextured primitifler (tüm şekil tipleri) |
| vulkan_textured | DrawImage — texture upload, sampling |
| vulkan_demo | Swapchain recreate (pencere yeniden boyutlandırma), tüm primitifler, opacity, validation layer çıktısı |
| vulkan_text | SDL_ttf + Vulkan backend entegrasyonu |
vulkan_demo özellikle swapchain recreate senaryosunu test eder: pencere boyutu değiştiğinde Vulkan swapchain yeniden oluşturulmalı, bu süreçte çizim bozulmamalıdır. IRenderer arayüzünün bu senaryoyu soyutlaması, Painter katmanında hiçbir ek kod gerektirmez.
'R' tuşu projeksiyon güncellemesini manuel olarak tetikler — bu, headless ortamda SDL_WINDOWEVENT_RESIZED olmadan aynı kod yolunun test edilmesini sağlar.
Amaç: transforms ile aynı görsel içeriği (dönen dikdörtgen + nabız gibi ölçeklenen daire) SDL boilerplate'i olmadan üretir.
| Yetenek | Detay |
|---|---|
| Application türetme | OnInit / OnUpdate / OnRender / OnKeyDown override |
| Otomatik yaşam döngüsü | SDL init, pencere, GL context, olay döngüsü ve yıkım çatıda |
| TimingMode::kVariable | Her frame bir OnUpdate, dt gerçek geçen süre |
Karşılaştırma noktası transforms'dur: aynı çıktı, ~250 satır daha az kod. Çatının değeri burada ölçülebilir hale gelir. Application yıkım sırası (Painter → pencere → SDL_Quit) türeyen sınıfın üyelerini de kapsayacak şekilde tasarlandığı için Image/Font üyeleri düz üye olarak tutulabilir (bkz. ADR-008).
Amaç: TimingMode::kFixed ile deterministik simülasyonu ve render interpolasyonunu doğrular. Bir top yerçekimiyle düşüp zemine çarparak seker.
| Yetenek | Detay |
|---|---|
| TimingMode::kFixed | Fizik sabit adımla; OnUpdate frame başına 0..N kez |
| OnRender(Painter&, alpha) | Önceki/geçerli konum arasında interpolasyon |
| fixed_update_hz = 30 | Kasıtlı düşük sim hızı — interpolasyonun etkisi görünür |
Sim hızı 30 Hz'e düşürülmesine rağmen hareket akıcı görünür; çünkü çizim alpha ile son iki durum arasında interpolasyon yapar (Game Programming Patterns "play catch up"). alpha yok sayılırsa aynı demo takılmalı görünür — interpolasyonun neden gerekli olduğunu doğrudan gösteren bir karşıtlık.
Amaç: Çatının girdi tarafını ve gerçek bir uygulamanın akışını gösterir. app_basics çatının temelini, game_loop zamanlamayı gösterirken bu demo diğer örneklerin hiçbirinde bulunmayan yetenekleri kapsar.
| Yetenek | Detay |
|---|---|
| OnMouseButtonDown | Tıklama koordinatı → tahta hücresi (hit testing) |
| OnMouseMove | İmlecin üzerindeki boş hücreyi yarı saydam vurgulama |
| OnResize | Tahta geometrisi pencere boyutundan yeniden hesaplanır |
| Durum makinesi | oynanıyor → kazanan / berabere → yeniden başlat |
| DrawText(Rect, ..., kCenter) | Alt durum çubuğunda ortalanmış metin |
| SetTitle | Pencere başlığı sırayı/sonucu yansıtır |
| Kalın çizgi + daire | Izgara ve X çapraz çizgilerle, O daireyle çizilir |
Kontroller: Sol tık hamle · R yeniden başlat · ESC çıkış. Oyun bittiğinde herhangi bir tık yeni oyun başlatır.
Oyun mantığı (examples/games/tictactoe_logic.h) çizimden ve SDL'den tamamen bağımsız saf fonksiyonlardır: kazanan tespiti, hamle geçerliliği ve hit testing. Bu ayrım sayesinde mantık headless olarak birim testlerle doğrulanabilir (tests/test_tictactoe_logic.cpp) — demo dosyası yalnızca girdi ve çizimden sorumlu kalır. Örnek uygulamalardan test edilen ilk mantık budur.
Sıra tabanlı bir oyun olduğu için TimingMode::kVariable kullanılır; sabit adıma ihtiyaç yoktur. game_loop ile yan yana okunduğunda iki zamanlama modunun ne zaman seçileceği somutlaşır: sürekli simülasyon → kFixed, olay güdümlü etkileşim → kVariable.
Duyarlı yerleşim OnResize içinde tek bir hesapla çözülür; tahta her zaman pencereye ortalanır ve kısa kenara göre ölçeklenir. Hit testing aynı geometriyi kullandığı için pencere boyutundan bağımsız olarak doğru çalışır.
Aşağıdaki desen hello_window–vulkan_text demoları içindir; app_basics, game_loop ve tictactoe bu boilerplate'i Application çatısına devreder:
Painter scope'u ayrı tutmanın nedeni: OpenGL nesneleri (glDeleteBuffers, glDeleteTextures vb.) context geçerliyken silinmelidir. SDL_DestroyWindow context'i yok eder; Painter bu noktadan önce yok edilmezse glDelete* çağrıları GL_INVALID_OPERATION (1282) hatası üretir. Açık scope bu sıralamayı garanti eder.