Instance · Device · Swapchain · Pipeline · Command · Sync · Buffer · Image · Window
One header (VCK.h) + a matching set of core/Vulkan*.cpp translation units.
Owns the raw Vulkan handles. Nothing above this layer creates or destroys
these objects.
| Class | Owns |
|---|---|
VulkanContext |
VkInstance, debug messenger, Win32 VkSurfaceKHR |
VulkanDevice |
physical + logical device, VMA VmaAllocator, graphics + present queues |
VulkanSwapchain |
VkSwapchainKHR + image views, default render pass, recreate path |
VulkanPipeline |
graphics pipeline + pipeline layout builder |
VulkanCommand |
command pool + one primary command buffer per frame slot |
VulkanSync |
image-available / render-finished semaphores + in-flight fences |
VulkanBuffer |
VkBuffer + VMA allocation, optional persistent map, Upload() |
VulkanImage |
VkImage + view + VMA allocation, layout transition helper |
LogVk(std::string)— writes to bothOutputDebugStringA(VS Output) andstdout+fflush(stdout). Safe to call from anywhere; no initialization required.VK_CHECK(expr)— wraps anyVkResult-returning call. On non-success prints the expression and theVkResultviaLogVk. Use it everywhere:VK_CHECK(vkQueueSubmit(queue, 1, &submit, fence));
VCK::MAX_FRAMES_IN_FLIGHT=3— compile-time upper bound on frames-in-flight. The runtime count is set viaConfig::SyncCfg::framesInFlight(default2) and clamped to this bound.
Init: Context → Device → Swapchain → Pipeline → Command → Sync
Shutdown: Sync → Command → Pipeline → Swapchain → Device → Context
Expansion objects, VMM resources, and any FrameScheduler must be shut down
before the core objects they reference.
Every core Initialize(...) has a zero-arg form and an overload that takes
a const VCK::Config&. One struct, nested by class. Pass only the fields
you care about; everything else defaults to the previous zero-arg behaviour.
struct VCK::Config
{
struct ContextCfg {
std::string appName = "VCK App";
bool enableValidation = true;
std::vector<const char*> extraInstanceLayers;
std::vector<const char*> extraInstanceExtensions;
} context;
struct DeviceCfg {
bool preferDiscreteGpu = true;
std::vector<const char*> extraDeviceExtensions;
QueuePreference queuePreference = QueuePreference::GraphicsOnly;
} device;
struct SwapchainCfg {
PresentMode presentMode = PresentMode::Mailbox; // falls back to FIFO automatically
uint32_t imageCount = 0; // 0 = minImageCount + 1
VkFormat surfaceFormat = VK_FORMAT_UNDEFINED; // UNDEFINED = auto-pick
VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT;
VkFormat depthFormat = VK_FORMAT_UNDEFINED; // UNDEFINED = auto
} swapchain;
struct SyncCfg {
uint32_t framesInFlight = 2; // clamped to MAX_FRAMES_IN_FLIGHT
} sync;
};
enum class PresentMode { Auto, Fifo, Mailbox, Immediate };
enum class QueuePreference { GraphicsOnly, GraphicsCompute, GraphicsComputeTransfer };VCK::Config cfg;
cfg.context.appName = "myapp";
cfg.swapchain.presentMode = VCK::PresentMode::Mailbox; // tears-nothing, low-latency
cfg.sync.framesInFlight = 3; // deeper CPU/GPU pipeline
ctx .Initialize(hwnd, cfg);
dev .Initialize(ctx, cfg);
sc .Initialize(dev, ctx, w, h, cfg);
pipe.Initialize(dev, sc, shaders, vertexInput);
cmd .Initialize(dev, cfg);
sync.Initialize(dev, cfg);Notes:
- Backward compatible. Zero-arg
Initialize(...)still works — that path is literally areturn Initialize(..., Config()). - Present mode fallbacks.
Mailbox/Immediatefall back toFifoif the driver doesn't expose them;AutopicksMailboxwhen available,Fifootherwise.LogVkreports the final choice. - MSAA (
cfg.swapchain.msaaSamples) — reserved, clamped to 1x today. The field + accessor exist so the API surface is stable, but proper MSAA needs a render-pass resolve attachment and a per-swapchain-image multi- sampled colour image — both are on the roadmap (see Design). SettingmsaaSamples > 1currently logs a warning and is clamped toVK_SAMPLE_COUNT_1_BITinVulkanPipeline. Do not rely on it yet. - framesInFlight is clamped to
[1, MAX_FRAMES_IN_FLIGHT](= 3). Going deeper requiresVK_KHR_timeline_semaphore— a separate track; seeTimelineSemaphorein Expansion API. cmdandsyncmust share the sameframesInFlight. Pass the samecfgto both. The zero-arg path uses2for both, so it's always consistent by default.
Every class also keeps a raw-handle overload so you can always drop down to manual Vulkan for anything VCK is not doing for you. Preferred overloads are literally a one-line forward to these:
dev.Initialize (VkInstance, VkSurfaceKHR);
dev.Initialize (VkInstance, VkSurfaceKHR, const Config&);
sc .Initialize (VulkanDevice&, VkSurfaceKHR, w, h);
sc .Initialize (VulkanDevice&, VkSurfaceKHR, w, h, const Config&);
pipe.Initialize(VulkanDevice&, VkFormat, shaders, vi,
VkSampleCountFlagBits = VK_SAMPLE_COUNT_1_BIT);VCK::VulkanContext ctx;
VCK::VulkanDevice dev;
VCK::VulkanSwapchain sc;
VCK::VulkanPipeline pipe;
VCK::VulkanCommand cmd;
VCK::VulkanSync sync;
ctx.Initialize (glfwGetWin32Window(win), "myapp");
dev.Initialize (ctx);
sc .Initialize (dev, ctx, 800, 600);
// ... pipeline setup ...
cmd.Initialize(dev);
sync.Initialize(dev);
// per-frame:
// vkWaitForFences(sync.GetInFlightFence(slot) ...)
// vkAcquireNextImageKHR(..., sync.GetImageAvailable(slot), ...)
// record into cmd.Get(slot)
// vkQueueSubmit with sync.GetImageAvailable(slot) → sync.GetRenderFinished(slot)
// vkQueuePresentKHR waiting on sync.GetRenderFinished(slot)
sync.Shutdown(); cmd.Shutdown(); pipe.Shutdown();
sc.Shutdown(); dev.Shutdown(); ctx.Shutdown();If you want the scheduler to drive the per-frame steps for you, see Execution Layer — the core API never changes.