"""Secondary-device SubViewport SRU recorder (D8 multi-GPU offload).
The cross-device offload coordinator (:class:`~..gpu.multi_device.SRUOffloadCoordinator`)
renders an offloaded SubViewport SRU on a *secondary* GPU and then transfers its
colour image to the primary for compositing. The per-device GPU recording is
delegated to this object so the shared :class:`~.forward.Renderer` is NOT made to
grow a multi-device entry point (the single-GPU path must stay byte-identical).
:class:`SecondarySRURenderer` wraps a ``Renderer`` already constructed + ``setup``
on a secondary device (via a :class:`~..gpu.secondary_engine.SecondaryRenderContext`
facade). It exposes one method, :meth:`render_sru_offscreen`, mirroring the primary
:meth:`~..scene_adapter.SceneAdapter.render_sru_from_plan` slice model but recording
into an OWN one-shot command buffer on the secondary device and submitting on the
secondary graphics queue (a secondary never shares the primary's frame command
buffer; its render must complete + signal before the cross-device transfer reads
the colour image).
Honest scope. This is rig-only: it runs on the 4x Arc Pro B70 rig, never on a
single-GPU box (no facade / secondary device is ever constructed there, so the
offload coordinator that would call this is never built). The code is import-safe
and structurally complete; the residency substitution it depends on (a secondary
``MeshHandle`` per primary mesh) is flagged where the primary CPU geometry must be
supplied (see :meth:`render_sru_offscreen`).
"""
from __future__ import annotations
import logging
from typing import Any
import vulkan as vk
from ..gpu.memory import begin_single_time_commands, end_single_time_commands
log = logging.getLogger(__name__)
__all__ = ["SecondarySRURenderer"]
[docs]
class SecondarySRURenderer:
"""Records one offloaded SubViewport SRU on a secondary device.
Constructed by the rig-side ``secondary_renderer_factory`` with the secondary
:class:`~..gpu.secondary_engine.SecondaryRenderContext` facade and a
``Renderer(facade)`` already set up on that device. The offload coordinator
calls :meth:`render_sru_offscreen` once per offloaded SRU per frame.
"""
def __init__(self, facade: Any, renderer: Any) -> None:
self._facade = facade
self._renderer = renderer
[docs]
def set_residency(self, residency: Any) -> None:
"""Bind the :class:`~..gpu.secondary_engine.SecondaryResidency` for handle remap.
The coordinator owns residency (it mirrors meshes/textures before calling
this); binding it here lets the recorder substitute each instance's primary
:class:`~..types.MeshHandle` for the secondary one. Optional: when unset the
recorder assumes the SRU's handles are already secondary-resident.
"""
self._residency = residency
[docs]
def render_sru_offscreen(self, sru: Any, target: Any) -> None:
"""Record + submit one SRU's draws into ``target`` on the secondary device.
Mirrors the primary :meth:`SceneAdapter.render_sru_from_plan` slice model on
the secondary renderer:
1. ``begin_frame`` the secondary renderer (clears its per-frame lists +
resets its indirect batches; this device records its OWN frame).
2. Remap each instance's primary :class:`MeshHandle` to the secondary one via
the bound residency (geometry was mirrored by the coordinator). FLAGGED:
requires the secondary mesh to be resident; an instance whose mesh is not
resident is skipped with a warning (it would index the wrong device's
buffers otherwise).
3. Install the SRU's single camera viewport, reserve a transform-SSBO slice,
upload the SRU transforms into it, and set materials/lights mirrored from
the primary (set by the coordinator via :meth:`set_materials`).
4. Allocate a one-shot command buffer on the secondary command pool, begin
the offscreen pass on ``target``, ``render_scene_content`` (``hdr_output``
0 so the offscreen image holds LDR), end the pass, overlay any Draw2D ops,
then submit on the secondary graphics queue and wait. The RenderTarget
leaves the colour image in ``SHADER_READ_ONLY_OPTIMAL``, ready for the
cross-device transfer's ``TRANSFER_SRC`` barrier.
"""
renderer = self._renderer
renderer.begin_frame()
instances = self._remap_instances(getattr(sru, "instances", []))
skinned = self._remap_instances(getattr(sru, "skinned_instances", []))
renderer._instances = instances
renderer._skinned_instances = skinned
vpm = renderer.viewport_manager
vpm.clear()
if sru.camera_view is not None and sru.camera_proj is not None:
vpm.create_viewport(0, 0, sru.width, sru.height, sru.camera_view, sru.camera_proj, None)
n_slots = len(instances) + len(skinned)
base = renderer._buffers.reserve_slots(n_slots)
renderer._first_instance_base = base
renderer._sru_id = sru.sru_id
renderer._buffers.upload_transforms(instances, upload_aabbs=False, base=base)
cmd = begin_single_time_commands(self._facade.ctx.device, self._facade.ctx.command_pool)
try:
target_clear = list(getattr(sru, "clear_colour", (0.0, 0.0, 0.0, 1.0)))
self._begin_target_pass(cmd, target, target_clear)
renderer._scene_renderer.render_scene_content(cmd, hdr_output=0)
vk.vkCmdEndRenderPass(cmd)
finally:
# Submit + wait on the secondary queue: the colour image is fully written
# (and in SHADER_READ_ONLY_OPTIMAL) before the cross-device transfer runs.
end_single_time_commands(
self._facade.ctx.device, self._facade.ctx.graphics_queue,
self._facade.ctx.command_pool, cmd,
)
def _remap_instances(self, instances: list) -> list:
"""Substitute each instance's primary MeshHandle for the secondary one.
Returns a new instance list with secondary handles. An instance whose mesh
is not resident on the secondary device is dropped with a one-time warning
(FLAGGED: the coordinator mirrors geometry via ``SecondaryResidency.ensure_meshes``;
a miss means the primary CPU geometry was not available to mirror).
"""
residency = getattr(self, "_residency", None)
if residency is None:
return list(instances)
out = []
for entry in instances:
mh = entry[0]
secondary = residency.secondary_mesh(id(mh))
if secondary is None:
log.warning("secondary SRU: mesh %s not resident on secondary device, skipping", id(mh))
continue
out.append((secondary, *entry[1:]))
return out
def _begin_target_pass(self, cmd: Any, target: Any, clear_colour: list) -> None:
"""Begin the offscreen colour+depth pass on a secondary-device RenderTarget."""
clear_values = [
vk.VkClearValue(color=vk.VkClearColorValue(float32=clear_colour)),
vk.VkClearValue(depthStencil=vk.VkClearDepthStencilValue(depth=1.0, stencil=0)),
]
rp_begin = vk.VkRenderPassBeginInfo(
renderPass=target.render_pass,
framebuffer=target.framebuffer,
renderArea=vk.VkRect2D(
offset=vk.VkOffset2D(x=0, y=0),
extent=vk.VkExtent2D(width=target.width, height=target.height),
),
clearValueCount=len(clear_values),
pClearValues=clear_values,
)
vk.vkCmdBeginRenderPass(cmd, rp_begin, vk.VK_SUBPASS_CONTENTS_INLINE)
vk.vkCmdSetViewport(cmd, 0, 1, [vk.VkViewport(
x=0.0, y=0.0, width=float(target.width), height=float(target.height), minDepth=0.0, maxDepth=1.0,
)])
vk.vkCmdSetScissor(cmd, 0, 1, [vk.VkRect2D(
offset=vk.VkOffset2D(x=0, y=0), extent=vk.VkExtent2D(width=target.width, height=target.height),
)])
[docs]
def set_materials(self, materials: Any) -> None:
"""Mirror the primary material SSBO contents onto the secondary renderer."""
self._renderer.set_materials(materials)
[docs]
def set_lights(self, lights: Any) -> None:
"""Mirror the primary light SSBO contents onto the secondary renderer."""
self._renderer.set_lights(lights)