Fix: Separate normal renderer output from diagnostics
This commit is contained in:
1 parent
69a8cb5647
commit
af8b83007f
11 files changed
+249
-90
No files matched your search
@@ -299,6 +299,7 @@ test: $(CAMERA_TEST_TARGETS) $(TEST_TARGET) $(ADAPTIVE_GEODESIC_TEST_TARGET) $(A
|
||||
$(SENSOR_BLOOM_TEST_TARGET)
|
||||
python3 tests/test_camera_cli.py $(BUILD_DIR) $(TEST_OUT_DIR)
|
||||
python3 tests/test_adaptive_cli.py $(BUILD_DIR) $(TEST_OUT_DIR)
|
||||
python3 tests/test_output_streams.py $(BUILD_DIR) $(TEST_OUT_DIR)
|
||||
|
||||
tone-map-test: $(TONE_MAP_TEST_TARGET)
|
||||
$(TONE_MAP_TEST_TARGET)
|
||||
|
||||
+7
-7
@@ -301,7 +301,7 @@ static void report_group(const DummyPsfSink *sink, int full_only,
|
||||
if (!full_only || sink->samples[i].events == sink->event_capacity)
|
||||
++count;
|
||||
if (count == 0) {
|
||||
fprintf(stderr, "Dummy PSF %s chunks: none\n", label);
|
||||
fprintf(stdout, "Dummy PSF %s chunks: none\n", label);
|
||||
return;
|
||||
}
|
||||
double *events = count > SIZE_MAX / (6 * sizeof *events)
|
||||
@@ -338,7 +338,7 @@ static void report_group(const DummyPsfSink *sink, int full_only,
|
||||
"maximum_successive_triangle_jump_px"};
|
||||
for (size_t field = 0; field < 6; ++field) {
|
||||
qsort(series[field], count, sizeof **series, compare_double);
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Dummy PSF %s %s: min=%.3f p10=%.3f p25=%.3f p50=%.3f "
|
||||
"p75=%.3f p90=%.3f p99=%.3f max=%.3f\n",
|
||||
label, names[field], series[field][0],
|
||||
@@ -349,7 +349,7 @@ static void report_group(const DummyPsfSink *sink, int full_only,
|
||||
percentile(series[field], count, 0.90),
|
||||
percentile(series[field], count, 0.99), series[field][count - 1]);
|
||||
}
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Dummy PSF %s adaptive criterion: %zu/%zu chunks tile16-eligible "
|
||||
"(events>=8192 and events/occupied_32px_tiles>=32)\n",
|
||||
label, adaptive, count);
|
||||
@@ -386,7 +386,7 @@ static void report_support_group(const DummyPsfSink *sink, int full_only,
|
||||
for (size_t field = 0; field < 5; ++field) {
|
||||
double *series = values + field * count;
|
||||
qsort(series, count, sizeof *series, compare_double);
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Dummy PSF %s %s: min=%.3f p10=%.3f p25=%.3f p50=%.3f "
|
||||
"p75=%.3f p90=%.3f p99=%.3f max=%.3f\n",
|
||||
label, names[field], series[0],
|
||||
@@ -395,7 +395,7 @@ static void report_support_group(const DummyPsfSink *sink, int full_only,
|
||||
percentile(series, count, 0.90), percentile(series, count, 0.99),
|
||||
series[count - 1]);
|
||||
}
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Dummy PSF %s support totals: references=%zu tasks=%zu summed_first_pass=%.3f s\n",
|
||||
label, total_refs, total_tasks, total_pass);
|
||||
free(values);
|
||||
@@ -407,7 +407,7 @@ void dummy_psf_sink_report(const DummyPsfSink *sink) {
|
||||
size_t full = 0;
|
||||
for (size_t i = 0; i < sink->count; ++i)
|
||||
full += sink->samples[i].events == sink->event_capacity;
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Dummy PSF diagnostic only: no HDR or image was accumulated/written.\n"
|
||||
"Dummy PSF chunks: total=%zu full=%zu partial=%zu events=%zu "
|
||||
"capacity=%zu selector_tile=%dpx\n",
|
||||
@@ -416,7 +416,7 @@ void dummy_psf_sink_report(const DummyPsfSink *sink) {
|
||||
report_group(sink, 0, "all");
|
||||
report_group(sink, 1, "full");
|
||||
if (sink->support_stats) {
|
||||
fputs("Dummy PSF support diagnostic: 16px rectangular cache-limited first pass only; no references were materialized or uploaded.\n", stderr);
|
||||
fputs("Dummy PSF support diagnostic: 16px rectangular cache-limited first pass only; no references were materialized or uploaded.\n", stdout);
|
||||
report_support_group(sink, 0, "all");
|
||||
report_support_group(sink, 1, "full");
|
||||
}
|
||||
|
||||
+4
-4
@@ -2260,7 +2260,7 @@ size_t frame_splat_catalog(const FrameLensMesh *mesh,
|
||||
(size_t)dummy_workers, completed, 1);
|
||||
}
|
||||
if (psf_event_sink_destroy(&owner)) dummy_failed = 1;
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Dummy PSF producers: %d workers; classification/chunk wall %.3f s\n",
|
||||
dummy_workers, omp_get_wtime() - dummy_start);
|
||||
copy_psf_splat_stats(psf_stats, (CatalogSplatStats){
|
||||
@@ -2349,14 +2349,14 @@ size_t frame_splat_catalog(const FrameLensMesh *mesh,
|
||||
}
|
||||
omp_destroy_lock(&submit_lock);
|
||||
if (psf_event_sink_destroy(&owner)) hip_failed = 1;
|
||||
fprintf(stderr, "HIP producers: %d workers; summed generation %.3f s, submission/fallback %.3f s; wall %.3f s\n",
|
||||
fprintf(stdout, "HIP producers: %d workers; summed generation %.3f s, submission/fallback %.3f s; wall %.3f s\n",
|
||||
hip_workers, hip_generate_seconds, hip_submit_seconds, omp_get_wtime() - hip_start);
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"HIP PSF accumulation: atomic %zu batches, tile16 %zu batches; selector %.3f s, binning %.3f s\n",
|
||||
owner.hip_timing.atomic_batch_count, owner.hip_timing.tile_batch_count,
|
||||
owner.hip_timing.selection_seconds, owner.hip_timing.bin_seconds);
|
||||
if (owner.hip_timing.tile_batch_count)
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"HIP PSF tile workload: references %zu, tasks %zu, merges %zu; peak references/chunk %zu\n",
|
||||
owner.hip_timing.tile_reference_count, owner.hip_timing.tile_task_count,
|
||||
owner.hip_timing.tile_merge_count,
|
||||
|
||||
+2
-2
@@ -605,7 +605,7 @@ static int submit_prepared(HipPsfSink *sink, HipPsfPreparedChunk *prepared,
|
||||
sink->timing.event_count += event_count;
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (std::chrono::duration<double>(now - sink->last_report).count() >= 5.0) {
|
||||
std::fprintf(stderr,
|
||||
std::fprintf(stdout,
|
||||
"HIP PSF progress: %zu submitted / %zu completed events; %zu / %zu batches timed; kernel %.3f s\n",
|
||||
sink->timing.event_count, sink->completed_events,
|
||||
sink->timing.timed_batch_count, sink->timing.batch_count,
|
||||
@@ -641,7 +641,7 @@ static int submit_prepared(HipPsfSink *sink, HipPsfPreparedChunk *prepared,
|
||||
sink->timing.event_count += event_count;
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (std::chrono::duration<double>(now - sink->last_report).count() >= 5.0) {
|
||||
std::fprintf(stderr, "HIP PSF progress: %zu submitted / %zu completed events; %zu / %zu batches timed; kernel %.3f s\n",
|
||||
std::fprintf(stdout, "HIP PSF progress: %zu submitted / %zu completed events; %zu / %zu batches timed; kernel %.3f s\n",
|
||||
sink->timing.event_count, sink->completed_events, sink->timing.timed_batch_count,
|
||||
sink->timing.batch_count, sink->timing.kernel_seconds);
|
||||
sink->last_report = now;
|
||||
|
||||
+64
-59
@@ -352,7 +352,7 @@ static int apply_sensor_bloom(const Settings *s, double *hdr, int width,
|
||||
stderr);
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Sensor bloom: saturated=%zu clamped=%zu iterations=%zu/%zu "
|
||||
"peak=%.6g max_overflow=%.6g absorbed=%.6g boundary=%.6g "
|
||||
"residual=%.6g elapsed=%.6fs\n",
|
||||
@@ -390,7 +390,8 @@ static int write_frame_outputs(const Settings *s, const FrameLensMesh *mesh,
|
||||
const int write_result =
|
||||
write_rgb8_timed(s, paths->output_path, rgb8, width, height, timing);
|
||||
free(rgb8);
|
||||
fprintf(stderr, "Rendered %zu images from %zu catalog stars to %s (%s%s)\n",
|
||||
fprintf(write_result == 0 ? stdout : stderr,
|
||||
"Rendered %zu images from %zu catalog stars to %s (%s%s)\n",
|
||||
images, stars, paths->output_path,
|
||||
write_result == 0 ? "ok" : "write failed", note);
|
||||
if (write_result)
|
||||
@@ -407,7 +408,7 @@ static int write_frame_outputs(const Settings *s, const FrameLensMesh *mesh,
|
||||
return -1;
|
||||
}
|
||||
free(mesh_rgb8);
|
||||
fprintf(stderr, "Wrote mesh overlay image: %s\n", paths->mesh_path);
|
||||
fprintf(stdout, "Wrote mesh overlay image: %s\n", paths->mesh_path);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -894,7 +895,7 @@ static void report_splat_progress(void *context, FrameSplatProgressStage stage,
|
||||
return;
|
||||
if (stage == FRAME_SPLAT_PROGRESS_PREFETCH_END && progress->all_sky_catalog) {
|
||||
const CatalogPrefetchStats *prefetch = progress->prefetch;
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Catalog prefetch: %zu requested, %zu newly loaded (%zu stars), "
|
||||
"%zu unavailable in %.3f s; %d loader workers\n",
|
||||
prefetch->requested_tiles, prefetch->newly_loaded_tiles,
|
||||
@@ -905,24 +906,24 @@ static void report_splat_progress(void *context, FrameSplatProgressStage stage,
|
||||
return;
|
||||
switch (stage) {
|
||||
case FRAME_SPLAT_PROGRESS_PREFETCH_BEGIN:
|
||||
fprintf(stderr, "Frame %zu: finding and prefetching catalog tiles...\n",
|
||||
fprintf(stdout, "Frame %zu: finding and prefetching catalog tiles...\n",
|
||||
progress->frame_id);
|
||||
break;
|
||||
case FRAME_SPLAT_PROGRESS_PREFETCH_END:
|
||||
fprintf(stderr, "Frame %zu: catalog prefetch finished (%zu candidate tiles).\n",
|
||||
fprintf(stdout, "Frame %zu: catalog prefetch finished (%zu candidate tiles).\n",
|
||||
progress->frame_id, total);
|
||||
break;
|
||||
case FRAME_SPLAT_PROGRESS_BEGIN:
|
||||
progress->splat_start = omp_get_wtime();
|
||||
fprintf(stderr, "Frame %zu: splatting %zu lens triangles...\n",
|
||||
fprintf(stdout, "Frame %zu: splatting %zu lens triangles...\n",
|
||||
progress->frame_id, total);
|
||||
break;
|
||||
case FRAME_SPLAT_PROGRESS_END:
|
||||
#ifdef PSF_BACKEND_DUMMY
|
||||
fprintf(stderr, "Frame %zu: catalog classification finished in %.1f s; reporting chunk statistics...\n",
|
||||
fprintf(stdout, "Frame %zu: catalog classification finished in %.1f s; reporting chunk statistics...\n",
|
||||
progress->frame_id, omp_get_wtime() - progress->splat_start);
|
||||
#else
|
||||
fprintf(stderr, "Frame %zu: catalog splatting finished in %.1f s; writing image...\n",
|
||||
fprintf(stdout, "Frame %zu: catalog splatting finished in %.1f s; writing image...\n",
|
||||
progress->frame_id, omp_get_wtime() - progress->splat_start);
|
||||
#endif
|
||||
break;
|
||||
@@ -936,13 +937,13 @@ static void report_splat_worker_progress(void *context, size_t worker_id,
|
||||
if (progress == NULL || !progress->verbose)
|
||||
return;
|
||||
if (finished)
|
||||
fprintf(stderr, "Frame %zu: splat worker %zu/%zu finished after %zu local triangles.\n",
|
||||
fprintf(stdout, "Frame %zu: splat worker %zu/%zu finished after %zu local triangles.\n",
|
||||
progress->frame_id, worker_id + 1, worker_count, triangle_count);
|
||||
else if (triangle_count == 0)
|
||||
fprintf(stderr, "Frame %zu: splat worker %zu/%zu started.\n",
|
||||
fprintf(stdout, "Frame %zu: splat worker %zu/%zu started.\n",
|
||||
progress->frame_id, worker_id + 1, worker_count);
|
||||
else
|
||||
fprintf(stderr, "Frame %zu: splat worker %zu/%zu reached %zu local triangles.\n",
|
||||
fprintf(stdout, "Frame %zu: splat worker %zu/%zu reached %zu local triangles.\n",
|
||||
progress->frame_id, worker_id + 1, worker_count, triangle_count);
|
||||
}
|
||||
|
||||
@@ -968,12 +969,12 @@ static void report_frame_refinement(void *context, size_t generation,
|
||||
if (settings == NULL || !settings->verbose)
|
||||
return;
|
||||
if (!finished)
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Frame 0: refinement generation %zu tracing %zu samples "
|
||||
"from %zu vertices and %zu triangles.\n",
|
||||
generation, sample_count, vertex_count, triangle_count);
|
||||
else
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Frame 0: refinement generation %zu finished; added %d vertices, "
|
||||
"now %zu vertices and %zu triangles.\n",
|
||||
generation, added_vertices, vertex_count, triangle_count);
|
||||
@@ -1089,7 +1090,7 @@ static void report_boundary_stats(const Settings *s,
|
||||
if (out_total != NULL)
|
||||
*out_total = total;
|
||||
if (s->verbose && frame_count > 0)
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Boundary accounting: EEE=%zu DDD=%zu EED/EDD=%zu UUD/UDD=%zu "
|
||||
"U+E=%zu UUU=%zu error=%zu; approx-black=%zu (%.6g px^2), "
|
||||
"retries=%zu, budget-incomplete=%zu.\n",
|
||||
@@ -1098,7 +1099,7 @@ static void report_boundary_stats(const Settings *s,
|
||||
total.approx_black_triangles, total.approx_black_area_pixels2,
|
||||
total.retry_requests, total.budget_incomplete_triangles);
|
||||
if (s->verbose && total.approx_black_triangles)
|
||||
fprintf(stderr, "Approx-black achieved scale: max edge=%.6g px, max area=%.6g px^2, level stops=%zu.\n",
|
||||
fprintf(stdout, "Approx-black achieved scale: max edge=%.6g px, max area=%.6g px^2, level stops=%zu.\n",
|
||||
total.approx_black_max_edge_pixels, total.approx_black_max_area_pixels2,
|
||||
total.approx_black_level_stops);
|
||||
}
|
||||
@@ -1134,7 +1135,7 @@ static void report_trace_stats(const Settings *s, const FrameTraceStats *stats,
|
||||
const char *label) {
|
||||
if (!s->verbose)
|
||||
return;
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"%s trace cost: accepted=%" PRIu64 " rejected=%" PRIu64
|
||||
" rhs=%" PRIu64 " over %zu vertices (saturated=%zu).\n",
|
||||
label, stats->accepted_steps, stats->rejected_steps,
|
||||
@@ -1400,7 +1401,7 @@ static void report_trace_config(const Settings *s,
|
||||
const char *label) {
|
||||
if (!s->verbose)
|
||||
return;
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"%s: integrator=%s step=%.8g max_steps=%u threshold=%.8g "
|
||||
"atol=(x %.8g, Pi %.8g, L %.8g) rtol=%.8g bounds=[%.8g, %.8g] "
|
||||
"max_rejections=%u lookback=%.8g\n",
|
||||
@@ -1494,14 +1495,14 @@ static int build_observer(const Settings *s, const SpacetimeSource *spacetime,
|
||||
|
||||
static void report_psf_splat(const Settings *s, const PsfSplatStats *stats) {
|
||||
if (s->fast_mode) {
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Fast PSF splats: deposited %zu, wing-clipped %zu, discarded "
|
||||
"below min-Y %zu\n",
|
||||
stats->cached_splats, stats->cached_wing_clipped,
|
||||
stats->discarded_below_min_y);
|
||||
return;
|
||||
}
|
||||
psf_kernel_cache_report(&s->psf_cache, stats, stderr);
|
||||
psf_kernel_cache_report(&s->psf_cache, stats, stdout);
|
||||
}
|
||||
|
||||
static int render_observer_frame(const Settings *s, StarCatalog *catalog,
|
||||
@@ -1521,7 +1522,7 @@ static int render_observer_frame(const Settings *s, StarCatalog *catalog,
|
||||
return -1;
|
||||
}
|
||||
if (s->verbose)
|
||||
fprintf(stderr, "Frame 0: tracing %zu initial rays from %zu mesh triangles...\n",
|
||||
fprintf(stdout, "Frame 0: tracing %zu initial rays from %zu mesh triangles...\n",
|
||||
mesh.vertex_count, mesh.triangle_count);
|
||||
const double initial_trace_start = omp_get_wtime();
|
||||
if (frame_lens_mesh_trace(&mesh, spacetime, observer, &trace)) {
|
||||
@@ -1530,10 +1531,10 @@ static int render_observer_frame(const Settings *s, StarCatalog *catalog,
|
||||
return -1;
|
||||
}
|
||||
if (s->verbose)
|
||||
fprintf(stderr, "Frame 0: initial ray trace finished in %.3f s.\n",
|
||||
fprintf(stdout, "Frame 0: initial ray trace finished in %.3f s.\n",
|
||||
omp_get_wtime() - initial_trace_start);
|
||||
if (s->refinement.max_level > 0 && s->verbose)
|
||||
fprintf(stderr, "Frame 0: starting adaptive ray-trace refinement (max level %u)...\n",
|
||||
fprintf(stdout, "Frame 0: starting adaptive ray-trace refinement (max level %u)...\n",
|
||||
s->refinement.max_level);
|
||||
const double refinement_start = omp_get_wtime();
|
||||
if (frame_lens_mesh_refine_with_progress(
|
||||
@@ -1544,7 +1545,7 @@ static int render_observer_frame(const Settings *s, StarCatalog *catalog,
|
||||
return -1;
|
||||
}
|
||||
if (s->refinement.max_level > 0 && s->verbose)
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Frame 0: adaptive ray-trace refinement finished in %.3f s; "
|
||||
"%zu vertices, %zu triangles.\n",
|
||||
omp_get_wtime() - refinement_start, mesh.vertex_count,
|
||||
@@ -1576,11 +1577,11 @@ static int render_observer_frame(const Settings *s, StarCatalog *catalog,
|
||||
free(hdr);
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr, "Wrote lens map: %s (%zu vertices, %zu triangles)\n",
|
||||
fprintf(stdout, "Wrote lens map: %s (%zu vertices, %zu triangles)\n",
|
||||
s->lens_map_output_path, mesh.vertex_count, mesh.triangle_count);
|
||||
}
|
||||
if (s->verbose)
|
||||
fprintf(stderr, "Frame 0: traced %zu lens vertices; starting catalog render.\n",
|
||||
fprintf(stdout, "Frame 0: traced %zu lens vertices; starting catalog render.\n",
|
||||
mesh.vertex_count);
|
||||
CatalogPrefetchStats prefetch = {0};
|
||||
PsfSplatStats psf_stats = {0};
|
||||
@@ -1654,7 +1655,7 @@ static int trace_movie_generation(Movie *movie, const Settings *s,
|
||||
}
|
||||
if (ray_count == 0) return 0;
|
||||
if (s->verbose)
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Ray trace generation %zu: collected %zu new samples across %zu frames.\n",
|
||||
generation, ray_count, movie->frame_count);
|
||||
if (ray_pool_init(&rays, ray_count)) return -1;
|
||||
@@ -1712,7 +1713,7 @@ static int trace_movie_generation(Movie *movie, const Settings *s,
|
||||
ray_pool_status_counts(&rays, &pending_after, &active_after,
|
||||
&terminated_after, &unresolved_after, &failed_after);
|
||||
if (s->verbose)
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Ray trace generation %zu, slab %zu [%.6g, %.6g]: activated %zu; "
|
||||
"live %zu -> %zu, terminated %zu, unresolved %zu, failed %zu.\n",
|
||||
generation, ++slab_id, slab_hi, slab_lo,
|
||||
@@ -1729,7 +1730,7 @@ static int trace_movie_generation(Movie *movie, const Settings *s,
|
||||
}
|
||||
ray_pool_destroy(&rays);
|
||||
if (s->verbose)
|
||||
fprintf(stderr, "Ray trace generation %zu: installing endpoints and refining meshes.\n",
|
||||
fprintf(stdout, "Ray trace generation %zu: installing endpoints and refining meshes.\n",
|
||||
generation);
|
||||
for (size_t f = 0; f < movie->frame_count; ++f) {
|
||||
/* Frames converge independently; only finish those traced this pass. */
|
||||
@@ -1744,11 +1745,11 @@ static int trace_movie_generation(Movie *movie, const Settings *s,
|
||||
}
|
||||
total_added += (size_t)added;
|
||||
if (s->verbose && added > 0)
|
||||
fprintf(stderr, "Ray trace generation %zu: frame %zu added %d vertices.\n",
|
||||
fprintf(stdout, "Ray trace generation %zu: frame %zu added %d vertices.\n",
|
||||
generation, movie->frames[f].frame_id, added);
|
||||
}
|
||||
if (s->verbose)
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Ray trace generation %zu: refinement finished; added %zu vertices "
|
||||
"across %zu frames.\n",
|
||||
generation, total_added, movie->frame_count);
|
||||
@@ -1792,7 +1793,7 @@ static void report_movie_timing_frame(const Settings *s, size_t frame_id,
|
||||
const MovieFrameTiming *t) {
|
||||
if (!s->verbose)
|
||||
return;
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Movie frame %zu timing: catalog_mark=%.4f catalog_load=%.4f "
|
||||
"fast_clear=%.4f splat=%.4f fftw=%.4f tone_map=%.4f output=%.4f "
|
||||
"wait=%.4f total=%.4f\n",
|
||||
@@ -1805,19 +1806,19 @@ static void report_movie_timing_frame(const Settings *s, size_t frame_id,
|
||||
static void report_movie_timing_summary(const MovieTimingAccumulator *acc) {
|
||||
if (acc->frames == 0)
|
||||
return;
|
||||
fprintf(stderr, "Movie timing total (%zu frames):", acc->frames);
|
||||
fprintf(stdout, "Movie timing total (%zu frames):", acc->frames);
|
||||
for (size_t i = 0; i < MOVIE_TIMING_COUNT; ++i)
|
||||
fprintf(stderr, " %s=%.4f", movie_timing_names[i], acc->sum[i]);
|
||||
fputc('\n', stderr);
|
||||
fprintf(stderr, "Movie timing avg (%zu frames):", acc->frames);
|
||||
fprintf(stdout, " %s=%.4f", movie_timing_names[i], acc->sum[i]);
|
||||
fputc('\n', stdout);
|
||||
fprintf(stdout, "Movie timing avg (%zu frames):", acc->frames);
|
||||
for (size_t i = 0; i < MOVIE_TIMING_COUNT; ++i)
|
||||
fprintf(stderr, " %s=%.4f", movie_timing_names[i],
|
||||
fprintf(stdout, " %s=%.4f", movie_timing_names[i],
|
||||
acc->sum[i] / (double)acc->frames);
|
||||
fputc('\n', stderr);
|
||||
fprintf(stderr, "Movie timing max (%zu frames):", acc->frames);
|
||||
fputc('\n', stdout);
|
||||
fprintf(stdout, "Movie timing max (%zu frames):", acc->frames);
|
||||
for (size_t i = 0; i < MOVIE_TIMING_COUNT; ++i)
|
||||
fprintf(stderr, " %s=%.4f", movie_timing_names[i], acc->max[i]);
|
||||
fputc('\n', stderr);
|
||||
fprintf(stdout, " %s=%.4f", movie_timing_names[i], acc->max[i]);
|
||||
fputc('\n', stdout);
|
||||
}
|
||||
|
||||
/* Producer half of the async movie output: finishes every HDR-side step
|
||||
@@ -1930,7 +1931,7 @@ static int render_movie(const Settings *s, StarCatalog *catalog,
|
||||
fprintf(stderr, "Failed to write lens map: %s\n", s->lens_map_output_path);
|
||||
goto done;
|
||||
}
|
||||
fprintf(stderr, "Wrote lens-map movie: %s (%zu frames)\n",
|
||||
fprintf(stdout, "Wrote lens-map movie: %s (%zu frames)\n",
|
||||
s->lens_map_output_path, movie.frame_count);
|
||||
}
|
||||
/* An all-sky movie prefetches the union of every finalized frame's requested
|
||||
@@ -1953,7 +1954,7 @@ static int render_movie(const Settings *s, StarCatalog *catalog,
|
||||
goto done;
|
||||
}
|
||||
const double load_seconds = omp_get_wtime() - load_start;
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Movie catalog prefetch: %zu unique requested, %zu newly loaded "
|
||||
"(%zu stars), %zu unavailable in %.3f s (mark %.3f, load+commit "
|
||||
"%.3f); %d loader workers\n",
|
||||
@@ -2029,11 +2030,11 @@ static int render_movie(const Settings *s, StarCatalog *catalog,
|
||||
result = movie_output_queue_finish(&output_queue) == 0 ? 0 : -1;
|
||||
const double now = omp_get_wtime();
|
||||
report_movie_timing_summary(&timing_acc);
|
||||
movie_output_queue_report(&output_queue, stderr);
|
||||
fprintf(stderr,
|
||||
movie_output_queue_report(&output_queue, stdout);
|
||||
fprintf(stdout,
|
||||
"Movie output pipeline wall: %.4f s (producer frame-time sum %.4f s)\n",
|
||||
now - endpoint_start, timing_acc.sum[MOVIE_TIMING_COUNT - 1]);
|
||||
fprintf(stderr, "Movie end-to-end wall: %.4f s\n", now - movie_wall_start);
|
||||
fprintf(stdout, "Movie end-to-end wall: %.4f s\n", now - movie_wall_start);
|
||||
done:
|
||||
if (output_queue_ready)
|
||||
movie_output_queue_destroy(&output_queue);
|
||||
@@ -2057,7 +2058,7 @@ static int render_lens_map(const Settings *s, StarCatalog *catalog) {
|
||||
return -1;
|
||||
}
|
||||
if (s->verbose)
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Imported lens map v%u: integrator=%s step=%.8g max_steps=%u "
|
||||
"lookback=%.8g retry_lookback=%.8g max_total_lookback=%.8g\n",
|
||||
map.file_version,
|
||||
@@ -2121,7 +2122,7 @@ static int render_lens_map(const Settings *s, StarCatalog *catalog) {
|
||||
}
|
||||
if (s->fast_mode) {
|
||||
fast = &local_fast;
|
||||
fast_psf_accumulator_report(&local_fast, stderr);
|
||||
fast_psf_accumulator_report(&local_fast, stdout);
|
||||
fast_psf_accumulator_set_verbose(&local_fast, s->verbose);
|
||||
}
|
||||
/* A multi-frame imported map runs the same movie-level union prefetch and
|
||||
@@ -2147,7 +2148,7 @@ static int render_lens_map(const Settings *s, StarCatalog *catalog) {
|
||||
return -1;
|
||||
}
|
||||
const double load_seconds = omp_get_wtime() - load_start;
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Movie catalog prefetch: %zu unique requested, %zu newly loaded "
|
||||
"(%zu stars), %zu unavailable in %.3f s (mark %.3f, load+commit "
|
||||
"%.3f); %d loader workers\n",
|
||||
@@ -2262,7 +2263,7 @@ static int render_lens_map(const Settings *s, StarCatalog *catalog) {
|
||||
if (s->draw_mesh)
|
||||
fputs("Dummy PSF backend ignores --draw-mesh.\n", stderr);
|
||||
free(hdr);
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Dummy PSF classified %zu images from %zu catalog stars; no HDR, PNG, or PPM was written.\n",
|
||||
images, catalog->count);
|
||||
report_psf_splat(s, &psf_stats);
|
||||
@@ -2273,8 +2274,8 @@ static int render_lens_map(const Settings *s, StarCatalog *catalog) {
|
||||
if (movie_output_queue_finish(&output_queue))
|
||||
result = -1;
|
||||
report_movie_timing_summary(&timing_acc);
|
||||
movie_output_queue_report(&output_queue, stderr);
|
||||
fprintf(stderr, "Movie output pipeline wall: %.4f s\n",
|
||||
movie_output_queue_report(&output_queue, stdout);
|
||||
fprintf(stdout, "Movie output pipeline wall: %.4f s\n",
|
||||
omp_get_wtime() - pipeline_start);
|
||||
movie_output_queue_destroy(&output_queue);
|
||||
}
|
||||
@@ -2300,6 +2301,10 @@ static int write_minkowski_accel_track(const Settings *s) {
|
||||
int main(int argc, char **argv) {
|
||||
Settings settings;
|
||||
const char *write_path;
|
||||
/* Line-buffer stdout so redirected progress is visible promptly, matching
|
||||
* stderr. This adds no new synchronization: the built-in stdout stream lock
|
||||
* already serializes the render workers and the async movie writer. */
|
||||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
if (argc == 2 && !strcmp(argv[1], "--help")) {
|
||||
print_help(argv[0]);
|
||||
return 0;
|
||||
@@ -2342,7 +2347,7 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
#ifdef GR_DEBUG
|
||||
fputs("Debug build: low-frequency render progress is enabled; direct PSF "
|
||||
"fallbacks log immediately from their splat worker.\n", stderr);
|
||||
"fallbacks log immediately from their splat worker.\n", stdout);
|
||||
#endif
|
||||
if (write_path != NULL)
|
||||
return catalog_write_octant_grid(write_path) == 0 ? 0
|
||||
@@ -2491,10 +2496,10 @@ int main(int argc, char **argv) {
|
||||
spacetime_destroy(&spacetime);
|
||||
return 1;
|
||||
}
|
||||
fprintf(stderr, "Created test catalog: %s\n", settings.catalog_path);
|
||||
fprintf(stdout, "Created test catalog: %s\n", settings.catalog_path);
|
||||
}
|
||||
if (blackbody_backend_init(settings.blackbody_table_path,
|
||||
0, NAN, NAN, NULL, stderr)) {
|
||||
0, NAN, NAN, NULL, stdout)) {
|
||||
fprintf(stderr,
|
||||
"Blackbody backend '%s' initialization failed; the LUT loads the "
|
||||
"repository CIE GRBBLUT3 table by default or a valid explicit "
|
||||
@@ -2504,7 +2509,7 @@ int main(int argc, char **argv) {
|
||||
spacetime_destroy(&spacetime);
|
||||
return 1;
|
||||
}
|
||||
fprintf(stderr, "Blackbody backend: %s\n", blackbody_backend_name());
|
||||
fprintf(stdout, "Blackbody backend: %s\n", blackbody_backend_name());
|
||||
/* `reserve-one` must be applied before any fast-mode accumulator is built:
|
||||
* the FFTW plans in fast_psf_accumulator_init() snapshot omp_get_max_threads()
|
||||
* at creation time, so shrinking the pool later would leave the plans (and
|
||||
@@ -2530,11 +2535,11 @@ int main(int argc, char **argv) {
|
||||
return 1;
|
||||
}
|
||||
settings.fast_psf = &fast_accumulator;
|
||||
fast_psf_accumulator_report(&fast_accumulator, stderr);
|
||||
fast_psf_accumulator_report(&fast_accumulator, stdout);
|
||||
fast_psf_accumulator_set_verbose(&fast_accumulator, settings.verbose);
|
||||
} else if (settings.fast_mode) {
|
||||
fputs("Fast mode on an imported lens map uses the map's own dimensions.\n",
|
||||
stderr);
|
||||
stdout);
|
||||
}
|
||||
if (!settings.fast_mode && !settings.psf_direct &&
|
||||
psf_kernel_cache_init(&settings.psf_cache, &settings.psf,
|
||||
@@ -2551,7 +2556,7 @@ int main(int argc, char **argv) {
|
||||
#endif
|
||||
}
|
||||
if (!settings.fast_mode)
|
||||
psf_kernel_cache_report_ready(&settings.psf_cache, stderr);
|
||||
psf_kernel_cache_report_ready(&settings.psf_cache, stdout);
|
||||
if (settings.lens_map_input_path != NULL) {
|
||||
const int result = render_lens_map(&settings, &catalog);
|
||||
catalog_destroy(&catalog);
|
||||
|
||||
+4
-4
@@ -16,23 +16,23 @@ static int movie_output_default_write(void *context, const MovieOutputJob *job,
|
||||
if (write_rgb8_image(job->output_path, job->clean_rgb8, job->width,
|
||||
job->height, settings))
|
||||
return -1;
|
||||
fprintf(stderr, "Rendered %zu images from %zu catalog stars to %s (ok%s)\n",
|
||||
fprintf(stdout, "Rendered %zu images from %zu catalog stars to %s (ok%s)\n",
|
||||
job->images, job->catalog_stars, job->output_path, job->note);
|
||||
if (job->draw_mesh && job->mesh_rgb8 != NULL) {
|
||||
if (write_rgb8_image(job->mesh_path, job->mesh_rgb8, job->width,
|
||||
job->height, settings))
|
||||
return -1;
|
||||
fprintf(stderr, "Wrote mesh overlay image: %s\n", job->mesh_path);
|
||||
fprintf(stdout, "Wrote mesh overlay image: %s\n", job->mesh_path);
|
||||
}
|
||||
if (job->has_psf_stats) {
|
||||
if (job->fast_mode)
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Fast PSF splats: deposited %zu, wing-clipped %zu, discarded "
|
||||
"below min-Y %zu\n",
|
||||
job->psf_stats.cached_splats, job->psf_stats.cached_wing_clipped,
|
||||
job->psf_stats.discarded_below_min_y);
|
||||
else
|
||||
psf_kernel_cache_report(NULL, &job->psf_stats, stderr);
|
||||
psf_kernel_cache_report(NULL, &job->psf_stats, stdout);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
+2
-2
@@ -251,7 +251,7 @@ void psf_kernel_cache_report(const PsfKernelCache *cache,
|
||||
stats->gpu_upload_seconds, stats->gpu_kernel_seconds,
|
||||
stats->gpu_download_seconds);
|
||||
if (stats != NULL && stats->discarded_below_min_y != 0)
|
||||
fputs("Warning: --psf-min-y discarded one or more PSF events.\n", stream);
|
||||
fputs("Warning: --psf-min-y discarded one or more PSF events.\n", stderr);
|
||||
#ifdef GR_DEBUG
|
||||
if (stats != NULL)
|
||||
fprintf(stream, "Debug: max raw magnification %.6g; magnification-clamped "
|
||||
@@ -705,7 +705,7 @@ int fast_psf_accumulator_resolve(FastPsfAccumulator *accumulator,
|
||||
accumulator->fftw_last_timing = timing;
|
||||
accumulator->fftw_frame_seconds = omp_get_wtime() - start;
|
||||
if (accumulator->verbose)
|
||||
fprintf(stderr,
|
||||
fprintf(stdout,
|
||||
"Fast FFTW frame: zero_pack=%.6f forward=%.6f "
|
||||
"multiply=%.6f inverse=%.6f crop_downsample=%.6f "
|
||||
"total=%.6f\n",
|
||||
|
||||
@@ -130,9 +130,9 @@ def endpoint_deviation(a, b):
|
||||
return mismatches, worst
|
||||
|
||||
|
||||
def trace_cost(stderr, label):
|
||||
def trace_cost(text, label):
|
||||
match = re.search(label + r' trace cost: accepted=(\d+) rejected=(\d+) '
|
||||
r'rhs=(\d+)', stderr)
|
||||
r'rhs=(\d+)', text)
|
||||
return None if match is None else match.groups()
|
||||
|
||||
|
||||
@@ -263,8 +263,8 @@ with tempfile.TemporaryDirectory(prefix='gr-adaptive-cli-',
|
||||
replay_hdr = replay_out.with_name(replay_out.stem + '_HDR.fits')
|
||||
assert dflt_out.with_name(dflt_out.stem + '_HDR.fits').read_bytes() \
|
||||
== replay_hdr.read_bytes()
|
||||
live_cost = trace_cost(dflt_run.stderr, 'Frame 0')
|
||||
replay_cost = trace_cost(replay_run.stderr, 'Imported map')
|
||||
live_cost = trace_cost(dflt_run.stdout, 'Frame 0')
|
||||
replay_cost = trace_cost(replay_run.stdout, 'Imported map')
|
||||
assert live_cost is not None and replay_cost is not None
|
||||
assert live_cost == replay_cost, (live_cost, replay_cost)
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ with tempfile.TemporaryDirectory(prefix='gr-camera-cli-', dir='/tmp/opencode') a
|
||||
fast_path = tmp / f'minkowski_fast.{ext}'
|
||||
fast = run(binary, *common, '--fast-mode', '--fast-supersample', 2,
|
||||
'--output', fast_path)
|
||||
assert 'Fast FFTW:' in fast.stderr, fast.stderr
|
||||
assert 'Fast FFTW:' in fast.stdout, fast.stdout
|
||||
assert image_payload(fast_path)
|
||||
|
||||
# Equivalent independently specified and inferred camera geometry.
|
||||
@@ -227,8 +227,8 @@ with tempfile.TemporaryDirectory(prefix='gr-camera-cli-', dir='/tmp/opencode') a
|
||||
tmp / 'missing_catalog.csv', '--output', long_path,
|
||||
ok=False)
|
||||
assert 'Mesh overlay output path is too long' in too_long.stderr, too_long.stderr
|
||||
assert 'Blackbody backend' not in too_long.stderr, too_long.stderr
|
||||
assert 'PSF cache ready' not in too_long.stderr
|
||||
assert 'Blackbody backend' not in (too_long.stdout + too_long.stderr), too_long.stderr
|
||||
assert 'PSF cache ready' not in (too_long.stdout + too_long.stderr)
|
||||
|
||||
errors = [
|
||||
(['--observer-time'], None),
|
||||
@@ -294,7 +294,7 @@ with tempfile.TemporaryDirectory(prefix='gr-camera-cli-', dir='/tmp/opencode') a
|
||||
if message:
|
||||
assert message in result.stderr, result.stderr
|
||||
assert not missing_catalog.exists(), result.stderr
|
||||
assert 'PSF cache ready' not in result.stderr
|
||||
assert 'PSF cache ready' not in (result.stdout + result.stderr)
|
||||
|
||||
track = tmp / f'{backend}.csv'
|
||||
run(TESTDIR / f'test_observer_{backend}', track)
|
||||
@@ -397,8 +397,8 @@ with tempfile.TemporaryDirectory(prefix='gr-camera-cli-', dir='/tmp/opencode') a
|
||||
'--sensor-bloom-transfer', 0.5,
|
||||
'--output', bloom_output)
|
||||
assert image_payload(bloom_output) != baseline
|
||||
assert 'Sensor bloom:' in bloom_run.stderr, bloom_run.stderr
|
||||
report = bloom_run.stderr.split('Sensor bloom:', 1)[1].splitlines()[0]
|
||||
assert 'Sensor bloom:' in bloom_run.stdout, bloom_run.stdout
|
||||
report = bloom_run.stdout.split('Sensor bloom:', 1)[1].splitlines()[0]
|
||||
fields = dict(token.split('=', 1) for token in report.split() if '=' in token)
|
||||
assert int(fields['saturated']) > 0, report
|
||||
assert int(fields['iterations'].split('/')[0]) >= 1, report
|
||||
@@ -443,8 +443,8 @@ with tempfile.TemporaryDirectory(prefix='gr-camera-cli-', dir='/tmp/opencode') a
|
||||
'--movie-track-samples', '--frames-dir', tmp,
|
||||
'--frames-prefix', 'mixed', '--verbose',
|
||||
'--lens-map-output', parallel_map)
|
||||
assert 'Ray trace generation 1: frame 0 added' in result.stderr
|
||||
assert 'Ray trace generation 0: frame 1 added' not in result.stderr
|
||||
assert 'Ray trace generation 1: frame 0 added' in result.stdout
|
||||
assert 'Ray trace generation 0: frame 1 added' not in result.stdout
|
||||
for frame in range(2):
|
||||
image_payload(tmp / f'mixed_{frame:06d}.{ext}',
|
||||
dimensions=(64, 36), allow_black=True)
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Regression test for the stdout/stderr split of the production CLI.
|
||||
|
||||
Normal progress, startup/backend configuration, successful file outputs,
|
||||
timings and cache summaries are normal informational output and must go to
|
||||
stdout. Genuine warnings, errors and the Debug per-event direct-fallback
|
||||
diagnostic go to stderr. A mixed success/error line such as
|
||||
``Rendered ... (ok|write failed)`` must follow its outcome.
|
||||
|
||||
The checks capture the two streams separately so that a future change which
|
||||
silently moves a normal message to stderr (or an error to stdout) is caught.
|
||||
Small deterministic Minkowski scenes keep the runtime short.
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
# Keep scratch data inside the pre-approved OpenCode scratch directory.
|
||||
TMP_ROOT = Path('/tmp/opencode')
|
||||
TMP_ROOT.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
BUILD = Path(sys.argv[1] if len(sys.argv) > 1 else 'build/Release').resolve()
|
||||
ENV = dict(os.environ, OMP_NUM_THREADS='4')
|
||||
|
||||
COMMON = ['--catalog', 'assets/sky_grid_5deg.csv', '--width', 16, '--height', 8,
|
||||
'--fov-deg', 80, '--exposure', 1e-3, '--coarse-cell-pixels', 8,
|
||||
'--refine-max-level', 0, '--psf-relative-tail', 1e-4]
|
||||
|
||||
|
||||
def run(binary, *args, env=ENV):
|
||||
return subprocess.run([str(binary), *map(str, args)], env=env,
|
||||
capture_output=True, text=True)
|
||||
|
||||
|
||||
def stderr_errors(result):
|
||||
"""Non-advisory stderr lines.
|
||||
|
||||
A Debug build deliberately logs each direct PSF fallback from the active
|
||||
splat worker; those ``Debug:`` lines are real diagnostics, not a misplaced
|
||||
normal message, so they are excluded when the Debug startup banner is
|
||||
present on stdout.
|
||||
"""
|
||||
lines = [line for line in result.stderr.splitlines() if line.strip()]
|
||||
if 'Debug build:' in result.stdout:
|
||||
lines = [line for line in lines if not line.startswith('Debug:')]
|
||||
return lines
|
||||
|
||||
|
||||
binary = BUILD / 'minkowski_sky'
|
||||
assert binary.exists(), f'missing {binary}'
|
||||
|
||||
# The image extension follows the build's compiled writer, exactly as
|
||||
# tests/test_camera_cli.py detects it from --help (a libpng build advertises
|
||||
# `.png`, a PNG-less build advertises `.ppm`).
|
||||
help_text = run(binary, '--help').stdout
|
||||
ext = 'png' if '.png' in help_text else 'ppm'
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix='gr-output-streams-',
|
||||
dir=str(TMP_ROOT)) as directory:
|
||||
tmp = Path(directory)
|
||||
|
||||
# 1) --help is informational: complete usage on stdout, nothing on stderr.
|
||||
help_result = run(binary, '--help')
|
||||
assert help_result.returncode == 0, help_result.stderr
|
||||
assert 'Usage:' in help_result.stdout, help_result.stdout
|
||||
assert help_result.stderr == '', help_result.stderr
|
||||
|
||||
# 2) An unknown option is an error: usage diagnostic on stderr only.
|
||||
bad_result = run(binary, '--not-an-option')
|
||||
assert bad_result.returncode != 0, bad_result.stdout
|
||||
assert bad_result.stdout == '', bad_result.stdout
|
||||
assert bad_result.stderr != '', 'missing error diagnostic on stderr'
|
||||
|
||||
# 3) A successful non-verbose single-frame render puts every startup,
|
||||
# statistics and success line on stdout with an empty stderr.
|
||||
single_out = tmp / f'single.{ext}'
|
||||
single = run(binary, *COMMON, '--output', single_out)
|
||||
assert single.returncode == 0, single.stderr
|
||||
assert single_out.exists(), single.stderr
|
||||
assert 'Blackbody backend:' in single.stdout, single.stdout
|
||||
assert 'PSF cache ready:' in single.stdout, single.stdout
|
||||
assert 'Rendered' in single.stdout and '(ok)' in single.stdout, single.stdout
|
||||
assert '(write failed)' not in single.stdout, single.stdout
|
||||
assert not stderr_errors(single), single.stderr
|
||||
|
||||
# 4) Verbose progress (including the trace-cost line) is still stdout.
|
||||
verbose_out = tmp / f'verbose.{ext}'
|
||||
verbose = run(binary, *COMMON, '--verbose', '--output', verbose_out)
|
||||
assert verbose.returncode == 0, verbose.stderr
|
||||
assert 'Frame 0: tracing' in verbose.stdout, verbose.stdout
|
||||
assert 'Frame 0 trace cost:' in verbose.stdout, verbose.stdout
|
||||
assert not stderr_errors(verbose), verbose.stderr
|
||||
|
||||
# 5) A short movie exercises the asynchronous writer; its per-frame logs,
|
||||
# timing summary and writer summary are stdout, stderr stays empty. The
|
||||
# 2 s fixture track plus --duration 1 --fps 1 yields two frames, so the
|
||||
# async producer/writer overlap is actually exercised.
|
||||
track = tmp / 'track.csv'
|
||||
track_run = run(binary, '--write-minkowski-accel-track', track,
|
||||
'--duration', 2, '--fps', 30, '--proper-acceleration', 1.52)
|
||||
assert track_run.returncode == 0, track_run.stderr
|
||||
assert track.exists(), track_run.stderr
|
||||
frames_dir = tmp / 'frames'
|
||||
frames_dir.mkdir()
|
||||
movie = run(binary, *COMMON, '--observer-track', track, '--frames-dir',
|
||||
frames_dir, '--frames-prefix', 'frame', '--duration', 1,
|
||||
'--fps', 1, '--verbose', '--output', tmp / f'movie.{ext}')
|
||||
assert movie.returncode == 0, movie.stderr
|
||||
assert (frames_dir / f'frame_000000.{ext}').exists(), movie.stderr
|
||||
assert (frames_dir / f'frame_000001.{ext}').exists(), movie.stderr
|
||||
assert 'Ray trace generation' in movie.stdout, movie.stdout
|
||||
assert movie.stdout.count('Rendered') == 2, movie.stdout
|
||||
assert movie.stdout.count('(ok)') == 2, movie.stdout
|
||||
assert 'Movie timing total' in movie.stdout, movie.stdout
|
||||
assert 'Movie writer summary:' in movie.stdout, movie.stdout
|
||||
assert 'Movie end-to-end wall:' in movie.stdout, movie.stdout
|
||||
assert not stderr_errors(movie), movie.stderr
|
||||
|
||||
# 6) A genuine warning goes to stderr and does not disturb the success line
|
||||
# on stdout. The fast-mode preview advisory is deterministic in the CPU
|
||||
# build.
|
||||
fast_out = tmp / f'fast.{ext}'
|
||||
fast = run(binary, *COMMON, '--fast-mode', '--output', fast_out)
|
||||
assert fast.returncode == 0, fast.stderr
|
||||
assert 'Fast mode is a preview approximation' in fast.stderr, fast.stderr
|
||||
assert fast.stdout.count('Rendered') == 1, fast.stdout
|
||||
assert '(ok)' in fast.stdout, fast.stdout
|
||||
# The only stderr content is the advisory: no normal line leaked across.
|
||||
assert len(stderr_errors(fast)) == 1, fast.stderr
|
||||
|
||||
# 7) A failed write must route the mixed success/error line to stderr and
|
||||
# leave stdout free of the success wording.
|
||||
missing_dir = tmp / 'missing_subdir' / f'out.{ext}'
|
||||
failed = run(binary, *COMMON, '--output', missing_dir)
|
||||
assert failed.returncode != 0, failed.stdout
|
||||
assert '(write failed)' in failed.stderr, failed.stderr
|
||||
assert '(write failed)' not in failed.stdout, failed.stdout
|
||||
|
||||
# 8) A rejected camera velocity is an error on stderr, not stdout.
|
||||
velocity = run(binary, *COMMON, '--observer-velocity', 10, 0, 0,
|
||||
'--output', tmp / f'velocity.{ext}')
|
||||
assert velocity.returncode != 0, velocity.stdout
|
||||
assert 'not timelike' in velocity.stderr, velocity.stderr
|
||||
# stdout may hold only the Debug startup banner; no render ran.
|
||||
assert 'Rendered' not in velocity.stdout, velocity.stdout
|
||||
|
||||
print('output-stream checks passed: normal success stdout / diagnostics '
|
||||
'stderr', flush=True)
|
||||
@@ -622,6 +622,9 @@ overlay alpha-composites image-plane triangle edges as one-pixel-wide 0.5
|
||||
linear-gray diagnostic lines at 0.5 opacity. The line rasterizer uses
|
||||
coverage-based antialiasing.
|
||||
|
||||
Normal progress and summaries go to stdout; warnings, errors, and Debug
|
||||
diagnostics go to stderr. Successful runs exit `0` even if warnings are emitted.
|
||||
|
||||
## HDR output
|
||||
|
||||
To preserve a single-frame render for later exposure and tone-mapping work,
|
||||
|
||||
Reference in new issue
Block a user