|
SDLPainter 0.1.0
QPainter benzeri SDL3 + OpenGL/Vulkan 2D çizim kütüphanesi
|
Bu belge IRenderer arayüzünün arkasındaki iki somut implementasyonu (OpenGL 3.3 ve Vulkan 1.1) detaylı inceler. Üst katman bağlamı için Mimari Genel Bakış, arayüz tanımları için Sınıf Diyagramı.
| Konu | OpenGL 3.3 Backend | Vulkan 1.1 Backend |
|---|---|---|
| Initialization karmaşıklığı | Düşük (context + GLAD) | Yüksek (instance/device/swapchain/sync) |
| Frame yapısı | Begin/End trivial | Begin/End: acquire + submit + present |
| Komut gönderimi | Anında (immediate) | Command buffer'a kaydedilir |
| Vertex upload | glBufferData her draw | Ring buffer + persistent map |
| Shader | GLSL 330 (runtime compile) | SPIR-V (offline glslc) |
| Uniform | glUniform* | Push constants |
| Pencere boyutu | Otomatik | Swapchain recreate gerekir |
| Sync | Sürücüye bırakılır | Manuel (semaphore + fence) |
| Texture | glGenTextures + bind | VkImage + VkImageView + sampler + descriptor |
| Sahip olunan SDL flag | SDL_WINDOW_OPENGL | SDL_WINDOW_VULKAN |
Özet: OpenGL backend basit ve hızlı bir başlangıç sağlar. Vulkan backend modern GPU mimarileriyle daha verimli çalışır ve daha fazla kontrol verir; karşılığında çok daha fazla kod ve dikkat gerektirir. İkisi de aynı IRenderer arayüzünü kullandığı için Painter ikisi arasında fark görmez.
flowchart TB
subgraph user["Painter / RenderBatcher"]
BATCH["DrawTriangles / DrawTextured çağrısı"]
end
subgraph ogl["OpenGLRenderer"]
INIT["Initialize:<br/>SDL_GL_CreateContext +<br/>gladLoadGL"]
SHADERS["ShaderProgram x2:<br/>basic + textured"]
VAO["VAO + VBO x2:<br/>untextured + textured"]
UNIFORMS["Uniform state:<br/>mProjection, mModel, mOpacity"]
DRAW["DrawTriangles:<br/>BindVAO + glBufferData +<br/>SetUniforms + glDrawArrays"]
TEX["CreateTexture:<br/>glGenTextures +<br/>glTexImage2D +<br/>glTexParameteri"]
end
subgraph gpu["GPU"]
DRIVER["Sürücü"]
end
BATCH --> DRAW
INIT --> SHADERS
INIT --> VAO
DRAW --> SHADERS
DRAW --> VAO
DRAW --> UNIFORMS
DRAW --> DRIVER
TEX --> DRIVER
style ogl fill:#cf222e20,stroke:#cf222e
style gpu fill:#6e778120,stroke:#6e7781
sequenceDiagram
participant B as RenderBatcher
participant R as OpenGLRenderer
participant SP as ShaderProgram (basic)
participant GL
B->>R: DrawTriangles(vector~Vertex~)
R->>SP: Use() (glUseProgram)
R->>SP: SetUniformMat4("u_projection", mProjection)
R->>SP: SetUniformMat3("u_model", mModel)
R->>SP: SetUniformFloat("u_opacity", mOpacity)
R->>GL: glBindVertexArray(mVao)
R->>GL: glBindBuffer(GL_ARRAY_BUFFER, mVbo)
R->>GL: glBufferData(verts.size, verts.data, GL_DYNAMIC_DRAW)
R->>GL: glEnable(GL_BLEND)
R->>GL: glBlendFunc(SRC_ALPHA, ONE_MINUS_SRC_ALPHA)
R->>GL: glDrawArrays(GL_TRIANGLES, 0, count)
İki ayrı program var:
flowchart LR
subgraph vs["Vertex Shader (basic.vert)"]
VIN["in vec2 a_position<br/>in vec4 a_color"]
VTRANS["mat3 u_model · vec3(a_position, 1.0)"]
VPROJ["mat4 u_projection · vec4(...)"]
VOUT["gl_Position +<br/>out vec4 v_color"]
end
subgraph fs["Fragment Shader (basic.frag)"]
FIN["in vec4 v_color"]
FALPHA["v_color * vec4(1, 1, 1, u_opacity)"]
FOUT["out vec4 frag_color"]
end
VIN --> VTRANS --> VPROJ --> VOUT
VOUT --> FIN --> FALPHA --> FOUT
Renk vertex'te taşındığı için aynı pen/brush dışında bile tek bir draw call içinde farklı renkli üçgenler çizilebilir. Bu RenderBatcher verimliliğinin temelidir.
TextureHandle OpenGL backend'de doğrudan GL texture ID'sidir (uint32_t). Vulkan backend'de ise iç haritada çözülen opak bir sayıdır.
OpenGL pencere koordinatlarında Y=0 alttadır; SDLPainter API'sinde ise Y=0 üstte. Painter projeksiyon matrisini buna göre kurar (UpdateProjection):
Scissor kullanırken ise OpenGL doğal Y'de (alttan) ister; bu yüzden Painter::ApplyScissor flip uygular:
flowchart TB
VR["VulkanRenderer"]
VR --> CTX["VkContext<br/>(instance, physicalDevice,<br/>device, queue, surface)"]
VR --> SC["VkSwapchain<br/>(images, views, renderpass,<br/>framebuffers)"]
VR --> FS["VkFrameSync<br/>(imageAvailable, renderFinished,<br/>inFlight, commandBuffers x2)"]
VR --> P1["VulkanPipeline<br/>(untextured)"]
VR --> P2["VulkanTexturedPipeline<br/>(descriptor set layout +<br/>combined image sampler)"]
VR --> RING1["VulkanBuffer (vertex ring)<br/>untextured"]
VR --> RING2["VulkanBuffer (vertex ring)<br/>textured"]
VR --> TEXMAP["unordered_map~Handle, VulkanTexture~"]
TEXMAP --> TEX["VulkanTexture x N<br/>(image, view, sampler,<br/>descriptor set)"]
SC --> CTX
FS --> CTX
P1 --> CTX
P2 --> CTX
RING1 --> CTX
RING2 --> CTX
style VR fill:#cf222e,color:#fff
style CTX fill:#1f6feb,color:#fff
style SC fill:#bf8700,color:#fff
style FS fill:#bf8700,color:#fff
sequenceDiagram
participant App
participant VR as VulkanRenderer
participant Ctx as VkContext
participant SC as VkSwapchain
participant FS as VkFrameSync
participant P as VulkanPipeline
App->>VR: Initialize(window)
VR->>Ctx: Create()
Ctx->>Ctx: vkCreateInstance (validation layers Debug'da)
Ctx->>Ctx: SDL_Vulkan_CreateSurface
Ctx->>Ctx: vkEnumeratePhysicalDevices + pick suitable
Ctx->>Ctx: vkCreateDevice + queue family
Ctx->>Ctx: vkCreateCommandPool
Ctx-->>VR: ready
VR->>SC: Create(width, height)
SC->>SC: vkCreateSwapchainKHR
SC->>SC: vkCreateImageView x N
SC->>SC: vkCreateRenderPass
SC->>SC: vkCreateFramebuffer x N
SC-->>VR: ready
VR->>FS: Create(kMaxFramesInFlight=2)
FS->>FS: vkCreateSemaphore x 2 (imageAvailable)
FS->>FS: vkCreateSemaphore x 2 (renderFinished)
FS->>FS: vkCreateFence x 2 (inFlight, signaled)
FS->>FS: vkAllocateCommandBuffers x 2
FS-->>VR: ready
VR->>P: Create (untextured pipeline)
P->>P: vkCreateShaderModule (vert, frag from .spv)
P->>P: vkCreatePipelineLayout (push constants)
P->>P: vkCreateGraphicsPipelines (dynamic viewport+scissor)
P-->>VR: ready
VR->>VR: VulkanTexturedPipeline + ring buffers
VR-->>App: bool true
Detaylar için Akış Diyagramları → Vulkan Frame. Üç önemli detay:
Toplam ≤ 128 byte (Vulkan minimum garanti). Her draw call'da değişebilir, descriptor set güncellemesi gerektirmez.
Pencere yeniden boyutlandığında:
flowchart LR
A["AcquireNextImage<br/>VK_ERROR_OUT_OF_DATE_KHR"] --> B["mSwapchainOutOfDate = true"]
B --> C["Recreate akışı:"]
C --> D["vkDeviceWaitIdle"]
D --> E["VkSwapchain.Destroy"]
E --> F["VkSwapchain.Create(new w, new h)"]
F --> G["Yeni framebuffer'lar"]
G --> H["mSwapchainOutOfDate = false"]
H --> I["Frame'i tekrar dene"]
style A fill:#cf222e,color:#fff
style I fill:#2da44e,color:#fff
QueuePresentKHR çağrısı da OUT_OF_DATE_KHR veya SUBOPTIMAL_KHR dönebilir; aynı recreate yolu tetiklenir.
Tek bir VulkanTexture aşağıdaki Vulkan nesnelerini kapsar:
classDiagram
class VulkanTexture {
+VkImage image
+VkImageView view
+VkSampler sampler
+VkDeviceMemory memory
+VkDescriptorSet descriptorSet
+VkExtent2D extent
}
Yaratım adımları:
Yıkım: yukarıdaki nesnelerin tersi sırada vkDestroy*. RAII helper sınıfları (vk_raii.h) bu sırayı garanti eder.
Aynı painter.FillCircle(400, 300, 50) çağrısının iki backend'de aldığı somut yol:
sequenceDiagram
participant P as Painter
participant T as Tessellator
participant B as RenderBatcher
participant OGL as OpenGLRenderer
participant VK as VulkanRenderer
participant GPU
P->>T: TessellateFilledCircle(400, 300, 50)
T-->>P: 75 vertex (25 segment, triangle fan)
P->>B: PushTriangles(verts, brush.color, opacity)
Note over B,GPU: Flush tetiklendi (state değişti veya End)
par OpenGL yolu
B->>OGL: DrawTriangles(buffer)
OGL->>GPU: glUseProgram(basicShader)
OGL->>GPU: glBufferData(verts)
OGL->>GPU: glUniform*(projection, model, opacity)
OGL->>GPU: glDrawArrays(GL_TRIANGLES, 0, 75)
and Vulkan yolu
B->>VK: DrawTriangles(buffer)
VK->>VK: vertex ring → memcpy(buffer)
VK->>GPU: vkCmdBindPipeline(untextured)
VK->>GPU: vkCmdBindVertexBuffers(ring, offset)
VK->>GPU: vkCmdPushConstants(projection+model+opacity)
VK->>GPU: ApplyDynamicViewportScissor
VK->>GPU: vkCmdDraw(75, 1, 0, 0)
end
Sonuç piksel düzeyinde aynıdır — tessellation aynı vertex'leri üretir, projeksiyon matrisleri aynıdır, blend state aynıdır. Tek olası fark: anti-aliasing yapılandırması (MSAA örnekleme) backend'in pipeline veya FBO ayarına bağlı olabilir.
Yeni bir backend eklemek (örn. Metal, DirectX 12, Software) için:
flowchart TB
A["1) include/sdl_painter/renderer.h<br/>RendererBackend enum'una kMetal ekle"] --> B
B["2) src/metal/metal_renderer.{h,cpp}<br/>IRenderer'ı implement et"] --> C
C["3) src/renderer.cpp<br/>CreateRenderer factory'sine case ekle"] --> D
D["4) CMakeLists.txt<br/>Opsiyonel SDLPAINTER_WITH_METAL flag'i ekle"] --> E
E["5) examples/<br/>Test demo yaz"] --> F
F["Painter, Tessellator, RenderBatcher,<br/>RenderState DEĞİŞMEZ"]
style F fill:#2da44e,color:#fff
Bu Açık/Kapalı Prensibi'nin somut karşılığıdır. IRenderer arayüzü genişlemez, mevcut backend'ler etkilenmez.
| ADR | Karar | Bu belgedeki ilgili bölüm |
|---|---|---|
| ADR-001 | OpenGL 3.3 Core (4.5/ES değil) | §2 |
| ADR-003 | glLineWidth yerine geometry quad | Tessellator (akislar.md) |
| ADR-004 | Tessellator backend-agnostic | §4 |
| ADR-005 | stb_image (SDL_image değil) | §2.4 |
| ADR-006 | Ear clipping triangulation | Tessellator (akislar.md) |