Source code for simvx.graphics._engine_init

"""Vulkan initialisation and swapchain lifecycle helpers for Engine."""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any

import vulkan as vk

from ._types import FRAMES_IN_FLIGHT, SHADER_DIR
from .gpu.commands import CommandContext
from .gpu.device import create_logical_device, select_physical_device
from .gpu.instance import create_debug_messenger, create_instance
from .gpu.memory import create_image
from .gpu.pipeline import create_graphics_pipeline, create_shader_module
from .gpu.swapchain import Swapchain
from .gpu.sync import FrameSync
from .materials.shader_compiler import compile_shader
from .renderer.passes import create_render_pass

if TYPE_CHECKING:
    from .engine import Engine

__all__ = [
    "init_vulkan",
    "create_depth_resources",
    "destroy_depth_resources",
    "create_framebuffers",
    "destroy_framebuffers",
    "recreate_swapchain",
]

log = logging.getLogger(__name__)


[docs] def init_vulkan(engine: Engine, use_triangle: bool = True) -> None: """Initialise Vulkan instance, device, swapchain, render pass, and command buffers.""" extensions = engine._window.get_required_instance_extensions() engine._instance, has_validation = create_instance("SimVX", extensions, validation=True) engine._debug_messenger = create_debug_messenger(engine._instance) if has_validation else None engine._surface = engine._window.create_graphics_surface(engine._instance) engine._physical_device, qf = select_physical_device(engine._instance, engine._surface) engine._graphics_qf = qf.graphics # Query device capabilities before creating logical device from .gpu.device import _query_device_features engine._device_features = _query_device_features(engine._physical_device) engine._has_mdi = engine._device_features["multi_draw_indirect"] if not engine._has_mdi: log.info("multiDrawIndirect not supported — using individual draw calls") engine._device, engine._graphics_queue, engine._present_queue = create_logical_device( engine._physical_device, qf, ) # Load swapchain-related KHR functions engine._vk_acquire = vk.vkGetInstanceProcAddr(engine._instance, "vkAcquireNextImageKHR") engine._vk_present = vk.vkGetInstanceProcAddr(engine._instance, "vkQueuePresentKHR") # Use framebuffer (physical) size for swapchain — may differ from logical # window size on HiDPI displays (e.g. 3200x1800 vs 1600x900 at 200%) fb_size = engine._window.get_framebuffer_size() if engine._window else (engine.width, engine.height) engine._swapchain = Swapchain( engine._instance, engine._device, engine._physical_device, engine._surface, fb_size, qf.graphics, qf.present, vsync=engine._vsync, ) engine._swapchain.create() depth_fmt = vk.VK_FORMAT_D32_SFLOAT if engine._use_depth else 0 engine._render_pass = create_render_pass(engine._device, engine._swapchain.image_format, depth_fmt) if engine._use_depth: create_depth_resources(engine) create_framebuffers(engine) if use_triangle: # Compile and load triangle shaders (built-in demo) vert_spv = compile_shader(SHADER_DIR / "triangle.vert") frag_spv = compile_shader(SHADER_DIR / "triangle.frag") engine._vert_module = create_shader_module(engine._device, vert_spv) engine._frag_module = create_shader_module(engine._device, frag_spv) engine._pipeline, engine._pipeline_layout = create_graphics_pipeline( engine._device, engine._vert_module, engine._frag_module, engine._render_pass, engine._swapchain.extent, ) engine._cmd_ctx = CommandContext(engine._device, qf.graphics) engine._cmd_ctx.create_pool() engine._cmd_buffers = engine._cmd_ctx.allocate(FRAMES_IN_FLIGHT) # Build the shared GPU context now that all handles exist from .gpu.context import GPUContext engine._ctx = GPUContext( device=engine._device, physical_device=engine._physical_device, graphics_queue=engine._graphics_queue, present_queue=engine._present_queue, graphics_qf=engine._graphics_qf, cmd_ctx=engine._cmd_ctx, ) engine._sync = FrameSync(engine._device, len(engine._swapchain.images)) engine._sync.create()
[docs] def create_depth_resources(engine: Engine) -> None: """Create depth buffer image, memory, and view.""" w, h = engine._swapchain.extent engine._depth_image, engine._depth_memory = create_image( engine._device, engine._physical_device, w, h, vk.VK_FORMAT_D32_SFLOAT, vk.VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, ) view_info = vk.VkImageViewCreateInfo( image=engine._depth_image, viewType=vk.VK_IMAGE_VIEW_TYPE_2D, format=vk.VK_FORMAT_D32_SFLOAT, subresourceRange=vk.VkImageSubresourceRange( aspectMask=vk.VK_IMAGE_ASPECT_DEPTH_BIT, baseMipLevel=0, levelCount=1, baseArrayLayer=0, layerCount=1, ), ) engine._depth_view = vk.vkCreateImageView(engine._device, view_info, None)
[docs] def destroy_depth_resources(engine: Engine) -> None: """Destroy depth buffer resources.""" if engine._depth_view: vk.vkDestroyImageView(engine._device, engine._depth_view, None) engine._depth_view = None if engine._depth_image: vk.vkDestroyImage(engine._device, engine._depth_image, None) engine._depth_image = None if engine._depth_memory: vk.vkFreeMemory(engine._device, engine._depth_memory, None) engine._depth_memory = None
[docs] def create_framebuffers(engine: Engine) -> None: """Create framebuffers for each swapchain image.""" engine._framebuffers = [] for view in engine._swapchain.image_views: attachments: list[Any] = [view] if engine._use_depth and engine._depth_view: attachments.append(engine._depth_view) fb_info = vk.VkFramebufferCreateInfo( renderPass=engine._render_pass, attachmentCount=len(attachments), pAttachments=attachments, width=engine._swapchain.extent[0], height=engine._swapchain.extent[1], layers=1, ) engine._framebuffers.append(vk.vkCreateFramebuffer(engine._device, fb_info, None))
[docs] def destroy_framebuffers(engine: Engine) -> None: """Destroy all swapchain framebuffers.""" for fb in engine._framebuffers: vk.vkDestroyFramebuffer(engine._device, fb, None) engine._framebuffers.clear()
[docs] def recreate_swapchain(engine: Engine) -> None: """Recreate swapchain, depth buffer, and framebuffers after resize.""" w, h = engine._window.get_framebuffer_size() while w == 0 or h == 0: engine._window.poll_events() w, h = engine._window.get_framebuffer_size() vk.vkDeviceWaitIdle(engine._device) destroy_framebuffers(engine) if engine._use_depth: destroy_depth_resources(engine) engine._sync.destroy() engine._swapchain.recreate((w, h)) engine.width, engine.height = w, h if engine._use_depth: create_depth_resources(engine) create_framebuffers(engine) engine._sync = FrameSync(engine._device, len(engine._swapchain.images)) engine._sync.create() if engine._pick_pass: engine._pick_pass.resize(engine._swapchain.extent)