"""WorldEnvironment synchronisation and custom post-process orchestration."""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any
import vulkan as vk
if TYPE_CHECKING:
from .forward import ForwardRenderer
from .post_process import PostProcessPass
__all__ = ["EnvironmentSync"]
log = logging.getLogger(__name__)
[docs]
class EnvironmentSync:
"""Syncs WorldEnvironment node properties to renderer settings and manages custom post-processing."""
def __init__(self, renderer: ForwardRenderer) -> None:
self._r = renderer
[docs]
def sync_world_environment(self) -> None:
"""Sync WorldEnvironment node properties to renderer settings."""
r = self._r
from simvx.core.world_environment import WorldEnvironment
tree = getattr(r._engine, "_scene_tree", None) or getattr(r._engine, "scene_tree", None)
if not tree or not tree.root:
return
env = tree.root.find(WorldEnvironment)
if not env:
return
pp = r._post_process
if pp:
pp.bloom_enabled = env.bloom_enabled
pp.bloom_threshold = env.bloom_threshold
pp.bloom_intensity = env.bloom_intensity
pp.ssao_enabled = env.ssao_enabled
pp.exposure = env.tonemap_exposure
pp.dof_enabled = env.dof_enabled
pp.dof_focus_distance = env.dof_focus_distance
pp.dof_focus_range = env.dof_focus_range
pp.motion_blur_enabled = env.motion_blur_enabled
pp.motion_blur_intensity = env.motion_blur_intensity
pp.motion_blur_samples = env.motion_blur_samples
pp.grain_enabled = env.film_grain_enabled
pp.grain_intensity = env.film_grain_intensity
pp.vignette_enabled = env.vignette_enabled
pp.vignette_intensity = env.vignette_intensity
pp.vignette_smoothness = env.vignette_smoothness
pp.chromatic_aberration_enabled = env.chromatic_aberration_enabled
pp.chromatic_aberration_intensity = env.chromatic_aberration_intensity
if r._ssao_pass:
r._ssao_pass.enabled = env.ssao_enabled
# Fog is applied post-tonemap in the fullscreen pass (not as a compute pass)
mode_map = {"linear": 0.0, "exponential": 1.0, "exponential_squared": 2.0}
if pp:
pp.fog_enabled = env.fog_enabled
pp.fog_colour = env.fog_colour[:3]
pp.fog_density = env.fog_density
pp.fog_start = env.fog_start
pp.fog_end = env.fog_end
pp.fog_mode = mode_map.get(env.fog_mode, 1.0)
# Sync sky colour to engine clear colour
if env.sky_mode == "colour":
c = env.sky_colour_top
if len(c) >= 4:
r._engine.clear_colour = [c[0], c[1], c[2], c[3]]
elif len(c) >= 3:
r._engine.clear_colour = [c[0], c[1], c[2], 1.0]
cg = getattr(pp, "colour_grading", None) if pp else None
if cg and env.colour_grading_enabled:
cg.enabled = True
[docs]
def run_custom_post_process(self, cmd: Any, pp: PostProcessPass) -> None:
"""Execute custom user effects between built-in post-processing and tonemap."""
r = self._r
if not r._custom_pp or not pp.hdr_target:
return
# Collect effects from WorldEnvironment nodes in the scene tree
effects = self._gather_post_process_effects()
if not effects:
return
r._custom_pp.sync_effects(effects)
if not r._custom_pp.has_effects:
return
w, h = r._engine.extent
hdr_view = pp.hdr_target.color_view
depth_view = pp.hdr_target.depth_view
result_view = r._custom_pp.render(cmd, hdr_view, depth_view, w, h)
# If custom effects produced output, update tonemap's HDR input descriptor
if result_view and result_view != hdr_view:
self._update_tonemap_hdr_input(result_view)
def _gather_post_process_effects(self) -> list:
"""Collect PostProcessEffects from all WorldEnvironment nodes in the scene."""
from simvx.core.world_environment import WorldEnvironment
r = self._r
tree = getattr(r._engine, "_scene_tree", None) or getattr(r._engine, "scene_tree", None)
if not tree:
return []
root = getattr(tree, "root", None)
if not root:
return []
effects = []
for node in root.find_all(WorldEnvironment):
effects.extend(node.get_post_processes())
effects.sort(key=lambda e: e.order)
return effects
def _update_tonemap_hdr_input(self, new_hdr_view: Any) -> None:
"""Rewrite the tonemap descriptor set binding 0 to point at the custom output."""
r = self._r
pp = r._post_process
if not pp or not pp._descriptor_set or not pp._sampler:
return
hdr_info = vk.VkDescriptorImageInfo(
sampler=pp._sampler, imageView=new_hdr_view,
imageLayout=vk.VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
)
vk.vkUpdateDescriptorSets(r._engine.ctx.device, 1, [vk.VkWriteDescriptorSet(
dstSet=pp._descriptor_set, dstBinding=0, dstArrayElement=0,
descriptorCount=1, descriptorType=vk.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
pImageInfo=[hdr_info],
)], 0, None)