perf: render pipeline optimizations — sample scaling, USD logging, persistent BVH
Task 1: Resolution-aware sample count - Auto-scale samples for resolutions <= 1024: max(32, samples * max_dim / 2048) - 512x512 thumbnails: 256 → 64 samples (75% GPU savings) - Thumbnail tasks capped at 64 samples via context manager - 2048x2048 HQ renders unchanged Task 2: USD path preference audit + logging - Verified USD master path is correctly preferred over GLB tessellation - Added clear emit() messages: "Using USD master" vs "No USD master — GLB path" - Dynamic render log label: "USD → Blender" vs "STEP → GLB → Blender" Task 3: Persistent BVH for turntable animations - Added scene.render.use_persistent_data = True before frame loop - BVH acceleration structure cached between frames (not rebuilt per frame) - Applies to both camera orbit and object rotation modes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -165,6 +165,11 @@ def render_order_line_task(self, order_line_id: str):
|
||||
_usd_candidate.name, cad_file.id,
|
||||
)
|
||||
|
||||
if usd_render_path:
|
||||
emit(order_line_id, "Using USD master for render (skipping GLB tessellation)")
|
||||
else:
|
||||
emit(order_line_id, "No USD master available — using GLB tessellation path")
|
||||
|
||||
part_colors = {}
|
||||
if cad_file and cad_file.parsed_objects:
|
||||
parsed_names = cad_file.parsed_objects.get("objects", [])
|
||||
@@ -312,6 +317,16 @@ def render_order_line_task(self, order_line_id: str):
|
||||
denoising_quality = str(rs.get("denoising_quality", ""))
|
||||
denoising_use_gpu = str(rs.get("denoising_use_gpu", ""))
|
||||
|
||||
# Auto-scale samples for lower resolutions (thumbnails/previews).
|
||||
# Only applies when the output type provides both samples and dimensions.
|
||||
if render_samples and render_width and render_height:
|
||||
max_dim = max(render_width, render_height)
|
||||
if max_dim <= 1024:
|
||||
scaled = max(32, int(render_samples * max_dim / 2048))
|
||||
if scaled < render_samples:
|
||||
emit(order_line_id, f"Auto-scaled samples {render_samples} \u2192 {scaled} for {render_width}x{render_height}")
|
||||
render_samples = scaled
|
||||
|
||||
transparent_bg = bool(line.output_type and line.output_type.transparent_bg)
|
||||
cycles_device_val = (line.output_type.cycles_device or "auto") if line.output_type else "auto"
|
||||
|
||||
@@ -395,7 +410,8 @@ def render_order_line_task(self, order_line_id: str):
|
||||
logger.error("Turntable render failed for %s: %s", order_line_id, exc)
|
||||
else:
|
||||
# ── Still image path ────────────────────────────────────────
|
||||
emit(order_line_id, f"Calling renderer (STEP → GLB → Blender) {render_width or 'default'}x{render_height or 'default'}{' [transparent]' if transparent_bg else ''}{f' engine={render_engine}' if render_engine else ''}{f' samples={render_samples}' if render_samples else ''}{tmpl_info}")
|
||||
_render_path_label = "USD → Blender" if usd_render_path else "STEP → GLB → Blender"
|
||||
emit(order_line_id, f"Calling renderer ({_render_path_label}) {render_width or 'default'}x{render_height or 'default'}{' [transparent]' if transparent_bg else ''}{f' engine={render_engine}' if render_engine else ''}{f' samples={render_samples}' if render_samples else ''}{tmpl_info}")
|
||||
pl.step_start("blender_still", {"width": render_width, "height": render_height})
|
||||
from app.services.step_processor import render_to_file
|
||||
|
||||
|
||||
@@ -14,6 +14,40 @@ from app.core.pipeline_logger import PipelineLogger
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Maximum samples for thumbnail renders (512x512).
|
||||
# Full-resolution renders use 256+ samples; thumbnails don't need more than 64.
|
||||
_THUMBNAIL_SAMPLE_CAP = 64
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _capped_thumbnail_samples():
|
||||
"""Temporarily cap render samples for thumbnail renders.
|
||||
|
||||
Thumbnails are 512x512 — using 256 Cycles samples is wasteful.
|
||||
This patches _get_all_settings in step_processor to cap samples
|
||||
at _THUMBNAIL_SAMPLE_CAP for the duration of the thumbnail render.
|
||||
"""
|
||||
import app.services.step_processor as _sp
|
||||
_original = _sp._get_all_settings
|
||||
|
||||
def _patched() -> dict[str, str]:
|
||||
settings = _original()
|
||||
for key in ("blender_cycles_samples", "blender_eevee_samples"):
|
||||
try:
|
||||
val = int(settings.get(key, "256"))
|
||||
if val > _THUMBNAIL_SAMPLE_CAP:
|
||||
logger.info("Capping thumbnail %s: %d -> %d", key, val, _THUMBNAIL_SAMPLE_CAP)
|
||||
settings[key] = str(_THUMBNAIL_SAMPLE_CAP)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
return settings
|
||||
|
||||
_sp._get_all_settings = _patched
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
_sp._get_all_settings = _original
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _pipeline_session(tenant_id: str | None = None):
|
||||
@@ -67,11 +101,12 @@ def render_step_thumbnail(self, cad_file_id: str):
|
||||
except Exception:
|
||||
logger.warning(f"step_file_hash computation failed for {cad_file_id} (non-fatal)")
|
||||
|
||||
# ── Render thumbnail ──────────────────────────────────────────────────
|
||||
# ── Render thumbnail (with capped samples for 512x512) ──────────────
|
||||
try:
|
||||
from app.services.step_processor import regenerate_cad_thumbnail
|
||||
pl.info("render_step_thumbnail", "Calling regenerate_cad_thumbnail")
|
||||
success = regenerate_cad_thumbnail(cad_file_id, part_colors={})
|
||||
with _capped_thumbnail_samples():
|
||||
success = regenerate_cad_thumbnail(cad_file_id, part_colors={})
|
||||
if not success:
|
||||
raise RuntimeError("regenerate_cad_thumbnail returned False")
|
||||
except Exception as exc:
|
||||
@@ -166,7 +201,8 @@ def regenerate_thumbnail(self, cad_file_id: str, part_colors: dict):
|
||||
|
||||
try:
|
||||
from app.services.step_processor import regenerate_cad_thumbnail
|
||||
success = regenerate_cad_thumbnail(cad_file_id, part_colors)
|
||||
with _capped_thumbnail_samples():
|
||||
success = regenerate_cad_thumbnail(cad_file_id, part_colors)
|
||||
if not success:
|
||||
raise RuntimeError("regenerate_cad_thumbnail returned False")
|
||||
except Exception as exc:
|
||||
|
||||
Reference in New Issue
Block a user