Fix: Skip empty movie refinement generations
Allow frames to finish refinement independently without aborting the movie. Report genuine refinement errors with generation and frame identifiers, and clarify the sample batch contract. Add a two-frame Schwarzschild regression covering unequal refinement progress. Validated Release and Debug make test suites and 640x360 rendering of the final eight free-fall frames in both builds.
This commit is contained in:
1 parent
b69c9cfd45
commit
8cf6106127
4 files changed
+36
-7
No files matched your search
+7
-3
@@ -51,6 +51,8 @@ typedef struct {
|
|||||||
size_t vertex_count, vertex_capacity;
|
size_t vertex_count, vertex_capacity;
|
||||||
size_t triangle_count, triangle_capacity;
|
size_t triangle_count, triangle_capacity;
|
||||||
FrameSample *samples;
|
FrameSample *samples;
|
||||||
|
/* Current generation's vertex/probe requests, retained through result
|
||||||
|
* installation and cleared by finish_generation; not a pending-ray count. */
|
||||||
size_t sample_count, sample_capacity;
|
size_t sample_count, sample_capacity;
|
||||||
int samples_include_probes;
|
int samples_include_probes;
|
||||||
size_t *probe_slots;
|
size_t *probe_slots;
|
||||||
@@ -94,15 +96,17 @@ int frame_lens_mesh_build_coarse(FrameLensMesh *mesh, int width, int height,
|
|||||||
int frame_lens_mesh_trace(FrameLensMesh *mesh, const SpacetimeSource *spacetime,
|
int frame_lens_mesh_trace(FrameLensMesh *mesh, const SpacetimeSource *spacetime,
|
||||||
const ObserverState *observer,
|
const ObserverState *observer,
|
||||||
const GeodesicTraceConfig *trace);
|
const GeodesicTraceConfig *trace);
|
||||||
/* Builds one generation of vertex/probe ray requests. Results are installed
|
/* Builds one generation of vertex/probe ray requests. Returns the request
|
||||||
* only after the caller has completed the generation's sweep. */
|
* count, zero when this frame needs no work, or -1 on failure. Results are
|
||||||
|
* installed only after the caller has completed the generation's sweep. */
|
||||||
int frame_lens_mesh_prepare_generation(FrameLensMesh *mesh,
|
int frame_lens_mesh_prepare_generation(FrameLensMesh *mesh,
|
||||||
const RefinementConfig *config);
|
const RefinementConfig *config);
|
||||||
const FrameSample *frame_lens_mesh_samples(const FrameLensMesh *mesh,
|
const FrameSample *frame_lens_mesh_samples(const FrameLensMesh *mesh,
|
||||||
size_t *count);
|
size_t *count);
|
||||||
int frame_lens_mesh_install_sample(FrameLensMesh *mesh, size_t sample_id,
|
int frame_lens_mesh_install_sample(FrameLensMesh *mesh, size_t sample_id,
|
||||||
const RayEndpoint *endpoint);
|
const RayEndpoint *endpoint);
|
||||||
/* Applies the completed generation. Returns the number of newly added
|
/* Applies a nonempty completed generation; callers skip frames for which
|
||||||
|
* prepare_generation returned zero. Returns the number of newly added
|
||||||
* vertices, zero when no topology change was made, or -1 on failure. */
|
* vertices, zero when no topology change was made, or -1 on failure. */
|
||||||
int frame_lens_mesh_finish_generation(FrameLensMesh *mesh,
|
int frame_lens_mesh_finish_generation(FrameLensMesh *mesh,
|
||||||
const RefinementConfig *config);
|
const RefinementConfig *config);
|
||||||
|
|||||||
+7
-1
@@ -782,10 +782,16 @@ static int trace_movie_generation(Movie *movie, const Settings *s,
|
|||||||
fprintf(stderr, "Ray trace generation %zu: installing endpoints and refining meshes.\n",
|
fprintf(stderr, "Ray trace generation %zu: installing endpoints and refining meshes.\n",
|
||||||
generation);
|
generation);
|
||||||
for (size_t f = 0; f < movie->frame_count; ++f) {
|
for (size_t f = 0; f < movie->frame_count; ++f) {
|
||||||
|
/* Frames converge independently; only finish those traced this pass. */
|
||||||
|
if (movie->frames[f].mesh.sample_count == 0)
|
||||||
|
continue;
|
||||||
const int added = frame_lens_mesh_finish_generation(&movie->frames[f].mesh,
|
const int added = frame_lens_mesh_finish_generation(&movie->frames[f].mesh,
|
||||||
&s->refinement);
|
&s->refinement);
|
||||||
if (added < 0)
|
if (added < 0) {
|
||||||
|
fprintf(stderr, "Ray trace generation %zu: frame %zu refinement failed.\n",
|
||||||
|
generation, movie->frames[f].frame_id);
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
total_added += (size_t)added;
|
total_added += (size_t)added;
|
||||||
if (s->verbose && added > 0)
|
if (s->verbose && added > 0)
|
||||||
fprintf(stderr, "Ray trace generation %zu: frame %zu added %d vertices.\n",
|
fprintf(stderr, "Ray trace generation %zu: frame %zu added %d vertices.\n",
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
t,tau,x,y,z,e0t,e0x,e0y,e0z,e1t,e1x,e1y,e1z,e2t,e2x,e2y,e2z,e3t,e3x,e3y,e3z
|
||||||
|
398.48185980038227,385,6.2695764947414609,0,0,1.1913347333893116,-0.52820527560786401,0,0,0.31666643933096511,-0.97979589711241188,1.1999039092174769e-16,0,0,0,0,1,-3.7996929847120426e-17,1.1756609271804282e-16,1,0
|
||||||
|
404.71892830936724,390,3.1592785705381488,0,0,1.3415634010738333,-0.7701012396930732,0,0,0.40833373573712628,-0.97979589711788617,1.1999039092174769e-16,0,0,0,0,1,-4.8996124577635443e-17,1.1756609271869967e-16,1,0
|
||||||
|
@@ -20,7 +20,7 @@ def run(binary, *args, ok=True):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def image_payload(path):
|
def image_payload(path, dimensions=(64, 48), allow_black=False):
|
||||||
data = path.read_bytes()
|
data = path.read_bytes()
|
||||||
assert data[:8] == b'\x89PNG\r\n\x1a\n'
|
assert data[:8] == b'\x89PNG\r\n\x1a\n'
|
||||||
offset, compressed = 8, bytearray()
|
offset, compressed = 8, bytearray()
|
||||||
@@ -28,12 +28,13 @@ def image_payload(path):
|
|||||||
count, kind = struct.unpack_from('>I4s', data, offset)
|
count, kind = struct.unpack_from('>I4s', data, offset)
|
||||||
payload = data[offset + 8:offset + 8 + count]
|
payload = data[offset + 8:offset + 8 + count]
|
||||||
if kind == b'IHDR':
|
if kind == b'IHDR':
|
||||||
assert struct.unpack_from('>II', payload) == (64, 48)
|
assert struct.unpack_from('>II', payload) == dimensions
|
||||||
if kind == b'IDAT':
|
if kind == b'IDAT':
|
||||||
compressed.extend(payload)
|
compressed.extend(payload)
|
||||||
offset += count + 12
|
offset += count + 12
|
||||||
raw = zlib.decompress(compressed)
|
raw = zlib.decompress(compressed)
|
||||||
assert any(raw), f'empty image: {path}'
|
assert raw, f'missing image data: {path}'
|
||||||
|
assert allow_black or any(raw), f'empty image: {path}'
|
||||||
return raw
|
return raw
|
||||||
|
|
||||||
|
|
||||||
@@ -137,4 +138,19 @@ with tempfile.TemporaryDirectory(prefix='gr-camera-cli-') as directory:
|
|||||||
assert max_error < 1e-9, max_error
|
assert max_error < 1e-9, max_error
|
||||||
# A map import must still work without evaluating a camera/metric.
|
# A map import must still work without evaluating a camera/metric.
|
||||||
assert single == render('import', '--lens-map-input', single_map)
|
assert single == render('import', '--lens-map-input', single_map)
|
||||||
|
if backend == 'schwarzschild':
|
||||||
|
# Two inward-looking free-fall samples at r=6.2696 and r=3.1593.
|
||||||
|
# At 16:9 the latter frame finishes in generation 0, while the
|
||||||
|
# former still needs refinement. Finishing the empty batch used
|
||||||
|
# to abort the whole movie in generation 1.
|
||||||
|
mixed = Path(__file__).parent / 'fixtures/schwarzschild_mixed_refinement.csv'
|
||||||
|
result = run(binary, *common, '--height', 36, '--fov-deg', 60,
|
||||||
|
'--refine-max-level', 3, '--observer-track', mixed,
|
||||||
|
'--movie-track-samples', '--frames-dir', tmp,
|
||||||
|
'--frames-prefix', 'mixed', '--verbose')
|
||||||
|
assert 'Ray trace generation 1: frame 0 added' in result.stderr
|
||||||
|
assert 'Ray trace generation 0: frame 1 added' not in result.stderr
|
||||||
|
for frame in range(2):
|
||||||
|
image_payload(tmp / f'mixed_{frame:06d}.png',
|
||||||
|
dimensions=(64, 36), allow_black=True)
|
||||||
print(f'{backend}: CLI checks passed; single/movie PNG identical, map max error {max_error:.3g}', flush=True)
|
print(f'{backend}: CLI checks passed; single/movie PNG identical, map max error {max_error:.3g}', flush=True)
|
||||||
Reference in new issue
Block a user