simvx.graphics.engine¶
Top-level engine entry point.
Module Contents¶
Classes¶
Opaque handle to a GPU cubemap returned by :meth: |
|
Lifecycle of the Vulkan logical device, mirroring the web GpuContext. |
|
Graphics engine: owns the window, GPU context, and render loop. |
Data¶
API¶
- simvx.graphics.engine.__all__¶
[‘CubemapHandle’, ‘Engine’]
- simvx.graphics.engine.log¶
‘getLogger(…)’
- class simvx.graphics.engine.CubemapHandle[source]¶
Opaque handle to a GPU cubemap returned by :meth:
Engine.load_cubemap.Pass to :meth:
~simvx.graphics.renderer.forward.Renderer.set_skyboxto install it as the scene skybox / IBL source. The engine takes ownership of the underlying Vulkan resources and destroys them at shutdown.- view: Any¶
None
- sampler: Any¶
None
- image: Any¶
None
- memory: Any¶
None
- class simvx.graphics.engine.DeviceState[source]¶
Bases:
enum.EnumLifecycle of the Vulkan logical device, mirroring the web GpuContext.
BOOTINGbefore the first device is created;READYwhile rendering;LOSTwhenVK_ERROR_DEVICE_LOSTis observed;RECOVERINGwhile a rebuild is in flight;DESTROYEDafter shutdown. The legacy_device_lostbool remains the per-frame submission gate; this state is the coarser machine the recovery path drives.- BOOTING¶
‘booting’
- READY¶
‘ready’
- LOST¶
‘lost’
- RECOVERING¶
‘recovering’
- DESTROYED¶
‘destroyed’
- __new__(value)¶
- __repr__()¶
- __str__()¶
- __dir__()¶
- __format__(format_spec)¶
- __hash__()¶
- __reduce_ex__(proto)¶
- __deepcopy__(memo)¶
- __copy__()¶
- name()¶
- value()¶
- class simvx.graphics.engine.Engine(width: int = 1280, height: int = 720, title: str = 'SimVX', backend: str | None = None, renderer: str = 'deferred', max_textures: int = MAX_TEXTURES, visible: bool = True, vsync: bool = False, target_fps: int | None = None)[source]¶
Graphics engine: owns the window, GPU context, and render loop.
Initialization
- property ctx: simvx.graphics.gpu.context.GPUContext | None[source]¶
GPU context holding device, physical_device, queues, and command pool.
Noneuntilrun()invokes_init_vulkan().
- property multi_device: Any[source]¶
The :class:
~simvx.graphics.gpu.multi_device.MultiDeviceManager.Noneuntilrun()invokes_init_vulkan(). On the single-GPU / unopted path it is a single-slot passthrough (multi_gpuisFalse).
- property render_pass: simvx.graphics.types._VkRenderPass | None[source]¶
Main render pass.
Noneuntilrun()invokes_init_vulkan().
- property extent: tuple[int, int] | None[source]¶
Swapchain extent.
Noneuntilrun()invokes_init_vulkan().
- property content_scale: tuple[float, float][source]¶
HiDPI content scale (e.g.
(2.0, 2.0)on a 200% display).
- set_vsync(value: bool) None[source]¶
Toggle vsync at runtime by recreating the swapchain with a new present mode.
No-op if the requested state matches the current one. Before the engine has a swapchain (pre-
run()) the value is stored and used at boot.
- property pre_render_callback: collections.abc.Callable[[simvx.graphics.types._VkCommandBuffer], None] | None[source]¶
Callback invoked after
vkBeginCommandBufferbut before the main render pass.
- property current_frame: int[source]¶
Index of the frame-in-flight slot currently being recorded (0..FRAMES_IN_FLIGHT-1).
Stable across a frame’s recording; advances at present. Per-frame ring buffers index their current slot by this so a write never lands on memory a still-pending previous frame reads.
- property current_timestamp_pool: simvx.graphics.gpu.timestamp_pool.TimestampPool | None[source]¶
Return the TimestampPool for the in-flight frame currently recording.
Noneif the device does not support timestamp queries (the pool list is left empty by_init_vulkanin that case). Renderers wrap each pass withpool.begin(cmd, label)/pool.end(cmd, label).
- property mesh_registry: simvx.graphics.renderer.mesh_registry.MeshRegistry[source]¶
Get mesh registry (lazy init).
- property capabilities: simvx.graphics.gpu.capabilities.RenderCapabilities | None[source]¶
Immutable host + GPU capability snapshot (set during
init_vulkan).Nonebefore Vulkan initialisation. Read-only: configuration flows throughWorldEnvironment/App, never through this object.
- property texture_manager: simvx.graphics.materials.texture.TextureManager[source]¶
Get texture manager (lazy init).
- property batch: simvx.graphics.renderer.gpu_batch.GPUBatch[source]¶
Get the GPU batch renderer (lazy init).
- property renderer: simvx.graphics.renderer.forward.Renderer[source]¶
Get the active renderer (creates the default renderer if none exists).
- create_renderer(renderer_type: str = 'forward') simvx.graphics.renderer.forward.Renderer[source]¶
Create and initialize a renderer.
- property texture_descriptor_layout: simvx.graphics.types._VkDescriptorSetLayout[source]¶
Get (lazily init) the texture descriptor set layout.
- property texture_descriptor_set: simvx.graphics.types._VkDescriptorSet[source]¶
Get the texture descriptor set (set 1).
- create_render_target(width: int, height: int, use_depth: bool = True) simvx.graphics.renderer.render_target.RenderTarget[source]¶
Create an offscreen render target for render-to-texture.
- load_cubemap(paths_or_hdr: list[str] | str | None = None, *, colour: tuple[float, float, float] | None = None, face_size: int = 256, faces: list | None = None) simvx.graphics.engine.CubemapHandle[source]¶
Load a cubemap from 6 face images, an equirectangular HDR, or a solid colour.
Args: paths_or_hdr: Either a list of 6 face paths
[+X, -X, +Y, -Y, +Z, -Z]for a pre-split cubemap, or a single.hdr(Radiance RGBE) equirectangular file projected onto 6 faces on the CPU.Nonefalls back tocolour. colour: RGB triple in 0..1 used when no paths are provided. face_size: Per-face resolution when projecting from an HDR equirect.Returns: A :class:
CubemapHandle; hand this to :meth:Renderer.set_skyboxto install it.
- register_lut(tex_id: int, lut_data: numpy.ndarray) int[source]¶
Register a 3D colour-grading LUT under
tex_id.lut_datais an(size, size, size, 4)uint8 array (e.g. fromsimvx.core.colour_grading.generate_warm_lut). Select it at render time withWorldEnvironment.lut_tex_id = tex_idandlut_enabled = True. The LUT is a 3Drgba8image sampled post-tonemap, matching the web runtime. Returnstex_id.
- register_texture(image_view: simvx.graphics.types._VkImageView, *, filter: str = 'linear', mip_count: int = 1) int[source]¶
Register a texture (image view) into the bindless array.
Returns the texture index. Reuses slots freed by
- Meth:
unregister_texture; otherwise allocates a new slot from the high-water mark.
filterselects the bound sampler:"linear"(default, bilinear) or"nearest"(point sampling, the right choice for pixel-art sprites). The same image view can be registered twice with different filter modes to give the same texture two bindless indices, which is how :class:SceneAdapterhonours per-Sprite2Dfilter.mip_countis the number of mip levels in the view. The default 1 binds the shared maxLod=0 sampler (single-mip behaviour unchanged); a value > 1 binds a cached maxLod=mip_count-1 sampler so the whole mip chain is sampled (without it every level beyond 0 is unreachable).
- update_texture(slot: int, image_view: simvx.graphics.types._VkImageView) None[source]¶
Rewrite the descriptor at an existing bindless slot.
Used when the backing image view changes (e.g. render-target resize) but the slot id must remain stable so callers that captured it don’t need re-notification.
- unregister_texture(slot: int) None[source]¶
Release a bindless texture slot for reuse by a later register_texture.
The descriptor at the slot is left pointing at its old view until something new is bound there; the slot is simply marked free.
- upload_texture_pixels(pixels: numpy.ndarray, width: int, height: int, *, filter: str = 'linear', colour_space: str = 'srgb', mipmaps: bool = False) int[source]¶
Upload raw RGBA pixel data to GPU. Returns the bindless texture index.
filteris forwarded to :meth:register_texture; pass"nearest"for pixel-art sprites that should keep crisp edges when scaled.colour_spaceselects the sampled image view format (linear-workflow contract, Stage 1):"srgb"(default) makes aVK_FORMAT_R8G8B8A8_SRGBview so the GPU decodes the stored sRGB bytes to linear on sample, round-tripping correctly through the sRGB swapchain (the right choice for colour textures: 2D sprites, tileset atlases, Draw2D images)."linear"keeps theVK_FORMAT_R8G8B8A8_UNORMview: the choice for data textures that are not perceptual colour (normal/data maps) and for the 3D material albedo path, which currently treats its uploaded bytes as already-linear. The pixel bytes uploaded to memory are identical in both cases; only the view’s interpretation differs.mipmaps=Trueallocates the full mip chain and generates every level on the GPU with a linear-filteredvkCmdBlitImagechain, then binds the maxLod=chain-1 sampler so minification actually reaches the chain. Falls back to a single mip when the view format lacks linear-blit support (so exotic formats degrade gracefully) or when the image is already 1x1. The default False keeps the historical single-mip upload byte-identical.
- format_supported(vk_format: int) bool[source]¶
Return True if
vk_formatcan be sampled from an optimal-tiled image.The authoritative per-format gate consulted by the compressed-texture upload path: the coarse
textureCompressionBCdevice feature must be present AND the specific BC format must reportSAMPLED_IMAGEin its optimal-tiling features.
- supports_compressed_format(vk_format: int) bool[source]¶
Whether a block-compressed
vk_formatis usable on this device.Gates on the coarse family feature for
vk_format’s block family (BC / ASTC-LDR / ETC2, the single source of truth inself._capabilities) AND the per-format sampled-image feature. Consulted byTextureManagerbefore dispatching a compressed upload.
- compressed_caps() dict[str, bool][source]¶
Coarse block-compression family support for this device.
Returns the
texture_compression_{bc,etc2,astc_ldr}subset of the probed capabilities. Consulted byTextureManagerto pick a UASTC transcode target (BC7 -> ASTC-4x4 -> ETC2). Each coarse bit is necessary but not sufficient: a per-format SAMPLED check (format_supported) is the second gate. Public accessor so callers never reach into the private_capabilities.
- upload_texture_blocks(blocks: list[bytes], width: int, height: int, fmt: int, block_size: int, *, filter: str = 'linear') int[source]¶
Upload block-compressed mip data to GPU. Returns the bindless texture index.
Parallel to :meth:
upload_texture_pixelsfor the uncompressed RGBA8 path: builds the image view with the COMPRESSEDfmtand a levelCount spanning all supplied mips, then registers it into the bindless array and tracks it for teardown exactly like the uncompressed path.
- load_mesh(file_path: str) simvx.graphics.types.MeshHandle[source]¶
Load a glTF file’s first mesh primitive from disk and register it.
Returns: MeshHandle for use in rendering.
- create_vertex_buffer(vertices: numpy.ndarray) tuple[Any, simvx.graphics.types._VkDeviceMemory][source]¶
Create a GPU vertex buffer and upload data. Returns (buffer, memory).
- create_index_buffer(indices: numpy.ndarray) tuple[Any, simvx.graphics.types._VkDeviceMemory][source]¶
Create a GPU index buffer and upload data. Returns (buffer, memory).
- create_ssbo(data: numpy.ndarray) tuple[Any, simvx.graphics.types._VkDeviceMemory][source]¶
Create an SSBO and upload data. Returns (buffer, memory).
- update_ssbo(memory: simvx.graphics.types._VkDeviceMemory, data: numpy.ndarray) None[source]¶
Update SSBO contents in-place via mapped memory.
- create_descriptor_pool(max_sets: int = 4) simvx.graphics.types._VkDescriptorPool[source]¶
Create a descriptor pool.
- create_descriptor_set_layout(binding_count: int = 3) simvx.graphics.types._VkDescriptorSetLayout[source]¶
Create a descriptor set layout with N SSBO bindings.
- allocate_descriptor_set(pool: simvx.graphics.types._VkDescriptorPool, layout: simvx.graphics.types._VkDescriptorSetLayout) simvx.graphics.types._VkDescriptorSet[source]¶
Allocate a descriptor set from pool.
- write_descriptor_ssbo(descriptor_set: simvx.graphics.types._VkDescriptorSet, binding: int, buffer: Any, size: int) None[source]¶
Bind an SSBO buffer to a descriptor set binding.
- compile_and_load_shader(name: str) simvx.graphics.types._VkShaderModule[source]¶
Compile a shader from SHADER_DIR and return its module.
- update_vertex_buffer(memory: simvx.graphics.types._VkDeviceMemory, data: numpy.ndarray) None[source]¶
Update vertex buffer contents in-place via mapped memory.
- push_constants(cmd: simvx.graphics.types._VkCommandBuffer, pipeline_layout: simvx.graphics.types._VkPipelineLayout, data: bytes | bytearray) None[source]¶
Push constant data (view + proj).
- enable_picking(descriptor_layout: simvx.graphics.types._VkDescriptorSetLayout, descriptor_set: simvx.graphics.types._VkDescriptorSet) None[source]¶
Initialize the GPU pick pass for mouse picking.
- pick_entity(x: int, y: int, view_proj_data: bytes, vertex_buffer: Any, index_buffer: Any, index_count: int, instance_count: int) int[source]¶
Read entity ID at screen position (x, y). Returns entity index or -1.
- set_selected_objects(selected: list[tuple[simvx.graphics.types.MeshHandle, numpy.ndarray, int]]) None[source]¶
Set the list of selected objects to highlight with outlines.
Args: selected: List of (mesh_handle, transform_4x4, material_id) tuples.
- property outline_pass: simvx.graphics.renderer.outline_pass.OutlinePass | None[source]¶
Access outline pass for configuration (colour, width, enabled).
- create_text_texture(font: str | None = None, size: int = 32, width: int = 256, height: int = 64) Any[source]¶
Create a texture with rendered text for use on 3D objects.
Returns a TextTexture with .text, .colour, and .texture_index properties. Setting .text or .colour re-renders and re-uploads the texture.
Web parity (
simvx.webEngineStub.create_text_texture): thefontargument is a no-op on web (text is rasterized from the atlas baked at export time), andtexture_indexis reassigned on every re-render there (the bindless slot is reused in place on desktop). So set.text/.colourbefore bindingtexture_indexto a material for identical results on both backends – which is how the examples author it.
- set_key_callback(callback: collections.abc.Callable[[int, int, int], None]) None[source]¶
Register callback(key, action, mods) for keyboard events.
- set_mouse_button_callback(callback: collections.abc.Callable[[int, int, int], None]) None[source]¶
Register callback(button, action, mods) for mouse button events.
- set_cursor_pos_callback(callback: collections.abc.Callable[[float, float], None]) None[source]¶
Register callback(x, y) for cursor position events.
- set_scroll_callback(callback: collections.abc.Callable[[float, float], None]) None[source]¶
Register callback(x_offset, y_offset) for scroll wheel events.
- set_char_callback(callback: collections.abc.Callable[[int], None]) None[source]¶
Register callback(codepoint) for character input events.
- set_cursor_shape(shape: int) None[source]¶
Set cursor shape. 0=arrow, 1=ibeam, 2=crosshair, 3=hand, 4=hresize, 5=vresize.
- set_mouse_capture(mode: int) None[source]¶
Apply a MouseCaptureMode int to the OS cursor via the active window backend.
- run(callback: collections.abc.Callable[[], None] | None = None, setup: collections.abc.Callable[[], None] | None = None, render: collections.abc.Callable[[simvx.graphics.types._VkCommandBuffer, tuple[int, int]], None] | None = None, pre_render: collections.abc.Callable[[simvx.graphics.types._VkCommandBuffer], None] | None = None, cleanup: collections.abc.Callable[[], None] | None = None, frame_driver: collections.abc.Callable[[], None] | None = None, pre_shutdown: collections.abc.Callable[[], None] | None = None) None[source]¶
Start the main loop.
Args: callback: Legacy per-frame callback (called before draw). setup: Called once after Vulkan init, before the loop. render: Custom render callback receiving (command_buffer, extent). If provided, replaces the built-in triangle rendering. pre_render: Called with command_buffer after vkBeginCommandBuffer but before the main render pass. Use for offscreen passes. cleanup: Called once when the loop exits, after the final
vkDeviceWaitIdlebut beforeshutdown()destroys the device. The right place to release caller-owned GPU resources (offscreen render targets, bindless slots) that must outlive the loop but die before the device. pre_shutdown: Called once when the loop exits, BEFORE the finalvkDeviceWaitIdle. The pipelined driver stops + joins its render thread here so no GPU work is issued against the device while/after it is being torn down.None(default) is a no-op, unchanged. frame_driver: Pipelined-mode hook replacing the per-frameself._draw_frame()call on the MAIN thread. When given, the engine loop runspoll_events -> callback() -> frame_driver()and never touches the GPU itself: the driver hands the frame’s render packet to a render thread that owns all GPU work.None(default) keeps the synchronous_draw_framepath, byte-identical to before.
- begin(*, setup: collections.abc.Callable[[], None] | None = None, render: collections.abc.Callable[[simvx.graphics.types._VkCommandBuffer, tuple[int, int]], None] | None = None, pre_render: collections.abc.Callable[[simvx.graphics.types._VkCommandBuffer], None] | None = None) None[source]¶
Create the window + Vulkan device and run
setup.The externally-driven counterpart to :meth:
run: pair with :meth:stepand :meth:endto advance frames under a caller-owned clock (the agent live-session / editor viewport).runisbegin+ a step loop +end.
- step(*, callback: collections.abc.Callable[[], None] | None = None, frame_driver: collections.abc.Callable[[], None] | None = None) bool[source]¶
Run one frame iteration (poll -> callback -> draw).
Returns False when the loop should stop (quit requested or window closed), True otherwise (including a paused/backgrounded frame, which is skipped).
- end(*, pre_shutdown: collections.abc.Callable[[], None] | None = None, cleanup: collections.abc.Callable[[], None] | None = None) None[source]¶
Tear down: quiesce the render thread, wait-idle, run cleanup, shutdown.
- arm_capture() None[source]¶
Arm a one-shot readback of the NEXT
_draw_framebefore it presents.The captured pixels are retrieved with :meth:
take_captured_frame. This is the acquire-before-transition capture path: the readback happens while the swapchain image is still acquired, so it never trips the “presentable image must be acquired” validation rule that the post-present :meth:capture_framedoes. Used by the synchronous headless capture sink.
- take_captured_frame() numpy.ndarray | None[source]¶
Return (and clear) the frame stashed by an armed capture, or
None.
- capture_frame() numpy.ndarray[source]¶
Capture the last rendered framebuffer as an RGBA numpy array.
Returns (height, width, 4) uint8 array. Must be called after _draw_frame().