Spaces:
Build error
Build error
| /* | |
| * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| * SPDX-License-Identifier: Apache-2.0 | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| /** @file testbed.h | |
| * @author Thomas Müller & Alex Evans, NVIDIA | |
| */ | |
| struct GLFWwindow; | |
| namespace ngp { | |
| struct Triangle; | |
| class GLTexture; | |
| struct ViewIdx { | |
| i16vec2 px; | |
| uint32_t view; | |
| }; | |
| class Testbed { | |
| public: | |
| Testbed(ETestbedMode mode = ETestbedMode::None); | |
| ~Testbed(); | |
| bool clear_tmp_dir(); | |
| void update_imgui_paths(); | |
| void set_mode(ETestbedMode mode); | |
| using distance_fun_t = std::function<void(uint32_t, const vec3*, float*, cudaStream_t)>; | |
| using normals_fun_t = std::function<void(uint32_t, const vec3*, vec3*, cudaStream_t)>; | |
| struct LevelStats { | |
| float mean() { return count ? (x / (float)count) : 0.f; } | |
| float variance() { return count ? (xsquared - (x * x) / (float)count) / (float)count : 0.f; } | |
| float sigma() { return sqrtf(variance()); } | |
| float fraczero() { return (float)numzero / float(count + numzero); } | |
| float fracquant() { return (float)numquant / float(count); } | |
| float x; | |
| float xsquared; | |
| float min; | |
| float max; | |
| int numzero; | |
| int numquant; | |
| int count; | |
| }; | |
| class CudaDevice; | |
| struct View { | |
| std::shared_ptr<CudaRenderBuffer> render_buffer = nullptr; | |
| ivec2 full_resolution = {1, 1}; | |
| int visualized_dimension = 0; | |
| mat4x3 camera0 = mat4x3::identity(); | |
| mat4x3 camera1 = mat4x3::identity(); | |
| mat4x3 prev_camera = mat4x3::identity(); | |
| Foveation foveation; | |
| Foveation prev_foveation; | |
| vec2 relative_focal_length; | |
| vec2 screen_center; | |
| Lens lens; | |
| CudaDevice* device = nullptr; | |
| GPUImage<ViewIdx> index_field; | |
| GPUImage<uint8_t> hole_mask; | |
| GPUImage<float> depth_buffer; | |
| vec2 fov() const { return relative_focal_length_to_fov(relative_focal_length); } | |
| uint32_t uid = 0; | |
| }; | |
| void render_by_reprojection(cudaStream_t stream, std::vector<View>& views); | |
| void render_frame( | |
| cudaStream_t stream, | |
| const mat4x3& camera_matrix0, | |
| const mat4x3& camera_matrix1, | |
| const mat4x3& prev_camera_matrix, | |
| const vec2& screen_center, | |
| const vec2& relative_focal_length, | |
| const Foveation& foveation, | |
| const Foveation& prev_foveation, | |
| const Lens& lens, | |
| int visualized_dimension, | |
| CudaRenderBuffer& render_buffer, | |
| bool to_srgb = true, | |
| CudaDevice* device = nullptr | |
| ); | |
| void render_frame_main( | |
| CudaDevice& device, | |
| const mat4x3& camera_matrix0, | |
| const mat4x3& camera_matrix1, | |
| const vec2& screen_center, | |
| const vec2& relative_focal_length, | |
| const Foveation& foveation, | |
| const Lens& lens, | |
| int visualized_dimension | |
| ); | |
| void render_frame_epilogue( | |
| cudaStream_t stream, | |
| const mat4x3& camera_matrix0, | |
| const mat4x3& prev_camera_matrix, | |
| const vec2& screen_center, | |
| const vec2& relative_focal_length, | |
| const Foveation& foveation, | |
| const Foveation& prev_foveation, | |
| const Lens& lens, | |
| CudaRenderBuffer& render_buffer, | |
| bool to_srgb = true | |
| ); | |
| void init_camera_path_from_reproject_src_cameras(); | |
| void visualize_reproject_src_cameras(ImDrawList* list, const mat4& world2proj); | |
| void clear_src_views(); | |
| void reset_accumulation(bool due_to_camera_movement = false, bool immediate_redraw = true, bool reset_pip = false); | |
| void redraw_next_frame() { m_render_skip_due_to_lack_of_camera_movement_counter = 0; } | |
| bool reprojection_available() { return m_dlss; } | |
| void load_mesh(const fs::path& data_path); | |
| void set_exposure(float exposure) { m_exposure = exposure; } | |
| void translate_camera(const vec3& rel, const mat3& rot, bool allow_up_down = true); | |
| mat3 rotation_from_angles(const vec2& angles) const; | |
| void mouse_drag(); | |
| void mouse_wheel(); | |
| void load_file(const fs::path& path); | |
| vec3 look_at() const; | |
| void set_look_at(const vec3& pos); | |
| float scale() const { return m_scale; } | |
| void set_scale(float scale); | |
| vec3 view_pos() const { return m_camera[3]; } | |
| vec3 view_dir() const { return m_camera[2]; } | |
| vec3 view_up() const { return m_camera[1]; } | |
| vec3 view_side() const { return m_camera[0]; } | |
| void set_view_dir(const vec3& dir); | |
| void reset_camera(); | |
| bool keyboard_event(); | |
| void update_density_grid_mean_and_bitfield(cudaStream_t stream); | |
| void mark_density_grid_in_sphere_empty(const vec3& pos, float radius, cudaStream_t stream); | |
| void prepare_next_camera_path_frame(); | |
| void overlay_fps(); | |
| void imgui(); | |
| vec2 calc_focal_length(const ivec2& resolution, const vec2& relative_focal_length, int fov_axis, float zoom) const; | |
| vec2 render_screen_center(const vec2& screen_center) const; | |
| void optimise_mesh_step(uint32_t N_STEPS); | |
| void compute_mesh_vertex_colors(); | |
| float get_depth_from_renderbuffer(const CudaRenderBuffer& render_buffer, const vec2& uv); | |
| vec3 get_3d_pos_from_pixel(const CudaRenderBuffer& render_buffer, const vec2& focus_pixel); | |
| void autofocus(); | |
| std::pair<pybind11::array_t<float>, 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_to_cpu_rgba(int width, int height, int spp, bool linear, float start_t, float end_t, float fps, float shutter_fraction); | |
| pybind11::array_t<float> view(bool linear, size_t view) const; | |
| std::pair<pybind11::array_t<float>, pybind11::array_t<uint32_t>> | |
| reproject(const mat4x3& src, const pybind11::array_t<float>& src_img, const pybind11::array_t<float>& src_depth, const mat4x3& dst); | |
| uint32_t add_src_view( | |
| mat4x3 camera_to_world, | |
| float fx, | |
| float fy, | |
| float cx, | |
| float cy, | |
| Lens lens, | |
| pybind11::array_t<float> img, | |
| pybind11::array_t<float> depth, | |
| float timestamp, | |
| bool is_srgb = false | |
| ); | |
| pybind11::array_t<uint32_t> src_view_ids() const; | |
| pybind11::array_t<float> screenshot(bool linear, bool front_buffer) const; | |
| mat4x3 view_camera(size_t view) const; | |
| void draw_visualizations(ImDrawList* list, const mat4x3& camera_matrix); | |
| void reproject_views(const std::vector<const View*> src, View& dst); | |
| void render(bool skip_rendering); | |
| void init_window(int resw, int resh, bool hidden = false, bool second_window = false); | |
| void destroy_window(); | |
| void init_vr(); | |
| void update_vr_performance_settings(); | |
| void apply_camera_smoothing(float elapsed_ms); | |
| bool begin_frame(); | |
| void handle_user_input(); | |
| vec3 vr_to_world(const vec3& pos) const; | |
| void begin_vr_frame_and_handle_vr_input(); | |
| void draw_gui(); | |
| bool frame(); | |
| bool want_repl(); | |
| 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); | |
| float fov() const; | |
| void set_fov(float val); | |
| vec2 fov_xy() const; | |
| void set_fov_xy(const vec2& val); | |
| CameraKeyframe copy_camera_to_keyframe() const; | |
| void set_camera_from_keyframe(const CameraKeyframe& k); | |
| void set_camera_from_time(float t); | |
| void load_camera_path(const fs::path& path); | |
| bool loop_animation(); | |
| void set_loop_animation(bool value); | |
| fs::path root_dir(); | |
| void set_root_dir(const fs::path& dir); | |
| bool m_want_repl = false; | |
| bool m_render_window = false; | |
| bool m_gather_histograms = false; | |
| bool m_render_ground_truth = false; | |
| EGroundTruthRenderMode m_ground_truth_render_mode = EGroundTruthRenderMode::Shade; | |
| float m_ground_truth_alpha = 1.0f; | |
| bool m_render = true; | |
| int m_max_spp = 0; | |
| ETestbedMode m_testbed_mode = ETestbedMode::None; | |
| // Rendering stuff | |
| ivec2 m_window_res = ivec2(0); | |
| bool m_dynamic_res = false; | |
| float m_dynamic_res_target_fps = 20.0f; | |
| int m_fixed_res_factor = 8; | |
| float m_scale = 1.0; | |
| float m_aperture_size = 0.0f; | |
| vec2 m_relative_focal_length = vec2(1.0f); | |
| uint32_t m_fov_axis = 1; | |
| float m_zoom = 1.f; // 2d zoom factor (for insets?) | |
| vec2 m_screen_center = vec2(0.5f); // center of 2d zoom | |
| float m_ndc_znear = 1.0f / 32.0f; | |
| float m_ndc_zfar = 128.0f; | |
| mat4x3 m_camera = mat4x3::identity(); | |
| mat4x3 m_default_camera = transpose(mat3x4{1.0f, 0.0f, 0.0f, 0.5f, 0.0f, -1.0f, 0.0f, 0.5f, 0.0f, 0.0f, -1.0f, 0.5f}); | |
| mat4x3 m_smoothed_camera = mat4x3::identity(); | |
| size_t m_render_skip_due_to_lack_of_camera_movement_counter = 0; | |
| bool m_fps_camera = false; | |
| bool m_camera_smoothing = false; | |
| bool m_autofocus = false; | |
| vec3 m_autofocus_target = vec3(0.5f); | |
| bool m_render_with_lens_distortion = false; | |
| Lens m_render_lens = {}; | |
| CameraPath m_camera_path = {}; | |
| bool m_record_camera_path = false; | |
| vec3 m_up_dir = {0.0f, 1.0f, 0.0f}; | |
| vec3 m_sun_dir = normalize(vec3(1.0f)); | |
| float m_bounding_radius = 1; | |
| float m_exposure = 0.f; | |
| ERenderMode m_render_mode = ERenderMode::Shade; | |
| uint32_t m_seed = 1337; | |
| GLFWwindow* m_glfw_window = nullptr; | |
| struct SecondWindow { | |
| GLFWwindow* window = nullptr; | |
| GLuint program = 0; | |
| GLuint vao = 0, vbo = 0; | |
| void draw(GLuint texture); | |
| } m_second_window; | |
| float m_drag_depth = 1.0f; | |
| // The VAO will be empty, but we need a valid one for attribute-less rendering | |
| GLuint m_blit_vao = 0; | |
| GLuint m_blit_program = 0; | |
| void init_opengl_shaders(); | |
| void blit_texture( | |
| const Foveation& foveation, | |
| GLint rgba_texture, | |
| GLint rgba_filter_mode, | |
| GLint depth_texture, | |
| GLint framebuffer, | |
| const ivec2& offset, | |
| const ivec2& resolution | |
| ); | |
| void create_second_window(); | |
| std::unique_ptr<OpenXRHMD> m_hmd; | |
| OpenXRHMD::FrameInfoPtr m_vr_frame_info; | |
| bool m_vr_use_depth_reproject = false; | |
| bool m_vr_use_hidden_area_mask = false; | |
| std::deque<View> m_reproject_src_views; | |
| View m_reproject_pending_view; | |
| int m_reproject_min_src_view_index = 0; | |
| int m_reproject_max_src_view_index = 1; | |
| int m_reproject_max_src_view_count = -1; // -1 indicates unlimited | |
| uint32_t m_reproject_selected_src_view = 0; | |
| bool m_reproject_freeze_src_views = false; | |
| int m_reproject_n_views_to_cache = 1; | |
| bool m_reproject_visualize_src_views = false; | |
| float m_reproject_min_t = 0.1f; | |
| float m_reproject_step_factor = 1.05f; | |
| vec3 m_reproject_parallax = vec3(0.0f, 0.0f, 0.0f); | |
| bool m_reproject_enable = false; | |
| bool m_reproject_reuse_last_frame = true; | |
| float m_reproject_lazy_render_ms = 100.0f; | |
| float m_reproject_lazy_render_res_factor = 1.25f; | |
| bool m_pm_enable = false; | |
| EPmVizMode m_pm_viz_mode = EPmVizMode::Shade; | |
| void set_n_views(size_t n_views); | |
| // Callback invoked when a keyboard event is detected. | |
| // If the callback returns `true`, the event is considered handled and the default behavior will not occur. | |
| std::function<bool()> m_keyboard_event_callback; | |
| // Callback invoked when a file is dropped onto the window. | |
| // If the callback returns `true`, the files are considered handled and the default behavior will not occur. | |
| std::function<bool(const std::vector<std::string>&)> m_file_drop_callback; | |
| std::shared_ptr<GLTexture> m_pip_render_texture; | |
| std::vector<std::shared_ptr<GLTexture>> m_rgba_render_textures; | |
| std::vector<std::shared_ptr<GLTexture>> m_depth_render_textures; | |
| std::shared_ptr<CudaRenderBuffer> m_pip_render_buffer; | |
| SharedQueue<std::unique_ptr<ICallable>> m_task_queue; | |
| void redraw_gui_next_frame() { m_gui_redraw = true; } | |
| bool m_gui_redraw = true; | |
| enum EDataType { | |
| Float, | |
| Half, | |
| }; | |
| struct VolPayload { | |
| vec3 dir; | |
| vec4 col; | |
| uint32_t pixidx; | |
| }; | |
| float m_camera_velocity = 1.0f; | |
| EColorSpace m_color_space = EColorSpace::Linear; | |
| ETonemapCurve m_tonemap_curve = ETonemapCurve::Identity; | |
| bool m_dlss = false; | |
| std::shared_ptr<IDlssProvider> m_dlss_provider; | |
| float m_dlss_sharpening = 0.0f; | |
| // 3D stuff | |
| float m_render_near_distance = 0.0f; | |
| float m_slice_plane_z = 0.0f; | |
| bool m_floor_enable = false; | |
| inline float get_floor_y() const { return m_floor_enable ? m_aabb.min.y + 0.001f : -10000.f; } | |
| BoundingBox m_raw_aabb; | |
| BoundingBox m_aabb = {vec3(0.0f), vec3(1.0f)}; | |
| BoundingBox m_render_aabb = {vec3(0.0f), vec3(1.0f)}; | |
| mat3 m_render_aabb_to_local = mat3::identity(); | |
| // Rendering/UI bookkeeping | |
| Ema<float> m_render_ms = {EEmaType::Time, 100}; | |
| // The frame contains everything, i.e. rendering + GUI and buffer swapping | |
| Ema<float> m_frame_ms = {EEmaType::Time, 100}; | |
| std::chrono::time_point<std::chrono::steady_clock> m_last_frame_time_point; | |
| std::chrono::time_point<std::chrono::steady_clock> m_last_gui_draw_time_point; | |
| vec4 m_background_color = {0.0f, 0.0f, 0.0f, 1.0f}; | |
| bool m_vsync = true; | |
| bool m_render_transparency_as_checkerboard = false; | |
| // Visualization of neuron activations | |
| int m_visualized_dimension = -1; | |
| int m_visualized_layer = 0; | |
| std::vector<View> m_views; | |
| ivec2 m_n_views = {1, 1}; | |
| float m_picture_in_picture_res = 0.f; // if non zero, requests a small second picture :) | |
| enum class ImGuiMode : uint32_t { | |
| Enabled, | |
| FpsOverlay, | |
| Disabled, | |
| // Don't set the below | |
| NumModes, | |
| }; | |
| struct ImGuiVars { | |
| static const uint32_t MAX_PATH_LEN = 1024; | |
| ImGuiMode mode = ImGuiMode::Enabled; // tab to cycle | |
| char cam_path_path[MAX_PATH_LEN] = "cam.json"; | |
| char video_path[MAX_PATH_LEN] = "video.mp4"; | |
| char cam_export_path[MAX_PATH_LEN] = "cam_export.json"; | |
| void* overlay_font = nullptr; | |
| } m_imgui; | |
| fs::path m_root_dir = ""; | |
| bool m_visualize_unit_cube = false; | |
| bool m_edit_render_aabb = false; | |
| bool m_edit_world_transform = true; | |
| bool m_snap_to_pixel_centers = false; | |
| vec3 m_parallax_shift = {0.0f, 0.0f, 0.0f}; // to shift the viewer's origin by some amount in camera space | |
| StreamAndEvent m_stream; | |
| class CudaDevice { | |
| public: | |
| struct Data { | |
| std::shared_ptr<Buffer2D<uint8_t>> hidden_area_mask; | |
| }; | |
| CudaDevice(int id, bool is_primary); | |
| CudaDevice(const CudaDevice&) = delete; | |
| CudaDevice& operator=(const CudaDevice&) = delete; | |
| CudaDevice(CudaDevice&&) = default; | |
| CudaDevice& operator=(CudaDevice&&) = default; | |
| ScopeGuard device_guard(); | |
| int id() const { return m_id; } | |
| bool is_primary() const { return m_is_primary; } | |
| std::string name() const { return cuda_device_name(m_id); } | |
| int compute_capability() const { return cuda_compute_capability(m_id); } | |
| cudaStream_t stream() const { return m_stream->get(); } | |
| void wait_for(cudaStream_t stream) const { | |
| CUDA_CHECK_THROW(cudaEventRecord(m_primary_device_event.event, stream)); | |
| m_stream->wait_for(m_primary_device_event.event); | |
| } | |
| void signal(cudaStream_t stream) const { m_stream->signal(stream); } | |
| const CudaRenderBufferView& render_buffer_view() const { return m_render_buffer_view; } | |
| void set_render_buffer_view(const CudaRenderBufferView& view) { m_render_buffer_view = view; } | |
| Data& data() const { return *m_data; } | |
| bool dirty() const { return m_dirty; } | |
| void set_dirty(bool value) { m_dirty = value; } | |
| void clear() { | |
| m_data = std::make_unique<Data>(); | |
| m_render_buffer_view = {}; | |
| set_dirty(true); | |
| } | |
| template <class F> auto enqueue_task(F&& f) -> std::future<std::result_of_t<F()>> { | |
| if (is_primary()) { | |
| return std::async(std::launch::deferred, std::forward<F>(f)); | |
| } else { | |
| return m_render_worker->enqueue_task(std::forward<F>(f)); | |
| } | |
| } | |
| private: | |
| int m_id; | |
| bool m_is_primary; | |
| std::unique_ptr<StreamAndEvent> m_stream; | |
| struct Event { | |
| Event() { CUDA_CHECK_THROW(cudaEventCreate(&event)); } | |
| ~Event() { cudaEventDestroy(event); } | |
| Event(const Event&) = delete; | |
| Event& operator=(const Event&) = delete; | |
| Event(Event&& other) { *this = std::move(other); } | |
| Event& operator=(Event&& other) { | |
| std::swap(event, other.event); | |
| return *this; | |
| } | |
| cudaEvent_t event = {}; | |
| }; | |
| Event m_primary_device_event; | |
| std::unique_ptr<Data> m_data; | |
| CudaRenderBufferView m_render_buffer_view = {}; | |
| bool m_dirty = true; | |
| std::unique_ptr<ThreadPool> m_render_worker; | |
| }; | |
| void sync_device(CudaRenderBuffer& render_buffer, CudaDevice& device); | |
| ScopeGuard use_device(cudaStream_t stream, CudaRenderBuffer& render_buffer, CudaDevice& device); | |
| void set_all_devices_dirty(); | |
| std::vector<CudaDevice> m_devices; | |
| CudaDevice& primary_device() { return m_devices.front(); } | |
| ThreadPool m_thread_pool; | |
| std::vector<std::future<void>> m_render_futures; | |
| bool m_use_aux_devices = false; | |
| bool m_foveated_rendering = false; | |
| bool m_dynamic_foveated_rendering = true; | |
| float m_foveated_rendering_full_res_diameter = 0.55f; | |
| float m_foveated_rendering_scaling = 1.0f; | |
| float m_foveated_rendering_max_scaling = 2.0f; | |
| bool m_foveated_rendering_visualize = false; | |
| default_rng_t m_rng; | |
| CudaRenderBuffer m_windowless_render_surface{std::make_shared<CudaSurface2D>()}; | |
| // ---------- Gen3C stuff | |
| /** | |
| * Common signature for Gen3C-related UI callback functions, to be implemented | |
| * in Python. | |
| * | |
| * Inputs: | |
| * name: name of the UI event (e.g. name of the button pressed). | |
| * | |
| * Returns: bool, whether the operation was successful. | |
| */ | |
| using gen3c_cb_t = std::function<bool(const std::string&)>; | |
| gen3c_cb_t m_gen3c_cb; | |
| // Info string to be displayed in the Gen3C UI window. | |
| std::string m_gen3c_info; | |
| // Path to an image or directory to use to seed the generative model. | |
| // The specific format is guessed based on what the path points to. | |
| std::string m_gen3c_seed_path; | |
| // Whether to automatically launch new inference requests. | |
| bool m_gen3c_auto_inference = false; | |
| EGen3cCameraSource m_gen3c_camera_source = EGen3cCameraSource::Authored; | |
| // Fake translation speed in scene unit / frame. | |
| vec3 m_gen3c_translation_speed = {0.05f, 0.f, 0.f}; | |
| // Fake rotation speed around (x, y, z) in radians / frame. | |
| vec3 m_gen3c_rotation_speed = {0.f, 0.05f, 0.f}; | |
| // Number of frames to request for each inference request. | |
| std::string m_gen3c_inference_info = ""; | |
| // Progress of seeding-related things (scale 0..1). Set to a negative value to hide the progress bar. | |
| float m_gen3c_seeding_progress = -1.0f; | |
| // Progress of inference-related things (scale 0..1). Set to a negative value to hide the progress bar. | |
| float m_gen3c_inference_progress = -1.0f; | |
| // Saving Gen3C inference outputs | |
| bool m_gen3c_save_frames = false; | |
| // Whether or not to display generated frames in the UI. | |
| // No display means that we can save some time by not de-compressing | |
| // the result video from the server, and even skip depth prediction for most frames. | |
| bool m_gen3c_display_frames = false; | |
| std::string m_gen3c_output_dir = ""; | |
| // When rendering with Gen3C, whether to include the rendered cache in the generated video (for debugging / visualization) | |
| bool m_gen3c_show_cache_renderings = false; | |
| bool m_gen3c_inference_is_connected = false; | |
| // Either we render the camera path from the local pointcloud or we use the inference server to get a photoreal video | |
| bool m_gen3c_render_with_gen3c = true; | |
| }; | |
| } // namespace ngp | |