Migration: RenderPipeline → handle-based wrapper¶
Problem¶
RenderPipeline is currently an owning C++ object that stores:
- handle_ — index in tc_pipeline pool (C-level, stores passes)
- name_ — duplicates tc_pipeline.name
- specs_ — resource specs
- fbo_pool_ — FBO cache for intermediate render targets
- shadow_arrays_ — shadow map texture arrays
It uses cpp_owner pointer in tc_pipeline to find itself from handle,
and py_wrapper to cache Python object reference.
This causes problems: - C++ creates pipeline, sets it on render target via handle — Python can't see it (py_wrapper not set) - Two ownership models (C++ unique_ptr in RenderingManager + Python wrapper) conflict - render target pipeline wrappers were not consistently reconstructed from C++ handles
Target¶
RenderPipeline becomes a lightweight handle wrapper (like TcViewport,
TcSceneRef). All data lives in tc_pipeline pool. No ownership semantics.
Steps¶
Phase 1: Move data into tc_pipeline pool¶
Files: tc_pipeline.h, tc_pipeline.c
- Add
void* render_cache+void (*render_cache_destructor)(void*)totc_pipelinestruct - Add C API:
tc_pipeline_get_render_cache(),tc_pipeline_set_render_cache(h, cache, destructor) - Free render_cache in
tc_pipeline_pool_free() - Remove
cpp_ownerandpy_wrapperfields and their API functions
Phase 2: Make RenderPipeline a thin handle wrapper¶
Files: render_pipeline.hpp, render_pipeline.cpp
- Remove fields:
name_,specs_,fbo_pool_,shadow_arrays_ - Add
PipelineRenderCachestruct (FBOPool + shadow_arrays + specs) — stored as render_cache in pool cache()method — lazy-creates PipelineRenderCache in render_cachename()reads from tc_pipeline pool,set_name()writes to poolspecs()/fbo_pool()/shadow_arrays()access via cache()- Remove destructor (does NOT destroy pipeline in pool)
- Explicit
destroy()method callstc_pipeline_destroy(handle_) - Remove move constructor/assignment (trivially copyable handle)
- Add
RenderPipeline(tc_pipeline_handle h)constructor
Phase 3: Update RenderingManager¶
Files: rendering_manager.hpp, rendering_manager.cpp
scene_pipelines_: change frommap<string, unique_ptr<RenderPipeline>>tomap<string, tc_pipeline_handle>clear_scene_pipelines(): calltc_pipeline_destroy()for each handlecompile_scene_pipeline(): returntc_pipeline_handleget_scene_pipeline(): returnRenderPipelineby value (from handle)create_pipeline()/make_default_pipeline(): returnRenderPipelineby valuePipelineFactorytypedef: returnRenderPipelineby value (ortc_pipeline_handle)mount_scene(): accepttc_pipeline_handleinstead ofRenderPipeline*render_viewport_offscreen()/render_scene_pipeline_offscreen(): createRenderPipelinewrapper on stack from handle- Remove
from_handle()static method (just use constructor)
Phase 4: Update PullRenderingManager¶
Files: pull_rendering_manager.hpp, pull_rendering_manager.cpp
Same changes as Phase 3 where applicable.
Phase 5: Update viewport bindings¶
Files: viewport_module.cpp
- Pipeline getter: create
RenderPipelinefrom handle viaRenderPipeline.from_handle()Python-side - Pipeline setter: extract handle from Python object, set on viewport
- Remove all
py_wrapper/Py_INCREF/Py_DECREFcode - Serialize: read name directly from
tc_pipeline_get_name(handle)
Phase 6: Update Python bindings¶
Files: render_pipeline_bindings.cpp, rendering_manager_bindings.cpp
- Add
RenderPipeline.from_handle(index, generation)static method - Remove move semantics from binding (RenderPipeline is copyable)
_pipeline_handleproperty returns (index, generation) tuple- Adapt
rendering_manager_bindings.cppto returnRenderPipelineby value - Pipeline factory callback: return RenderPipeline by value
Phase 7: Update Python editor code¶
Files: rendering_controller.py, editor_scene_attachment.py,
viewport_inspector.py
EditorSceneAttachment: pipeline created via factory, assigned to render target — should work as before (Python sets pipeline, py setter stores handle)rendering_controller.sync_viewport_configs_to_scene():render_target.pipeline.namenow works because getter creates wrapper from handleviewport_inspector._select_viewport_pipeline(): same — works via getter- Remove workaround code (re-assign pipeline after attach)
Phase 8: Cleanup¶
- Remove
tc_pipeline_get_cpp_owner/tc_pipeline_set_cpp_owner - Remove
tc_pipeline_get_py_wrapper/tc_pipeline_set_py_wrapper - Remove
cpp_owner/py_wrapperfromtc_pipelinestruct - Remove debug logs added during investigation
Testing¶
- Editor: create Display 0, viewport main, assign Default pipeline, save, reload — pipeline visible in inspector and renders
- Play/Stop game mode — viewport pipeline preserved
- Scene pipeline compilation — still works
- Pipeline inspector — can change pipeline, viewport updates
- Multiple viewports with different pipelines
Notes¶
PipelineFactorycurrently returnsRenderPipeline*— change to return by value. Python factory callback creates pipeline, returns wrapper.RenderEngine::render_view_to_fbo()takesRenderPipeline*— change to reference or value.- FBO ownership: FBOs in
fbo_pool_are per-pipeline GPU resources. Moving to render_cache means they follow the pipeline handle in the pool.tc_pipeline_destroy()frees render_cache → FBOs freed.