sdl-painter – Proje Altyapısı (faz-0)

C++ kullanarak 2B uygulama geliştirme için biricik yardımcınız olacak olan sdl-painter maceramıza devam ediyoruz. Okumayanlar için ilk yazımın bağlantısını aşağıya koyuyorum. Bir göz atmanızda fayda var.

[Duyuru] sdl-painter

Özetle, sdl-painter, SDL3 + OpenGL 3.3 Core ve Vulkan 1.1 ile modern C++ kullanılarak , çoklu platform desteği sunan, sağlam ve güncel bir yazılım altyapısı ve araçları ile geliştirilen 2B çizim kütüphanesidir.

Yazılarımı ve sdlpainter içerisindeki örneklerimi fazlara böldüm. Yazı ve kod içerisinde bunlara çok rast gelebilirsiniz. Neden “faz”lara böldüğümü de söyleyeyim: açıkçası, kütüphaneyi tek hamlede anlatmak yerine, her yazıda çalışan, elle tutulur bir parça ekleyerek ilerlemeyi tercih ettim. Her faz bir öncekinin üstüne biniyor olacak

Geri dönecek olursak, bir önceki yazımda, çizimlere yönelik çok detaya girmemiştik. Bu yazımda da pek giremeyeceğiz ne yazık ki 🙂 Neden peki? Öncelikle temelimizi biraz daha güçlendirmemizde fayda var, bunun için de bu yazımı da bu konuya ayıracağım. Bununla birlikte bir sonraki yazımda, artık can cana giriş yapıyoruz.

Bu arada şunu da söylememe izin verin lütfen, sdl-painter’ı başka işlerimde de aktif olarak kullananmaya devam ediyorum. Bu yazıların gecikmesinin sebebi de biraz o işler oluyor ama kullanım sırasında da bir çok iyileştirme veye eklenebilecek yeni kabilyetlerin de farkın avarıyorum. Onun için aslında kütüphanenin palazlanması ve biraz hırpalanmasında fayda var. Bu kullanımlar o açıdan iyi oluyor.

Peki bu yazımın kapsmaı nedir? Hemen sıralayayım, bu yazıda mimariyi, kullanılan temel tipleri, yazılım oluşturma (build) sistemini, OpenGL’I ve tabi ki CI/CD alt yapısını ele alacağız. Bu arada, burada bahsi geçen altyapısal bir çok mekanizmayı kendi projelerinizde de kullanabilirsiniz.

O zaman ilk başlık ile başlayalım.

Proje Mimarisi

Bir önceki yazımda da bahsettiğim gibi, sdlpainter, katmanlı bir yapıya sahip ve her katmanın ayrı sorumlulukları bulunmakta:

 

Katmanları teker teker inceleyelim:

  • Painter (Public API): Geliştiricinin gördüğü katman. DrawCircle, FillRect, SetPen gibi çağrılar bu katman üzerinden yapılıyor. Pen, Brush, transform stack burada bulunuyor. Çoğu zaman kütüphaneye yönelik kullanacağınız kısım burası olacak. Burayı olabildiğince sade tutmak amacım,
  • Shape Tessellator: Şekilleri (daire, dikdörtgen, poligon) backend’in (burada opengl ve vulkan oluyor) anlayacağı vertex listelerine dönüştürüyor. Bu katman ile gelen üst seviye çizim isteklerini alıp, bunları bir seviye ayrıştırıp şekil koordinatlarına çeviriyoruz. Bunu yaparken de kullanılacak görselleştirme backendinden bunu bağımsız yapıyoruz. Bir diğer ifade ile şu an için OpenGL ve Vulkan ama ileride DirectX olsa da bunu orada kullanabilecektik. Bu açıdan önemli bir katman,
  • IRenderer: Şimdi geldik asıl aksiyonun döndüğü katmana, backend soyutlama katmanına. Bu katman ile geliştiriciden aldığımız üst seviye çizim isteklerini DrawTriangles,  CreateTexture,  SetProjectionMatrix gibi daha alt seviye çağrılara dönüştürüyoruz. Tabi bu seviyede artık, bu çağrılar OpenGL ve Vulkan’a göre farklılık gösteriyor. Bir diğer ifadeyle, IRenderer arayüzünü bu backend’ler için implement ediyor. Bu katmanlama sayesinde hem OpenGL hem de Vulkan backend’ini destekliyoruz.
  • SDL3 Platform Katmanı: Bu seviye de artık SDL ile pencere yönetimi, OpenGL context oluşturma, girdilerin kotarılması ve benzeri işleri yapıyoruz.

IRenderer: Backend Soyutlama Arayüzü

sdl-painter’ın merkezinde IRenderer arayüz sınıfı bulunuyor.

RendererBackend enum’u ile hangi backend kullanılacağını ifade ediyoruz:

CreateRenderer factory fonksiyonu ile ilgili backend implementasyonu nu heap’te oluşturup unique_ptr olarak döndürüyor. Geliştirici IRenderer* üzerinden gerekli işlevleri gerçekleştiriyor; OpenGLRenderer ya da VulkanRenderer’ı doğrudan görmüyor.

TextureHandle ise yine benzer şekilde backend-agnostic bir tanımlayıcı (uint32_t alias’ı), OpenGL’de texture ID, Vulkan’da descriptor index olarak kullanılıyor olacak:

Bu arayüzdeki çizim API’lerine inşallah sonraki yazılarımda gireceğiz, şimdilik burada bırakıyoruz ve sonraki başlığa geçiyoruz.


Temel Tipler

Bu başlık altında sdl-painter kullanarak geliştireceğiniz uygulamalarda, sıklıkla ihtiyaç duyacağınız veya karşılaşacağınız tiplere değineceğiz, bunların çoğu zaten kendini açıklar nitelikte, bu sebeple detaylara çok girmeyeceğim. İlki ile başlayalım

Color

RedF() fonksiyonları, shader uniform’larına float göndermek için kullanıyoruz, her kanal [0, 255] aralığında uint8_t normalde.

Pen ve Brush

sdl-painter API’lerinde, Pen çizgi stilini, Brush dolgu stilini temsil eder.

NoPen() ve NoBrush() factory metodları, “çizme ama doldur” ya da “doldurma ama çiz” senaryoları için kullanıyor olacağız, temel olarak içi boş dikdörtgen ve çerçevesiz ama içi dolu dikdörtgen.

Vertex ve TexturedVertex

IRenderer’ın aldığı ham vertex tipleri:

Renk bilgisi de vertex’in içinde taşınıyor — bu sayede Tessellator her vertex’e Pen/Brush rengini doğrudan yazabiliyor ve ayrı bir uniform güncellemesi gerekmiyor.

Point, Rect, Size

Geometri tipleri de temelde aşağıdaki gibi tanımlanmakta:


Build Sistemi: CMake + Conan 2

Conan 2 — Bağımlılık Yönetimi

conanfile.py tüm bağımlılıkları tanımlar ve ilgili paketleri otomatik bir şekilde conan-center’dan çekilmesine imkan sağlar. Daha önce, template ve uengine projelerinde en çok vakit alan kısımlar aslında bu bağımlılıkları kurmak oluyordu. Burada o yükü conan yükleniyor.

CMakeLists.txt — Ana Yapı

CMakelist’in genel yapısı daha önce sizler ile paylaştığım, CMake şablonlarına çok benziyor:

PUBLIC / PRIVATE ayrımına dikkat etmenizde fayda var dostlar. SDL3  ve OpenGL  public, çünkü geliştirici kodu da bu header’lara dolaylı olarak bağımlı. glad ve stb ise gerçekleme detayı olduğu için, kullanıcı uygulamalarına geçirilmesine gerek yok.

OpenGL ve Vulkan uygulamalarında özellikle ihtiyaç duyacağınız shader dosyaları için de satırlar mevcut:

CMake Presetleri

Projedeki tüm yapılandırmaları CMakePresets.json içinde tanımladık. Bunun güzel yanı: lokalde çalıştırılan komutlar CI’da çalışan komutlar ile birebir aynı. Bu sayede iki ortam arasında oluşabilecek farklılıkların önüne geçmiş oluyoruz.

Preset Açıklama
linux-debug / linux-release Ninja + GCC, CI’daki ana build’ler
linux-debug-asan AddressSanitizer + UBSan aktif debug build
windows-debug / windows-release Visual Studio 2022 (MSVC), native Windows
windows-mingw-debug / windows-mingw-release Linux’tan MinGW-w64 ile Windows cross-compile

Her preset kendi build/<preset-adı>/ dizinine, Conan’ın o preset için ürettiği toolchain dosyasını göstererek yapılandırılıyoruz. Yani bir preset = bir Conan kurulumu + bir build dizini.

Derleme için izlenebilecek örnek adımları şöyle sıralayabiliriz:

Bu arada dikkatli takipçilerim, bu komutları bu şekilde çağırmanıza gerek olmadığını da fark etmiştir. /scripts dizini altında farklı platformlar için gerekli betikler mevcut 😉

Bunun yanında kullanabileceğiniz temel bir takım profil dosyalarını da sizlerle ilerleyen dönemde paylaşacağım.


Neden OpenGL 3.3?

İlk yazıda kısaca değindiğim bir diğer konu da opengl 3.3 kullanımıydı.

Mimari kararları kayıt altına almak için bildiğiniz gibi ADR (Architecture Decision Record) formatını kullanıyoruz (buna yönelik de bir yazım mevcut). Bu konuya yönelik de ADR-001’i oluşturduk. Detaylara ilgili dokümandan ulaşabilirsiniz. Kısaca alternatiflere bakacak olursak:

Seçenek Neden reddedildi
OpenGL 2.1 Deprecated; fixed-function pipeline; VAO yok
OpenGL 4.5+ macOS desteği yok (Apple 4.1’de takılı)
OpenGL ES 2.0 Desktop’ta sınırlı destek
Vulkan (Phase 0) Yüksek kurulum maliyeti; 2D için fazla karmaşık

OpenGL 3.3 Core Profile seçiminin gerekçesi:

  • 2012 sonrası tüm masaüstü GPU’larda destekleniyor,
  • VAO, VBO, UBO, GLSL 330 — 2D kütüphane için gereken her şey mevcut,
  • Core Profile: fixed-function pipeline kaldırılmış amd davranış tahmin edilebilir.

Faz 0 Demomuz, İlk SDL Penceresi

Aslında bu yazımda CI/CD konularına da girecektim ama yazı çok uzadı, onun için faz 0 örnek uygulaması ile tamamlamaya karar verdim.

Faz 0 demosu aslında gerçek çizim yapmıyor, bununla birlikte altyapının derlendiğini ve çalıştığını doğruluyor. Aynı zamanda loglama sistemini de test ediyor:

Dikkat ettiyseniz SDL3’te SDL_Init başarısız olduğunda false döndürüyor — SDL2’deki negatif integer yerine, buna da SDL3’e yönelik yazımda değinmiştim. Fakat, bu API değişiklikleri genelde SDL3’e geçişte karşılaşılan en yaygın derleme hatalarından birisi.

Bu arada, loglama için despdlog kullanıyoruz. [%H:%M:%S][%L] formatı zamanı bize veriyor ve kayıtları daha okunabilir kılıyor.

Uygulamayı çalıştırdığınızda çıktı şöyle olmalı:


Sonuç

İlk yazımızla faz-0 aşamasını tamamlıyoruz. Peki nelere değindik bu yazımda:

  • Katmanlı bir mimarimiz var — Painter, Tessellator, IRenderer, Backend
  • IRenderer arayüzü önemli — OpenGL ve Vulkan için ortak sözleşme
  • Temel tiplerimiz — Color, Pen, Brush, Vertex, Point/Rect/Size
  • CMake + Conan 2 build sistemi — cross-platform, preset destekli,
  • OpenGL 3.3 Core seçim kararımız,
  • Çalışan bir SDL3 penceresi.

Önemli konuları inceledikten sonra artık çizim ve diğer konulara girmeye hazırız. Bir sonraki yazımda görüşmek üzere, bol kodlu günler.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Bu site, istenmeyenleri azaltmak için Akismet kullanıyor. Yorum verilerinizin nasıl işlendiği hakkında daha fazla bilgi edinin.