Feat: Use FFTW linear convolution for CPU fast-mode PSF resolve
Replace the nested spatial global convolution in fast_psf_accumulator_resolve with a reusable double-precision FFTW linear convolution on the CPU PSF backend.
- Add the private src/fast_psf_fftw.{c,h} module: zero-padded R2C/C2R plans, cached kernel spectrum and planar scratch, exact 1/(Pwidth*Pheight) and 1/N^2 normalization, (R,R) crop, and additive HDR output.
- Keep the previous nested loops as fast_psf_accumulator_resolve_spatial_reference for tests/benchmarks only; it is not a runtime fallback.
- Cache the circular row spans on FastPsfAccumulator and report one-time plan, kernel transform, scratch, and per-frame stage timings.
- Require fftw3_omp for CPU builds; HIP and dummy builds do not link FFTW.
- Namespace test/helper binaries by spacetime and build tag, and reject make test / psf-capture for non-CPU backends.
- Add tests/test_fast_psf_fftw.c (FFTW versus spatial), tests/benchmark_fast_psf_fftw.c, an FFTW CLI smoke check, and the 2026-09-25 benchmark record.
This commit is contained in:
1 parent
3deebfb2fa
commit
229f50cd86
14 files changed
+2372
-55
No files matched your search
@@ -29,22 +29,14 @@ else
|
||||
IMAGE_EXT := ppm
|
||||
endif
|
||||
|
||||
COMMON_SOURCES := $(filter-out src/main.c src/dummy_psf.c src/spacetime_minkowski.c src/spacetime_schwarzschild.c,$(wildcard src/*.c))
|
||||
COMMON_SOURCES := $(filter-out src/main.c src/dummy_psf.c src/fast_psf_fftw.c src/spacetime_minkowski.c src/spacetime_schwarzschild.c,$(wildcard src/*.c))
|
||||
PROVIDER_SOURCE := src/spacetime_$(SPACETIME).c
|
||||
BUILD_DIR := build/$(BUILD_TYPE)
|
||||
TARGET_BASENAME := $(SPACETIME)_sky
|
||||
OBJECT_DIR := $(BUILD_DIR)/obj/$(SPACETIME)
|
||||
CORE_MINKOWSKI_SOURCES := $(COMMON_SOURCES) src/spacetime_minkowski.c
|
||||
TEST_TARGET := $(BUILD_DIR)/test_geodesic
|
||||
FRAME_TEST_TARGET := $(BUILD_DIR)/test_frame
|
||||
SCHWARZSCHILD_TEST_TARGET := $(BUILD_DIR)/test_schwarzschild
|
||||
OBSERVER_TRACK_TEST_TARGET := $(BUILD_DIR)/test_observer_track
|
||||
CATALOG_PREFETCH_TEST_TARGET := $(BUILD_DIR)/test_catalog_prefetch
|
||||
HIP_PSF_TEST_TARGET := $(BUILD_DIR)/test_hip_psf
|
||||
HIP_PSF_BENCH_TARGET := $(BUILD_DIR)/benchmark_hip_psf
|
||||
CAMERA_TEST_TARGETS := $(BUILD_DIR)/test_observer_minkowski $(BUILD_DIR)/test_observer_schwarzschild
|
||||
|
||||
.PHONY: all backend clean run test hip-psf-test hip-psf-bench minkowski schwarzschild FORCE
|
||||
.PHONY: all backend clean run test hip-psf-test hip-psf-bench fast-psf-fftw-bench minkowski schwarzschild FORCE
|
||||
|
||||
ifneq ($(filter 0 1,$(PSF_EVENT_SINK)),$(PSF_EVENT_SINK))
|
||||
$(error Unknown PSF_EVENT_SINK '$(PSF_EVENT_SINK)'; choose 0 or 1)
|
||||
@@ -53,6 +45,15 @@ BUILD_CPPFLAGS += -DFRAME_PSF_EVENT_SINK=$(PSF_EVENT_SINK)
|
||||
|
||||
ifeq ($(PSF_BACKEND),cpu)
|
||||
RENDER_LINKER := $(CC)
|
||||
ifeq ($(shell pkg-config --exists fftw3_omp && echo yes),yes)
|
||||
CPU_FFTW_CPPFLAGS := -DFAST_PSF_FFTW $(shell pkg-config --cflags fftw3_omp)
|
||||
CPU_FFTW_LDLIBS := $(shell pkg-config --libs fftw3_omp)
|
||||
CPU_FFTW_SOURCES := src/fast_psf_fftw.c
|
||||
BUILD_CPPFLAGS += $(CPU_FFTW_CPPFLAGS)
|
||||
LDLIBS += $(CPU_FFTW_LDLIBS)
|
||||
else
|
||||
$(error PSF_BACKEND=cpu requires the FFTW OpenMP package 'fftw3_omp' (pkg-config --exists fftw3_omp). On Gentoo enable sci-libs/fftw[openmp].)
|
||||
endif
|
||||
else ifeq ($(PSF_BACKEND),hip)
|
||||
RENDER_LINKER := $(HIPCC)
|
||||
BUILD_CPPFLAGS += -DPSF_BACKEND_HIP
|
||||
@@ -63,6 +64,23 @@ else
|
||||
$(error Unknown PSF_BACKEND '$(PSF_BACKEND)'; choose cpu, hip, or dummy)
|
||||
endif
|
||||
|
||||
# `make test` builds and runs the CPU regression suite. The dummy and hip
|
||||
# backends do not build those binaries, so reject the goal explicitly instead of
|
||||
# silently reusing a stale test binary left by an earlier CPU build.
|
||||
ifneq ($(filter test test-reference-images,$(MAKECMDGOALS)),)
|
||||
ifneq ($(PSF_BACKEND),cpu)
|
||||
$(error make test requires PSF_BACKEND=cpu)
|
||||
endif
|
||||
endif
|
||||
|
||||
# psf-capture is a CPU producer diagnostic; the dummy and hip backends do not
|
||||
# provide the producer/frame symbols it links.
|
||||
ifneq ($(filter psf-capture,$(MAKECMDGOALS)),)
|
||||
ifneq ($(PSF_BACKEND),cpu)
|
||||
$(error psf-capture requires PSF_BACKEND=cpu)
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(SPACETIME),minkowski)
|
||||
BACKEND_CPPFLAGS := -DSPACETIME_MINKOWSKI
|
||||
else ifeq ($(SPACETIME),schwarzschild)
|
||||
@@ -81,6 +99,21 @@ else
|
||||
$(error Unknown ENABLE_HDR '$(ENABLE_HDR)'; choose 0 or 1)
|
||||
endif
|
||||
|
||||
# Test binaries are namespaced by spacetime and build tag so that switching
|
||||
# PSF_BACKEND/ENABLE_HDR/PSF_EVENT_SINK cannot silently reuse a stale binary
|
||||
# built for a different backend.
|
||||
TEST_OUT_DIR := $(OBJECT_DIR)/$(HDR_BUILD_TAG)
|
||||
TEST_TARGET := $(TEST_OUT_DIR)/test_geodesic
|
||||
FRAME_TEST_TARGET := $(TEST_OUT_DIR)/test_frame
|
||||
SCHWARZSCHILD_TEST_TARGET := $(TEST_OUT_DIR)/test_schwarzschild
|
||||
OBSERVER_TRACK_TEST_TARGET := $(TEST_OUT_DIR)/test_observer_track
|
||||
CATALOG_PREFETCH_TEST_TARGET := $(TEST_OUT_DIR)/test_catalog_prefetch
|
||||
HIP_PSF_TEST_TARGET := $(TEST_OUT_DIR)/test_hip_psf
|
||||
HIP_PSF_BENCH_TARGET := $(TEST_OUT_DIR)/benchmark_hip_psf
|
||||
CAMERA_TEST_TARGETS := $(TEST_OUT_DIR)/test_observer_minkowski $(TEST_OUT_DIR)/test_observer_schwarzschild
|
||||
FAST_PSF_FFTW_TEST_TARGET := $(TEST_OUT_DIR)/test_fast_psf_fftw
|
||||
FAST_PSF_FFTW_BENCH_TARGET := $(TEST_OUT_DIR)/benchmark_fast_psf_fftw
|
||||
|
||||
ifeq ($(PSF_BACKEND),hip)
|
||||
TARGET := $(BUILD_DIR)/$(TARGET_BASENAME)_hip
|
||||
else ifeq ($(PSF_BACKEND),dummy)
|
||||
@@ -89,6 +122,7 @@ else
|
||||
TARGET := $(BUILD_DIR)/$(TARGET_BASENAME)
|
||||
endif
|
||||
RENDER_SOURCES := $(COMMON_SOURCES) $(PROVIDER_SOURCE) src/main.c
|
||||
RENDER_SOURCES += $(CPU_FFTW_SOURCES)
|
||||
ifeq ($(PSF_BACKEND),dummy)
|
||||
RENDER_SOURCES += src/dummy_psf.c
|
||||
endif
|
||||
@@ -140,10 +174,10 @@ ifeq ($(PSF_BACKEND),hip)
|
||||
hip-psf-test: $(HIP_PSF_TEST_TARGET)
|
||||
hip-psf-bench: $(HIP_PSF_BENCH_TARGET)
|
||||
|
||||
$(HIP_PSF_BENCH_TARGET): tests/benchmark_hip_psf.hip $(HIP_PSF_OBJECT) $(OBJECT_DIR)/$(HDR_BUILD_TAG)/src/optics.o | $(BUILD_DIR)
|
||||
$(HIP_PSF_BENCH_TARGET): tests/benchmark_hip_psf.hip $(HIP_PSF_OBJECT) $(OBJECT_DIR)/$(HDR_BUILD_TAG)/src/optics.o | $(TEST_OUT_DIR)
|
||||
$(HIPCC) $(HIP_CXXFLAGS) $(OPENMP_FLAGS) -Isrc -x hip $< -x none $(filter-out $<,$^) $(LDLIBS) $(HDR_LDLIBS) -o $@
|
||||
|
||||
$(HIP_PSF_TEST_TARGET): tests/test_hip_psf.hip $(HIP_PSF_OBJECT) $(OBJECT_DIR)/$(HDR_BUILD_TAG)/src/optics.o | $(BUILD_DIR)
|
||||
$(HIP_PSF_TEST_TARGET): tests/test_hip_psf.hip $(HIP_PSF_OBJECT) $(OBJECT_DIR)/$(HDR_BUILD_TAG)/src/optics.o | $(TEST_OUT_DIR)
|
||||
$(HIPCC) $(HIP_CXXFLAGS) $(OPENMP_FLAGS) -Isrc -x hip $< -x none $(filter-out $<,$^) $(LDLIBS) $(HDR_LDLIBS) -o $@
|
||||
else
|
||||
hip-psf-test hip-psf-bench:
|
||||
@@ -154,36 +188,57 @@ run: $(TARGET)
|
||||
mkdir -p output/imgs
|
||||
./$(TARGET) --catalog assets/sky_grid_5deg.csv --output output/imgs/$(SPACETIME)_sky.$(IMAGE_EXT)
|
||||
|
||||
$(TEST_TARGET): tests/test_geodesic.c $(CORE_MINKOWSKI_SOURCES) | $(BUILD_DIR)
|
||||
$(TEST_OUT_DIR): | $(BUILD_DIR)
|
||||
mkdir -p $@
|
||||
|
||||
$(TEST_TARGET): tests/test_geodesic.c $(CORE_MINKOWSKI_SOURCES) $(CPU_FFTW_SOURCES) | $(TEST_OUT_DIR)
|
||||
$(CC) $(CPPFLAGS) $(BUILD_CPPFLAGS) $(CFLAGS) $(BUILD_CFLAGS) $(OPENMP_FLAGS) -Isrc $^ $(LDLIBS) -o $@
|
||||
|
||||
$(FRAME_TEST_TARGET): tests/test_frame.c $(CORE_MINKOWSKI_SOURCES) | $(BUILD_DIR)
|
||||
$(FRAME_TEST_TARGET): tests/test_frame.c $(CORE_MINKOWSKI_SOURCES) $(CPU_FFTW_SOURCES) | $(TEST_OUT_DIR)
|
||||
$(CC) $(CPPFLAGS) $(BUILD_CPPFLAGS) $(CFLAGS) $(BUILD_CFLAGS) $(OPENMP_FLAGS) -Isrc $^ $(LDLIBS) -o $@
|
||||
|
||||
$(SCHWARZSCHILD_TEST_TARGET): tests/test_schwarzschild.c $(COMMON_SOURCES) src/spacetime_schwarzschild.c | $(BUILD_DIR)
|
||||
$(SCHWARZSCHILD_TEST_TARGET): tests/test_schwarzschild.c $(COMMON_SOURCES) src/spacetime_schwarzschild.c $(CPU_FFTW_SOURCES) | $(TEST_OUT_DIR)
|
||||
$(CC) $(CPPFLAGS) $(BUILD_CPPFLAGS) $(CFLAGS) $(BUILD_CFLAGS) $(OPENMP_FLAGS) -Isrc $^ $(LDLIBS) -o $@
|
||||
|
||||
$(OBSERVER_TRACK_TEST_TARGET): tests/test_observer_track.c $(COMMON_SOURCES) | $(BUILD_DIR)
|
||||
$(OBSERVER_TRACK_TEST_TARGET): tests/test_observer_track.c $(COMMON_SOURCES) $(CPU_FFTW_SOURCES) | $(TEST_OUT_DIR)
|
||||
$(CC) $(CPPFLAGS) $(BUILD_CPPFLAGS) $(CFLAGS) $(BUILD_CFLAGS) $(OPENMP_FLAGS) -Isrc $^ $(LDLIBS) -o $@
|
||||
|
||||
$(CATALOG_PREFETCH_TEST_TARGET): tests/test_catalog_prefetch.c $(CORE_MINKOWSKI_SOURCES) | $(BUILD_DIR)
|
||||
$(CATALOG_PREFETCH_TEST_TARGET): tests/test_catalog_prefetch.c $(CORE_MINKOWSKI_SOURCES) $(CPU_FFTW_SOURCES) | $(TEST_OUT_DIR)
|
||||
$(CC) $(CPPFLAGS) $(BUILD_CPPFLAGS) $(CFLAGS) $(BUILD_CFLAGS) $(OPENMP_FLAGS) -Isrc $^ $(LDLIBS) -o $@
|
||||
|
||||
$(BUILD_DIR)/test_observer_minkowski: tests/test_observer.c $(CORE_MINKOWSKI_SOURCES) | $(BUILD_DIR)
|
||||
$(TEST_OUT_DIR)/test_observer_minkowski: tests/test_observer.c $(CORE_MINKOWSKI_SOURCES) $(CPU_FFTW_SOURCES) | $(TEST_OUT_DIR)
|
||||
$(CC) $(CPPFLAGS) $(BUILD_CPPFLAGS) $(CFLAGS) $(BUILD_CFLAGS) $(OPENMP_FLAGS) -Isrc $^ $(LDLIBS) -o $@
|
||||
|
||||
$(BUILD_DIR)/test_observer_schwarzschild: tests/test_observer.c $(COMMON_SOURCES) src/spacetime_schwarzschild.c | $(BUILD_DIR)
|
||||
$(TEST_OUT_DIR)/test_observer_schwarzschild: tests/test_observer.c $(COMMON_SOURCES) src/spacetime_schwarzschild.c $(CPU_FFTW_SOURCES) | $(TEST_OUT_DIR)
|
||||
$(CC) $(CPPFLAGS) $(BUILD_CPPFLAGS) $(CFLAGS) $(BUILD_CFLAGS) $(OPENMP_FLAGS) -DSPACETIME_SCHWARZSCHILD -Isrc $^ $(LDLIBS) -o $@
|
||||
|
||||
test: $(CAMERA_TEST_TARGETS) $(TEST_TARGET) $(FRAME_TEST_TARGET) $(SCHWARZSCHILD_TEST_TARGET) $(OBSERVER_TRACK_TEST_TARGET) $(CATALOG_PREFETCH_TEST_TARGET)
|
||||
./$(BUILD_DIR)/test_observer_minkowski
|
||||
./$(BUILD_DIR)/test_observer_schwarzschild
|
||||
./$(TEST_TARGET)
|
||||
./$(FRAME_TEST_TARGET)
|
||||
./$(SCHWARZSCHILD_TEST_TARGET)
|
||||
./$(OBSERVER_TRACK_TEST_TARGET)
|
||||
./$(CATALOG_PREFETCH_TEST_TARGET)
|
||||
python3 tests/test_camera_cli.py $(BUILD_DIR)
|
||||
$(FAST_PSF_FFTW_TEST_TARGET): tests/test_fast_psf_fftw.c $(CORE_MINKOWSKI_SOURCES) $(CPU_FFTW_SOURCES) | $(TEST_OUT_DIR)
|
||||
$(CC) $(CPPFLAGS) $(BUILD_CPPFLAGS) $(CFLAGS) $(BUILD_CFLAGS) $(OPENMP_FLAGS) -Isrc $^ $(LDLIBS) -o $@
|
||||
|
||||
$(FAST_PSF_FFTW_BENCH_TARGET): tests/benchmark_fast_psf_fftw.c $(CORE_MINKOWSKI_SOURCES) $(CPU_FFTW_SOURCES) | $(TEST_OUT_DIR)
|
||||
$(CC) $(CPPFLAGS) $(BUILD_CPPFLAGS) $(CFLAGS) $(BUILD_CFLAGS) $(OPENMP_FLAGS) -Isrc $^ $(LDLIBS) -o $@
|
||||
|
||||
# The FFTW-vs-spatial test is meaningful only in the CPU PSF build.
|
||||
ifneq ($(CPU_FFTW_SOURCES),)
|
||||
FAST_PSF_FFTW_TEST_DEP := $(FAST_PSF_FFTW_TEST_TARGET)
|
||||
FAST_PSF_FFTW_TEST_RUN := $(FAST_PSF_FFTW_TEST_TARGET)
|
||||
else
|
||||
FAST_PSF_FFTW_TEST_DEP :=
|
||||
FAST_PSF_FFTW_TEST_RUN :=
|
||||
endif
|
||||
|
||||
test: $(CAMERA_TEST_TARGETS) $(TEST_TARGET) $(FRAME_TEST_TARGET) $(SCHWARZSCHILD_TEST_TARGET) $(OBSERVER_TRACK_TEST_TARGET) $(CATALOG_PREFETCH_TEST_TARGET) $(FAST_PSF_FFTW_TEST_DEP)
|
||||
$(TEST_OUT_DIR)/test_observer_minkowski
|
||||
$(TEST_OUT_DIR)/test_observer_schwarzschild
|
||||
$(TEST_TARGET)
|
||||
$(FRAME_TEST_TARGET)
|
||||
$(SCHWARZSCHILD_TEST_TARGET)
|
||||
$(OBSERVER_TRACK_TEST_TARGET)
|
||||
$(CATALOG_PREFETCH_TEST_TARGET)
|
||||
$(FAST_PSF_FFTW_TEST_RUN)
|
||||
python3 tests/test_camera_cli.py $(BUILD_DIR) $(TEST_OUT_DIR)
|
||||
|
||||
fast-psf-fftw-bench: $(FAST_PSF_FFTW_BENCH_TARGET)
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
@@ -193,14 +248,14 @@ include mk/reference_images.mk
|
||||
|
||||
# Test-only producer consumer: never linked into a renderer.
|
||||
.PHONY: psf-capture
|
||||
psf-capture: $(BUILD_DIR)/capture_psf
|
||||
$(BUILD_DIR)/capture_psf: tests/capture_psf.c $(CORE_MINKOWSKI_SOURCES) | $(BUILD_DIR)
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) $(BUILD_CFLAGS) $(OPENMP_FLAGS) -Isrc $< $(filter-out src/frame.c,$(CORE_MINKOWSKI_SOURCES)) $(LDLIBS) -o $@
|
||||
psf-capture: $(TEST_OUT_DIR)/capture_psf
|
||||
$(TEST_OUT_DIR)/capture_psf: tests/capture_psf.c $(CORE_MINKOWSKI_SOURCES) $(CPU_FFTW_SOURCES) | $(TEST_OUT_DIR)
|
||||
$(CC) $(CPPFLAGS) $(BUILD_CPPFLAGS) $(CFLAGS) $(BUILD_CFLAGS) $(OPENMP_FLAGS) -Isrc $< $(filter-out src/frame.c,$(CORE_MINKOWSKI_SOURCES)) $(CPU_FFTW_SOURCES) $(LDLIBS) -o $@
|
||||
|
||||
.PHONY: hip-psf-replay
|
||||
hip-psf-replay: $(BUILD_DIR)/replay_psf
|
||||
$(BUILD_DIR)/replay_psf: tests/replay_psf.hip src/hip_psf.hip src/hip_psf.h src/optics.h $(OBJECT_DIR)/$(HDR_BUILD_TAG)/src/optics.o | $(BUILD_DIR)
|
||||
hip-psf-replay: $(TEST_OUT_DIR)/replay_psf
|
||||
$(TEST_OUT_DIR)/replay_psf: tests/replay_psf.hip src/hip_psf.hip src/hip_psf.h src/optics.h $(OBJECT_DIR)/$(HDR_BUILD_TAG)/src/optics.o | $(TEST_OUT_DIR)
|
||||
$(HIPCC) $(HIP_CXXFLAGS) $(OPENMP_FLAGS) -Isrc -x hip $< -x none $(filter %.o,$^) $(LDLIBS) $(HDR_LDLIBS) -o $@
|
||||
|
||||
$(BUILD_DIR)/make_psf_fixture: tests/make_psf_fixture.c src/optics.c src/optics.h | $(BUILD_DIR)
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) $(BUILD_CFLAGS) $(OPENMP_FLAGS) -Isrc $< src/optics.c $(LDLIBS) -o $@
|
||||
$(TEST_OUT_DIR)/make_psf_fixture: tests/make_psf_fixture.c src/optics.c src/optics.h $(CPU_FFTW_SOURCES) | $(TEST_OUT_DIR)
|
||||
$(CC) $(CPPFLAGS) $(BUILD_CPPFLAGS) $(CFLAGS) $(BUILD_CFLAGS) $(OPENMP_FLAGS) -Isrc $< src/optics.c $(CPU_FFTW_SOURCES) $(LDLIBS) -o $@
|
||||
Reference in new issue
Block a user