diff --git a/include/neural-graphics-primitives/camera_path.h b/include/neural-graphics-primitives/camera_path.h index 492cd8385a6998bd5a7c45cf9078abd7289857d1..856087f823cab1599c827177aa8406e6c12775fb 100644 --- a/include/neural-graphics-primitives/camera_path.h +++ b/include/neural-graphics-primitives/camera_path.h @@ -126,8 +126,8 @@ struct CameraPath { return spline(t-floorf(t), get_keyframe(t1-1), get_keyframe(t1), get_keyframe(t1+1), get_keyframe(t1+2)); } - void save(const std::string& filepath_string); - void load(const std::string& filepath_string, const Eigen::Matrix<float, 3, 4> &first_xform); + void save(const fs::path& path); + void load(const fs::path& path, const Eigen::Matrix<float, 3, 4> &first_xform); #ifdef NGP_GUI ImGuizmo::MODE m_gizmo_mode = ImGuizmo::LOCAL; diff --git a/include/neural-graphics-primitives/common.h b/include/neural-graphics-primitives/common.h index 6e2b1e4b3c6c23603f032f5b077ca8b1be6ad881..8d3f6068b8462f29fedaa4837f3596d5187d1993 100644 --- a/include/neural-graphics-primitives/common.h +++ b/include/neural-graphics-primitives/common.h @@ -63,10 +63,20 @@ NGP_NAMESPACE_BEGIN +namespace fs = filesystem; + bool is_wsl(); -filesystem::path get_executable_dir(); -filesystem::path get_root_dir(); +fs::path get_executable_dir(); +fs::path get_root_dir(); + +#ifdef _WIN32 +std::string utf16_to_utf8(const std::wstring& utf16); +std::wstring utf8_to_utf16(const std::string& utf16); +std::wstring native_string(const fs::path& path); +#else +std::string native_string(const fs::path& path); +#endif bool ends_with(const std::string& str, const std::string& ending); bool ends_with_case_insensitive(const std::string& str, const std::string& ending); @@ -333,4 +343,12 @@ private: std::chrono::time_point<std::chrono::steady_clock> m_creation_time; }; +uint8_t* load_stbi(const fs::path& path, int* width, int* height, int* comp, int req_comp); +float* load_stbi_float(const fs::path& path, int* width, int* height, int* comp, int req_comp); +uint16_t* load_stbi_16(const fs::path& path, int* width, int* height, int* comp, int req_comp); +bool is_hdr_stbi(const fs::path& path); +int write_stbi(const fs::path& path, int width, int height, int comp, const uint8_t* pixels, int quality = 100); + +FILE* native_fopen(const fs::path& path, const char* mode); + NGP_NAMESPACE_END diff --git a/include/neural-graphics-primitives/common_device.cuh b/include/neural-graphics-primitives/common_device.cuh index 8cf8c42f570ea4fabb678b0f999dae9c0616d149..389fc3bdab7aff5925f14c5cfe88d8aebbd94e39 100644 --- a/include/neural-graphics-primitives/common_device.cuh +++ b/include/neural-graphics-primitives/common_device.cuh @@ -756,7 +756,7 @@ inline NGP_HOST_DEVICE float read_depth(Eigen::Vector2f pos, const Eigen::Vector Eigen::Matrix<float, 3, 4> log_space_lerp(const Eigen::Matrix<float, 3, 4>& begin, const Eigen::Matrix<float, 3, 4>& end, float t); -tcnn::GPUMemory<float> load_exr(const std::string& filename, int& width, int& height); -tcnn::GPUMemory<float> load_stbi(const std::string& filename, int& width, int& height); +tcnn::GPUMemory<float> load_exr_gpu(const fs::path& path, int* width, int* height); +tcnn::GPUMemory<float> load_stbi_gpu(const fs::path& path, int* width, int* height); NGP_NAMESPACE_END diff --git a/include/neural-graphics-primitives/marching_cubes.h b/include/neural-graphics-primitives/marching_cubes.h index d09511b7cc0e8074b7194984a0f7f67798f54624..ceb7481a89bda254808af848992123a92379219d 100644 --- a/include/neural-graphics-primitives/marching_cubes.h +++ b/include/neural-graphics-primitives/marching_cubes.h @@ -46,7 +46,7 @@ void save_mesh( tcnn::GPUMemory<Eigen::Vector3f>& normals, tcnn::GPUMemory<Eigen::Vector3f>& colors, tcnn::GPUMemory<uint32_t>& indices, - const char* outputname, + const fs::path& path, bool unwrap_it, float nerf_scale, Eigen::Vector3f nerf_offset @@ -70,10 +70,10 @@ uint32_t compile_shader(bool pixel, const char* code); bool check_shader(uint32_t handle, const char* desc, bool program); #endif -void save_density_grid_to_png(const tcnn::GPUMemory<float>& density, const char* filename, Eigen::Vector3i res3d, float thresh, bool swap_y_z = true, float density_range = 4.f); +void save_density_grid_to_png(const tcnn::GPUMemory<float>& density, const fs::path& path, Eigen::Vector3i res3d, float thresh, bool swap_y_z = true, float density_range = 4.f); -void save_rgba_grid_to_png_sequence(const tcnn::GPUMemory<Eigen::Array4f>& rgba, const char *path, Eigen::Vector3i res3d, bool swap_y_z = true); +void save_rgba_grid_to_png_sequence(const tcnn::GPUMemory<Eigen::Array4f>& rgba, const fs::path& path, Eigen::Vector3i res3d, bool swap_y_z = true); -void save_rgba_grid_to_raw_file(const tcnn::GPUMemory<Eigen::Array4f>& rgba, const char* path, Eigen::Vector3i res3d, bool swap_y_z, int cascade); +void save_rgba_grid_to_raw_file(const tcnn::GPUMemory<Eigen::Array4f>& rgba, const fs::path& path, Eigen::Vector3i res3d, bool swap_y_z, int cascade); NGP_NAMESPACE_END diff --git a/include/neural-graphics-primitives/nerf_loader.h b/include/neural-graphics-primitives/nerf_loader.h index 426d99bce841b7faa64995a41dbb611245867522..61ae9eedf34d441d59ecb1fa1faaf9f51355804f 100644 --- a/include/neural-graphics-primitives/nerf_loader.h +++ b/include/neural-graphics-primitives/nerf_loader.h @@ -180,7 +180,7 @@ struct NerfDataset { } }; -NerfDataset load_nerf(const std::vector<filesystem::path>& jsonpaths, float sharpen_amount = 0.f); +NerfDataset load_nerf(const std::vector<fs::path>& jsonpaths, float sharpen_amount = 0.f); NerfDataset create_empty_nerf_dataset(size_t n_images, int aabb_scale = 1, bool is_hdr = false); NGP_NAMESPACE_END diff --git a/include/neural-graphics-primitives/render_buffer.h b/include/neural-graphics-primitives/render_buffer.h index 73466ff3008d78863dea9a075240b5907d2d8552..2e29f72a6fd896c815645fa81273bbf20e219ff2 100644 --- a/include/neural-graphics-primitives/render_buffer.h +++ b/include/neural-graphics-primitives/render_buffer.h @@ -105,7 +105,7 @@ public: bool is_8bit() { return m_is_8bit; } - void load(const char* fname); + void load(const fs::path& path); void load(const float* data, Eigen::Vector2i new_size, int n_channels); diff --git a/include/neural-graphics-primitives/testbed.h b/include/neural-graphics-primitives/testbed.h index c766a84a19521949b58e2030113c7aa0d52eab06..e6db007b6ce0c39437b4ac51ff1820566c368ed3 100644 --- a/include/neural-graphics-primitives/testbed.h +++ b/include/neural-graphics-primitives/testbed.h @@ -31,8 +31,6 @@ #include <json/json.hpp> -#include <filesystem/path.h> - #ifdef NGP_PYTHON # include <pybind11/pybind11.h> # include <pybind11/numpy.h> @@ -64,13 +62,14 @@ public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW Testbed(ETestbedMode mode = ETestbedMode::None); ~Testbed(); - Testbed(ETestbedMode mode, const std::string& data_path) : Testbed(mode) { load_training_data(data_path); } - Testbed(ETestbedMode mode, const std::string& data_path, const std::string& network_config_path) : Testbed(mode, data_path) { reload_network_from_file(network_config_path); } - Testbed(ETestbedMode mode, const std::string& data_path, const nlohmann::json& network_config) : Testbed(mode, data_path) { reload_network_from_json(network_config); } + + Testbed(ETestbedMode mode, const fs::path& data_path) : Testbed(mode) { load_training_data(data_path); } + Testbed(ETestbedMode mode, const fs::path& data_path, const fs::path& network_config_path) : Testbed(mode, data_path) { reload_network_from_file(network_config_path); } + Testbed(ETestbedMode mode, const fs::path& data_path, const nlohmann::json& network_config) : Testbed(mode, data_path) { reload_network_from_json(network_config); } bool clear_tmp_dir(); void update_imgui_paths(); - void load_training_data(const std::string& data_path); + void load_training_data(const fs::path& path); void reload_training_data(); void clear_training_data(); @@ -274,7 +273,7 @@ public: ); void train_volume(size_t target_batch_size, bool get_loss_scalar, cudaStream_t stream); void training_prep_volume(uint32_t batch_size, cudaStream_t stream) {} - void load_volume(const filesystem::path& data_path); + void load_volume(const fs::path& data_path); void render_sdf( const distance_fun_t& distance_function, @@ -291,9 +290,9 @@ public: void render_image(CudaRenderBuffer& render_buffer, cudaStream_t stream); void render_frame(const Eigen::Matrix<float, 3, 4>& camera_matrix0, const Eigen::Matrix<float, 3, 4>& camera_matrix1, const Eigen::Vector4f& nerf_rolling_shutter, CudaRenderBuffer& render_buffer, bool to_srgb = true) ; void visualize_nerf_cameras(ImDrawList* list, const Eigen::Matrix<float, 4, 4>& world2proj); - filesystem::path find_network_config(const filesystem::path& network_config_path); - nlohmann::json load_network_config(const filesystem::path& network_config_path); - void reload_network_from_file(const std::string& network_config_path = ""); + fs::path find_network_config(const fs::path& network_config_path); + nlohmann::json load_network_config(const fs::path& network_config_path); + void reload_network_from_file(const fs::path& path = ""); void reload_network_from_json(const nlohmann::json& json, const std::string& config_base_path=""); // config_base_path is needed so that if the passed in json uses the 'parent' feature, we know where to look... be sure to use a filename, or if a directory, end with a trailing slash void reset_accumulation(bool due_to_camera_movement = false, bool immediate_redraw = true); void redraw_next_frame() { @@ -303,9 +302,9 @@ public: static ELossType string_to_loss_type(const std::string& str); void reset_network(bool clear_density_grid = true); void create_empty_nerf_dataset(size_t n_images, int aabb_scale = 1, bool is_hdr = false); - void load_nerf(const filesystem::path& data_path); + void load_nerf(const fs::path& data_path); void load_nerf_post(); - void load_mesh(const filesystem::path& data_path); + void load_mesh(const fs::path& data_path); void set_exposure(float exposure) { m_exposure = exposure; } void set_max_level(float maxlevel); void set_min_level(float minlevel); @@ -314,7 +313,7 @@ public: void translate_camera(const Eigen::Vector3f& rel); void mouse_drag(const Eigen::Vector2f& rel, int button); void mouse_wheel(Eigen::Vector2f m, float delta); - void load_file(const std::string& file); + void load_file(const fs::path& path); void set_nerf_camera_matrix(const Eigen::Matrix<float, 3, 4>& cam); Eigen::Vector3f look_at() const; void set_look_at(const Eigen::Vector3f& pos); @@ -385,7 +384,6 @@ public: #ifdef NGP_PYTHON pybind11::dict compute_marching_cubes_mesh(Eigen::Vector3i res3d = Eigen::Vector3i::Constant(128), BoundingBox aabb = BoundingBox{Eigen::Vector3f::Zero(), Eigen::Vector3f::Ones()}, float thresh=2.5f); pybind11::array_t<float> render_to_cpu(int width, int height, int spp, bool linear, float start_t, float end_t, float fps, float shutter_fraction); - pybind11::array_t<float> render_with_rolling_shutter_to_cpu(const Eigen::Matrix<float, 3, 4>& camera_transform_start, const Eigen::Matrix<float, 3, 4>& camera_transform_end, const Eigen::Vector4f& rolling_shutter, int width, int height, int spp, bool linear); pybind11::array_t<float> screenshot(bool linear) const; void override_sdf_training_data(pybind11::array_t<float> points, pybind11::array_t<float> distances); #endif @@ -393,7 +391,7 @@ public: double calculate_iou(uint32_t n_samples=128*1024*1024, float scale_existing_results_factor=0.0, bool blocking=true, bool force_use_octree = true); void draw_visualizations(ImDrawList* list, const Eigen::Matrix<float, 3, 4>& camera_matrix); void train_and_render(bool skip_rendering); - filesystem::path training_data_path() const; + fs::path training_data_path() const; void init_window(int resw, int resh, bool hidden = false, bool second_window = false); void destroy_window(); void apply_camera_smoothing(float elapsed_ms); @@ -403,22 +401,22 @@ public: void draw_gui(); bool frame(); bool want_repl(); - void load_image(const filesystem::path& data_path); - void load_exr_image(const filesystem::path& data_path); - void load_stbi_image(const filesystem::path& data_path); - void load_binary_image(const filesystem::path& data_path); + void load_image(const fs::path& data_path); + void load_exr_image(const fs::path& data_path); + void load_stbi_image(const fs::path& data_path); + void load_binary_image(const fs::path& data_path); uint32_t n_dimensions_to_visualize() const; float fov() const ; void set_fov(float val) ; Eigen::Vector2f fov_xy() const ; void set_fov_xy(const Eigen::Vector2f& val); - void save_snapshot(const std::string& filepath_string, bool include_optimizer_state, bool compress); - void load_snapshot(const std::string& filepath_string); + void save_snapshot(const fs::path& path, bool include_optimizer_state, bool compress); + void load_snapshot(const fs::path& path); CameraKeyframe copy_camera_to_keyframe() const; void set_camera_from_keyframe(const CameraKeyframe& k); void set_camera_from_time(float t); void update_loss_graph(); - void load_camera_path(const std::string& filepath_string); + void load_camera_path(const fs::path& path); bool loop_animation(); void set_loop_animation(bool value); @@ -640,7 +638,7 @@ public: #endif void reset_camera_extrinsics(); - void export_camera_extrinsics(const std::string& filename, bool export_extrinsics_in_quat_format = true); + void export_camera_extrinsics(const fs::path& path, bool export_extrinsics_in_quat_format = true); } training = {}; tcnn::GPUMemory<float> density_grid; // NERF_GRIDSIZE()^3 grid of EMA smoothed densities from the network @@ -865,8 +863,8 @@ public: bool m_train_encoding = true; bool m_train_network = true; - filesystem::path m_data_path; - filesystem::path m_network_config_path = "base.json"; + fs::path m_data_path; + fs::path m_network_config_path = "base.json"; nlohmann::json m_network_config; diff --git a/include/neural-graphics-primitives/tinyexr_wrapper.h b/include/neural-graphics-primitives/tinyexr_wrapper.h index 05adf882694c64305c0104a706bec17bdea79c92..db690368bdce1bad34ec09f51472614a45b29bff 100644 --- a/include/neural-graphics-primitives/tinyexr_wrapper.h +++ b/include/neural-graphics-primitives/tinyexr_wrapper.h @@ -20,8 +20,8 @@ NGP_NAMESPACE_BEGIN -void save_exr(const float* data, int width, int height, int nChannels, int channelStride, const char* outfilename); -void load_exr(float** data, int* width, int* height, const char* filename); -__half* load_exr_to_gpu(int* width, int* height, const char* filename, bool fix_premult); +void save_exr(const float* data, int width, int height, int nChannels, int channelStride, const fs::path& path); +void load_exr(float** data, int* width, int* height, const fs::path& path); +__half* load_exr_to_gpu(int* width, int* height, const fs::path& path, bool fix_premult); NGP_NAMESPACE_END diff --git a/include/neural-graphics-primitives/tinyobj_loader_wrapper.h b/include/neural-graphics-primitives/tinyobj_loader_wrapper.h index 187617b804afe3c0da7af6a86e412f505d2debc7..cbd823263aca7d13ec5b3fc3e1cf5ac35ef3724c 100644 --- a/include/neural-graphics-primitives/tinyobj_loader_wrapper.h +++ b/include/neural-graphics-primitives/tinyobj_loader_wrapper.h @@ -23,6 +23,6 @@ NGP_NAMESPACE_BEGIN -std::vector<Eigen::Vector3f> load_obj(const std::string& filename); +std::vector<Eigen::Vector3f> load_obj(const fs::path& path); NGP_NAMESPACE_END diff --git a/src/camera_path.cu b/src/camera_path.cu index 9c23a71633d71378f1346ca344ea9a11318f994a..5f3a3ff00d29b3ae936df685f2297b95ef5307c3 100644 --- a/src/camera_path.cu +++ b/src/camera_path.cu @@ -107,20 +107,20 @@ void from_json(bool is_first, const json& j, CameraKeyframe& p, const CameraKeyf } -void CameraPath::save(const std::string& filepath_string) { +void CameraPath::save(const fs::path& path) { json j = { {"loop", loop}, {"time", play_time}, {"path", keyframes}, }; - std::ofstream f(filepath_string); + std::ofstream f(native_string(path)); f << j; } -void CameraPath::load(const std::string& filepath_string, const Eigen::Matrix<float, 3, 4>& first_xform) { - std::ifstream f(filepath_string); +void CameraPath::load(const fs::path& path, const Eigen::Matrix<float, 3, 4>& first_xform) { + std::ifstream f{native_string(path)}; if (!f) { - throw std::runtime_error{fmt::format("Camera path {} does not exist.", filepath_string)}; + throw std::runtime_error{fmt::format("Camera path {} does not exist.", path.str())}; } json j; diff --git a/src/common.cu b/src/common.cu index a0b43891de843397f8f7aa86d560a79fc9033d5e..b6c2ec6abdd91ad0f6c027d01ad97b9c1a5bb0cf 100644 --- a/src/common.cu +++ b/src/common.cu @@ -18,13 +18,39 @@ #include <filesystem/path.h> -#ifndef _WIN32 +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef __NVCC__ +# ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +# pragma nv_diag_suppress 550 +# else +# pragma diag_suppress 550 +# endif +#endif +#include <stb_image/stb_image.h> +#include <stb_image/stb_image_write.h> +#ifdef __NVCC__ +# ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +# pragma nv_diag_default 550 +# else +# pragma diag_default 550 +# endif +#endif + +#ifdef _WIN32 +# include <windows.h> +#else # include <unistd.h> # include <linux/limits.h> #endif +#undef min +#undef max +#undef near +#undef far + using namespace tcnn; -namespace fs = filesystem; NGP_NAMESPACE_BEGIN @@ -37,34 +63,61 @@ bool is_wsl() { return false; } - std::ifstream f{path.str()}; + std::ifstream f{native_string(path)}; std::string content((std::istreambuf_iterator<char>(f)), (std::istreambuf_iterator<char>())); return content.find("microsoft") != std::string::npos; #endif } +#ifdef _WIN32 +std::string utf16_to_utf8(const std::wstring& utf16) { + std::string utf8; + if (!utf16.empty()) { + int size = WideCharToMultiByte(CP_UTF8, 0, &utf16[0], (int)utf16.size(), NULL, 0, NULL, NULL); + utf8.resize(size, 0); + WideCharToMultiByte(CP_UTF8, 0, &utf16[0], (int)utf16.size(), &utf8[0], size, NULL, NULL); + } + return utf8; +} + +std::wstring utf8_to_utf16(const std::string& utf8) { + std::wstring utf16; + if (!utf8.empty()) { + int size = MultiByteToWideChar(CP_UTF8, 0, &utf8[0], (int)utf8.size(), NULL, 0); + utf16.resize(size, 0); + MultiByteToWideChar(CP_UTF8, 0, &utf8[0], (int)utf8.size(), &utf16[0], size); + } + return utf16; +} + +std::wstring native_string(const fs::path& path) { return path.wstr(); } +#else +std::string native_string(const fs::path& path) { return path.str(); } +#endif + fs::path get_executable_dir() { #ifdef _WIN32 - WCHAR path[MAX_PATH]; - if (GetModuleFileNameW(NULL, path, MAX_PATH) == 0) { + WCHAR path[1024]; + if (GetModuleFileNameW(NULL, path, 1024) == 0) { return "."; } + return fs::path{std::wstring{path}}.parent_path(); #else char path[PATH_MAX]; ssize_t count = readlink("/proc/self/exe", path, PATH_MAX); if (count == -1) { return "."; } + return fs::path{std::string{path}}.parent_path(); #endif - return fs::path{path}.parent_path(); } -filesystem::path get_root_dir() { +fs::path get_root_dir() { auto executable_dir = get_executable_dir(); - fs::path exists_in_root_dir = "./scripts"; + fs::path exists_in_root_dir = "scripts"; for (const auto& candidate : { - exists_in_root_dir, - fs::path{"../"}/exists_in_root_dir, + fs::path{"."}/exists_in_root_dir, + fs::path{".."}/exists_in_root_dir, executable_dir/exists_in_root_dir, executable_dir/".."/exists_in_root_dir, }) { @@ -130,4 +183,69 @@ std::string to_string(ETestbedMode mode) { } } +static const stbi_io_callbacks istream_stbi_callbacks = { + // Read + [](void* context, char* data, int size) { + auto stream = reinterpret_cast<std::istream*>(context); + stream->read(data, size); + return (int)stream->gcount(); + }, + // Seek + [](void* context, int size) { + reinterpret_cast<std::istream*>(context)->seekg(size, std::ios_base::cur); + }, + // EOF + [](void* context) { + return (int)!!(*reinterpret_cast<std::istream*>(context)); + }, +}; + +void istream_stbi_write_func(void* context, void* data, int size) { + reinterpret_cast<std::ostream*>(context)->write(reinterpret_cast<char*>(data), size); +} + +uint8_t* load_stbi(const fs::path& path, int* width, int* height, int* comp, int req_comp) { + std::ifstream f{native_string(path), std::ios::in | std::ios::binary}; + return stbi_load_from_callbacks(&istream_stbi_callbacks, &f, width, height, comp, req_comp); +} + +float* load_stbi_float(const fs::path& path, int* width, int* height, int* comp, int req_comp) { + std::ifstream f{native_string(path), std::ios::in | std::ios::binary}; + return stbi_loadf_from_callbacks(&istream_stbi_callbacks, &f, width, height, comp, req_comp); +} + +uint16_t* load_stbi_16(const fs::path& path, int* width, int* height, int* comp, int req_comp) { + std::ifstream f{native_string(path), std::ios::in | std::ios::binary}; + return stbi_load_16_from_callbacks(&istream_stbi_callbacks, &f, width, height, comp, req_comp); +} + +bool is_hdr_stbi(const fs::path& path) { + std::ifstream f{native_string(path), std::ios::in | std::ios::binary}; + return stbi_is_hdr_from_callbacks(&istream_stbi_callbacks, &f); +} + +int write_stbi(const fs::path& path, int width, int height, int comp, const uint8_t* pixels, int quality) { + std::ofstream f{native_string(path), std::ios::out | std::ios::binary}; + + if (equals_case_insensitive(path.extension(), "jpg") || equals_case_insensitive(path.extension(), "jpeg")) { + return stbi_write_jpg_to_func(istream_stbi_write_func, &f, width, height, comp, pixels, quality); + } else if (equals_case_insensitive(path.extension(), "png")) { + return stbi_write_png_to_func(istream_stbi_write_func, &f, width, height, comp, pixels, width * comp); + } else if (equals_case_insensitive(path.extension(), "tga")) { + return stbi_write_tga_to_func(istream_stbi_write_func, &f, width, height, comp, pixels); + } else if (equals_case_insensitive(path.extension(), "bmp")) { + return stbi_write_bmp_to_func(istream_stbi_write_func, &f, width, height, comp, pixels); + } else { + throw std::runtime_error{fmt::format("write_stbi: unknown image extension '{}'", path.extension())}; + } +} + +FILE* native_fopen(const fs::path& path, const char* mode) { +#ifdef _WIN32 + return _wfopen(path.wstr().c_str(), utf8_to_utf16(mode).c_str()); +#else + return fopen(path.str().c_str(), mode); +#endif +} + NGP_NAMESPACE_END diff --git a/src/common_device.cu b/src/common_device.cu index 5ae268348fcace47ade376acfcc6d9929feb61f9..add7bb6e4fba273f2c27ea557e01230e4817a574 100644 --- a/src/common_device.cu +++ b/src/common_device.cu @@ -36,25 +36,25 @@ Matrix<float, 3, 4> log_space_lerp(const Matrix<float, 3, 4>& begin, const Matri return ((log_space_a_to_b * t).exp() * A).block<3,4>(0,0); } -GPUMemory<float> load_exr(const std::string& filename, int& width, int& height) { +GPUMemory<float> load_exr_gpu(const fs::path& path, int* width, int* height) { float* out; // width * height * RGBA - load_exr(&out, &width, &height, filename.c_str()); + load_exr(&out, width, height, path.str().c_str()); ScopeGuard mem_guard{[&]() { free(out); }}; - GPUMemory<float> result(width * height * 4); + GPUMemory<float> result((*width) * (*height) * 4); result.copy_from_host(out); return result; } -GPUMemory<float> load_stbi(const std::string& filename, int& width, int& height) { - bool is_hdr = stbi_is_hdr(filename.c_str()); +GPUMemory<float> load_stbi_gpu(const fs::path& path, int* width, int* height) { + bool is_hdr = is_hdr_stbi(path); void* data; // width * height * RGBA int comp; if (is_hdr) { - data = stbi_loadf(filename.c_str(), &width, &height, &comp, 4); + data = load_stbi_float(path, width, height, &comp, 4); } else { - data = stbi_load(filename.c_str(), &width, &height, &comp, 4); + data = load_stbi(path, width, height, &comp, 4); } if (!data) { @@ -63,17 +63,17 @@ GPUMemory<float> load_stbi(const std::string& filename, int& width, int& height) ScopeGuard mem_guard{[&]() { stbi_image_free(data); }}; - if (width == 0 || height == 0) { + if (*width == 0 || *height == 0) { throw std::runtime_error{"Image has zero pixels."}; } - GPUMemory<float> result(width * height * 4); + GPUMemory<float> result((*width) * (*height) * 4); if (is_hdr) { result.copy_from_host((float*)data); } else { - GPUMemory<uint8_t> bytes(width * height * 4); + GPUMemory<uint8_t> bytes((*width) * (*height) * 4); bytes.copy_from_host((uint8_t*)data); - linear_kernel(from_rgba32<float>, 0, nullptr, width * height, bytes.data(), result.data(), false, false, 0); + linear_kernel(from_rgba32<float>, 0, nullptr, (*width) * (*height), bytes.data(), result.data(), false, false, 0); } return result; diff --git a/src/dlss.cu b/src/dlss.cu index 995f1c7df6b7961b1517fb6fd15cf12772edf6c7..d81df9390f895cd0f4f47039e6db9a849081d805 100644 --- a/src/dlss.cu +++ b/src/dlss.cu @@ -52,7 +52,6 @@ static_assert(false, "DLSS can only be compiled when both Vulkan and GUI support using namespace Eigen; using namespace tcnn; -namespace fs = filesystem; NGP_NAMESPACE_BEGIN diff --git a/src/main.cu b/src/main.cu index 078014fab9632fb5faeeb7fa97553c1efd5bf58e..8c30e833de4c89b30b115cf73f276b6168f10e72 100644 --- a/src/main.cu +++ b/src/main.cu @@ -24,9 +24,10 @@ using namespace args; using namespace ngp; using namespace std; using namespace tcnn; -namespace fs = ::filesystem; -int main(int argc, char** argv) { +NGP_NAMESPACE_BEGIN + +int main_func(const std::vector<std::string>& arguments) { ArgumentParser parser{ "Instant Neural Graphics Primitives\n" "Version " NGP_VERSION, @@ -112,7 +113,13 @@ int main(int argc, char** argv) { // Parse command line arguments and react to parsing // errors using exceptions. try { - parser.ParseCLI(argc, argv); + if (arguments.empty()) { + tlog::error() << "Number of arguments must be bigger than 0."; + return -3; + } + + parser.Prog(arguments.front()); + parser.ParseArgs(begin(arguments) + 1, end(arguments)); } catch (const Help&) { cout << parser; return 0; @@ -131,47 +138,67 @@ int main(int argc, char** argv) { return 0; } - try { - if (mode_flag) { - tlog::warning() << "The '--mode' argument is no longer in use. It has no effect. The mode is automatically chosen based on the scene."; - } + if (mode_flag) { + tlog::warning() << "The '--mode' argument is no longer in use. It has no effect. The mode is automatically chosen based on the scene."; + } - Testbed testbed; + Testbed testbed; - for (auto file : get(files)) { - testbed.load_file(file); - } + for (auto file : get(files)) { + testbed.load_file(file); + } - if (scene_flag) { - testbed.load_training_data(get(scene_flag)); - } + if (scene_flag) { + testbed.load_training_data(get(scene_flag)); + } - if (snapshot_flag) { - testbed.load_snapshot(get(snapshot_flag)); - } else if (network_config_flag) { - testbed.reload_network_from_file(get(network_config_flag)); - } + if (snapshot_flag) { + testbed.load_snapshot(get(snapshot_flag)); + } else if (network_config_flag) { + testbed.reload_network_from_file(get(network_config_flag)); + } - testbed.m_train = !no_train_flag; + testbed.m_train = !no_train_flag; #ifdef NGP_GUI - bool gui = !no_gui_flag; + bool gui = !no_gui_flag; #else - bool gui = false; + bool gui = false; #endif - if (gui) { - testbed.init_window(width_flag ? get(width_flag) : 1920, height_flag ? get(height_flag) : 1080); + if (gui) { + testbed.init_window(width_flag ? get(width_flag) : 1920, height_flag ? get(height_flag) : 1080); + } + + // Render/training loop + while (testbed.frame()) { + if (!gui) { + tlog::info() << "iteration=" << testbed.m_training_step << " loss=" << testbed.m_loss_scalar.val(); } + } +} + +NGP_NAMESPACE_END - // Render/training loop - while (testbed.frame()) { - if (!gui) { - tlog::info() << "iteration=" << testbed.m_training_step << " loss=" << testbed.m_loss_scalar.val(); - } +#ifdef _WIN32 +int wmain(int argc, wchar_t* argv[]) { + SetConsoleOutputCP(CP_UTF8); +#else +int main(int argc, char* argv[]) { +#endif + try { + std::vector<std::string> arguments; + for (int i = 0; i < argc; ++i) { +#ifdef _WIN32 + arguments.emplace_back(ngp::utf16_to_utf8(argv[i])); +#else + arguments.emplace_back(argv[i]); +#endif } + + return ngp::main_func(arguments); } catch (const exception& e) { - tlog::error() << "Uncaught exception: " << e.what(); + tlog::error() << fmt::format("Uncaught exception: {}", e.what()); return 1; } } diff --git a/src/marching_cubes.cu b/src/marching_cubes.cu index 7bc2d28c3e201a5e131737c38c8a24cd663c62b0..28c28585ab86bd8e8104319ecde973a325fae723 100644 --- a/src/marching_cubes.cu +++ b/src/marching_cubes.cu @@ -21,9 +21,6 @@ #include <tiny-cuda-nn/gpu_memory.h> #include <filesystem/path.h> -#define STB_IMAGE_WRITE_IMPLEMENTATION - -#include <stb_image/stb_image_write.h> #include <stdarg.h> #ifdef NGP_GUI @@ -40,7 +37,6 @@ using namespace Eigen; using namespace tcnn; -namespace fs = filesystem; NGP_NAMESPACE_BEGIN @@ -819,7 +815,7 @@ void save_mesh( GPUMemory<Vector3f>& normals, GPUMemory<Vector3f>& colors, GPUMemory<uint32_t>& indices, - const char* outputname, + const fs::path& path, bool unwrap_it, float nerf_scale, Vector3f nerf_offset @@ -862,16 +858,17 @@ void save_mesh( tex[x*3+y*3*texw+2]=b; } } - stbi_write_tga(fs::path(outputname).with_extension(".tga").str().c_str(), texw, texh, 3, tex); + + write_stbi(path.with_extension(".tga"), texw, texh, 3, tex); free(tex); } - FILE* f = fopen(outputname, "wb"); + FILE* f = native_fopen(path, "wb"); if (!f) { - throw std::runtime_error{"Failed to open " + std::string(outputname) + " for writing."}; + throw std::runtime_error{fmt::format("Failed to open '{}' for writing", path.str())}; } - if (fs::path(outputname).extension() == "ply") { + if (equals_case_insensitive(path.extension(), "ply")) { // ply file fprintf(f, "ply\n" @@ -958,7 +955,7 @@ void save_mesh( fclose(f); } -void save_density_grid_to_png(const GPUMemory<float>& density, const char* filename, Vector3i res3d, float thresh, bool swap_y_z, float density_range) { +void save_density_grid_to_png(const GPUMemory<float>& density, const fs::path& path, Vector3i res3d, float thresh, bool swap_y_z, float density_range) { float density_scale = 128.f / density_range; // map from -density_range to density_range into 0-255 std::vector<float> density_cpu; density_cpu.resize(density.size()); @@ -1025,9 +1022,9 @@ void save_density_grid_to_png(const GPUMemory<float>& density, const char* filen } } - stbi_write_png(filename, w, h, 1, pngpixels, w); + write_stbi(path, w, h, 1, pngpixels); - tlog::success() << "Wrote density PNG to " << filename; + tlog::success() << "Wrote density PNG to " << path.str(); tlog::info() << " #lattice points=" << N << " #zero-x voxels=" << num_voxels << " (" << ((num_voxels*100.0)/N) << "%%)" @@ -1039,7 +1036,7 @@ void save_density_grid_to_png(const GPUMemory<float>& density, const char* filen // Distinct from `save_density_grid_to_png` not just in that is writes RGBA, but also // in that it writes a sequence of PNGs rather than a single large PNG. // TODO: make both methods configurable to do either single PNG or PNG sequence. -void save_rgba_grid_to_png_sequence(const GPUMemory<Array4f>& rgba, const char* path, Vector3i res3d, bool swap_y_z) { +void save_rgba_grid_to_png_sequence(const GPUMemory<Array4f>& rgba, const fs::path& path, Vector3i res3d, bool swap_y_z) { std::vector<Array4f> rgba_cpu; rgba_cpu.resize(rgba.size()); rgba.copy_to_host(rgba_cpu); @@ -1066,10 +1063,8 @@ void save_rgba_grid_to_png_sequence(const GPUMemory<Array4f>& rgba, const char* *dst++ = (uint8_t)tcnn::clamp(rgba_cpu[i].w() * 255.f, 0.f, 255.f); } } - // write slice - char filename[256]; - snprintf(filename, sizeof(filename), "%s/%04d_%dx%d.png", path, z, w, h); - stbi_write_png(filename, w, h, 4, pngpixels, w*4); + + write_stbi(path / fmt::format("{:04d}_{}x{}.png", z, w, h), w, h, 4, pngpixels); free(pngpixels); progress.update(++n_saved); @@ -1077,7 +1072,7 @@ void save_rgba_grid_to_png_sequence(const GPUMemory<Array4f>& rgba, const char* tlog::success() << "Wrote RGBA PNG sequence to " << path; } -void save_rgba_grid_to_raw_file(const GPUMemory<Array4f>& rgba, const char* path, Vector3i res3d, bool swap_y_z, int cascade) { +void save_rgba_grid_to_raw_file(const GPUMemory<Array4f>& rgba, const fs::path& path, Vector3i res3d, bool swap_y_z, int cascade) { std::vector<Array4f> rgba_cpu; rgba_cpu.resize(rgba.size()); rgba.copy_to_host(rgba_cpu); @@ -1089,9 +1084,9 @@ void save_rgba_grid_to_raw_file(const GPUMemory<Array4f>& rgba, const char* path uint32_t w = res3d.x(); uint32_t h = res3d.y(); uint32_t d = res3d.z(); - char filename[256]; - snprintf(filename, sizeof(filename), "%s/%dx%dx%d_%d.bin", path, w, h, d, cascade); - FILE *f=fopen(filename,"wb"); + + auto actual_path = path / fmt::format("{}x{}x{}_{}.bin", w, h, d, cascade); + FILE* f = native_fopen(actual_path, "wb"); if (!f) return ; const static float zero[4]={0.f,0.f,0.f,0.f}; @@ -1111,7 +1106,7 @@ void save_rgba_grid_to_raw_file(const GPUMemory<Array4f>& rgba, const char* path } } fclose(f); - tlog::success() << "Wrote RGBA raw file to " << filename; + tlog::success() << "Wrote RGBA raw file to " << actual_path.str(); } NGP_NAMESPACE_END diff --git a/src/nerf_loader.cu b/src/nerf_loader.cu index c3b7a3a6d4ca2a89940eec12a3dbcf379d0aa3a2..3fd76ca003e845e183bdd3b914e3af12bc21c9c1 100644 --- a/src/nerf_loader.cu +++ b/src/nerf_loader.cu @@ -23,6 +23,8 @@ #include <filesystem/path.h> +#include <stb_image/stb_image.h> + #define _USE_MATH_DEFINES #include <cmath> #include <cstdlib> @@ -31,28 +33,9 @@ #include <string> #include <vector> -#define STB_IMAGE_IMPLEMENTATION - -#ifdef __NVCC__ -# ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ -# pragma nv_diag_suppress 550 -# else -# pragma diag_suppress 550 -# endif -#endif -#include <stb_image/stb_image.h> -#ifdef __NVCC__ -# ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ -# pragma nv_diag_default 550 -# else -# pragma diag_default 550 -# endif -#endif - using namespace tcnn; using namespace std::literals; using namespace Eigen; -namespace fs = filesystem; NGP_NAMESPACE_BEGIN @@ -284,7 +267,7 @@ bool read_focal_length(const nlohmann::json &json, Vector2f &focal_length, const return true; } -NerfDataset load_nerf(const std::vector<filesystem::path>& jsonpaths, float sharpen_amount) { +NerfDataset load_nerf(const std::vector<fs::path>& jsonpaths, float sharpen_amount) { if (jsonpaths.empty()) { throw std::runtime_error{"Cannot load NeRF data from an empty set of paths."}; } @@ -293,7 +276,7 @@ NerfDataset load_nerf(const std::vector<filesystem::path>& jsonpaths, float shar NerfDataset result{}; - std::ifstream f{jsonpaths.front().str()}; + std::ifstream f{native_string(jsonpaths.front())}; nlohmann::json transforms = nlohmann::json::parse(f, nullptr, true, true); ThreadPool pool; @@ -322,7 +305,7 @@ NerfDataset load_nerf(const std::vector<filesystem::path>& jsonpaths, float shar std::transform( jsonpaths.begin(), jsonpaths.end(), std::back_inserter(jsons), [](const auto& path) { - return nlohmann::json::parse(std::ifstream{path.str()}, nullptr, true, true); + return nlohmann::json::parse(std::ifstream{native_string(path)}, nullptr, true, true); } ); @@ -546,10 +529,10 @@ NerfDataset load_nerf(const std::vector<filesystem::path>& jsonpaths, float shar } if (equals_case_insensitive(envmap_path.extension(), "exr")) { - result.envmap_data = load_exr(envmap_path.str(), result.envmap_resolution.x(), result.envmap_resolution.y()); + result.envmap_data = load_exr_gpu(envmap_path, &result.envmap_resolution.x(), &result.envmap_resolution.y()); result.is_hdr = true; } else { - result.envmap_data = load_stbi(envmap_path.str(), result.envmap_resolution.x(), result.envmap_resolution.y()); + result.envmap_data = load_stbi_gpu(envmap_path, &result.envmap_resolution.x(), &result.envmap_resolution.y()); } } @@ -580,7 +563,7 @@ NerfDataset load_nerf(const std::vector<filesystem::path>& jsonpaths, float shar result.is_hdr = true; } else { dst.image_data_on_gpu = false; - uint8_t* img = stbi_load(path.str().c_str(), &dst.res.x(), &dst.res.y(), &comp, 4); + uint8_t* img = load_stbi(path, &dst.res.x(), &dst.res.y(), &comp, 4); if (!img) { throw std::runtime_error{"Could not open image file: "s + std::string{stbi_failure_reason()}}; } @@ -588,7 +571,7 @@ NerfDataset load_nerf(const std::vector<filesystem::path>& jsonpaths, float shar fs::path alphapath = resolve_path(base_path, fmt::format("{}.alpha.{}", frame["file_path"], path.extension())); if (alphapath.exists()) { int wa = 0, ha = 0; - uint8_t* alpha_img = stbi_load(alphapath.str().c_str(), &wa, &ha, &comp, 4); + uint8_t* alpha_img = load_stbi(alphapath, &wa, &ha, &comp, 4); if (!alpha_img) { throw std::runtime_error{"Could not load alpha image "s + alphapath.str()}; } @@ -607,7 +590,7 @@ NerfDataset load_nerf(const std::vector<filesystem::path>& jsonpaths, float shar fs::path maskpath = path.parent_path() / fmt::format("dynamic_mask_{}.png", path.basename()); if (maskpath.exists()) { int wa = 0, ha = 0; - uint8_t* mask_img = stbi_load(maskpath.str().c_str(), &wa, &ha, &comp, 4); + uint8_t* mask_img = load_stbi(maskpath, &wa, &ha, &comp, 4); if (!mask_img) { throw std::runtime_error{fmt::format("Dynamic mask {} could not be loaded.", maskpath.str())}; } @@ -637,7 +620,7 @@ NerfDataset load_nerf(const std::vector<filesystem::path>& jsonpaths, float shar fs::path depthpath = resolve_path(base_path, frame["depth_path"]); if (depthpath.exists()) { int wa = 0, ha = 0; - dst.depth_pixels = stbi_load_16(depthpath.str().c_str(), &wa, &ha, &comp, 1); + dst.depth_pixels = load_stbi_16(depthpath, &wa, &ha, &comp, 1); if (!dst.depth_pixels) { throw std::runtime_error{fmt::format("Could not load depth image '{}'.", depthpath.str())}; } @@ -653,7 +636,7 @@ NerfDataset load_nerf(const std::vector<filesystem::path>& jsonpaths, float shar uint32_t n_pixels = dst.res.prod(); dst.rays = (Ray*)malloc(n_pixels * sizeof(Ray)); - std::ifstream rays_file{rayspath.str(), std::ios::binary}; + std::ifstream rays_file{native_string(rayspath), std::ios::binary}; rays_file.read((char*)dst.rays, n_pixels * sizeof(Ray)); std::streampos fsize = 0; diff --git a/src/python_api.cu b/src/python_api.cu index f69fc8cce6ef98d7c8480c184f061ac117b452bc..f69056f3f46a65aff0b7f18e7cec7a5fa56f239b 100644 --- a/src/python_api.cu +++ b/src/python_api.cu @@ -326,11 +326,18 @@ PYBIND11_MODULE(pyngp, m) { .def_readwrite("max", &BoundingBox::max) ; + py::class_<fs::path>(m, "path") + .def(py::init<>()) + .def(py::init<const std::string&>()) + ; + + py::implicitly_convertible<std::string, fs::path>(); + py::class_<Testbed> testbed(m, "Testbed"); testbed .def(py::init<ETestbedMode>(), py::arg("mode") = ETestbedMode::None) - .def(py::init<ETestbedMode, const std::string&, const std::string&>()) - .def(py::init<ETestbedMode, const std::string&, const json&>()) + .def(py::init<ETestbedMode, const fs::path&, const fs::path&>()) + .def(py::init<ETestbedMode, const fs::path&, const json&>()) .def_readonly("mode", &Testbed::m_testbed_mode) .def("create_empty_nerf_dataset", &Testbed::create_empty_nerf_dataset, "Allocate memory for a nerf dataset with a given size", py::arg("n_images"), py::arg("aabb_scale")=1, py::arg("is_hdr")=false) .def("load_training_data", &Testbed::load_training_data, py::call_guard<py::gil_scoped_release>(), "Load training data from a given path.") @@ -387,8 +394,8 @@ PYBIND11_MODULE(pyngp, m) { .def("n_encoding_params", &Testbed::n_encoding_params, "Number of trainable parameters in the encoding") .def("save_snapshot", &Testbed::save_snapshot, py::arg("path"), py::arg("include_optimizer_state")=false, py::arg("compress")=true, "Save a snapshot of the currently trained model. Optionally compressed (only when saving '.ingp' files).") .def("load_snapshot", &Testbed::load_snapshot, py::arg("path"), "Load a previously saved snapshot") - .def("load_camera_path", &Testbed::load_camera_path, "Load a camera path", py::arg("path")) - .def("load_file", &Testbed::load_file, "Load a file and automatically determine how to handle it. Can be a snapshot, dataset, network config, or camera path.", py::arg("path")) + .def("load_camera_path", &Testbed::load_camera_path, py::arg("path"), "Load a camera path") + .def("load_file", &Testbed::load_file, py::arg("path"), "Load a file and automatically determine how to handle it. Can be a snapshot, dataset, network config, or camera path.") .def_property("loop_animation", &Testbed::loop_animation, &Testbed::set_loop_animation) .def("compute_and_save_png_slices", &Testbed::compute_and_save_png_slices, py::arg("filename"), diff --git a/src/render_buffer.cu b/src/render_buffer.cu index e70aae04172219c0be70591d5d78d21fd27254d0..c6d06e250c5200c96c8455b8de2db5a375a3ff0b 100644 --- a/src/render_buffer.cu +++ b/src/render_buffer.cu @@ -35,7 +35,6 @@ using namespace Eigen; using namespace tcnn; -namespace fs = filesystem; NGP_NAMESPACE_BEGIN @@ -117,10 +116,10 @@ void GLTexture::blit_from_cuda_mapping() { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, m_size.x(), m_size.y(), 0, GL_RGBA, GL_FLOAT, data_cpu); } -void GLTexture::load(const char* fname) { +void GLTexture::load(const fs::path& path) { uint8_t* out; // width * height * RGBA - int comp,width,height; - out = stbi_load(fname, &width, &height, &comp, 4); + int comp, width, height; + out = load_stbi(path, &width, &height, &comp, 4); if (!out) { throw std::runtime_error{std::string{stbi_failure_reason()}}; } @@ -161,7 +160,7 @@ void GLTexture::resize(const Vector2i& new_size, int n_channels, bool is_8bit) { case 2: m_internal_format = is_8bit ? GL_RG8 : GL_RG32F; m_format = GL_RG; break; case 3: m_internal_format = is_8bit ? GL_RGB8 : GL_RGB32F; m_format = GL_RGB; break; case 4: m_internal_format = is_8bit ? GL_RGBA8 : GL_RGBA32F; m_format = GL_RGBA; break; - default: tlog::error() << "Unsupported number of channels: " << n_channels; + default: throw std::runtime_error{fmt::format("GLTexture: unsupported number of channels {}", n_channels)}; } m_is_8bit = is_8bit; m_size = new_size; diff --git a/src/testbed.cu b/src/testbed.cu index dac713813c63d592f6ea3945bd42c06590761275..23735633885daba0ad7550ee7edfe59a93c851de 100644 --- a/src/testbed.cu +++ b/src/testbed.cu @@ -38,9 +38,6 @@ #include <filesystem/directory.h> #include <filesystem/path.h> -#include <stb_image/stb_image.h> -#include <stb_image/stb_image_write.h> - #include <zstr.hpp> #include <fstream> @@ -73,30 +70,30 @@ using namespace Eigen; using namespace std::literals::chrono_literals; using namespace tcnn; -namespace fs = filesystem; + +NGP_NAMESPACE_BEGIN int do_system(const std::string& cmd) { #ifdef _WIN32 tlog::info() << "> " << cmd; + return _wsystem(utf8_to_utf16(cmd).c_str()); #else tlog::info() << "$ " << cmd; -#endif return system(cmd.c_str()); +#endif } -NGP_NAMESPACE_BEGIN - std::atomic<size_t> g_total_n_bytes_allocated{0}; -json merge_parent_network_config(const json& child, const fs::path& child_filename) { +json merge_parent_network_config(const json& child, const fs::path& child_path) { if (!child.contains("parent")) { return child; } - fs::path parent_filename = child_filename.parent_path() / std::string(child["parent"]); - tlog::info() << "Loading parent network config from: " << parent_filename.str(); - std::ifstream f{parent_filename.str()}; + fs::path parent_path = child_path.parent_path() / std::string(child["parent"]); + tlog::info() << "Loading parent network config from: " << parent_path.str(); + std::ifstream f{native_string(parent_path)}; json parent = json::parse(f, nullptr, true, true); - parent = merge_parent_network_config(parent, parent_filename); + parent = merge_parent_network_config(parent, parent_path); parent.merge_patch(child); return parent; } @@ -127,27 +124,26 @@ void Testbed::update_imgui_paths() { snprintf(m_imgui.video_path, sizeof(m_imgui.video_path), "%s", get_filename_in_data_path_with_suffix(m_data_path, m_network_config_path, "_video.mp4").c_str()); } -void Testbed::load_training_data(const std::string& data_path_str) { - fs::path data_path = data_path_str; - if (!data_path.exists()) { - throw std::runtime_error{fmt::format("Data path '{}' does not exist.", data_path.str())}; +void Testbed::load_training_data(const fs::path& path) { + if (!path.exists()) { + throw std::runtime_error{fmt::format("Data path '{}' does not exist.", path.str())}; } // Automatically determine the mode from the first scene that's loaded - ETestbedMode scene_mode = mode_from_scene(data_path.str()); + ETestbedMode scene_mode = mode_from_scene(path.str()); if (scene_mode == ETestbedMode::None) { - throw std::runtime_error{fmt::format("Unknown scene format for path '{}'.", data_path.str())}; + throw std::runtime_error{fmt::format("Unknown scene format for path '{}'.", path.str())}; } set_mode(scene_mode); - m_data_path = data_path; + m_data_path = path; switch (m_testbed_mode) { - case ETestbedMode::Nerf: load_nerf(data_path); break; - case ETestbedMode::Sdf: load_mesh(data_path); break; - case ETestbedMode::Image: load_image(data_path); break; - case ETestbedMode::Volume: load_volume(data_path); break; + case ETestbedMode::Nerf: load_nerf(path); break; + case ETestbedMode::Sdf: load_mesh(path); break; + case ETestbedMode::Image: load_image(path); break; + case ETestbedMode::Volume: load_volume(path); break; default: throw std::runtime_error{"Invalid testbed mode."}; } @@ -226,17 +222,17 @@ json Testbed::load_network_config(const fs::path& network_config_path) { json result; if (is_snapshot) { + std::ifstream f{native_string(network_config_path), std::ios::in | std::ios::binary}; if (equals_case_insensitive(network_config_path.extension(), "ingp")) { // zstr::ifstream applies zlib compression. - zstr::ifstream f{network_config_path.str(), std::ios::in | std::ios::binary}; - result = json::from_msgpack(f); + zstr::istream zf{f}; + result = json::from_msgpack(zf); } else { - std::ifstream f{network_config_path.str(), std::ios::in | std::ios::binary}; result = json::from_msgpack(f); } // we assume parent pointers are already resolved in snapshots. } else if (equals_case_insensitive(network_config_path.extension(), "json")) { - std::ifstream f{network_config_path.str()}; + std::ifstream f{native_string(network_config_path)}; result = json::parse(f, nullptr, true, true); result = merge_parent_network_config(result, network_config_path); } @@ -244,15 +240,15 @@ json Testbed::load_network_config(const fs::path& network_config_path) { return result; } -void Testbed::reload_network_from_file(const std::string& network_config_path_string) { - if (!network_config_path_string.empty()) { - fs::path candidate = find_network_config(network_config_path_string); +void Testbed::reload_network_from_file(const fs::path& path) { + if (!path.empty()) { + fs::path candidate = find_network_config(path); if (candidate.exists() || !m_network_config_path.exists()) { // Store the path _argument_ in the member variable. E.g. for the base config, // it'll store `base.json`, even though the loaded config will be // config/<mode>/base.json. This has the benefit of switching to the // appropriate config when switching modes. - m_network_config_path = network_config_path_string; + m_network_config_path = path; } } @@ -285,46 +281,46 @@ void Testbed::reload_network_from_json(const json& json, const std::string& conf reset_network(); } -void Testbed::load_file(const std::string& file_path) { - if (!fs::path{file_path}.exists()) { +void Testbed::load_file(const fs::path& path) { + if (!path.exists()) { // If the path doesn't exist, but a network config can be resolved, load that. - if (ends_with_case_insensitive(file_path, ".json") && find_network_config(file_path).exists()) { - reload_network_from_file(file_path); + if (equals_case_insensitive(path.extension(), "json") && find_network_config(path).exists()) { + reload_network_from_file(path); return; } - tlog::error() << "File '" << file_path << "' does not exist."; + tlog::error() << "File '" << path.str() << "' does not exist."; return; } - if (ends_with_case_insensitive(file_path, ".ingp") || ends_with_case_insensitive(file_path, ".msgpack")) { - load_snapshot(file_path); + if (equals_case_insensitive(path.extension(), "ingp") || equals_case_insensitive(path.extension(), "msgpack")) { + load_snapshot(path); return; } // If we get a json file, we need to parse it to determine its purpose. - if (ends_with_case_insensitive(file_path, ".json")) { + if (equals_case_insensitive(path.extension(), "json")) { json file; { - std::ifstream f{file_path}; + std::ifstream f{native_string(path)}; file = json::parse(f, nullptr, true, true); } // Snapshot in json format... inefficient, but technically supported. if (file.contains("snapshot")) { - load_snapshot(file_path); + load_snapshot(path); return; } // Regular network config if (file.contains("parent") || file.contains("network") || file.contains("encoding") || file.contains("loss") || file.contains("optimizer")) { - reload_network_from_file(file_path); + reload_network_from_file(path); return; } // Camera path if (file.contains("path")) { - load_camera_path(file_path); + load_camera_path(path); return; } } @@ -332,7 +328,7 @@ void Testbed::load_file(const std::string& file_path) { // If the dragged file isn't any of the above, assume that it's training data try { bool was_training_data_available = m_training_data_available; - load_training_data(file_path); + load_training_data(path); if (!was_training_data_available) { // If we previously didn't have any training data and only now dragged @@ -1984,7 +1980,7 @@ void Testbed::prepare_next_camera_path_frame() { m_render_futures.emplace_back(m_thread_pool.enqueue_task([image_data=std::move(image_data), frame_idx=m_camera_path.render_frame_idx++, res, tmp_dir] { std::vector<uint8_t> cpu_image_data(image_data.size()); CUDA_CHECK_THROW(cudaMemcpy(cpu_image_data.data(), image_data.data(), image_data.bytes(), cudaMemcpyDeviceToHost)); - stbi_write_jpg(fmt::format("{}/{:06d}.jpg", tmp_dir.str(), frame_idx).c_str(), res.x(), res.y(), 3, cpu_image_data.data(), 100); + write_stbi(tmp_dir / fmt::format("{:06d}.jpg", frame_idx), res.x(), res.y(), 3, cpu_image_data.data(), 100); })); reset_accumulation(true); @@ -2025,7 +2021,7 @@ void Testbed::prepare_next_camera_path_frame() { #endif auto ffmpeg_command = fmt::format( - "{} -loglevel error -y -framerate {} -i tmp/%06d.jpg -c:v libx264 -preset slow -crf {} -pix_fmt yuv420p {}", + "{} -loglevel error -y -framerate {} -i tmp/%06d.jpg -c:v libx264 -preset slow -crf {} -pix_fmt yuv420p \"{}\"", ffmpeg.str(), m_camera_path.render_settings.fps, // Quality goes from 0 to 10. This conversion to CRF means a quality of 10 @@ -3499,8 +3495,7 @@ void Testbed::gather_histograms() { // Increment this number when making a change to the snapshot format static const size_t SNAPSHOT_FORMAT_VERSION = 1; -void Testbed::save_snapshot(const std::string& filepath_string, bool include_optimizer_state, bool compress) { - fs::path filepath = filepath_string; +void Testbed::save_snapshot(const fs::path& path, bool include_optimizer_state, bool compress) { m_network_config["snapshot"] = m_trainer->serialize(include_optimizer_state); auto& snapshot = m_network_config["snapshot"]; @@ -3533,23 +3528,23 @@ void Testbed::save_snapshot(const std::string& filepath_string, bool include_opt snapshot["nerf"]["dataset"] = m_nerf.training.dataset; } - m_network_config_path = filepath; + m_network_config_path = path; + std::ofstream f{native_string(m_network_config_path), std::ios::out | std::ios::binary}; if (equals_case_insensitive(m_network_config_path.extension(), "ingp")) { // zstr::ofstream applies zlib compression. - zstr::ofstream f{m_network_config_path.str(), std::ios::out | std::ios::binary, compress ? Z_DEFAULT_COMPRESSION : Z_NO_COMPRESSION}; - json::to_msgpack(m_network_config, f); + zstr::ostream zf{f, zstr::default_buff_size, compress ? Z_DEFAULT_COMPRESSION : Z_NO_COMPRESSION}; + json::to_msgpack(m_network_config, zf); } else { - std::ofstream f{m_network_config_path.str(), std::ios::out | std::ios::binary}; json::to_msgpack(m_network_config, f); } - tlog::success() << "Saved snapshot '" << filepath_string << "'"; + tlog::success() << "Saved snapshot '" << path.str() << "'"; } -void Testbed::load_snapshot(const std::string& filepath_string) { - auto config = load_network_config(filepath_string); +void Testbed::load_snapshot(const fs::path& path) { + auto config = load_network_config(path); if (!config.contains("snapshot")) { - throw std::runtime_error{fmt::format("File '{}' does not contain a snapshot.", filepath_string)}; + throw std::runtime_error{fmt::format("File '{}' does not contain a snapshot.", path.str())}; } const auto& snapshot = config["snapshot"]; @@ -3610,7 +3605,7 @@ void Testbed::load_snapshot(const std::string& filepath_string) { if (snapshot.contains("render_aabb_to_local")) from_json(snapshot.at("render_aabb_to_local"), m_render_aabb_to_local); m_render_aabb = snapshot.value("render_aabb", m_render_aabb); - m_network_config_path = filepath_string; + m_network_config_path = path; m_network_config = std::move(config); reset_network(false); @@ -3621,8 +3616,8 @@ void Testbed::load_snapshot(const std::string& filepath_string) { m_trainer->deserialize(m_network_config["snapshot"]); } -void Testbed::load_camera_path(const std::string& filepath_string) { - m_camera_path.load(filepath_string, Matrix<float, 3, 4>::Identity()); +void Testbed::load_camera_path(const fs::path& path) { + m_camera_path.load(path, Matrix<float, 3, 4>::Identity()); } bool Testbed::loop_animation() { diff --git a/src/testbed_image.cu b/src/testbed_image.cu index 0b8313f72ec97f6fdfd41e272a5b1c0e9183be98..5be8aa74bf6f11b99d70b8663835956b66137fd0 100644 --- a/src/testbed_image.cu +++ b/src/testbed_image.cu @@ -17,6 +17,7 @@ #include <neural-graphics-primitives/random_val.cuh> #include <neural-graphics-primitives/render_buffer.h> #include <neural-graphics-primitives/testbed.h> +#include <neural-graphics-primitives/tinyexr_wrapper.h> #include <tiny-cuda-nn/gpu_matrix.h> #include <tiny-cuda-nn/network_with_input_encoding.h> @@ -27,7 +28,6 @@ using namespace Eigen; using namespace tcnn; -namespace fs = filesystem; NGP_NAMESPACE_BEGIN @@ -382,7 +382,7 @@ void Testbed::load_exr_image(const fs::path& data_path) { tlog::info() << "Loading EXR image from " << data_path; // First step: load an image that we'd like to learn - GPUMemory<float> image = load_exr(data_path.str(), m_image.resolution.x(), m_image.resolution.y()); + GPUMemory<float> image = load_exr_gpu(data_path, &m_image.resolution.x(), &m_image.resolution.y()); m_image.data.resize(image.size() * sizeof(float)); CUDA_CHECK_THROW(cudaMemcpy(m_image.data.data(), image.data(), image.size() * sizeof(float), cudaMemcpyDeviceToDevice)); @@ -397,7 +397,7 @@ void Testbed::load_stbi_image(const fs::path& data_path) { tlog::info() << "Loading STBI image from " << data_path; // First step: load an image that we'd like to learn - GPUMemory<float> image = load_stbi(data_path.str(), m_image.resolution.x(), m_image.resolution.y()); + GPUMemory<float> image = load_stbi_gpu(data_path, &m_image.resolution.x(), &m_image.resolution.y()); m_image.data.resize(image.size() * sizeof(float)); CUDA_CHECK_THROW(cudaMemcpy(m_image.data.data(), image.data(), image.size() * sizeof(float), cudaMemcpyDeviceToDevice)); @@ -412,7 +412,7 @@ void Testbed::load_binary_image(const fs::path& data_path) { tlog::info() << "Loading binary image from " << data_path; - std::ifstream f(data_path.str(), std::ios::in | std::ios::binary); + std::ifstream f{native_string(data_path), std::ios::in | std::ios::binary}; f.read(reinterpret_cast<char*>(&m_image.resolution.y()), sizeof(int)); f.read(reinterpret_cast<char*>(&m_image.resolution.x()), sizeof(int)); diff --git a/src/testbed_nerf.cu b/src/testbed_nerf.cu index c020237f3bbff01494d0035d611a6d06013711d9..33a663d4894ad43422b55af9fa2b8d97eec7dd4c 100644 --- a/src/testbed_nerf.cu +++ b/src/testbed_nerf.cu @@ -40,7 +40,6 @@ using namespace Eigen; using namespace tcnn; -namespace fs = filesystem; NGP_NAMESPACE_BEGIN @@ -2462,8 +2461,8 @@ void Testbed::Nerf::Training::reset_camera_extrinsics() { } } -void Testbed::Nerf::Training::export_camera_extrinsics(const std::string& filename, bool export_extrinsics_in_quat_format) { - tlog::info() << "Saving a total of " << n_images_for_training << " poses to " << filename; +void Testbed::Nerf::Training::export_camera_extrinsics(const fs::path& path, bool export_extrinsics_in_quat_format) { + tlog::info() << "Saving a total of " << n_images_for_training << " poses to " << path.str(); nlohmann::json trajectory; for(int i = 0; i < n_images_for_training; ++i) { nlohmann::json frame{{"id", i}}; @@ -2495,7 +2494,8 @@ void Testbed::Nerf::Training::export_camera_extrinsics(const std::string& filena trajectory.emplace_back(frame); } - std::ofstream file(filename); + + std::ofstream file{native_string(path)}; file << std::setw(2) << trajectory << std::endl; } diff --git a/src/testbed_sdf.cu b/src/testbed_sdf.cu index 2fcfc5ef4ffbd2692c3d483688781ecbe54e1b72..aced131525d5198518e14ed2a97ac75e180f6a6d 100644 --- a/src/testbed_sdf.cu +++ b/src/testbed_sdf.cu @@ -32,7 +32,6 @@ using namespace Eigen; using namespace tcnn; -namespace fs = filesystem; NGP_NAMESPACE_BEGIN @@ -1018,23 +1017,23 @@ void Testbed::render_sdf( } } -std::vector<Vector3f> load_stl(const std::string& filename) { +std::vector<Vector3f> load_stl(const fs::path& path) { std::vector<Vector3f> vertices; - std::ifstream f{filename, std::ios::in | std::ios::binary}; + std::ifstream f{native_string(path), std::ios::in | std::ios::binary}; if (!f) { - throw std::runtime_error{fmt::format("Mesh file '{}' not found", filename)}; + throw std::runtime_error{fmt::format("Mesh file '{}' not found", path.str())}; } uint32_t buf[21] = {}; f.read((char*)buf, 4 * 21); if (f.gcount() < 4 * 21) { - throw std::runtime_error{fmt::format("Mesh file '{}' too small for STL header", filename)}; + throw std::runtime_error{fmt::format("Mesh file '{}' too small for STL header", path.str())}; } uint32_t nfaces = buf[20]; if (memcmp(buf, "solid", 5) == 0 || buf[20] == 0) { - throw std::runtime_error{fmt::format("ASCII STL file '{}' not supported", filename)}; + throw std::runtime_error{fmt::format("ASCII STL file '{}' not supported", path.str())}; } vertices.reserve(nfaces * 3); diff --git a/src/testbed_volume.cu b/src/testbed_volume.cu index 168b666fb6b063f26a81d7d9dc9a84d9515f7581..10306cb0ca6e0dbca0f59f32923c9c84df79b6a8 100644 --- a/src/testbed_volume.cu +++ b/src/testbed_volume.cu @@ -33,7 +33,6 @@ using namespace Eigen; using namespace tcnn; -namespace fs = filesystem; NGP_NAMESPACE_BEGIN @@ -555,7 +554,7 @@ void Testbed::load_volume(const fs::path& data_path) { throw std::runtime_error{data_path.str() + " does not exist."}; } tlog::info() << "Loading NanoVDB file from " << data_path; - std::ifstream f(data_path.str(), std::ios::in | std::ios::binary); + std::ifstream f{native_string(data_path), std::ios::in | std::ios::binary}; NanoVDBFileHeader header; NanoVDBMetaData metadata; f.read(reinterpret_cast<char*>(&header), sizeof(header)); diff --git a/src/tinyexr_wrapper.cu b/src/tinyexr_wrapper.cu index ea1608c9e59984462075ccb4f7fa3c2a4430c8d7..00e451a81c43e7278f75398caf3323870b37f364 100644 --- a/src/tinyexr_wrapper.cu +++ b/src/tinyexr_wrapper.cu @@ -54,7 +54,7 @@ __global__ void interleave_and_cast_kernel(const uint32_t num_pixels, bool has_a *((uint64_t*)&out[i*4]) = *((uint64_t*)&rgba_out[0]); } -void save_exr(const float* data, int width, int height, int n_channels, int channel_stride, const char* outfilename) { +void save_exr(const float* data, int width, int height, int n_channels, int channel_stride, const fs::path& path) { EXRHeader header; InitEXRHeader(&header); @@ -103,23 +103,43 @@ void save_exr(const float* data, int width, int height, int n_channels, int chan } const char* err = NULL; // or nullptr in C++11 or later. - int ret = SaveEXRImageToFile(&image, &header, outfilename, &err); - if (ret != TINYEXR_SUCCESS) { + uint8_t* buffer; + size_t n_bytes = SaveEXRImageToMemory(&image, &header, &buffer, &err); + if (n_bytes == 0) { std::string error_message = std::string("Failed to save EXR image: ") + err; FreeEXRErrorMessage(err); // free's buffer for an error message throw std::runtime_error(error_message); } - tlog::info() << "Saved exr file: " << outfilename; + + { + std::ofstream f{native_string(path), std::ios::out | std::ios::binary}; + f.write((char*)buffer, n_bytes); + } + + tlog::info() << "Saved exr file: " << path.str(); free(header.channels); free(header.pixel_types); free(header.requested_pixel_types); + free(buffer); } -void load_exr(float** data, int* width, int* height, const char* filename) { - const char* err = nullptr; +void load_exr(float** data, int* width, int* height, const fs::path& path) { + std::vector<uint8_t> buffer; + + { + std::ifstream f{native_string(path), std::ios::in | std::ios::binary | std::ios::ate}; + size_t size = f.tellg(); + f.seekg(0, std::ios::beg); - int ret = LoadEXR(data, width, height, filename, &err); + buffer.resize(size); + if (!f.read((char*)buffer.data(), size)) { + throw std::runtime_error("Failed to open EXR file"); + } + } + + const char* err = nullptr; + int ret = LoadEXRFromMemory(data, width, height, buffer.data(), buffer.size(), &err); if (ret != TINYEXR_SUCCESS) { if (err) { @@ -132,11 +152,24 @@ void load_exr(float** data, int* width, int* height, const char* filename) { } } -__half* load_exr_to_gpu(int* width, int* height, const char* filename, bool fix_premult) { +__half* load_exr_to_gpu(int* width, int* height, const fs::path& path, bool fix_premult) { + std::vector<uint8_t> buffer; + + { + std::ifstream f{native_string(path), std::ios::in | std::ios::binary | std::ios::ate}; + size_t size = f.tellg(); + f.seekg(0, std::ios::beg); + + buffer.resize(size); + if (!f.read((char*)buffer.data(), size)) { + throw std::runtime_error("Failed to open EXR file"); + } + } + // 1. Read EXR version. EXRVersion exr_version; - int ret = ParseEXRVersionFromFile(&exr_version, filename); + int ret = ParseEXRVersionFromMemory(&exr_version, buffer.data(), buffer.size()); if (ret != 0) { std::string error_message = std::string("Failed to parse EXR image version"); throw std::runtime_error(error_message); @@ -151,7 +184,7 @@ __half* load_exr_to_gpu(int* width, int* height, const char* filename, bool fix_ InitEXRHeader(&exr_header); const char* err = NULL; // or `nullptr` in C++11 or later. - ret = ParseEXRHeaderFromFile(&exr_header, &exr_version, filename, &err); + ret = ParseEXRHeaderFromMemory(&exr_header, &exr_version, buffer.data(), buffer.size(), &err); if (ret != 0) { std::string error_message = std::string("Failed to parse EXR image header: ") + err; FreeEXRErrorMessage(err); // free's buffer for an error message @@ -170,7 +203,7 @@ __half* load_exr_to_gpu(int* width, int* height, const char* filename, bool fix_ EXRImage exr_image; InitEXRImage(&exr_image); - ret = LoadEXRImageFromFile(&exr_image, &exr_header, filename, &err); + ret = LoadEXRImageFromMemory(&exr_image, &exr_header, buffer.data(), buffer.size(), &err); if (ret != 0) { std::string error_message = std::string("Failed to load EXR image: ") + err; FreeEXRHeader(&exr_header); diff --git a/src/tinyobj_loader_wrapper.cpp b/src/tinyobj_loader_wrapper.cpp index 4ad738460f2ce9031be5f60d487fc49ef6b57088..19da0ef8f926f31f814875dc4b570970ed6ee233 100644 --- a/src/tinyobj_loader_wrapper.cpp +++ b/src/tinyobj_loader_wrapper.cpp @@ -29,7 +29,7 @@ using namespace Eigen; NGP_NAMESPACE_BEGIN -std::vector<Vector3f> load_obj(const std::string& filename) { +std::vector<Vector3f> load_obj(const fs::path& path) { tinyobj::attrib_t attrib; std::vector<tinyobj::shape_t> shapes; std::vector<tinyobj::material_t> materials; @@ -37,19 +37,20 @@ std::vector<Vector3f> load_obj(const std::string& filename) { std::string warn; std::string err; - bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, filename.c_str()); + std::ifstream f{native_string(path), std::ios::in | std::ios::binary}; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, &f); if (!warn.empty()) { - tlog::warning() << warn << " while loading '" << filename << "'"; + tlog::warning() << warn << " while loading '" << path.str() << "'"; } if (!err.empty()) { - throw std::runtime_error{fmt::format("Error loading '{}': {}", filename, err)}; + throw std::runtime_error{fmt::format("Error loading '{}': {}", path.str(), err)}; } std::vector<Vector3f> result; - tlog::success() << "Loaded mesh \"" << filename << "\" file with " << shapes.size() << " shapes."; + tlog::success() << "Loaded mesh \"" << path.str() << "\" file with " << shapes.size() << " shapes."; // Loop over shapes for (size_t s = 0; s < shapes.size(); s++) { @@ -59,7 +60,7 @@ std::vector<Vector3f> load_obj(const std::string& filename) { size_t fv = size_t(shapes[s].mesh.num_face_vertices[f]); if (shapes[s].mesh.num_face_vertices[f] != 3) { - tlog::warning() << "Non-triangle face found in " << filename; + tlog::warning() << "Non-triangle face found in " << path.str(); index_offset += fv; continue; }