diff --git a/sandbox/icons/lightpathstab_next_light_path.png b/sandbox/icons/lightpaths_toolbar_next_light_path.png similarity index 100% rename from sandbox/icons/lightpathstab_next_light_path.png rename to sandbox/icons/lightpaths_toolbar_next_light_path.png diff --git a/sandbox/icons/lightpathstab_next_light_path_disabled.png b/sandbox/icons/lightpaths_toolbar_next_light_path_disabled.png similarity index 100% rename from sandbox/icons/lightpathstab_next_light_path_disabled.png rename to sandbox/icons/lightpaths_toolbar_next_light_path_disabled.png diff --git a/sandbox/icons/lightpathstab_next_light_path_hover.png b/sandbox/icons/lightpaths_toolbar_next_light_path_hover.png similarity index 100% rename from sandbox/icons/lightpathstab_next_light_path_hover.png rename to sandbox/icons/lightpaths_toolbar_next_light_path_hover.png diff --git a/sandbox/icons/lightpathstab_prev_light_path.png b/sandbox/icons/lightpaths_toolbar_prev_light_path.png similarity index 100% rename from sandbox/icons/lightpathstab_prev_light_path.png rename to sandbox/icons/lightpaths_toolbar_prev_light_path.png diff --git a/sandbox/icons/lightpathstab_prev_light_path_disabled.png b/sandbox/icons/lightpaths_toolbar_prev_light_path_disabled.png similarity index 100% rename from sandbox/icons/lightpathstab_prev_light_path_disabled.png rename to sandbox/icons/lightpaths_toolbar_prev_light_path_disabled.png diff --git a/sandbox/icons/lightpathstab_prev_light_path_hover.png b/sandbox/icons/lightpaths_toolbar_prev_light_path_hover.png similarity index 100% rename from sandbox/icons/lightpathstab_prev_light_path_hover.png rename to sandbox/icons/lightpaths_toolbar_prev_light_path_hover.png diff --git a/sandbox/icons/lightpathstab_save_light_paths.png b/sandbox/icons/lightpaths_toolbar_save_light_paths.png similarity index 100% rename from sandbox/icons/lightpathstab_save_light_paths.png rename to sandbox/icons/lightpaths_toolbar_save_light_paths.png diff --git a/sandbox/icons/lightpathstab_save_light_paths_disabled.png b/sandbox/icons/lightpaths_toolbar_save_light_paths_disabled.png similarity index 100% rename from sandbox/icons/lightpathstab_save_light_paths_disabled.png rename to sandbox/icons/lightpaths_toolbar_save_light_paths_disabled.png diff --git a/sandbox/icons/lightpathstab_save_light_paths_hover.png b/sandbox/icons/lightpaths_toolbar_save_light_paths_hover.png similarity index 100% rename from sandbox/icons/lightpathstab_save_light_paths_hover.png rename to sandbox/icons/lightpaths_toolbar_save_light_paths_hover.png diff --git a/sandbox/icons/lightpathstab_synchronize_camera.png b/sandbox/icons/opengl_viewport_tab_synchronize_camera.png similarity index 100% rename from sandbox/icons/lightpathstab_synchronize_camera.png rename to sandbox/icons/opengl_viewport_tab_synchronize_camera.png diff --git a/sandbox/icons/lightpathstab_synchronize_camera_hover.png b/sandbox/icons/opengl_viewport_tab_synchronize_camera_hover.png similarity index 100% rename from sandbox/icons/lightpathstab_synchronize_camera_hover.png rename to sandbox/icons/opengl_viewport_tab_synchronize_camera_hover.png diff --git a/sandbox/icons/lightpathstab_toggle_backface_culling.png b/sandbox/icons/opengl_viewport_tab_toggle_backface_culling.png similarity index 100% rename from sandbox/icons/lightpathstab_toggle_backface_culling.png rename to sandbox/icons/opengl_viewport_tab_toggle_backface_culling.png diff --git a/sandbox/icons/lightpathstab_toggle_backface_culling_hover.png b/sandbox/icons/opengl_viewport_tab_toggle_backface_culling_hover.png similarity index 100% rename from sandbox/icons/lightpathstab_toggle_backface_culling_hover.png rename to sandbox/icons/opengl_viewport_tab_toggle_backface_culling_hover.png diff --git a/src/appleseed.studio/CMakeLists.txt b/src/appleseed.studio/CMakeLists.txt index c99875173e..36a80abe27 100644 --- a/src/appleseed.studio/CMakeLists.txt +++ b/src/appleseed.studio/CMakeLists.txt @@ -252,14 +252,22 @@ source_group ("mainwindow\\pythonconsole" FILES set (mainwindow_rendering_sources mainwindow/rendering/cameracontroller.cpp mainwindow/rendering/cameracontroller.h + mainwindow/rendering/finalrenderviewporttab.cpp + mainwindow/rendering/finalrenderviewporttab.h + mainwindow/rendering/glscenelayer.cpp + mainwindow/rendering/glscenelayer.h + mainwindow/rendering/lightpathslayer.cpp + mainwindow/rendering/lightpathslayer.h + mainwindow/rendering/lightpathsmanager.cpp + mainwindow/rendering/lightpathsmanager.h mainwindow/rendering/lightpathspickinghandler.cpp mainwindow/rendering/lightpathspickinghandler.h - mainwindow/rendering/lightpathstab.cpp - mainwindow/rendering/lightpathstab.h - mainwindow/rendering/lightpathswidget.cpp - mainwindow/rendering/lightpathswidget.h + mainwindow/rendering/lightpathsviewporttoolbar.cpp + mainwindow/rendering/lightpathsviewporttoolbar.h mainwindow/rendering/materialdrophandler.cpp mainwindow/rendering/materialdrophandler.h + mainwindow/rendering/openglviewporttab.cpp + mainwindow/rendering/openglviewporttab.h mainwindow/rendering/pixelcolortracker.cpp mainwindow/rendering/pixelcolortracker.h mainwindow/rendering/pixelinspectorhandler.cpp @@ -272,14 +280,16 @@ set (mainwindow_rendering_sources mainwindow/rendering/renderclipboardhandler.h mainwindow/rendering/renderingmanager.cpp mainwindow/rendering/renderingmanager.h - mainwindow/rendering/renderregionhandler.cpp - mainwindow/rendering/renderregionhandler.h - mainwindow/rendering/rendertab.cpp - mainwindow/rendering/rendertab.h - mainwindow/rendering/renderwidget.cpp - mainwindow/rendering/renderwidget.h + mainwindow/rendering/renderlayer.cpp + mainwindow/rendering/renderlayer.h mainwindow/rendering/scenepickinghandler.cpp mainwindow/rendering/scenepickinghandler.h + mainwindow/rendering/viewportcanvas.cpp + mainwindow/rendering/viewportcanvas.h + mainwindow/rendering/viewportregionselectionhandler.cpp + mainwindow/rendering/viewportregionselectionhandler.h + mainwindow/rendering/viewporttab.cpp + mainwindow/rendering/viewporttab.h ) list (APPEND appleseed.studio_sources ${mainwindow_rendering_sources} @@ -343,6 +353,8 @@ source_group ("python\\studio" FILES ) set (utility_sources + utility/gl.cpp + utility/gl.h utility/inputwidgetproxies.cpp utility/inputwidgetproxies.h utility/settingskeys.h diff --git a/src/appleseed.studio/main/main.cpp b/src/appleseed.studio/main/main.cpp index 2746a9503d..ad79d6983a 100644 --- a/src/appleseed.studio/main/main.cpp +++ b/src/appleseed.studio/main/main.cpp @@ -332,7 +332,7 @@ int main(int argc, char* argv[]) // Set default surface format before creating application instance. This is // required on macOS in order to use an OpenGL Core profile context. QSurfaceFormat default_format; - default_format.setVersion(3, 3); + default_format.setVersion(4, 2); default_format.setProfile(QSurfaceFormat::CoreProfile); QSurfaceFormat::setDefaultFormat(default_format); diff --git a/src/appleseed.studio/mainwindow/mainwindow.cpp b/src/appleseed.studio/mainwindow/mainwindow.cpp index 380444ed29..157e4761f2 100644 --- a/src/appleseed.studio/mainwindow/mainwindow.cpp +++ b/src/appleseed.studio/mainwindow/mainwindow.cpp @@ -39,8 +39,12 @@ #include "mainwindow/project/attributeeditor.h" #include "mainwindow/project/projectexplorer.h" #include "mainwindow/pythonconsole/pythonconsolewidget.h" -#include "mainwindow/rendering/lightpathstab.h" -#include "mainwindow/rendering/renderwidget.h" +#include "mainwindow/rendering/finalrenderviewporttab.h" +#include "mainwindow/rendering/lightpathsmanager.h" +#include "mainwindow/rendering/materialdrophandler.h" +#include "mainwindow/rendering/openglviewporttab.h" +#include "mainwindow/rendering/renderlayer.h" +#include "mainwindow/rendering/viewportcanvas.h" #include "utility/settingskeys.h" // appleseed.qtcommon headers. @@ -130,7 +134,6 @@ MainWindow::MainWindow(QWidget* parent) , m_project_explorer(nullptr) , m_attribute_editor(nullptr) , m_project_file_watcher(nullptr) - , m_light_paths_tab(nullptr) { initialize_ocio(); @@ -203,7 +206,7 @@ bool MainWindow::open_project(const QString& filepath) m_rendering_manager.wait_until_rendering_end(); } - remove_render_tabs(); + remove_viewport_tabs(); set_file_widgets_enabled(false, RenderingMode::NotRendering); set_project_explorer_enabled(false); @@ -218,7 +221,7 @@ bool MainWindow::open_project(const QString& filepath) } else { - recreate_render_tabs(); + recreate_viewport_tabs(); update_workspace(); } @@ -235,7 +238,7 @@ void MainWindow::open_project_async(const QString& filepath) m_rendering_manager.wait_until_rendering_end(); } - remove_render_tabs(); + remove_viewport_tabs(); set_file_widgets_enabled(false, RenderingMode::NotRendering); set_project_explorer_enabled(false); @@ -727,6 +730,10 @@ void MainWindow::build_connections() connect( &m_rendering_manager, SIGNAL(signal_rendering_end()), SLOT(slot_rendering_end())); + + connect( + m_ui->tab_render_channels, &QTabWidget::currentChanged, + this, &MainWindow::slot_current_viewport_tab_changed); } void MainWindow::update_workspace() @@ -740,12 +747,6 @@ void MainWindow::update_workspace() set_diagnostics_widgets_enabled(true, RenderingMode::NotRendering); update_pause_resume_checkbox(false); m_ui->attribute_editor_scrollarea_contents->setEnabled(true); - - // Add/remove light paths tab. - if (m_project_manager.is_project_open() && - m_project_manager.get_project()->get_light_path_recorder().get_light_path_count() > 0) - add_light_paths_tab(); - else remove_light_paths_tab(); } void MainWindow::update_project_explorer() @@ -778,8 +779,8 @@ void MainWindow::update_project_explorer() SLOT(slot_project_modified())); connect( - m_project_explorer, SIGNAL(signal_frame_modified()), - SLOT(slot_frame_modified())); + m_project_explorer, &ProjectExplorer::signal_frame_resolution_changed, + this, &MainWindow::slot_frame_resolution_changed); } m_ui->lineedit_filter->clear(); @@ -883,27 +884,25 @@ void MainWindow::set_rendering_widgets_enabled(const bool is_enabled, const Rend m_ui->action_rendering_rendering_settings->setEnabled(allow_start); m_action_rendering_settings->setEnabled(allow_start); - // Render tab buttons. + // Viewport tab buttons. const int current_tab_index = m_ui->tab_render_channels->currentIndex(); if (current_tab_index != -1) { - const auto render_tab_it = m_tab_index_to_render_tab.find(current_tab_index); - if (render_tab_it != m_tab_index_to_render_tab.end()) - { - RenderTab* render_tab = render_tab_it->second; + // Clear frame. + m_final_render_viewport_tab->set_clear_frame_button_enabled( + is_enabled && is_project_open && rendering_mode == RenderingMode::NotRendering); - // Clear frame. - render_tab->set_clear_frame_button_enabled( - is_enabled && is_project_open && rendering_mode == RenderingMode::NotRendering); + // Set/clear rendering region. + m_final_render_viewport_tab->set_render_region_buttons_enabled( + is_enabled && is_project_open && rendering_mode != RenderingMode::FinalRendering); - // Set/clear rendering region. - render_tab->set_render_region_buttons_enabled( - is_enabled && is_project_open && rendering_mode != RenderingMode::FinalRendering); + // Scene picker. + m_final_render_viewport_tab->get_scene_picking_handler()->set_enabled( + is_enabled && is_project_open && rendering_mode != RenderingMode::FinalRendering); - // Scene picker. - render_tab->get_scene_picking_handler()->set_enabled( - is_enabled && is_project_open && rendering_mode != RenderingMode::FinalRendering); - } + // Light paths overlay. + const bool enable_light_paths_overlay = is_enabled && is_project_open && rendering_mode == RenderingMode::NotRendering; + m_final_render_viewport_tab->set_light_paths_toggle_enabled(enable_light_paths_overlay); } } @@ -921,18 +920,18 @@ void MainWindow::save_state_before_project_open() m_state_before_project_open->m_is_rendering = m_rendering_manager.is_rendering(); - for (const_each i = m_render_tabs; i; ++i) - m_state_before_project_open->m_render_tab_states[i->first] = i->second->save_state(); + for (const_each i = m_viewport_tabs; i; ++i) + m_state_before_project_open->m_viewport_tab_states[i->first] = i->second->save_state(); } void MainWindow::restore_state_after_project_open() { if (m_state_before_project_open.get()) { - for (const_each i = m_render_tabs; i; ++i) + for (const_each i = m_viewport_tabs; i; ++i) { - const RenderTabStateCollection& tab_states = m_state_before_project_open->m_render_tab_states; - const RenderTabStateCollection::const_iterator tab_state_it = tab_states.find(i->first); + const ViewportTabStateCollection& tab_states = m_state_before_project_open->m_viewport_tab_states; + const ViewportTabStateCollection::const_iterator tab_state_it = tab_states.find(i->first); if (tab_state_it != tab_states.end()) i->second->load_state(tab_state_it->second); @@ -943,112 +942,120 @@ void MainWindow::restore_state_after_project_open() } } -void MainWindow::recreate_render_tabs() +void MainWindow::recreate_viewport_tabs() { - remove_render_tabs(); + remove_viewport_tabs(); if (m_project_manager.is_project_open()) - add_render_tab("RGB"); + { + m_light_paths_manager.reset( + new LightPathsManager( + *m_project_manager.get_project(), + m_application_settings)); + + create_final_render_tab(); + create_opengl_tab(); + } } -void MainWindow::remove_render_tabs() +void MainWindow::remove_viewport_tabs() { - for (const_each i = m_render_tabs; i; ++i) - delete i->second; - - m_render_tabs.clear(); - m_tab_index_to_render_tab.clear(); + m_ui->tab_render_channels->blockSignals(true); while (m_ui->tab_render_channels->count() > 0) m_ui->tab_render_channels->removeTab(0); + + m_ui->tab_render_channels->blockSignals(false); + + for (const_each i = m_viewport_tabs; i; ++i) + delete i->second; + + m_viewport_tabs.clear(); + m_tab_index_to_viewport_tab.clear(); + + m_final_render_viewport_tab = 0; + m_opengl_viewport_tab = 0; } -void MainWindow::add_render_tab(const QString& label) +void MainWindow::create_final_render_tab() { - // Create render tab. - RenderTab* render_tab = - new RenderTab( + m_final_render_viewport_tab = + new FinalRenderViewportTab( *m_project_explorer, *m_project_manager.get_project(), m_rendering_manager, + *m_light_paths_manager, m_ocio_config); - // Connect the render tab to the main window and the rendering manager. - connect( - render_tab, SIGNAL(signal_render_widget_context_menu(const QPoint&)), - SLOT(slot_render_widget_context_menu(const QPoint&))); + // Connect the beauty viewport tab to the main window and the rendering manager. connect( - render_tab, SIGNAL(signal_set_render_region(const QRect&)), + m_final_render_viewport_tab, SIGNAL(signal_set_render_region(const QRect&)), SLOT(slot_set_render_region(const QRect&))); connect( - render_tab, SIGNAL(signal_clear_render_region()), + m_final_render_viewport_tab, SIGNAL(signal_viewport_canvas_context_menu(const QPoint&)), + SLOT(slot_viewport_canvas_context_menu(const QPoint&))); + connect( + m_final_render_viewport_tab, SIGNAL(signal_clear_render_region()), SLOT(slot_clear_render_region())); connect( - render_tab, SIGNAL(signal_save_frame_and_aovs()), + m_final_render_viewport_tab, SIGNAL(signal_save_frame_and_aovs()), SLOT(slot_save_frame_and_aovs())); connect( - render_tab, SIGNAL(signal_quicksave_frame_and_aovs()), + m_final_render_viewport_tab, SIGNAL(signal_quicksave_frame_and_aovs()), SLOT(slot_quicksave_frame_and_aovs())); connect( - render_tab, SIGNAL(signal_reset_zoom()), - SLOT(slot_reset_zoom())); - connect( - render_tab, SIGNAL(signal_clear_frame()), + m_final_render_viewport_tab, SIGNAL(signal_clear_frame()), SLOT(slot_clear_frame())); connect( - render_tab, SIGNAL(signal_entity_picked(renderer::ScenePicker::PickingResult)), + m_final_render_viewport_tab, SIGNAL(signal_entity_picked(renderer::ScenePicker::PickingResult)), SLOT(slot_clear_filter())); connect( - render_tab, SIGNAL(signal_camera_change_begin()), + m_final_render_viewport_tab, SIGNAL(signal_camera_change_begin()), &m_rendering_manager, SLOT(slot_camera_change_begin())); connect( - render_tab, SIGNAL(signal_camera_change_end()), + m_final_render_viewport_tab, SIGNAL(signal_camera_change_end()), &m_rendering_manager, SLOT(slot_camera_change_end())); connect( - render_tab, SIGNAL(signal_camera_changed()), + m_final_render_viewport_tab, SIGNAL(signal_camera_changed()), &m_rendering_manager, SLOT(slot_camera_changed())); - // Add the render tab to the tab bar. - const int tab_index = m_ui->tab_render_channels->addTab(render_tab, label); - - // Update mappings. - m_render_tabs[label.toStdString()] = render_tab; - m_tab_index_to_render_tab[tab_index] = render_tab; + m_final_render_viewport_tab_index = add_viewport_tab(m_final_render_viewport_tab, "Beauty"); } -void MainWindow::add_light_paths_tab() +void MainWindow::create_opengl_tab() { - if (m_light_paths_tab == nullptr) - { - // Create light paths tab. - m_light_paths_tab = - new LightPathsTab( - *m_project_manager.get_project(), - m_application_settings); + m_opengl_viewport_tab = + new OpenGLViewportTab( + *m_project_manager.get_project(), + *m_light_paths_manager, + m_ocio_config); - // Connect render tabs to the light paths tab. - for (const auto& kv : m_render_tabs) - { - connect( - kv.second, SIGNAL(signal_entity_picked(renderer::ScenePicker::PickingResult)), - m_light_paths_tab, SLOT(slot_entity_picked(renderer::ScenePicker::PickingResult))); - connect( - kv.second, SIGNAL(signal_rectangle_selection(const QRect&)), - m_light_paths_tab, SLOT(slot_rectangle_selection(const QRect&))); - } + // Connect the opengl viewport tab to the main window and the rendering manager. + connect( + m_opengl_viewport_tab, SIGNAL(signal_viewport_canvas_context_menu(const QPoint&)), + SLOT(slot_viewport_canvas_context_menu(const QPoint&))); - // Add the light paths tab to the tab bar. - m_ui->tab_render_channels->addTab(m_light_paths_tab, "Light Paths"); - } + m_opengl_viewport_tab_index = add_viewport_tab(m_opengl_viewport_tab, "OpenGL"); } -void MainWindow::remove_light_paths_tab() +int MainWindow::add_viewport_tab(ViewportTab* viewport_tab, const QString& label) { - if (m_light_paths_tab != nullptr) - { - delete m_light_paths_tab; - m_light_paths_tab = nullptr; - } + const int tab_index = static_cast(m_viewport_tabs.size()); + + // Update mappings before inserting the tab. + // When inserting a tab, it may show the tab and + // trigger QTabWidget::currentChanged signal. + // We want to make sure our mappings are ready + // before running our slots. + m_viewport_tabs[label.toStdString()] = viewport_tab; + m_tab_index_to_viewport_tab[tab_index] = viewport_tab; + + // Add the viewport tab to the tab bar. + const int final_tab_index = m_ui->tab_render_channels->insertTab(tab_index, viewport_tab, label); + + assert(final_tab_index == tab_index); + + return final_tab_index; } ParamArray MainWindow::get_project_params(const char* configuration_name) const @@ -1124,7 +1131,7 @@ bool MainWindow::can_close_project() void MainWindow::on_project_change() { update_project_explorer(); - recreate_render_tabs(); + recreate_viewport_tabs(); update_override_shading_menu_item(); m_false_colors_window.reset(); @@ -1220,6 +1227,9 @@ void MainWindow::start_rendering(const RenderingMode rendering_mode) m_false_colors_window.reset(); + // Switch to the final render viewport tab. + m_ui->tab_render_channels->setCurrentIndex(m_final_render_viewport_tab_index); + // Enable/disable menus and widgets appropriately. set_file_widgets_enabled(false, rendering_mode); set_project_explorer_enabled(rendering_mode == RenderingMode::InteractiveRendering); @@ -1227,9 +1237,6 @@ void MainWindow::start_rendering(const RenderingMode rendering_mode) set_diagnostics_widgets_enabled(rendering_mode == RenderingMode::InteractiveRendering, rendering_mode); m_ui->attribute_editor_scrollarea_contents->setEnabled(rendering_mode == RenderingMode::InteractiveRendering); - // Remove light paths tab. - remove_light_paths_tab(); - // Stop monitoring the project file in Final rendering mode. if (rendering_mode == RenderingMode::FinalRendering) { @@ -1238,16 +1245,14 @@ void MainWindow::start_rendering(const RenderingMode rendering_mode) } Project* project = m_project_manager.get_project(); - Frame* frame = project->get_frame(); + project->get_frame()->clear_main_and_aov_images(); + project->get_light_path_recorder().clear(); - frame->clear_main_and_aov_images(); + m_light_paths_manager->clear_light_paths(); // Darken render widgets. - for (const_each i = m_render_tabs; i; ++i) - { - i->second->darken(); - i->second->update(); - } + for (const_each i = m_viewport_tabs; i; ++i) + i->second->render_began(); // Retrieve the appropriate rendering configuration. const char* configuration_name = @@ -1261,7 +1266,7 @@ void MainWindow::start_rendering(const RenderingMode rendering_mode) rendering_mode == RenderingMode::InteractiveRendering ? RenderingManager::RenderingMode::InteractiveRendering : RenderingManager::RenderingMode::FinalRendering, - m_render_tabs["RGB"]); + m_final_render_viewport_tab); } void MainWindow::apply_false_colors_settings() @@ -1299,10 +1304,10 @@ void MainWindow::apply_false_colors_settings() else { // Blit the regular frame into the render widget. - for (const_each i = m_render_tabs; i; ++i) + for (const_each i = m_viewport_tabs; i; ++i) { - i->second->get_render_widget()->blit_frame(*frame); - i->second->get_render_widget()->update(); + i->second->get_viewport_canvas()->get_render_layer()->blit_frame(*frame); + i->second->get_viewport_canvas()->get_render_layer()->update(); } } } @@ -1322,10 +1327,10 @@ void MainWindow::apply_post_processing_stage( stage.execute(working_frame); // Blit the frame copy into the render widget. - for (const_each i = m_render_tabs; i; ++i) + for (const_each i = m_viewport_tabs; i; ++i) { - i->second->get_render_widget()->blit_frame(working_frame); - i->second->get_render_widget()->update(); + i->second->get_viewport_canvas()->get_render_layer()->blit_frame(working_frame); + i->second->get_viewport_canvas()->get_render_layer()->update(); } } } @@ -1372,7 +1377,7 @@ void MainWindow::closeEvent(QCloseEvent* event) if (m_benchmark_window.get()) m_benchmark_window->close(); - remove_render_tabs(); + remove_viewport_tabs(); m_project_manager.close_project(); @@ -1490,7 +1495,7 @@ void MainWindow::slot_open_project_complete(const QString& filepath, const bool else { show_project_file_loading_failed_message_box(this, filepath); - recreate_render_tabs(); + recreate_viewport_tabs(); update_workspace(); } } @@ -1929,7 +1934,7 @@ void MainWindow::slot_set_render_region(const QRect& rect) } } -void MainWindow::slot_render_widget_context_menu(const QPoint& point) +void MainWindow::slot_viewport_canvas_context_menu(const QPoint& point) { if (!(QApplication::keyboardModifiers() & Qt::ShiftModifier)) return; @@ -2069,27 +2074,22 @@ void MainWindow::slot_save_render_widget_content() return; // todo: this is sketchy. The render tab should be retrieved from the signal. - m_render_tabs["RGB"]->get_render_widget()->capture().save(filepath); + m_final_render_viewport_tab->get_viewport_canvas()->get_render_layer()->capture().save(filepath); RENDERER_LOG_INFO("wrote image file %s.", filepath.toStdString().c_str()); } void MainWindow::slot_clear_frame() { - Frame* frame = m_project_manager.get_project()->get_frame(); - frame->clear_main_and_aov_images(); + Project* project = m_project_manager.get_project(); + project->get_frame()->clear_main_and_aov_images(); + project->get_light_path_recorder().clear(); - // Clear all render widgets to black. - for (const_each i = m_render_tabs; i; ++i) - i->second->clear(); -} + m_light_paths_manager->clear_light_paths(); -void MainWindow::slot_reset_zoom() -{ - const int current_tab_index = m_ui->tab_render_channels->currentIndex(); - const auto render_tab_it = m_tab_index_to_render_tab.find(current_tab_index); - if (render_tab_it != m_tab_index_to_render_tab.end()) - render_tab_it->second->reset_zoom(); + // Clear all render widgets to black. + for (const std::pair& kvp : m_viewport_tabs) + kvp.second->clear(); } void MainWindow::slot_filter_text_changed(const QString& pattern) @@ -2103,10 +2103,12 @@ void MainWindow::slot_clear_filter() m_ui->lineedit_filter->clear(); } -void MainWindow::slot_frame_modified() +void MainWindow::slot_frame_resolution_changed() { - for (each i = m_render_tabs; i; ++i) - i->second->update_size(); + m_project_manager.get_project()->get_light_path_recorder().clear(); + m_light_paths_manager->clear_light_paths(); + + recreate_viewport_tabs(); } void MainWindow::slot_fullscreen() @@ -2143,6 +2145,15 @@ void MainWindow::slot_check_fullscreen() m_action_fullscreen->setChecked(is_fullscreen); } +void MainWindow::slot_current_viewport_tab_changed(int index) +{ + if (index == -1) return; + + ViewportTab* tab = m_tab_index_to_viewport_tab[index]; + + tab->on_tab_selected(); +} + void MainWindow::slot_show_application_settings_window() { if (m_application_settings_window.get() == nullptr) diff --git a/src/appleseed.studio/mainwindow/mainwindow.h b/src/appleseed.studio/mainwindow/mainwindow.h index ba6b8c20c5..451a35c897 100644 --- a/src/appleseed.studio/mainwindow/mainwindow.h +++ b/src/appleseed.studio/mainwindow/mainwindow.h @@ -34,8 +34,10 @@ #include "debug/tests/testwindow.h" #include "mainwindow/applicationsettingswindow.h" #include "mainwindow/falsecolorswindow.h" +#include "mainwindow/rendering/finalrenderviewporttab.h" +#include "mainwindow/rendering/openglviewporttab.h" #include "mainwindow/rendering/renderingmanager.h" -#include "mainwindow/rendering/rendertab.h" +#include "mainwindow/rendering/viewporttab.h" #include "mainwindow/renderingsettingswindow.h" #include "mainwindow/statusbar.h" @@ -62,7 +64,7 @@ namespace OCIO = OCIO_NAMESPACE; // Forward declarations. namespace appleseed { namespace studio { class AttributeEditor; } } -namespace appleseed { namespace studio { class LightPathsTab; } } +namespace appleseed { namespace studio { class LightPathsManager; } } namespace appleseed { namespace studio { class MinimizeButton; } } namespace appleseed { namespace studio { class ProjectExplorer; } } namespace renderer { class Project; } @@ -161,17 +163,22 @@ class MainWindow AttributeEditor* m_attribute_editor; RenderingManager m_rendering_manager; - typedef std::map RenderTabCollection; - typedef std::map RenderTabStateCollection; + typedef std::map ViewportTabCollection; + typedef std::map ViewportTabStateCollection; - RenderTabCollection m_render_tabs; - std::map m_tab_index_to_render_tab; - LightPathsTab* m_light_paths_tab; + ViewportTabCollection m_viewport_tabs; + std::map m_tab_index_to_viewport_tab; + + FinalRenderViewportTab* m_final_render_viewport_tab; + int m_final_render_viewport_tab_index; + OpenGLViewportTab* m_opengl_viewport_tab; + int m_opengl_viewport_tab_index; + std::unique_ptr m_light_paths_manager; struct StateBeforeProjectOpen { bool m_is_rendering; - RenderTabStateCollection m_render_tab_states; + ViewportTabStateCollection m_viewport_tab_states; }; std::unique_ptr m_state_before_project_open; @@ -208,11 +215,11 @@ class MainWindow void restore_state_after_project_open(); // Render tabs. - void recreate_render_tabs(); - void remove_render_tabs(); - void add_render_tab(const QString& label); - void add_light_paths_tab(); - void remove_light_paths_tab(); + void recreate_viewport_tabs(); + void remove_viewport_tabs(); + void create_final_render_tab(); + void create_opengl_tab(); + int add_viewport_tab(ViewportTab* viewport_tab, const QString& label); // Project file handling. renderer::ParamArray get_project_params(const char* configuration_name) const; @@ -289,22 +296,22 @@ class MainWindow void slot_set_render_region(const QRect& rect); // Render widget actions. - void slot_render_widget_context_menu(const QPoint& point); + void slot_viewport_canvas_context_menu(const QPoint& point); void slot_save_frame(); void slot_save_frame_and_aovs(); void slot_quicksave_frame_and_aovs(); void slot_save_render_widget_content(); void slot_clear_frame(); - void slot_reset_zoom(); // Project explorer. void slot_filter_text_changed(const QString& pattern); void slot_clear_filter(); - void slot_frame_modified(); + void slot_frame_resolution_changed(); // General UI actions. void slot_fullscreen(); void slot_check_fullscreen(); + void slot_current_viewport_tab_changed(int index); // Child windows. void slot_show_application_settings_window(); diff --git a/src/appleseed.studio/mainwindow/project/projectbuilder.cpp b/src/appleseed.studio/mainwindow/project/projectbuilder.cpp index f9a3870005..5033179a25 100644 --- a/src/appleseed.studio/mainwindow/project/projectbuilder.cpp +++ b/src/appleseed.studio/mainwindow/project/projectbuilder.cpp @@ -70,13 +70,17 @@ Frame* ProjectBuilder::edit_frame( const size_t new_canvas_width = new_frame->image().properties().m_canvas_width; const size_t new_canvas_height = new_frame->image().properties().m_canvas_height; - if (new_canvas_width != old_canvas_width || new_canvas_height != old_canvas_height) + const bool resolution_changed = new_canvas_width != old_canvas_width || new_canvas_height != old_canvas_height; + + if (resolution_changed) new_frame->reset_crop_window(); m_project.set_frame(new_frame); slot_notify_project_modification(); - emit signal_frame_modified(); + + if (resolution_changed) + emit signal_frame_resolution_changed(); return m_project.get_frame(); } diff --git a/src/appleseed.studio/mainwindow/project/projectbuilder.h b/src/appleseed.studio/mainwindow/project/projectbuilder.h index 54e6ffc7d9..8fd7d35840 100644 --- a/src/appleseed.studio/mainwindow/project/projectbuilder.h +++ b/src/appleseed.studio/mainwindow/project/projectbuilder.h @@ -101,7 +101,7 @@ class ProjectBuilder signals: void signal_project_modified() const; - void signal_frame_modified() const; + void signal_frame_resolution_changed() const; public slots: void slot_notify_project_modification() const; diff --git a/src/appleseed.studio/mainwindow/project/projectexplorer.cpp b/src/appleseed.studio/mainwindow/project/projectexplorer.cpp index 7f6f9306da..ff14f811ee 100644 --- a/src/appleseed.studio/mainwindow/project/projectexplorer.cpp +++ b/src/appleseed.studio/mainwindow/project/projectexplorer.cpp @@ -116,8 +116,8 @@ ProjectExplorer::ProjectExplorer( SIGNAL(signal_project_modified())); connect( - &m_project_builder, SIGNAL(signal_frame_modified()), - SIGNAL(signal_frame_modified())); + &m_project_builder, &ProjectBuilder::signal_frame_resolution_changed, + this, &ProjectExplorer::signal_frame_resolution_changed); } ProjectExplorer::~ProjectExplorer() diff --git a/src/appleseed.studio/mainwindow/project/projectexplorer.h b/src/appleseed.studio/mainwindow/project/projectexplorer.h index 67730da9e5..06db60ab5f 100644 --- a/src/appleseed.studio/mainwindow/project/projectexplorer.h +++ b/src/appleseed.studio/mainwindow/project/projectexplorer.h @@ -86,7 +86,7 @@ class ProjectExplorer signals: void signal_project_modified() const; - void signal_frame_modified() const; + void signal_frame_resolution_changed() const; private: QTreeWidget* m_tree_widget; diff --git a/src/appleseed.studio/mainwindow/rendering/cameracontroller.cpp b/src/appleseed.studio/mainwindow/rendering/cameracontroller.cpp index fc95cd8a54..645275b8cd 100644 --- a/src/appleseed.studio/mainwindow/rendering/cameracontroller.cpp +++ b/src/appleseed.studio/mainwindow/rendering/cameracontroller.cpp @@ -146,11 +146,6 @@ void CameraController::slot_entity_picked(ScenePicker::PickingResult result) } } -void CameraController::slot_frame_modified() -{ - configure_controller(); -} - bool CameraController::eventFilter(QObject* object, QEvent* event) { if (m_enabled) diff --git a/src/appleseed.studio/mainwindow/rendering/cameracontroller.h b/src/appleseed.studio/mainwindow/rendering/cameracontroller.h index 449fe5ec17..09d0bfbae7 100644 --- a/src/appleseed.studio/mainwindow/rendering/cameracontroller.h +++ b/src/appleseed.studio/mainwindow/rendering/cameracontroller.h @@ -86,7 +86,6 @@ class CameraController public slots: void slot_entity_picked(renderer::ScenePicker::PickingResult result); - void slot_frame_modified(); signals: void signal_camera_change_begin(); diff --git a/src/appleseed.studio/mainwindow/rendering/rendertab.cpp b/src/appleseed.studio/mainwindow/rendering/finalrenderviewporttab.cpp similarity index 57% rename from src/appleseed.studio/mainwindow/rendering/rendertab.cpp rename to src/appleseed.studio/mainwindow/rendering/finalrenderviewporttab.cpp index 7552f27ca9..1e7216e137 100644 --- a/src/appleseed.studio/mainwindow/rendering/rendertab.cpp +++ b/src/appleseed.studio/mainwindow/rendering/finalrenderviewporttab.cpp @@ -5,8 +5,7 @@ // // This software is released under the MIT license. // -// Copyright (c) 2010-2013 Francois Beaune, Jupiter Jazz Limited -// Copyright (c) 2014-2018 Francois Beaune, The appleseedhq Organization +// Copyright (c) 2020 Kevin Masson, The appleseedhq Organization // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -28,25 +27,20 @@ // // Interface header. -#include "rendertab.h" +#include "finalrenderviewporttab.h" // appleseed.studio headers. #include "mainwindow/project/projectexplorer.h" -#include "mainwindow/rendering/renderwidget.h" - -// appleseed.qtcommon headers. -#include "utility/miscellaneous.h" +#include "mainwindow/rendering/lightpathspickinghandler.h" +#include "mainwindow/rendering/renderingmanager.h" // appleseed.renderer headers. #include "renderer/api/frame.h" #include "renderer/api/project.h" -// appleseed.foundation headers. -#include "foundation/image/canvasproperties.h" -#include "foundation/image/image.h" - -// OpenColorIO headers. -#include +// appleseed.qtcommon headers. +#include "utility/miscellaneous.h" +#include "widgets/mousecoordinatestracker.h" // Qt headers. #include @@ -60,9 +54,6 @@ #include #include -// Standard headers. -#include - using namespace appleseed::qtcommon; using namespace foundation; using namespace renderer; @@ -71,169 +62,186 @@ namespace OCIO = OCIO_NAMESPACE; namespace appleseed { namespace studio { -// -// RenderTab class implementation. -// - -RenderTab::RenderTab( - ProjectExplorer& project_explorer, - Project& project, - RenderingManager& rendering_manager, - OCIO::ConstConfigRcPtr ocio_config) - : m_project_explorer(project_explorer) - , m_project(project) - , m_rendering_manager(rendering_manager) - , m_ocio_config(ocio_config) +FinalRenderViewportTab::FinalRenderViewportTab( + ProjectExplorer& project_explorer, + Project& project, + RenderingManager& rendering_manager, + LightPathsManager& light_paths_manager, + OCIO::ConstConfigRcPtr ocio_config) + : ViewportTab(project) + , m_project_explorer(project_explorer) + , m_rendering_manager(rendering_manager) + , m_light_paths_manager(light_paths_manager) + , m_ocio_config(ocio_config) { - setObjectName("render_widget_tab"); + setObjectName("final_render_viewport_tab"); setLayout(new QGridLayout()); layout()->setSpacing(0); layout()->setMargin(0); - create_render_widget(); + create_viewport_canvas(); create_toolbar(); create_scrollarea(); - layout()->addWidget(m_toolbar); - layout()->addWidget(m_scroll_area); - recreate_handlers(); -} - -RenderWidget* RenderTab::get_render_widget() const -{ - return m_render_widget; -} -CameraController* RenderTab::get_camera_controller() const -{ - return m_camera_controller.get(); + layout()->addWidget(m_toolbar); + layout()->addWidget(m_light_paths_viewport_toolbar->toolbar()); + layout()->addWidget(m_scroll_area); } -ScenePickingHandler* RenderTab::get_scene_picking_handler() const +ViewportCanvas* FinalRenderViewportTab::get_viewport_canvas() const { - return m_scene_picking_handler.get(); + return m_viewport_canvas; } -void RenderTab::set_clear_frame_button_enabled(const bool enabled) +void FinalRenderViewportTab::set_clear_frame_button_enabled(const bool enabled) { m_clear_frame_button->setEnabled(enabled); } -void RenderTab::set_render_region_buttons_enabled(const bool enabled) +void FinalRenderViewportTab::set_render_region_buttons_enabled(const bool enabled) { m_set_render_region_button->setEnabled(enabled); m_clear_render_region_button->setEnabled(enabled); } -void RenderTab::clear() +void FinalRenderViewportTab::render_began() { - m_render_widget->clear(); - m_render_widget->repaint(); + ViewportTab::render_began(); + + m_light_paths_viewport_toolbar.get()->reset(&m_project); } -void RenderTab::darken() +void FinalRenderViewportTab::on_tab_selected() { - m_render_widget->multiply(0.2f); + const bool display_light_paths = m_light_paths_manager.should_display_light_paths(); + m_light_paths_viewport_toolbar->set_enabled(display_light_paths); + m_light_paths_toggle_button->setChecked(display_light_paths); } -void RenderTab::reset_zoom() +CameraController* FinalRenderViewportTab::get_camera_controller() const { - m_zoom_handler->reset_zoom(); + return m_camera_controller.get(); } -void RenderTab::update() +ScenePickingHandler* FinalRenderViewportTab::get_scene_picking_handler() const { - m_render_widget->update(); + return m_scene_picking_handler.get(); } -void RenderTab::update_size() +void FinalRenderViewportTab::slot_camera_changed() { - m_set_render_region_button->setChecked(false); - - const CanvasProperties& props = m_project.get_frame()->image().properties(); - - m_render_widget->resize( - props.m_canvas_width, - props.m_canvas_height); + m_viewport_canvas->get_light_paths_layer()->set_transform(m_camera_controller->get_transform()); + m_viewport_canvas->get_gl_scene_layer()->set_transform(m_camera_controller->get_transform()); + m_viewport_canvas->update(); +} - recreate_handlers(); +void FinalRenderViewportTab::slot_toggle_render_region(const bool checked) +{ + m_scene_picking_handler->set_enabled(!checked); + m_viewport_selection_handler->set_mode( + checked + ? ViewportRegionSelectionHandler::RenderRegionMode + : ViewportRegionSelectionHandler::RectangleSelectionMode); } -RenderTab::State RenderTab::save_state() const +void FinalRenderViewportTab::slot_toggle_light_paths(const bool checked) { - State state; - state.m_zoom_handler_state = m_zoom_handler->save_state(); - state.m_pan_handler_state = m_pan_handler->save_state(); - return state; + m_light_paths_picking_handler->set_enabled(checked); + m_light_paths_viewport_toolbar->set_enabled(checked); + m_scene_picking_handler->set_enabled(!checked); + m_light_paths_manager.display_light_paths(checked); } -void RenderTab::load_state(const State& state) +void FinalRenderViewportTab::slot_toggle_pixel_inspector(const bool checked) { - // The order matters here. - m_zoom_handler->load_state(state.m_zoom_handler_state); - m_pan_handler->load_state(state.m_pan_handler_state); + m_pixel_inspector_handler->set_enabled(checked); + m_pixel_inspector_handler->update_tooltip_visibility(); } -void RenderTab::slot_render_widget_context_menu(const QPoint& point) +void FinalRenderViewportTab::slot_set_render_region(const QRect& rect) { - emit signal_render_widget_context_menu(m_render_widget->mapToGlobal(point)); + m_set_render_region_button->setChecked(false); + emit signal_set_render_region(rect); } -void RenderTab::slot_toggle_render_region(const bool checked) +void FinalRenderViewportTab::slot_viewport_canvas_context_menu(const QPoint& point) { - m_scene_picking_handler->set_enabled(!checked); - m_render_region_handler->set_mode( - checked - ? RenderRegionHandler::RenderRegionMode - : RenderRegionHandler::RectangleSelectionMode); + emit signal_viewport_canvas_context_menu(m_viewport_canvas->mapToGlobal(point)); } -void RenderTab::slot_set_render_region(const QRect& rect) +void FinalRenderViewportTab::slot_clear_frame() { - m_set_render_region_button->setChecked(false); - emit signal_set_render_region(rect); + m_light_paths_toggle_button->setChecked(false); + m_light_paths_picking_handler->set_enabled(false); + m_light_paths_viewport_toolbar->set_enabled(false); + m_scene_picking_handler->set_enabled(false); + + emit signal_clear_frame(); } -void RenderTab::slot_toggle_pixel_inspector(const bool checked) +void FinalRenderViewportTab::slot_display_transform_changed(const QString& transform) { - m_pixel_inspector_handler->set_enabled(checked); - m_pixel_inspector_handler->update_tooltip_visibility(); + m_viewport_canvas->get_render_layer()->set_display_transform(transform); + m_viewport_canvas->update(); } -void RenderTab::create_render_widget() +void FinalRenderViewportTab::create_viewport_canvas() { const CanvasProperties& props = m_project.get_frame()->image().properties(); - m_render_widget = - new RenderWidget( + m_viewport_canvas = + new ViewportCanvas( + m_project, props.m_canvas_width, props.m_canvas_height, - m_ocio_config); + m_ocio_config, + m_light_paths_manager, + ViewportCanvas::BaseLayer::FinalRender, + this); - m_render_widget->setContextMenuPolicy(Qt::CustomContextMenu); + m_viewport_canvas->setContextMenuPolicy(Qt::CustomContextMenu); connect( - m_render_widget, SIGNAL(customContextMenuRequested(const QPoint&)), - SLOT(slot_render_widget_context_menu(const QPoint&))); + m_viewport_canvas, &ViewportCanvas::customContextMenuRequested, + this, &FinalRenderViewportTab::slot_viewport_canvas_context_menu); - m_render_widget->setMouseTracking(true); + m_viewport_canvas->setMouseTracking(true); } -void RenderTab::create_toolbar() +void FinalRenderViewportTab::create_toolbar() { + // Create the light path toolbar. + m_light_paths_viewport_toolbar.reset( + new LightPathsViewportToolbar( + this, + &m_project, + m_light_paths_manager)); + // Create the render toolbar. m_toolbar = new QToolBar(); m_toolbar->setObjectName("render_toolbar"); m_toolbar->setIconSize(QSize(18, 18)); + // Display Light Paths button. + m_light_paths_toggle_button = new QToolButton(); + m_light_paths_toggle_button->setText("Display Light Paths Overlay"); + m_light_paths_toggle_button->setCheckable(true); + connect( + m_light_paths_toggle_button, &QToolButton::toggled, + this, &FinalRenderViewportTab::slot_toggle_light_paths); + m_toolbar->addWidget(m_light_paths_toggle_button); + + m_toolbar->addSeparator(); + // Save Frame and AOVs button. QToolButton* save_aovs_button = new QToolButton(); save_aovs_button->setIcon(load_icons("rendertab_save_all_aovs")); save_aovs_button->setToolTip("Save Frame and AOVs..."); connect( - save_aovs_button, SIGNAL(clicked()), - SIGNAL(signal_save_frame_and_aovs())); + save_aovs_button, &QToolButton::clicked, + this, &FinalRenderViewportTab::signal_save_frame_and_aovs); m_toolbar->addWidget(save_aovs_button); // Quicksave Frame and AOVs button. @@ -241,8 +249,8 @@ void RenderTab::create_toolbar() quicksave_aovs_button->setIcon(load_icons("rendertab_quicksave_all_aovs")); quicksave_aovs_button->setToolTip("Quicksave Frame and AOVs"); connect( - quicksave_aovs_button, SIGNAL(clicked()), - SIGNAL(signal_quicksave_frame_and_aovs())); + quicksave_aovs_button, &QToolButton::clicked, + this, &FinalRenderViewportTab::signal_quicksave_frame_and_aovs); m_toolbar->addWidget(quicksave_aovs_button); m_toolbar->addSeparator(); @@ -254,8 +262,8 @@ void RenderTab::create_toolbar() m_set_render_region_button->setToolTip(combine_name_and_shortcut("Set Render Region", m_set_render_region_button->shortcut())); m_set_render_region_button->setCheckable(true); connect( - m_set_render_region_button, SIGNAL(toggled(bool)), - SLOT(slot_toggle_render_region(const bool))); + m_set_render_region_button, &QToolButton::toggled, + this, &FinalRenderViewportTab::slot_toggle_render_region); m_toolbar->addWidget(m_set_render_region_button); // Clear Render Region button. @@ -264,8 +272,8 @@ void RenderTab::create_toolbar() m_clear_render_region_button->setShortcut(Qt::Key_C); m_clear_render_region_button->setToolTip(combine_name_and_shortcut("Clear Render Region", m_clear_render_region_button->shortcut())); connect( - m_clear_render_region_button, SIGNAL(clicked()), - SIGNAL(signal_clear_render_region())); + m_clear_render_region_button, &QToolButton::clicked, + this, &FinalRenderViewportTab::signal_clear_render_region); m_toolbar->addWidget(m_clear_render_region_button); // Clear Frame button. @@ -274,8 +282,8 @@ void RenderTab::create_toolbar() m_clear_frame_button->setShortcut(Qt::Key_X); m_clear_frame_button->setToolTip(combine_name_and_shortcut("Clear Frame", m_clear_frame_button->shortcut())); connect( - m_clear_frame_button, SIGNAL(clicked()), - SIGNAL(signal_clear_frame())); + m_clear_frame_button, &QToolButton::clicked, + this, &FinalRenderViewportTab::slot_clear_frame); m_toolbar->addWidget(m_clear_frame_button); m_toolbar->addSeparator(); @@ -286,8 +294,8 @@ void RenderTab::create_toolbar() reset_zoom_button->setShortcut(Qt::Key_Asterisk); reset_zoom_button->setToolTip(combine_name_and_shortcut("Reset Zoom", reset_zoom_button->shortcut())); connect( - reset_zoom_button, SIGNAL(clicked()), - SIGNAL(signal_reset_zoom())); + reset_zoom_button, &QToolButton::clicked, + this, &FinalRenderViewportTab::slot_reset_zoom); m_toolbar->addWidget(reset_zoom_button); m_toolbar->addSeparator(); @@ -300,8 +308,8 @@ void RenderTab::create_toolbar() pixel_inspector_button->setCheckable(true); pixel_inspector_button->setChecked(false); connect( - pixel_inspector_button, SIGNAL(toggled(bool)), - SLOT(slot_toggle_pixel_inspector(const bool))); + pixel_inspector_button, &QToolButton::toggled, + this, &FinalRenderViewportTab::slot_toggle_pixel_inspector); m_toolbar->addWidget(pixel_inspector_button); m_toolbar->addSeparator(); @@ -320,12 +328,12 @@ void RenderTab::create_toolbar() m_toolbar->addSeparator(); // Create the label preceding the display combobox. - QLabel* display_label = new QLabel("Display:"); + QLabel* display_label = new QLabel("Display Transform:"); display_label->setObjectName("display_label"); m_toolbar->addWidget(display_label); // Create the display combobox. - QComboBox* m_display_transform_combo = new QComboBox(); + m_display_transform_combo = new QComboBox(); m_display_transform_combo->setObjectName("display_combo"); { const char* display_name = m_ocio_config->getDefaultDisplay(); @@ -345,8 +353,8 @@ void RenderTab::create_toolbar() } m_toolbar->addWidget(m_display_transform_combo); connect( - m_display_transform_combo, SIGNAL(currentIndexChanged(QString)), - m_render_widget, SLOT(slot_display_transform_changed(QString))); + m_display_transform_combo, qOverload(&QComboBox::currentIndexChanged), + this, &FinalRenderViewportTab::slot_display_transform_changed); // Add stretchy spacer. // This places interactive widgets on the left and info on the right. @@ -384,7 +392,7 @@ void RenderTab::create_toolbar() m_toolbar->addWidget(m_a_label); } -void RenderTab::create_scrollarea() +void FinalRenderViewportTab::create_scrollarea() { // Encapsulate the render widget into another widget that adds a margin around it. QWidget* render_widget_wrapper = new QWidget(); @@ -392,7 +400,7 @@ void RenderTab::create_scrollarea() render_widget_wrapper->setLayout(new QGridLayout()); render_widget_wrapper->layout()->setSizeConstraint(QLayout::SetFixedSize); render_widget_wrapper->layout()->setContentsMargins(20, 20, 20, 20); - render_widget_wrapper->layout()->addWidget(m_render_widget); + render_widget_wrapper->layout()->addWidget(m_viewport_canvas); // Wrap the render widget in a scroll area. m_scroll_area = new QScrollArea(); @@ -401,13 +409,13 @@ void RenderTab::create_scrollarea() m_scroll_area->setWidget(render_widget_wrapper); } -void RenderTab::recreate_handlers() +void FinalRenderViewportTab::recreate_handlers() { // Handler for zooming the render widget in and out with the keyboard or the mouse wheel. m_zoom_handler.reset( new WidgetZoomHandler( m_scroll_area, - m_render_widget)); + m_viewport_canvas)); // Handler for panning the render widget with the mouse. m_pan_handler.reset( @@ -417,13 +425,13 @@ void RenderTab::recreate_handlers() // Handler for tracking and displaying mouse coordinates. m_mouse_tracker.reset( new MouseCoordinatesTracker( - m_render_widget, + m_viewport_canvas, m_info_label)); - // Handle for tracking and displaying the color of the pixel under the mouse cursor. + // Handler for tracking and displaying the color of the pixel under the mouse cursor. m_pixel_color_tracker.reset( new PixelColorTracker( - m_render_widget, + m_viewport_canvas, m_r_label, m_g_label, m_b_label, @@ -434,74 +442,93 @@ void RenderTab::recreate_handlers() // Handler for pixel inspection in the render widget. m_pixel_inspector_handler.reset( new PixelInspectorHandler( - m_render_widget, + m_viewport_canvas, *m_mouse_tracker.get(), m_project)); // Camera handler. m_camera_controller.reset( new CameraController( - m_render_widget, + m_viewport_canvas, m_project)); connect( - m_camera_controller.get(), SIGNAL(signal_camera_change_begin()), - SIGNAL(signal_camera_change_begin())); + m_camera_controller.get(), &CameraController::signal_camera_change_begin, + this, &FinalRenderViewportTab::signal_camera_change_begin); connect( - m_camera_controller.get(), SIGNAL(signal_camera_change_end()), - SIGNAL(signal_camera_change_end())); + m_camera_controller.get(), &CameraController::signal_camera_change_end, + this, &FinalRenderViewportTab::signal_camera_change_end); connect( - m_camera_controller.get(), SIGNAL(signal_camera_changed()), - SIGNAL(signal_camera_changed())); + m_camera_controller.get(), &CameraController::signal_camera_changed, + this, &FinalRenderViewportTab::signal_camera_changed); connect( - &m_project_explorer, SIGNAL(signal_frame_modified()), - m_camera_controller.get(), SLOT(slot_frame_modified())); + m_camera_controller.get(), &CameraController::signal_camera_changed, + this, &FinalRenderViewportTab::slot_camera_changed); // Handler for picking scene entities in the render widget. m_scene_picking_handler.reset( new ScenePickingHandler( - m_render_widget, + m_viewport_canvas, m_picking_mode_combo, *m_mouse_tracker.get(), m_project_explorer, m_project)); connect( - m_scene_picking_handler.get(), SIGNAL(signal_entity_picked(renderer::ScenePicker::PickingResult)), - SIGNAL(signal_entity_picked(renderer::ScenePicker::PickingResult))); + m_scene_picking_handler.get(), &ScenePickingHandler::signal_entity_picked, + this, &FinalRenderViewportTab::signal_entity_picked); connect( - m_scene_picking_handler.get(), SIGNAL(signal_entity_picked(renderer::ScenePicker::PickingResult)), - m_camera_controller.get(), SLOT(slot_entity_picked(renderer::ScenePicker::PickingResult))); + m_scene_picking_handler.get(), &ScenePickingHandler::signal_entity_picked, + m_camera_controller.get(), &CameraController::slot_entity_picked); - // Handler for setting render regions with the mouse. - m_render_region_handler.reset( - new RenderRegionHandler( - m_render_widget, - *m_mouse_tracker.get())); - connect( - m_render_region_handler.get(), SIGNAL(signal_rectangle_selection(const QRect&)), - SIGNAL(signal_rectangle_selection(const QRect&))); - connect( - m_render_region_handler.get(), SIGNAL(signal_render_region(const QRect&)), - SLOT(slot_set_render_region(const QRect&))); + // Light paths picking handler. + m_light_paths_picking_handler.reset( + new LightPathsPickingHandler( + m_light_paths_manager, + m_viewport_canvas, + m_project)); + m_light_paths_picking_handler->set_enabled(false); // Clipboard handler. - m_clipboard_handler.reset(new RenderClipboardHandler(m_render_widget, m_render_widget)); + m_clipboard_handler.reset(new RenderClipboardHandler(m_viewport_canvas, m_viewport_canvas)); // Material drop handler. m_material_drop_handler.reset( new MaterialDropHandler( m_project, m_rendering_manager)); + + // Handler for setting render regions with the mouse. + m_viewport_selection_handler.reset( + new ViewportRegionSelectionHandler( + m_viewport_canvas, + *m_mouse_tracker.get())); connect( - m_render_widget, SIGNAL(signal_material_dropped(const foundation::Vector2d&, const QString&)), - m_material_drop_handler.get(), SLOT(slot_material_dropped(const foundation::Vector2d&, const QString&))); + m_viewport_selection_handler.get(), &ViewportRegionSelectionHandler::signal_rectangle_selection, + m_light_paths_picking_handler.get(), &LightPathsPickingHandler::slot_rectangle_selection); + connect( + m_viewport_selection_handler.get(), &ViewportRegionSelectionHandler::signal_render_region, + this, &FinalRenderViewportTab::slot_set_render_region); + connect( + m_viewport_selection_handler.get(), &ViewportRegionSelectionHandler::signal_render_region, + this, &FinalRenderViewportTab::slot_set_render_region); // Set initial state. m_pixel_inspector_handler->set_enabled(false); m_camera_controller->set_enabled(false); m_scene_picking_handler->set_enabled(true); + + connect( + m_viewport_canvas, &ViewportCanvas::signal_material_dropped, + m_material_drop_handler.get(), &MaterialDropHandler::slot_material_dropped); +} + +void FinalRenderViewportTab::set_light_paths_toggle_enabled(const bool enabled) +{ + if (!enabled) + m_light_paths_toggle_button->setChecked(false); + + m_light_paths_toggle_button->setDisabled(!enabled); } } // namespace studio } // namespace appleseed -#include "mainwindow/rendering/moc_cpp_rendertab.cxx" diff --git a/src/appleseed.studio/mainwindow/rendering/rendertab.h b/src/appleseed.studio/mainwindow/rendering/finalrenderviewporttab.h similarity index 69% rename from src/appleseed.studio/mainwindow/rendering/rendertab.h rename to src/appleseed.studio/mainwindow/rendering/finalrenderviewporttab.h index 62008a2979..12d064d9f5 100644 --- a/src/appleseed.studio/mainwindow/rendering/rendertab.h +++ b/src/appleseed.studio/mainwindow/rendering/finalrenderviewporttab.h @@ -5,8 +5,7 @@ // // This software is released under the MIT license. // -// Copyright (c) 2010-2013 Francois Beaune, Jupiter Jazz Limited -// Copyright (c) 2014-2018 Francois Beaune, The appleseedhq Organization +// Copyright (c) 2020 Kevin Masson, The appleseedhq Organization // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -31,92 +30,64 @@ // appleseed.studio headers. #include "mainwindow/rendering/cameracontroller.h" +#include "mainwindow/rendering/lightpathsmanager.h" +#include "mainwindow/rendering/lightpathsviewporttoolbar.h" #include "mainwindow/rendering/materialdrophandler.h" #include "mainwindow/rendering/pixelcolortracker.h" #include "mainwindow/rendering/pixelinspectorhandler.h" -#include "mainwindow/rendering/renderclipboardhandler.h" -#include "mainwindow/rendering/renderregionhandler.h" #include "mainwindow/rendering/scenepickinghandler.h" - -// appleseed.qtcommon headers. -#include "widgets/mousecoordinatestracker.h" -#include "widgets/scrollareapanhandler.h" -#include "widgets/widgetzoomhandler.h" +#include "mainwindow/rendering/viewporttab.h" +#include "mainwindow/rendering/viewportregionselectionhandler.h" // OpenColorIO headers. #include namespace OCIO = OCIO_NAMESPACE; -// Qt headers. -#include -#include - -// Standard headers. -#include - // Forward declarations. +namespace appleseed { namespace qtcommon { class MouseCoordinatesTracker; } } +namespace appleseed { namespace studio { class LightPathsPickingHandler; } } namespace appleseed { namespace studio { class ProjectExplorer; } } namespace appleseed { namespace studio { class RenderingManager; } } -namespace appleseed { namespace studio { class RenderWidget; } } namespace renderer { class Project; } -class QComboBox; -class QLabel; -class QPoint; -class QRect; -class QScrollArea; -class QToolBar; class QToolButton; namespace appleseed { namespace studio { -// -// A tab wrapping a render widget and its toolbar. -// - -class RenderTab - : public QWidget +class FinalRenderViewportTab + : public ViewportTab { Q_OBJECT public: - RenderTab( - ProjectExplorer& project_explorer, - renderer::Project& project, - RenderingManager& rendering_manager, - OCIO::ConstConfigRcPtr ocio_config); + FinalRenderViewportTab( + ProjectExplorer& project_explorer, + renderer::Project& project, + RenderingManager& rendering_manager, + LightPathsManager& light_paths_manager, + OCIO::ConstConfigRcPtr ocio_config); + + ViewportCanvas* get_viewport_canvas() const override; + + void set_light_paths_toggle_enabled(const bool enabled); + + void render_began() override; + void on_tab_selected() override; - RenderWidget* get_render_widget() const; CameraController* get_camera_controller() const; ScenePickingHandler* get_scene_picking_handler() const; void set_clear_frame_button_enabled(const bool enabled); void set_render_region_buttons_enabled(const bool enabled); - void clear(); - void darken(); - void reset_zoom(); - - void update(); - void update_size(); - - struct State - { - qtcommon::WidgetZoomHandler::State m_zoom_handler_state; - qtcommon::ScrollAreaPanHandler::State m_pan_handler_state; - }; - - State save_state() const; - void load_state(const State& state); - signals: void signal_save_frame_and_aovs(); void signal_quicksave_frame_and_aovs(); void signal_set_render_region(const QRect& rect); void signal_clear_render_region(); - void signal_render_widget_context_menu(const QPoint& point); void signal_reset_zoom(); void signal_clear_frame(); + void signal_viewport_canvas_context_menu(const QPoint& point); void signal_camera_change_begin(); void signal_camera_changed(); @@ -126,43 +97,50 @@ class RenderTab void signal_rectangle_selection(const QRect& rect); private slots: - void slot_render_widget_context_menu(const QPoint& point); + void slot_camera_changed(); + void slot_toggle_pixel_inspector(const bool checked); void slot_toggle_render_region(const bool checked); + void slot_toggle_light_paths(const bool checked); void slot_set_render_region(const QRect& rect); - void slot_toggle_pixel_inspector(const bool checked); + void slot_viewport_canvas_context_menu(const QPoint& point); + void slot_clear_frame(); + void slot_display_transform_changed(const QString& transform); private: - RenderWidget* m_render_widget; - QScrollArea* m_scroll_area; - QToolBar* m_toolbar; + ProjectExplorer& m_project_explorer; + RenderingManager& m_rendering_manager; + LightPathsManager& m_light_paths_manager; + OCIO::ConstConfigRcPtr m_ocio_config; + + ViewportCanvas* m_viewport_canvas; QToolButton* m_set_render_region_button; QToolButton* m_clear_render_region_button; QToolButton* m_clear_frame_button; + QScrollArea* m_scroll_area; + QToolBar* m_toolbar; + QToolButton* m_light_paths_toggle_button; QComboBox* m_picking_mode_combo; + QComboBox* m_display_transform_combo; + QComboBox* m_base_layer_combo; QLabel* m_info_label; QLabel* m_r_label; QLabel* m_g_label; QLabel* m_b_label; QLabel* m_a_label; - ProjectExplorer& m_project_explorer; - renderer::Project& m_project; - RenderingManager& m_rendering_manager; - - std::unique_ptr m_zoom_handler; - std::unique_ptr m_pan_handler; std::unique_ptr m_material_drop_handler; std::unique_ptr m_mouse_tracker; std::unique_ptr m_pixel_color_tracker; std::unique_ptr m_pixel_inspector_handler; std::unique_ptr m_camera_controller; std::unique_ptr m_scene_picking_handler; - std::unique_ptr m_render_region_handler; + std::unique_ptr m_viewport_selection_handler; std::unique_ptr m_clipboard_handler; - OCIO::ConstConfigRcPtr m_ocio_config; + std::unique_ptr m_light_paths_viewport_toolbar; + std::unique_ptr m_light_paths_picking_handler; - void create_render_widget(); + void create_viewport_canvas(); void create_toolbar(); void create_scrollarea(); void recreate_handlers(); diff --git a/src/appleseed.studio/mainwindow/rendering/glscenelayer.cpp b/src/appleseed.studio/mainwindow/rendering/glscenelayer.cpp new file mode 100644 index 0000000000..6c14ffb437 --- /dev/null +++ b/src/appleseed.studio/mainwindow/rendering/glscenelayer.cpp @@ -0,0 +1,565 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2019 Gray Olson, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// Interface header. +#include "glscenelayer.h" + +// appleseed.qtcommon headers. +#include "utility/miscellaneous.h" + +// appleseed.studio headers. +#include "utility/gl.h" + +// appleseed.renderer headers. +#include "renderer/api/camera.h" +#include "renderer/api/entity.h" +#include "renderer/api/object.h" +#include "renderer/api/project.h" +#include "renderer/api/rasterization.h" +#include "renderer/api/scene.h" +#include "renderer/api/utility.h" + +// appleseed.foundation headers. +#include "foundation/image/color.h" +#include "foundation/image/colorspace.h" +#include "foundation/math/scalar.h" +#include "foundation/platform/types.h" +#include "foundation/string/string.h" +#include "foundation/utility/api/apistring.h" + +// Qt headers. +#include +#include +#include +#include +#include +#include + +// Standard headers. +#include +#include + +using namespace foundation; +using namespace renderer; + +namespace appleseed { +namespace studio { + +namespace +{ + // Number of floats per OpenGL vertex for a piece of scene geometry. + // Vector3 position and Vector3 normal. + const std::size_t SceneVertexFloatStride = 6; + + // Number of bytes per OpenGL vertex for a piece of scene geometry. + const std::size_t SceneVertexByteStride = SceneVertexFloatStride * sizeof(float); + + // Number of floats per triangle for a piece of scene geometry. + const std::size_t SceneTriangleFloatStride = SceneVertexFloatStride * 3; + + // Number of floats per OpenGL vertex for a light path. + // Vector3 position and Vector3 color. + const std::size_t LightPathVertexFloatStride = 6; + + // Number of bytes per OpenGL vertex for a piece of scene geometry. + const std::size_t LightPathVertexByteStride = LightPathVertexFloatStride * sizeof(float); + + // Number of floats per line for a light path. + const std::size_t LightPathVertexLineFloatStride = LightPathVertexFloatStride * 2; + + // Number of floats per OpenGL transform matrix. + const std::size_t TransformFloatStride = 16; + + // Number of bytes per OpenGL transform matrix. + const std::size_t TransformByteStride = TransformFloatStride * sizeof(float); + + struct OpenGLRasterizer + : public ObjectRasterizer + { + std::vector m_buffer; + std::size_t m_prim_count; + + void begin_object(const std::size_t triangle_count_hint) override + { + m_buffer.clear(); + m_buffer.reserve(triangle_count_hint * SceneTriangleFloatStride); + m_prim_count = 0; + } + + void end_object() override {} + + void rasterize(const Triangle& triangle) override + { + const float temp_store[SceneTriangleFloatStride] = + { + static_cast(triangle.m_v0[0]), static_cast(triangle.m_v0[1]), static_cast(triangle.m_v0[2]), + static_cast(triangle.m_n0[0]), static_cast(triangle.m_n0[1]), static_cast(triangle.m_n0[2]), + + static_cast(triangle.m_v1[0]), static_cast(triangle.m_v1[1]), static_cast(triangle.m_v1[2]), + static_cast(triangle.m_n1[0]), static_cast(triangle.m_n1[1]), static_cast(triangle.m_n1[2]), + + static_cast(triangle.m_v2[0]), static_cast(triangle.m_v2[1]), static_cast(triangle.m_v2[2]), + static_cast(triangle.m_n2[0]), static_cast(triangle.m_n2[1]), static_cast(triangle.m_n2[2]), + }; + + m_buffer.reserve(m_buffer.size() + SceneTriangleFloatStride); + m_buffer.insert(m_buffer.end(), temp_store, temp_store + SceneTriangleFloatStride); + m_prim_count++; + } + }; +} + +GLSceneLayer::GLSceneLayer( + const Project& project, + const std::size_t width, + const std::size_t height) + : m_project(project) + , m_camera(*m_project.get_uncached_active_camera()) + , m_backface_culling_enabled(false) + , m_initialized(false) +{ + m_camera.transform_sequence().prepare(); + const float time = m_camera.get_shutter_middle_time(); + set_transform(m_camera.transform_sequence().evaluate(time)); +} + +GLSceneLayer::~GLSceneLayer() +{ + cleanup_gl_data(); +} + +void GLSceneLayer::set_gl_functions(QOpenGLFunctions_4_1_Core* functions) +{ + m_gl = functions; +} + +void GLSceneLayer::set_transform(const Transformd& transform) +{ + m_camera_matrix = transform.get_parent_to_local(); + m_gl_view_matrix = transpose(m_camera_matrix); +} + +void GLSceneLayer::slot_synchronize_camera() +{ + m_camera.transform_sequence().clear(); + m_camera.transform_sequence() + .set_transform( + 0.0f, + Transformd::from_local_to_parent(inverse(m_camera_matrix))); +} + +void GLSceneLayer::load_object_instance( + const ObjectInstance& object_instance, + const Matrix4f& assembly_transform_matrix) +{ + const Object* object = object_instance.find_object(); + + // This would already be logged in GLSceneLayer::load_scene_data + if (object == nullptr) + return; + + const Transformd& transform = object_instance.get_transform(); + const Matrix4f& object_transform_matrix(transform.get_local_to_parent()); + const Matrix4f model_matrix = assembly_transform_matrix * object_transform_matrix; + + // Object vertex buffer data has already been loaded, just add an instance. + const std::string obj_name = std::string(object->get_name()); + const std::size_t buffer_index = m_scene_object_index_map.at(obj_name); + + const GLuint object_instances_vbo = m_scene_object_instance_vbos[buffer_index]; + const GLuint object_vao = m_scene_object_vaos[buffer_index]; + const GLsizei current_instance = m_scene_object_current_instances[buffer_index]; + m_scene_object_current_instances[buffer_index] += 1; + + m_gl->glBindVertexArray(object_vao); + m_gl->glBindBuffer(GL_ARRAY_BUFFER, object_instances_vbo); + + const Matrix4f gl_matrix = transpose(model_matrix); + m_gl->glBufferSubData( + GL_ARRAY_BUFFER, + current_instance * TransformByteStride, + TransformByteStride, + reinterpret_cast(&gl_matrix[0])); +} + +void GLSceneLayer::load_assembly_instance( + const AssemblyInstance& assembly_instance, + const float time) +{ + const Assembly* assembly = assembly_instance.find_assembly(); + + // This would already be logged in GLSceneLayer::load_scene_data + if (assembly == nullptr) + return; + + const Transformd transform = assembly_instance.transform_sequence().evaluate(time); + const Matrix4f transform_matrix(transform.get_local_to_parent()); + + for (const auto& object_instance : assembly->object_instances()) + load_object_instance(object_instance, transform_matrix); + + for (const auto& child_assembly_instance : assembly->assembly_instances()) + load_assembly_instance(child_assembly_instance, time); +} + +void GLSceneLayer::load_object_data(const Object& object) +{ + const std::string obj_name = std::string(object.get_name()); + + RENDERER_LOG_DEBUG("uploading OpenGL mesh data for object \"%s\"...", obj_name.c_str()); + + if (m_scene_object_index_map.count(obj_name) == 0) + { + // Object vertex buffer data has not been loaded, load it. + const std::size_t buffer_index = m_scene_object_data_vbos.size(); + + GLuint object_vao; + m_gl->glGenVertexArrays(1, &object_vao); + + GLuint object_data_vbo; + m_gl->glGenBuffers(1, &object_data_vbo); + m_gl->glBindVertexArray(object_vao); + m_gl->glBindBuffer(GL_ARRAY_BUFFER, object_data_vbo); + + m_scene_object_vaos.push_back(object_vao); + m_scene_object_data_vbos.push_back(object_data_vbo); + m_scene_object_index_map[obj_name] = buffer_index; + + OpenGLRasterizer rasterizer; + object.rasterize(rasterizer); + m_scene_object_data_index_counts.push_back(static_cast(rasterizer.m_prim_count * 3)); + + m_gl->glBufferData( + GL_ARRAY_BUFFER, + rasterizer.m_buffer.size() * sizeof(float), + reinterpret_cast(&rasterizer.m_buffer[0]), + GL_STATIC_DRAW); + + m_gl->glVertexAttribPointer( + 0, + 3, + GL_FLOAT, + GL_FALSE, + SceneVertexByteStride, + reinterpret_cast(0)); + m_gl->glVertexAttribPointer( + 1, + 3, + GL_FLOAT, + GL_FALSE, + SceneVertexByteStride, + reinterpret_cast(SceneVertexByteStride / 2)); + + m_gl->glEnableVertexAttribArray(0); + m_gl->glEnableVertexAttribArray(1); + } +} + +void GLSceneLayer::load_assembly_data(const Assembly& assembly) +{ + for (const Object& object : assembly.objects()) + load_object_data(object); + + for (const Assembly& child_assembly : assembly.assemblies()) + load_assembly_data(child_assembly); +} + +void GLSceneLayer::load_scene_data() +{ + const float time = m_camera.get_shutter_middle_time(); + + RENDERER_LOG_DEBUG("uploading OpenGL scene data..."); + + // First, load all the unique object vertex buffer data into static VBOs + for (const auto& assembly : m_project.get_scene()->assemblies()) + load_assembly_data(assembly); + + // Create space for per-instance data + for (std::size_t i = 0; i < m_scene_object_index_map.size(); i++) + { + m_scene_object_instance_vbos.push_back(0); + m_scene_object_instance_counts.push_back(0); + m_scene_object_current_instances.push_back(0); + } + + // Generate instance buffers for each object + if (m_scene_object_instance_vbos.size() > 0) + m_gl->glGenBuffers( + static_cast(m_scene_object_instance_vbos.size()), + &m_scene_object_instance_vbos[0]); + + // Figure out how many instances of each mesh are required for all assembly instances + for (const AssemblyInstance& assembly_instance : m_project.get_scene()->assembly_instances()) + { + const Assembly* assembly = assembly_instance.find_assembly(); + + if (assembly == nullptr) + { + RENDERER_LOG_ERROR( + "assembly instance \"%s\" has null base assembly reference.", + assembly_instance.get_name()); + continue; + } + + for (const ObjectInstance& object_instance : assembly->object_instances()) + { + const Object* object = object_instance.find_object(); + + if (object == nullptr) + { + RENDERER_LOG_ERROR( + "object instance \"%s\" has null base object reference.", + object_instance.get_name()); + continue; + } + + const std::string obj_name = std::string(object->get_name()); + const std::size_t buf_idx = m_scene_object_index_map[obj_name]; + m_scene_object_instance_counts[buf_idx] += 1; + } + } + + // Setup instance buffers by allocating a buffer big enough for the number + // of required instances and setting up vertex attributes. + for (std::size_t i = 0; i < m_scene_object_instance_vbos.size(); i++) + { + const GLuint object_vao = m_scene_object_vaos[i]; + const GLuint object_instance_vbo = m_scene_object_instance_vbos[i]; + const GLsizei object_instance_count = m_scene_object_instance_counts[i]; + + m_gl->glBindVertexArray(object_vao); + m_gl->glBindBuffer(GL_ARRAY_BUFFER, object_instance_vbo); + m_gl->glBufferData( + GL_ARRAY_BUFFER, + object_instance_count * TransformByteStride, + NULL, + GL_DYNAMIC_DRAW); + + // Attributes for a 4x4 model matrix requires four separate attributes + // to be setup, one for each column of the matrix. + for (int i = 0; i < 4; i++) + { + m_gl->glVertexAttribPointer( + 2 + i, + 4, + GL_FLOAT, + GL_FALSE, + TransformByteStride, + reinterpret_cast(sizeof(float) * 4 * i)); + m_gl->glEnableVertexAttribArray(2 + i); + m_gl->glVertexAttribDivisor(2 + i, 1); + } + } + + // Actually load the transform data for each instance into the allocated instance buffers. + for (const AssemblyInstance& assembly_instance : m_project.get_scene()->assembly_instances()) + load_assembly_instance(assembly_instance, time); +} + + +void GLSceneLayer::init_gl(QSurfaceFormat format) +{ + // If there was already previous data, clean up. + cleanup_gl_data(); + + const QByteArray vertex_shader = load_gl_shader("scene.vert"); + const QByteArray fragment_shader = load_gl_shader("scene.frag"); + + m_scene_shader_program = create_shader_program( + m_gl, + &vertex_shader, + &fragment_shader); + + m_scene_view_mat_location = m_gl->glGetUniformLocation(m_scene_shader_program, "u_view"); + m_scene_proj_mat_location = m_gl->glGetUniformLocation(m_scene_shader_program, "u_proj"); + + m_depthonly_shader_program = create_shader_program( + m_gl, + &vertex_shader); + + m_depthonly_view_mat_location = m_gl->glGetUniformLocation(m_depthonly_shader_program, "u_view"); + m_depthonly_proj_mat_location = m_gl->glGetUniformLocation(m_depthonly_shader_program, "u_proj"); + + const float z_near = 0.01f; + const float z_far = 1000.0f; + + const RasterizationCamera& rc = m_camera.get_rasterization_camera(); + + const float fy = std::tan(rc.m_hfov / rc.m_aspect_ratio * 0.5) * z_near; + const float fx = fy * rc.m_aspect_ratio; + + const float shift_x = rc.m_shift_x * 2.0 * fx; + const float shift_y = rc.m_shift_y * 2.0 * fy; + + const float left = -fx + shift_x; + const float right = fx + shift_x; + const float top = -fy + shift_y; + const float bottom = fy + shift_y; + + // Top and bottom are flipped because QOpenGLWidget draws to a framebuffer object and then blits + // from the FBO to the default framebuffer, which flips the image. + m_gl_proj_matrix = transpose(Matrix4f::make_frustum(top, bottom, left, right, z_near, z_far)); + + load_scene_data(); + + m_initialized = true; +} + +void GLSceneLayer::cleanup_gl_data() +{ + if (!m_scene_object_vaos.empty()) + { + m_gl->glDeleteVertexArrays( + static_cast(m_scene_object_vaos.size()), + &m_scene_object_vaos[0]); + m_scene_object_vaos.clear(); + } + + if (!m_scene_object_data_vbos.empty()) + { + m_gl->glDeleteBuffers( + static_cast(m_scene_object_data_vbos.size()), + &m_scene_object_data_vbos[0]); + m_scene_object_data_vbos.clear(); + } + + if (!m_scene_object_instance_vbos.empty()) + { + m_gl->glDeleteBuffers( + static_cast(m_scene_object_instance_vbos.size()), + &m_scene_object_instance_vbos[0]); + m_scene_object_instance_vbos.clear(); + } + + if (m_scene_shader_program != 0) + { + m_gl->glDeleteProgram(m_scene_shader_program); + } + + if (m_depthonly_shader_program != 0) + { + m_gl->glDeleteProgram(m_depthonly_shader_program); + } + + m_scene_object_index_map.clear(); + m_scene_object_data_index_counts.clear(); + m_scene_object_instance_counts.clear(); + m_scene_object_current_instances.clear(); +} + +void GLSceneLayer::toggle_backface_culling(const bool checked) +{ + m_backface_culling_enabled = checked; +} + +void GLSceneLayer::draw() +{ + if (!m_initialized) + return; + + if (m_backface_culling_enabled) + glEnable(GL_CULL_FACE); + else + glDisable(GL_CULL_FACE); + + m_gl->glDepthMask(GL_FALSE); + m_gl->glEnable(GL_DEPTH_TEST); + m_gl->glDepthFunc(GL_LEQUAL); + m_gl->glUseProgram(m_scene_shader_program); + + m_gl->glUniformMatrix4fv( + m_scene_view_mat_location, + 1, + false, + const_cast(&m_gl_view_matrix[0])); + m_gl->glUniformMatrix4fv( + m_scene_proj_mat_location, + 1, + false, + const_cast(&m_gl_proj_matrix[0])); + + render_scene(); + + m_gl->glDepthMask(GL_TRUE); + + glDisable(GL_CULL_FACE); +} + +void GLSceneLayer::draw_depth_only() +{ + if (!m_initialized) + return; + + if (m_backface_culling_enabled) + glEnable(GL_CULL_FACE); + else glDisable(GL_CULL_FACE); + + m_gl->glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + m_gl->glDepthMask(GL_TRUE); + m_gl->glEnable(GL_DEPTH_TEST); + m_gl->glDepthFunc(GL_LEQUAL); + m_gl->glUseProgram(m_depthonly_shader_program); + + m_gl->glUniformMatrix4fv( + m_depthonly_view_mat_location, + 1, + false, + const_cast(&m_gl_view_matrix[0])); + m_gl->glUniformMatrix4fv( + m_depthonly_proj_mat_location, + 1, + false, + const_cast(&m_gl_proj_matrix[0])); + + render_scene(); + + m_gl->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + glDisable(GL_CULL_FACE); +} + +void GLSceneLayer::render_scene() +{ + for (std::size_t i = 0; i < m_scene_object_data_vbos.size(); i++) + { + const GLuint vao = m_scene_object_vaos[i]; + const int index_count = m_scene_object_data_index_counts[i]; + const int instance_count = m_scene_object_instance_counts[i]; + + m_gl->glBindVertexArray(vao); + m_gl->glDrawArraysInstanced( + GL_TRIANGLES, + 0, + index_count, + instance_count); + } +} + +} // namespace studio +} // namespace appleseed + diff --git a/src/appleseed.studio/mainwindow/rendering/glscenelayer.h b/src/appleseed.studio/mainwindow/rendering/glscenelayer.h new file mode 100644 index 0000000000..68c27bf463 --- /dev/null +++ b/src/appleseed.studio/mainwindow/rendering/glscenelayer.h @@ -0,0 +1,147 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2019 Gray Olson, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#pragma once + +// appleseed.studio headers. +#include "mainwindow/rendering/renderclipboardhandler.h" + +// appleseed.renderer headers. +#include "renderer/api/lighting.h" + +// appleseed.foundation headers. +#include "foundation/math/matrix.h" +#include "foundation/math/transform.h" +#include "foundation/math/vector.h" + +// Qt headers. +#include +#include + +// Standard headers. +#include +#include +#include +#include + +// Forward declarations. +namespace renderer { class Assembly; } +namespace renderer { class AssemblyInstance; } +namespace renderer { class Camera; } +namespace renderer { class Object; } +namespace renderer { class ObjectInstance; } +namespace renderer { class Project; } +class QImage; +class QKeyEvent; +class QOpenGLFunctions_4_1_Core; +class QSurfaceFormat; + +namespace appleseed { +namespace studio { + +// +// A widget providing an hardware-accelerated visualization of the loaded scene. +// + +class GLSceneLayer + : public QObject +{ + Q_OBJECT + + public: + GLSceneLayer( + const renderer::Project& project, + const std::size_t width, + const std::size_t height); + + ~GLSceneLayer(); + + void init_gl(QSurfaceFormat format); + + void set_transform(const foundation::Transformd& transform); + + void set_gl_functions(QOpenGLFunctions_4_1_Core* functions); + + void toggle_backface_culling(const bool checked); + + void draw(); + void draw_depth_only(); + + public slots: + void slot_synchronize_camera(); + + private: + const renderer::Project& m_project; + renderer::Camera& m_camera; + foundation::Matrix4d m_camera_matrix; + + bool m_backface_culling_enabled; + + QOpenGLFunctions_4_1_Core* m_gl; + + std::vector m_scene_object_data_vbos; + std::vector m_scene_object_data_index_counts; + std::vector m_scene_object_instance_vbos; + std::vector m_scene_object_instance_counts; + std::vector m_scene_object_current_instances; + std::vector m_scene_object_vaos; + std::unordered_map m_scene_object_index_map; + GLuint m_scene_shader_program; + GLint m_scene_view_mat_location; + GLint m_scene_proj_mat_location; + + GLuint m_depthonly_shader_program; + GLint m_depthonly_view_mat_location; + GLint m_depthonly_proj_mat_location; + + foundation::Matrix4f m_gl_view_matrix; + foundation::Matrix4f m_gl_proj_matrix; + + bool m_initialized; + + void render_scene(); + void cleanup_gl_data(); + void load_scene_data(); + + void load_assembly_data( + const renderer::Assembly& object); + + void load_assembly_instance( + const renderer::AssemblyInstance& assembly_instance, + const float time); + + void load_object_data(const renderer::Object& object); + + void load_object_instance( + const renderer::ObjectInstance& object_instance, + const foundation::Matrix4f& assembly_transform_matrix); +}; + +} // namespace studio +} // namespace appleseed + diff --git a/src/appleseed.studio/mainwindow/rendering/lightpathslayer.cpp b/src/appleseed.studio/mainwindow/rendering/lightpathslayer.cpp new file mode 100644 index 0000000000..2ea0d6f8e7 --- /dev/null +++ b/src/appleseed.studio/mainwindow/rendering/lightpathslayer.cpp @@ -0,0 +1,503 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2019 Gray Olson, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// Interface header. +#include "lightpathslayer.h" + +// appleseed.studio headers. +#include "utility/gl.h" + +// appleseed.renderer headers. +#include "renderer/api/camera.h" +#include "renderer/api/entity.h" +#include "renderer/api/object.h" +#include "renderer/api/project.h" +#include "renderer/api/rasterization.h" +#include "renderer/api/scene.h" +#include "renderer/api/utility.h" +#include "renderer/kernel/lighting/lightpathrecorder.h" + +// appleseed.foundation headers. +#include "foundation/image/color.h" +#include "foundation/image/colorspace.h" +#include "foundation/math/scalar.h" +#include "foundation/string/string.h" +#include "foundation/utility/api/apistring.h" + +// Qt headers. +#include +#include +#include +#include + +// Standard headers. +#include +#include +#include + +using namespace foundation; +using namespace renderer; + +namespace appleseed { +namespace studio { + +namespace +{ + // Byte stride of a vec3. + constexpr std::size_t Vec3ByteStride = sizeof(float) * 3; + + // Struct of an element of the "others" vertex buffer + struct OtherAttributes + { + GLint v_bitmask; + GLfloat v_color[3]; + GLfloat v_surface_normal[3]; + }; +} + +LightPathsLayer::LightPathsLayer( + const Project& project, + const LightPathsManager& light_paths_manager, + const std::size_t width, + const std::size_t height) + : m_project(project) + , m_light_paths_manager(light_paths_manager) + , m_camera(*m_project.get_uncached_active_camera()) + , m_gl_initialized(false) + , m_width(width) + , m_height(height) +{ + const float time = m_camera.get_shutter_middle_time(); + set_transform(m_camera.transform_sequence().evaluate(time)); + + connect( + &m_light_paths_manager, &LightPathsManager::signal_light_path_selection_changed, + this, &LightPathsLayer::slot_light_path_selection_changed); +} + +void LightPathsLayer::resize(const std::size_t width, const std::size_t height) +{ + m_width = width, + m_height = height; +} + +void LightPathsLayer::set_gl_functions(QOpenGLFunctions_4_1_Core* functions) +{ + m_gl = functions; +} + +void LightPathsLayer::update_render_camera_transform() +{ + const float time = m_camera.get_shutter_middle_time(); + m_render_camera_matrix = m_camera.transform_sequence().evaluate(time).get_parent_to_local(); + m_gl_render_view_matrix = transpose(m_render_camera_matrix); +} + +void LightPathsLayer::set_transform(const Transformd& transform) +{ + m_camera_matrix = transform.get_parent_to_local(); + m_gl_view_matrix = transpose(m_camera_matrix); +} + +void LightPathsLayer::slot_synchronize_camera() +{ + m_camera.transform_sequence().clear(); + m_camera.transform_sequence().set_transform( + 0.0f, + Transformd::from_local_to_parent(inverse(m_camera_matrix))); +} + +void LightPathsLayer::slot_light_path_selection_changed( + const bool display_light_paths, + const int selected_light_path_index, + const int total_light_paths) +{ + load_light_paths_data(); +} + +void LightPathsLayer::load_light_paths_data() +{ + m_path_terminator_vertex_indices.clear(); + + const renderer::LightPathArray& light_paths = m_light_paths_manager.get_light_paths(); + + if (!light_paths.empty()) + { + m_path_terminator_vertex_indices.push_back(0); + + const auto& light_path_recorder = m_project.get_light_path_recorder(); + + m_total_triangle_count = 0; + + // Vertex count * 4 since we will be adding two vertices for each point along the line and + // will be adding each point twice for the beginning and end parts of each segment. + // Add two because we need extra at the front and back for the extra 'previous' and 'next' attributes. + const std::size_t total_gl_vertex_count = 2 * (light_path_recorder.get_vertex_count() + 2); + + std::vector indices; + std::vector positions_buffer; + // * 3 since we want 3 floats per position + positions_buffer.reserve(total_gl_vertex_count * 3); + indices.reserve(total_gl_vertex_count); + + // Add an empty vertex at the beginning to serve as first 'previous'. + std::array empty_positions = + { + 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, + }; + positions_buffer.insert( + positions_buffer.end(), + empty_positions.begin(), + empty_positions.end()); + + std::vector others_buffer; + others_buffer.reserve(total_gl_vertex_count); + std::array others; + + for (std::size_t light_path_idx = 0; light_path_idx < light_paths.size(); light_path_idx++) + { + const LightPath& path = light_paths[light_path_idx]; + assert(path.m_vertex_end_index - path.m_vertex_begin_index >= 2); + + LightPathVertex prev; + light_path_recorder.get_light_path_vertex(path.m_vertex_begin_index, prev); + + for (std::size_t vertex_idx = path.m_vertex_begin_index + 1; vertex_idx < path.m_vertex_end_index; vertex_idx++) + { + LightPathVertex vert; + light_path_recorder.get_light_path_vertex(vertex_idx, vert); + + const Color3f piece_radiance = + linear_rgb_to_srgb( + Color3f::from_array(vert.m_radiance)); + const float piece_luminance = luminance(piece_radiance); + const Color3f normalized_piece_radiance = piece_radiance / piece_luminance; + + std::array positions_temp_store = { + prev.m_position[0], prev.m_position[1], prev.m_position[2], + prev.m_position[0], prev.m_position[1], prev.m_position[2], + vert.m_position[0], vert.m_position[1], vert.m_position[2], + vert.m_position[0], vert.m_position[1], vert.m_position[2], + }; + positions_buffer.insert( + positions_buffer.end(), + positions_temp_store.begin(), + positions_temp_store.end()); + + const auto start_index = static_cast(others_buffer.size()); + + others = {{ + { + 0, + {normalized_piece_radiance[0], normalized_piece_radiance[1], normalized_piece_radiance[2]}, + {prev.m_surface_normal[0], prev.m_surface_normal[1], prev.m_surface_normal[2]} + }, + { + 1, + {normalized_piece_radiance[0], normalized_piece_radiance[1], normalized_piece_radiance[2]}, + {prev.m_surface_normal[0], prev.m_surface_normal[1], prev.m_surface_normal[2]} + }, + { + 2, + {normalized_piece_radiance[0], normalized_piece_radiance[1], normalized_piece_radiance[2]}, + {vert.m_surface_normal[0], vert.m_surface_normal[1], vert.m_surface_normal[2]} + }, + { + 3, + {normalized_piece_radiance[0], normalized_piece_radiance[1], normalized_piece_radiance[2]}, + {vert.m_surface_normal[0], vert.m_surface_normal[1], vert.m_surface_normal[2]} + }, + }}; + others_buffer.insert( + others_buffer.end(), + others.begin(), + others.end()); + + std::array indices_scratch = { + start_index, start_index + 1, start_index + 2, + start_index + 1, start_index + 2, start_index + 3, + }; + indices.insert( + indices.end(), + indices_scratch.begin(), + indices_scratch.end()); + + m_total_triangle_count += 6; + prev = vert; + } + + m_path_terminator_vertex_indices.push_back(static_cast(others_buffer.size() - 1)); + } + + // Add an empty vertex at the end to serve as last 'next'. + positions_buffer.insert( + positions_buffer.end(), + empty_positions.begin(), + empty_positions.end()); + + // Upload the data to the buffers. + m_gl->glBindBuffer(GL_ARRAY_BUFFER, m_positions_vbo); + m_gl->glBufferData( + GL_ARRAY_BUFFER, + positions_buffer.size() * sizeof(float), + reinterpret_cast(&positions_buffer[0]), + GL_STATIC_DRAW); + m_gl->glBindBuffer(GL_ARRAY_BUFFER, m_others_vbo); + m_gl->glBufferData( + GL_ARRAY_BUFFER, + others_buffer.size() * sizeof(OtherAttributes), + reinterpret_cast(&others_buffer[0]), + GL_STATIC_DRAW); + m_gl->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indices_ebo); + m_gl->glBufferData( + GL_ELEMENT_ARRAY_BUFFER, + indices.size() * sizeof(unsigned int), + reinterpret_cast(&indices[0]), + GL_STATIC_DRAW); + } +} + +void LightPathsLayer::init_gl(QSurfaceFormat format) +{ + // If there was already previous data, clean up. + cleanup_gl_data(); + + glEnable(GL_DEPTH_TEST); + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + + const QByteArray vertex_shader = load_gl_shader("lightpaths.vert"); + const QByteArray fragment_shader = load_gl_shader("lightpaths.frag"); + + m_shader_program = create_shader_program( + m_gl, + &vertex_shader, + &fragment_shader); + + m_view_mat_loc = m_gl->glGetUniformLocation(m_shader_program, "u_view"); + m_proj_mat_loc = m_gl->glGetUniformLocation(m_shader_program, "u_proj"); + m_res_loc = m_gl->glGetUniformLocation(m_shader_program, "u_res"); + m_first_selected_loc = m_gl->glGetUniformLocation(m_shader_program, "u_first_selected"); + m_last_selected_loc = m_gl->glGetUniformLocation(m_shader_program, "u_last_selected"); + + const float z_near = 0.01f; + const float z_far = 1000.0f; + + const RasterizationCamera& rc = m_camera.get_rasterization_camera(); + + const float fy = tan(rc.m_hfov / rc.m_aspect_ratio * 0.5) * z_near; + const float fx = fy * rc.m_aspect_ratio; + + const float shift_x = rc.m_shift_x * 2.0 * fx; + const float shift_y = rc.m_shift_y * 2.0 * fy; + + const float left = -fx + shift_x; + const float right = fx + shift_x; + const float top = -fy + shift_y; + const float bottom = fy + shift_y; + + // Top and bottom are flipped because QOpenGLWidget draws to a framebuffer object and then blits + // from the FBO to the default framebuffer, which flips the image. + m_gl_proj_matrix = transpose(Matrix4f::make_frustum(top, bottom, left, right, z_near, z_far)); + + GLuint temp_light_paths_vao = 0; + m_gl->glGenVertexArrays(1, &temp_light_paths_vao); + m_light_paths_vao = temp_light_paths_vao; + + GLuint temp_vbos[3] = {0, 0, 0}; + m_gl->glGenBuffers(3, &temp_vbos[0]); + m_positions_vbo = temp_vbos[0]; + m_others_vbo = temp_vbos[1]; + m_indices_ebo = temp_vbos[2]; + + m_gl->glBindVertexArray(m_light_paths_vao); + + m_gl->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indices_ebo); + + // v_previous, v_position, and v_next all reference offsets into the same vertex data buffer. + // v_previous is at vertex offset 0, v_position is at vertex offset 2 and v_next is at vertex offset 4. + // See http://codeflow.org/entries/2012/aug/05/webgl-rendering-of-solid-trails/ for reference. + m_gl->glBindBuffer(GL_ARRAY_BUFFER, m_positions_vbo); + + // vec3 v_previous; + GLint layout_attribute_location = 0; + m_gl->glVertexAttribPointer( + layout_attribute_location, + 3, + GL_FLOAT, + GL_FALSE, + Vec3ByteStride, + reinterpret_cast(0)); + m_gl->glEnableVertexAttribArray(layout_attribute_location); + + // vec3 v_position; + layout_attribute_location = 1; + m_gl->glVertexAttribPointer( + layout_attribute_location, + 3, + GL_FLOAT, + GL_FALSE, + Vec3ByteStride, + reinterpret_cast(Vec3ByteStride * 2)); + m_gl->glEnableVertexAttribArray(layout_attribute_location); + + // vec3 v_next; + layout_attribute_location = 2; + m_gl->glVertexAttribPointer( + layout_attribute_location, + 3, + GL_FLOAT, + GL_FALSE, + Vec3ByteStride, + reinterpret_cast(Vec3ByteStride * 4)); + m_gl->glEnableVertexAttribArray(layout_attribute_location); + + // The rest of the attributes are stored in a separate data buffer, interleaved. + m_gl->glBindBuffer(GL_ARRAY_BUFFER, m_others_vbo); + + // This attribute stores 2 bools: + // bit 0: normal direction which maps to -1.0 (false) or 1.0 (true) + // bit 1: whether this is the end vertex of a new path + // int v_bitmask; + layout_attribute_location = 3; + unsigned long long offset = 0; + m_gl->glVertexAttribIPointer( + layout_attribute_location, + 1, + GL_INT, + sizeof(OtherAttributes), + reinterpret_cast(offset)); + m_gl->glEnableVertexAttribArray(layout_attribute_location); + + // vec3 v_color; + layout_attribute_location = 4; + offset += sizeof(GLint); + m_gl->glVertexAttribPointer( + layout_attribute_location, + 3, + GL_FLOAT, + GL_FALSE, + sizeof(OtherAttributes), + reinterpret_cast(offset)); + m_gl->glEnableVertexAttribArray(layout_attribute_location); + + // vec3 v_surface_normal; + layout_attribute_location = 5; + offset += 3 * sizeof(GLfloat); + m_gl->glVertexAttribPointer( + layout_attribute_location, + 3, + GL_FLOAT, + GL_FALSE, + sizeof(OtherAttributes), + reinterpret_cast(offset)); + m_gl->glEnableVertexAttribArray(layout_attribute_location); + + m_gl->glBindVertexArray(0); + m_gl->glBindBuffer(GL_ARRAY_BUFFER, 0); + + load_light_paths_data(); + + m_gl_initialized = true; +} + +void LightPathsLayer::cleanup_gl_data() +{ + if (m_shader_program != 0) + m_gl->glDeleteProgram(m_shader_program); +} + +void LightPathsLayer::draw_render_camera() const +{ + const auto gl_view_matrix = const_cast(&m_gl_render_view_matrix[0]); + render_scene(gl_view_matrix); +} + +void LightPathsLayer::draw() const +{ + const auto gl_view_matrix = const_cast(&m_gl_view_matrix[0]); + render_scene(gl_view_matrix); +} + +void LightPathsLayer::render_scene(const GLfloat* gl_view_matrix) const +{ + if (!m_gl_initialized) + return; + + if (m_total_triangle_count > 1) + { + const int selected_light_path_index = m_light_paths_manager.get_selected_light_path_index(); + + GLint first_selected, last_selected; + + if (selected_light_path_index == -1) + { + first_selected = 0; + last_selected = static_cast(m_path_terminator_vertex_indices[m_path_terminator_vertex_indices.size() - 1]); + } + else + { + assert(m_path_terminator_vertex_indices.size() > selected_light_path_index + 1); + first_selected = static_cast(m_path_terminator_vertex_indices[selected_light_path_index]); + last_selected = static_cast(m_path_terminator_vertex_indices[selected_light_path_index + 1]); + } + + m_gl->glUseProgram(m_shader_program); + + m_gl->glUniformMatrix4fv( + m_view_mat_loc, + 1, + false, + gl_view_matrix); + m_gl->glUniformMatrix4fv( + m_proj_mat_loc, + 1, + false, + const_cast(&m_gl_proj_matrix[0])); + m_gl->glUniform2f( + m_res_loc, + (GLfloat)m_width, + (GLfloat)m_height); + m_gl->glUniform1i( + m_first_selected_loc, + first_selected); + m_gl->glUniform1i( + m_last_selected_loc, + last_selected); + + m_gl->glBindVertexArray(m_light_paths_vao); + + m_gl->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indices_ebo); + m_gl->glDrawElements(GL_TRIANGLES, m_total_triangle_count, GL_UNSIGNED_INT, static_cast(0)); + m_gl->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } +} + +} // namespace studio +} // namespace appleseed diff --git a/src/appleseed.studio/mainwindow/rendering/lightpathswidget.h b/src/appleseed.studio/mainwindow/rendering/lightpathslayer.h similarity index 50% rename from src/appleseed.studio/mainwindow/rendering/lightpathswidget.h rename to src/appleseed.studio/mainwindow/rendering/lightpathslayer.h index d3d33f10f9..aa674176f6 100644 --- a/src/appleseed.studio/mainwindow/rendering/lightpathswidget.h +++ b/src/appleseed.studio/mainwindow/rendering/lightpathslayer.h @@ -5,7 +5,7 @@ // // This software is released under the MIT license. // -// Copyright (c) 2018 Francois Beaune, The appleseedhq Organization +// Copyright (c) 2019 Gray Olson, The appleseedhq Organization // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -29,6 +29,7 @@ #pragma once // appleseed.studio headers. +#include "mainwindow/rendering/lightpathsmanager.h" #include "mainwindow/rendering/renderclipboardhandler.h" // appleseed.renderer headers. @@ -58,7 +59,7 @@ namespace renderer { class ObjectInstance; } namespace renderer { class Project; } class QKeyEvent; class QImage; -class QOpenGLFunctions_3_3_Core; +class QOpenGLFunctions_4_1_Core; namespace appleseed { namespace studio { @@ -67,100 +68,76 @@ namespace studio { // A widget providing an hardware-accelerated visualization of recorded light paths. // -class LightPathsWidget - : public QOpenGLWidget - , public ICapturableWidget +class LightPathsLayer + : public QObject { Q_OBJECT public: - LightPathsWidget( + LightPathsLayer( const renderer::Project& project, - const size_t width, - const size_t height); + const LightPathsManager& light_paths_manager, + const std::size_t width, + const std::size_t height); - QImage capture() override; + void resize(const std::size_t width, const std::size_t height); + + void update_render_camera_transform(); void set_transform( const foundation::Transformd& transform); - void set_light_paths( - const renderer::LightPathArray& light_paths); + void set_gl_functions( + QOpenGLFunctions_4_1_Core* functions); - void set_selected_light_path_index( - const int selected_light_path_index); + void init_gl(QSurfaceFormat format); - signals: - void signal_light_path_selection_changed( - const int selected_light_path_index, - const int total_light_paths) const; + void draw() const; + + void draw_render_camera() const; public slots: - void slot_display_all_light_paths(); - void slot_display_previous_light_path(); - void slot_display_next_light_path(); - void slot_toggle_backface_culling(const bool checked); void slot_synchronize_camera(); + private slots: + void slot_light_path_selection_changed( + const bool display_light_paths, + const int selected_light_path_index, + const int total_light_paths); + private: const renderer::Project& m_project; + const LightPathsManager& m_light_paths_manager; renderer::Camera& m_camera; foundation::Matrix4d m_camera_matrix; + foundation::Matrix4d m_render_camera_matrix; + + std::size_t m_width; + std::size_t m_height; + + QOpenGLFunctions_4_1_Core* m_gl; - bool m_backface_culling_enabled; - - renderer::LightPathArray m_light_paths; - int m_selected_light_path_index; // -1 == display all paths - - QOpenGLFunctions_3_3_Core* m_gl; - - std::vector m_scene_object_data_vbos; - std::vector m_scene_object_data_index_counts; - std::vector m_scene_object_instance_vbos; - std::vector m_scene_object_instance_counts; - std::vector m_scene_object_current_instances; - std::vector m_scene_object_vaos; - std::unordered_map m_scene_object_index_map; - GLuint m_scene_shader_program; - GLint m_scene_view_mat_location; - GLint m_scene_proj_mat_location; - GLuint m_light_paths_vbo; - std::vector m_light_paths_index_offsets; + GLuint m_positions_vbo; + GLuint m_others_vbo; + GLuint m_indices_ebo; + std::vector m_path_terminator_vertex_indices; // Store the first and last vertex index of each light path. + GLsizei m_total_triangle_count; GLuint m_light_paths_vao; - GLuint m_light_paths_shader_program; - GLint m_light_paths_view_mat_location; - GLint m_light_paths_proj_mat_location; + GLuint m_shader_program; + GLint m_view_mat_loc; + GLint m_proj_mat_loc; + GLint m_res_loc; + GLint m_first_selected_loc; + GLint m_last_selected_loc; + foundation::Matrix4f m_gl_render_view_matrix; foundation::Matrix4f m_gl_view_matrix; foundation::Matrix4f m_gl_proj_matrix; bool m_gl_initialized; - void initializeGL() override; - void resizeGL(int w, int h) override; - void paintGL() override; - void keyPressEvent(QKeyEvent* event) override; - void cleanup_gl_data(); - void load_scene_data(); void load_light_paths_data(); - void load_assembly_data( - const renderer::Assembly& object); - - void load_assembly_instance( - const renderer::AssemblyInstance& assembly_instance, - const float time); - - void load_object_data( - const renderer::Object& object); - - void load_object_instance( - const renderer::ObjectInstance& object_instance, - const foundation::Matrix4f& assembly_transform_matrix); - - void render_geometry(); - void render_light_paths(); - - void dump_selected_light_path() const; + void render_scene(const GLfloat* gl_view_matrix) const; }; } // namespace studio diff --git a/src/appleseed.studio/mainwindow/rendering/lightpathsmanager.cpp b/src/appleseed.studio/mainwindow/rendering/lightpathsmanager.cpp new file mode 100644 index 0000000000..e9a47f4091 --- /dev/null +++ b/src/appleseed.studio/mainwindow/rendering/lightpathsmanager.cpp @@ -0,0 +1,237 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2020 Kevin Masson, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// Interface header. +#include "lightpathsmanager.h" + +// appleseed.studio headers. +#include "mainwindow/rendering/lightpathslayer.h" +#include "mainwindow/rendering/viewportcanvas.h" +#include "utility/settingskeys.h" + +// appleseed.qtcommon headers. +#include "utility/miscellaneous.h" + +// appleseed.renderer headers. +#include "renderer/api/frame.h" +#include "renderer/api/lighting.h" +#include "renderer/api/log.h" +#include "renderer/api/project.h" + +// appleseed.foundation headers. +#include "foundation/image/canvasproperties.h" +#include "foundation/image/image.h" +#include "foundation/math/vector.h" +#include "foundation/string/string.h" + +// Qt headers. +#include +#include +#include +#include +#include + +// Standard headers. +#include +#include +#include + +using namespace appleseed::qtcommon; +using namespace foundation; +using namespace renderer; + +namespace appleseed { +namespace studio { + +LightPathsManager::LightPathsManager( + const renderer::Project& project, + renderer::ParamArray& application_settings) + : m_project(project) + , m_application_settings(application_settings) + , m_selected_light_path_index(-1) + , m_display_light_paths(false) +{ +} + +void LightPathsManager::set_light_paths(const LightPathArray& light_paths) +{ + m_light_paths = light_paths; + + if (m_light_paths.size() > 1) + { + // Sort paths by descending radiance at the camera. + const auto& light_path_recorder = m_project.get_light_path_recorder(); + std::sort( + &m_light_paths[0], + &m_light_paths[0] + m_light_paths.size(), + [&light_path_recorder](const LightPath& lhs, const LightPath& rhs) + { + LightPathVertex lhs_v; + light_path_recorder.get_light_path_vertex(lhs.m_vertex_end_index - 1, lhs_v); + + LightPathVertex rhs_v; + light_path_recorder.get_light_path_vertex(rhs.m_vertex_end_index - 1, rhs_v); + + return + sum_value(Color3f::from_array(lhs_v.m_radiance)) > + sum_value(Color3f::from_array(rhs_v.m_radiance)); + }); + } + + set_selected_light_path_index(-1); +} + +void LightPathsManager::clear_light_paths() +{ + m_light_paths.clear(); + set_selected_light_path_index(-1); +} + +void LightPathsManager::display_light_paths(const bool on) +{ + m_display_light_paths = on; + set_selected_light_path_index(m_selected_light_path_index); +} + +bool LightPathsManager::should_display_light_paths() const +{ + return m_display_light_paths; +} + +void LightPathsManager::save_all_light_paths(QWidget* source) const +{ + QString filepath = + get_save_filename( + source, + "Save Light Paths As...", + "Light Paths Files (*.aspaths);;All Files (*.*)", + m_application_settings, + SETTINGS_FILE_DIALOG_LIGHT_PATHS); + + if (filepath.isEmpty()) + return; + + if (QFileInfo(filepath).suffix().isEmpty()) + filepath += ".aspaths"; + + // Write light paths to disk. + m_project.get_light_path_recorder().write(filepath.toUtf8().constData()); +} + +const renderer::LightPathArray& LightPathsManager::get_light_paths() const +{ + return m_light_paths; +} + +int LightPathsManager::get_selected_light_path_index() const +{ + return m_selected_light_path_index; +} + +void LightPathsManager::slot_select_all_light_paths() +{ + set_selected_light_path_index(-1); +} + +void LightPathsManager::slot_select_previous_light_path() +{ + if (m_selected_light_path_index > -1) + set_selected_light_path_index(m_selected_light_path_index - 1); +} + +void LightPathsManager::slot_select_next_light_path() +{ + if (m_selected_light_path_index < static_cast(m_light_paths.size()) - 1) + set_selected_light_path_index(m_selected_light_path_index + 1); +} + +void LightPathsManager::slot_clear_light_paths() +{ + m_light_paths.clear(); + set_selected_light_path_index(-1); + + m_project.get_light_path_recorder().clear(); +} + +void LightPathsManager::set_selected_light_path_index(int index) +{ + m_selected_light_path_index = index; + + if (m_display_light_paths) + print_selected_light_paths(); + + emit signal_light_path_selection_changed( + m_display_light_paths, + m_selected_light_path_index, + static_cast(m_light_paths.size())); +} + +void LightPathsManager::print_selected_light_paths() const +{ + if (m_selected_light_path_index == -1) + { + if (m_light_paths.empty()) + RENDERER_LOG_INFO("no light path to display."); + else + { + RENDERER_LOG_INFO("displaying %s light path%s.", + pretty_uint(m_light_paths.size()).c_str(), + m_light_paths.size() > 1 ? "s" : ""); + } + } + else + { + RENDERER_LOG_INFO("displaying light path %s/%s:", + pretty_int(m_selected_light_path_index + 1).c_str(), + pretty_int(m_light_paths.size()).c_str()); + + const auto& light_path_recorder = m_project.get_light_path_recorder(); + const auto& path = m_light_paths[m_selected_light_path_index]; + + for (std::size_t i = path.m_vertex_begin_index; i < path.m_vertex_end_index; ++i) + { + LightPathVertex v; + light_path_recorder.get_light_path_vertex(i, v); + + const std::string entity_name = + v.m_entity != nullptr + ? foundation::format("\"{0}\"", v.m_entity->get_path().c_str()) + : "n/a"; + + RENDERER_LOG_INFO(" vertex " FMT_SIZE_T ": entity: %s - position: (%f, %f, %f) - radiance: (%f, %f, %f) - total radiance: %f", + i - path.m_vertex_begin_index + 1, + entity_name.c_str(), + v.m_position[0], v.m_position[1], v.m_position[2], + v.m_radiance[0], v.m_radiance[1], v.m_radiance[2], + v.m_radiance[0] + v.m_radiance[1] + v.m_radiance[2]); + } + } +} + +} // namespace studio +} // namespace appleseed diff --git a/src/appleseed.studio/mainwindow/rendering/lightpathsmanager.h b/src/appleseed.studio/mainwindow/rendering/lightpathsmanager.h new file mode 100644 index 0000000000..1ab1145559 --- /dev/null +++ b/src/appleseed.studio/mainwindow/rendering/lightpathsmanager.h @@ -0,0 +1,106 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2020 Kevin Masson, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#pragma once + +// appleseed.foundation headers. +#include "foundation/math/aabb.h" +#include "foundation/math/vector.h" + +// appleseed.renderer headers. +#include "renderer/api/lighting.h" +#include "renderer/utility/paramarray.h" + +// Qt headers. +#include + +// Forward declarations. +namespace appleseed { namespace studio { class LightPathsLayer; } } +namespace appleseed { namespace studio { class ViewportCanvas; } } +namespace renderer { class Project; } +class QEvent; +class QPoint; +class QWidget; + +namespace appleseed { +namespace studio { + +// +// Keep track of selected light paths. +// + +class LightPathsManager + : public QObject +{ + Q_OBJECT + + public: + LightPathsManager( + const renderer::Project& project, + renderer::ParamArray& application_settings); + + void set_light_paths( + const renderer::LightPathArray& light_paths); + + void clear_light_paths(); + + void display_light_paths(const bool on); + bool should_display_light_paths() const; + + void save_all_light_paths(QWidget* source) const; + + const renderer::LightPathArray& get_light_paths() const; + + int get_selected_light_path_index() const; + + signals: + void signal_light_path_selection_changed( + const bool display_light_paths, + const int selected_light_path_index, + const int total_light_paths) const; + + public slots: + void slot_select_all_light_paths(); + void slot_select_previous_light_path(); + void slot_select_next_light_path(); + void slot_clear_light_paths(); + + private: + const renderer::Project& m_project; + renderer::ParamArray& m_application_settings; + + renderer::LightPathArray m_light_paths; + int m_selected_light_path_index; // The index of the shown path. All paths are displayed when using -1. + bool m_display_light_paths; + + void set_selected_light_path_index(int index); + void print_selected_light_paths() const; +}; + +} // namespace studio +} // namespace appleseed diff --git a/src/appleseed.studio/mainwindow/rendering/lightpathspickinghandler.cpp b/src/appleseed.studio/mainwindow/rendering/lightpathspickinghandler.cpp index ef06f45a09..36f349ab57 100644 --- a/src/appleseed.studio/mainwindow/rendering/lightpathspickinghandler.cpp +++ b/src/appleseed.studio/mainwindow/rendering/lightpathspickinghandler.cpp @@ -30,10 +30,8 @@ #include "lightpathspickinghandler.h" // appleseed.studio headers. -#include "mainwindow/rendering/lightpathswidget.h" - -// appleseed.qtcommon headers. -#include "widgets/mousecoordinatestracker.h" +#include "mainwindow/rendering/lightpathslayer.h" +#include "mainwindow/rendering/viewportcanvas.h" // appleseed.renderer headers. #include "renderer/api/frame.h" @@ -52,7 +50,6 @@ #include #include -using namespace appleseed::qtcommon; using namespace foundation; using namespace renderer; @@ -60,20 +57,15 @@ namespace appleseed { namespace studio { LightPathsPickingHandler::LightPathsPickingHandler( - LightPathsWidget* light_paths_widget, - const MouseCoordinatesTracker& mouse_tracker, + LightPathsManager& light_paths_manager, + ViewportCanvas* viewport_canvas, const Project& project) - : m_light_paths_widget(light_paths_widget) - , m_mouse_tracker(mouse_tracker) + : m_light_paths_manager(light_paths_manager) + , m_viewport_canvas(viewport_canvas) , m_project(project) - , m_enabled(true) + , m_enabled(false) { - m_light_paths_widget->installEventFilter(this); -} - -LightPathsPickingHandler::~LightPathsPickingHandler() -{ - m_light_paths_widget->removeEventFilter(this); + m_viewport_canvas->installEventFilter(this); } void LightPathsPickingHandler::set_enabled(const bool enabled) @@ -81,63 +73,40 @@ void LightPathsPickingHandler::set_enabled(const bool enabled) m_enabled = enabled; } -void LightPathsPickingHandler::pick(const Vector2i& pixel) const +void LightPathsPickingHandler::slot_rectangle_selection(const QRect& rect) { - const Image& image = m_project.get_frame()->image(); - const CanvasProperties& props = image.properties(); + if (!m_enabled) return; - if (pixel.x >= 0 && - pixel.y >= 0 && - pixel.x < static_cast(props.m_canvas_width) && - pixel.y < static_cast(props.m_canvas_height)) - { - LightPathArray light_paths; - m_project.get_light_path_recorder().query( - static_cast(pixel.x), - static_cast(pixel.y), - static_cast(pixel.x), - static_cast(pixel.y), - light_paths); - - if (light_paths.empty()) - RENDERER_LOG_INFO("no light path found at pixel (%d, %d).", pixel.x, pixel.y); - else - { - RENDERER_LOG_INFO( - "%s light path%s found at pixel (%d, %d).", - pretty_uint(light_paths.size()).c_str(), - light_paths.size() > 1 ? "s" : "", - pixel.x, - pixel.y); - } - - m_light_paths_widget->set_light_paths(light_paths); - m_light_paths_widget->update(); - } -} - -void LightPathsPickingHandler::pick(const AABB2i& rect) const -{ const Image& image = m_project.get_frame()->image(); const CanvasProperties& props = image.properties(); + const AABB2i rectangle_selection( + Vector2i(rect.x(), rect.y()), + Vector2i(rect.x() + rect.width() - 1, rect.y() + rect.height() - 1)); + const AABB2i image_rect( Vector2i(0, 0), Vector2i( static_cast(props.m_canvas_width - 1), static_cast(props.m_canvas_height - 1))); - const auto final_rect = AABB2i::intersect(rect, image_rect); + const auto final_rect = AABB2i::intersect(rectangle_selection, image_rect); if (final_rect.is_valid()) { LightPathArray light_paths; - m_project.get_light_path_recorder().query( - static_cast(final_rect.min.x), - static_cast(final_rect.min.y), - static_cast(final_rect.max.x), - static_cast(final_rect.max.y), - light_paths); + + const LightPathRecorder& light_path_recorder = m_project.get_light_path_recorder(); + + if (light_path_recorder.get_light_path_count() > 0) + { + light_path_recorder.query( + static_cast(final_rect.min.x), + static_cast(final_rect.min.y), + static_cast(final_rect.max.x), + static_cast(final_rect.max.y), + light_paths); + } if (light_paths.empty()) { @@ -159,8 +128,55 @@ void LightPathsPickingHandler::pick(const AABB2i& rect) const final_rect.max.y); } - m_light_paths_widget->set_light_paths(light_paths); - m_light_paths_widget->update(); + m_light_paths_manager.set_light_paths(light_paths); + } +} + +void LightPathsPickingHandler::pick(const QPoint& point) const +{ + const Image& image = m_project.get_frame()->image(); + const CanvasProperties& props = image.properties(); + + const Vector2d ndc( + static_cast(point.x()) / m_viewport_canvas->width(), + static_cast(point.y()) / m_viewport_canvas->height()); + + const Vector2i pixel( + ndc[0] * static_cast(props.m_canvas_width), + ndc[1] * static_cast(props.m_canvas_height)); + + if (pixel.x >= 0 && + pixel.y >= 0 && + pixel.x < static_cast(props.m_canvas_width) && + pixel.y < static_cast(props.m_canvas_height)) + { + LightPathArray light_paths; + + const LightPathRecorder& light_path_recorder = m_project.get_light_path_recorder(); + + if (light_path_recorder.get_light_path_count() > 0) + { + light_path_recorder.query( + static_cast(pixel.x), + static_cast(pixel.y), + static_cast(pixel.x), + static_cast(pixel.y), + light_paths); + } + + if (light_paths.empty()) + RENDERER_LOG_INFO("no light path found at pixel (%d, %d).", pixel.x, pixel.y); + else + { + RENDERER_LOG_INFO( + "%s light path%s found at pixel (%d, %d).", + pretty_uint(light_paths.size()).c_str(), + light_paths.size() > 1 ? "s" : "", + pixel.x, + pixel.y); + } + + m_light_paths_manager.set_light_paths(light_paths); } } @@ -168,14 +184,15 @@ bool LightPathsPickingHandler::eventFilter(QObject* object, QEvent* event) { if (m_enabled) { - if (event->type() == QEvent::MouseButtonPress) + if (event->type() == QEvent::MouseButtonRelease) { const QMouseEvent* mouse_event = static_cast(event); if (!(mouse_event->modifiers() & (Qt::AltModifier | Qt::ShiftModifier | Qt::ControlModifier))) { if (mouse_event->button() == Qt::LeftButton) { - pick(m_mouse_tracker.widget_to_pixel(mouse_event->pos())); + pick(mouse_event->pos()); + return true; } } diff --git a/src/appleseed.studio/mainwindow/rendering/lightpathspickinghandler.h b/src/appleseed.studio/mainwindow/rendering/lightpathspickinghandler.h index f2c8cadbe6..f786734e8e 100644 --- a/src/appleseed.studio/mainwindow/rendering/lightpathspickinghandler.h +++ b/src/appleseed.studio/mainwindow/rendering/lightpathspickinghandler.h @@ -28,6 +28,9 @@ #pragma once +// appleseed.studio headers. +#include "mainwindow/rendering/lightpathsmanager.h" + // appleseed.foundation headers. #include "foundation/math/aabb.h" #include "foundation/math/vector.h" @@ -36,10 +39,10 @@ #include // Forward declarations. -namespace appleseed { namespace qtcommon { class MouseCoordinatesTracker; } } -namespace appleseed { namespace studio { class LightPathsWidget; } } +namespace appleseed { namespace studio { class ViewportCanvas; } } namespace renderer { class Project; } class QEvent; +class QPoint; namespace appleseed { namespace studio { @@ -51,23 +54,23 @@ class LightPathsPickingHandler public: LightPathsPickingHandler( - LightPathsWidget* light_paths_widget, - const qtcommon::MouseCoordinatesTracker& mouse_tracker, + LightPathsManager& light_paths_manager, + ViewportCanvas* viewport_canvas, const renderer::Project& project); - ~LightPathsPickingHandler() override; - void set_enabled(const bool enabled); - void pick(const foundation::Vector2i& pixel) const; - void pick(const foundation::AABB2i& rect) const; + public slots: + void slot_rectangle_selection(const QRect& rect); private: - LightPathsWidget* m_light_paths_widget; - const qtcommon::MouseCoordinatesTracker& m_mouse_tracker; + LightPathsManager& m_light_paths_manager; + ViewportCanvas* m_viewport_canvas; const renderer::Project& m_project; bool m_enabled; + void pick(const QPoint& point) const; + bool eventFilter(QObject* object, QEvent* event) override; }; diff --git a/src/appleseed.studio/mainwindow/rendering/lightpathstab.cpp b/src/appleseed.studio/mainwindow/rendering/lightpathstab.cpp deleted file mode 100644 index 2ace0d3416..0000000000 --- a/src/appleseed.studio/mainwindow/rendering/lightpathstab.cpp +++ /dev/null @@ -1,341 +0,0 @@ - -// -// This source file is part of appleseed. -// Visit https://appleseedhq.net/ for additional information and resources. -// -// This software is released under the MIT license. -// -// Copyright (c) 2018 Francois Beaune, The appleseedhq Organization -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -// Interface header. -#include "lightpathstab.h" - -// appleseed.studio headers. -#include "mainwindow/rendering/lightpathspickinghandler.h" -#include "mainwindow/rendering/lightpathswidget.h" -#include "utility/settingskeys.h" - -// appleseed.qtcommon headers. -#include "utility/miscellaneous.h" - -// appleseed.renderer headers. -#include "renderer/api/frame.h" -#include "renderer/api/lighting.h" -#include "renderer/api/project.h" - -// appleseed.foundation headers. -#include "foundation/image/canvasproperties.h" -#include "foundation/image/image.h" -#include "foundation/math/aabb.h" -#include "foundation/math/vector.h" - -// Qt headers. -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Standard headers. -#include - -using namespace appleseed::qtcommon; -using namespace foundation; -using namespace renderer; - -namespace appleseed { -namespace studio { - -// -// LightPathsTab class implementation. -// - -LightPathsTab::LightPathsTab(Project& project, ParamArray& settings) - : m_project(project) - , m_settings(settings) -{ - setObjectName("render_widget_tab"); - setLayout(new QGridLayout()); - layout()->setSpacing(0); - layout()->setMargin(0); - - create_light_paths_widget(); - create_toolbar(); - create_scrollarea(); - - layout()->addWidget(m_toolbar); - layout()->addWidget(m_scroll_area); - - recreate_handlers(); -} - -void LightPathsTab::slot_entity_picked(const ScenePicker::PickingResult& result) -{ - const CanvasProperties& props = m_project.get_frame()->image().properties(); - - m_screen_space_paths_picking_handler->pick( - Vector2i( - result.m_ndc[0] * static_cast(props.m_canvas_width), - result.m_ndc[1] * static_cast(props.m_canvas_height))); -} - -void LightPathsTab::slot_rectangle_selection(const QRect& rect) -{ - m_screen_space_paths_picking_handler->pick( - AABB2i( - Vector2i(rect.x(), rect.y()), - Vector2i(rect.x() + rect.width() - 1, rect.y() + rect.height() - 1))); -} - -void LightPathsTab::slot_light_path_selection_changed( - const int selected_light_path_index, - const int total_light_paths) const -{ - if (total_light_paths > 0) - { - m_prev_path_button->setEnabled(selected_light_path_index > -1); - m_next_path_button->setEnabled(selected_light_path_index < total_light_paths - 1); - } - else - { - m_prev_path_button->setEnabled(false); - m_next_path_button->setEnabled(false); - } -} - -void LightPathsTab::slot_context_menu(const QPoint& point) -{ - if (!(QApplication::keyboardModifiers() & Qt::ShiftModifier)) - return; - - QMenu* menu = new QMenu(this); - - const auto light_path_count = m_project.get_light_path_recorder().get_light_path_count(); - menu->addAction( - QString("Save %1 Light Path%2...") - .arg(QString::fromStdString(pretty_uint(light_path_count))) - .arg(light_path_count > 1 ? "s" : ""), - this, SLOT(slot_save_light_paths())); - - menu->exec(m_light_paths_widget->mapToGlobal(point)); -} - -void LightPathsTab::slot_save_light_paths() -{ - QString filepath = - get_save_filename( - this, - "Save Light Paths As...", - "Light Paths Files (*.aspaths);;All Files (*.*)", - m_settings, - SETTINGS_FILE_DIALOG_LIGHT_PATHS); - - if (filepath.isEmpty()) - return; - - if (QFileInfo(filepath).suffix().isEmpty()) - filepath += ".aspaths"; - - // Write light paths to disk. - m_project.get_light_path_recorder().write(filepath.toUtf8().constData()); -} - -void LightPathsTab::slot_camera_changed() -{ - m_light_paths_widget->set_transform(m_camera_controller->get_transform()); - m_light_paths_widget->update(); -} - -void LightPathsTab::create_light_paths_widget() -{ - // Create the OpenGL widget. - const CanvasProperties& props = m_project.get_frame()->image().properties(); - m_light_paths_widget = - new LightPathsWidget( - m_project, - props.m_canvas_width, - props.m_canvas_height); - - // Enable context menu on the OpenGL widget. - m_light_paths_widget->setContextMenuPolicy(Qt::CustomContextMenu); - connect( - m_light_paths_widget, SIGNAL(signal_light_path_selection_changed(const int, const int)), - SLOT(slot_light_path_selection_changed(const int, const int))); - connect( - m_light_paths_widget, SIGNAL(customContextMenuRequested(const QPoint&)), - SLOT(slot_context_menu(const QPoint&))); -} - -void LightPathsTab::create_toolbar() -{ - // Create the render toolbar. - m_toolbar = new QToolBar(); - m_toolbar->setObjectName("render_toolbar"); - m_toolbar->setIconSize(QSize(18, 18)); - - // Save Light Paths button. - QToolButton* save_light_paths_button = new QToolButton(); - save_light_paths_button->setIcon(load_icons("lightpathstab_save_light_paths")); - const auto light_path_count = m_project.get_light_path_recorder().get_light_path_count(); - save_light_paths_button->setToolTip( - QString("Save %1 Light Path%2...") - .arg(QString::fromStdString(pretty_uint(light_path_count))) - .arg(light_path_count > 1 ? "s" : "")); - connect( - save_light_paths_button , SIGNAL(clicked()), - SLOT(slot_save_light_paths())); - m_toolbar->addWidget(save_light_paths_button); - - m_toolbar->addSeparator(); - - // Previous Light Path button. - m_prev_path_button = new QToolButton(); - m_prev_path_button->setIcon(load_icons("lightpathstab_prev_light_path")); - m_prev_path_button->setToolTip("Display previous light path"); - m_prev_path_button->setEnabled(false); - connect( - m_prev_path_button, SIGNAL(clicked()), - m_light_paths_widget, SLOT(slot_display_previous_light_path())); - m_toolbar->addWidget(m_prev_path_button); - - // Next Light Path button. - m_next_path_button = new QToolButton(); - m_next_path_button->setIcon(load_icons("lightpathstab_next_light_path")); - m_next_path_button->setToolTip("Display next light path"); - m_next_path_button->setEnabled(false); - connect( - m_next_path_button, SIGNAL(clicked()), - m_light_paths_widget, SLOT(slot_display_next_light_path())); - m_toolbar->addWidget(m_next_path_button); - - m_toolbar->addSeparator(); - - // Toggle Backface Culling button. - QToolButton* backface_culling_button = new QToolButton(); - backface_culling_button->setIcon(load_icons("lightpathstab_toggle_backface_culling")); - backface_culling_button->setToolTip("Show/hide backfacing surfaces"); - backface_culling_button->setCheckable(true); - backface_culling_button->setChecked(false); - connect( - backface_culling_button, SIGNAL(toggled(bool)), - m_light_paths_widget, SLOT(slot_toggle_backface_culling(const bool))); - m_toolbar->addWidget(backface_culling_button); - - // Synchronize Camera button. - QToolButton* sync_camera_button = new QToolButton(); - sync_camera_button->setIcon(load_icons("lightpathstab_synchronize_camera")); - sync_camera_button->setToolTip("Synchronize the rendering camera with this camera"); - connect( - sync_camera_button, SIGNAL(clicked()), - m_light_paths_widget, SLOT(slot_synchronize_camera())); - m_toolbar->addWidget(sync_camera_button); - - // Add stretchy spacer. - // This places interactive widgets on the left and info on the right. - QWidget* spacer = new QWidget(); - spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - m_toolbar->addWidget(spacer); - - // Create a label to display various information such as mouse coordinates, etc. - m_info_label = new QLabel(); - m_info_label->setObjectName("info_label"); - m_toolbar->addWidget(m_info_label); -} - -void LightPathsTab::create_scrollarea() -{ - // Encapsulate the OpenGL widget into another widget that adds a margin around it. - QWidget* gl_widget_wrapper = new QWidget(); - gl_widget_wrapper->setObjectName("render_widget_wrapper"); - gl_widget_wrapper->setLayout(new QGridLayout()); - gl_widget_wrapper->layout()->setSizeConstraint(QLayout::SetFixedSize); - gl_widget_wrapper->layout()->setContentsMargins(20, 20, 20, 20); - gl_widget_wrapper->layout()->addWidget(m_light_paths_widget); - - // Wrap the OpenGL widget in a scroll area. - m_scroll_area = new QScrollArea(); - m_scroll_area->setObjectName("render_widget_scrollarea"); - m_scroll_area->setAlignment(Qt::AlignCenter); - m_scroll_area->setWidget(gl_widget_wrapper); -} - -void LightPathsTab::recreate_handlers() -{ - // Handler for zooming the render widget in and out with the keyboard or the mouse wheel. - m_zoom_handler.reset( - new WidgetZoomHandler( - m_scroll_area, - m_light_paths_widget)); - - // Handler for panning the render widget with the mouse. - m_pan_handler.reset( - new ScrollAreaPanHandler( - m_scroll_area)); - - // Handler for tracking and displaying mouse coordinates. - m_mouse_tracker.reset( - new MouseCoordinatesTracker( - m_light_paths_widget, - m_info_label)); - - // The screen-space paths picking handler is used to pick paths from the render widget. - m_screen_space_paths_picking_handler.reset( - new LightPathsPickingHandler( - m_light_paths_widget, - *m_mouse_tracker.get(), - m_project)); - m_screen_space_paths_picking_handler->set_enabled(false); - - // The world-space paths picking handler is used to pick paths in the light paths widget. - // Commented out because we don't want to allow that. - // m_world_space_paths_picking_handler.reset( - // new LightPathsPickingHandler( - // m_light_paths_widget, - // *m_mouse_tracker.get(), - // m_project)); - - // Camera handler. - m_light_paths_widget->setMouseTracking(true); - m_camera_controller.reset( - new CameraController( - m_light_paths_widget, - m_project, - m_project.get_uncached_active_camera())); - connect( - m_camera_controller.get(), SIGNAL(signal_camera_changed()), - SLOT(slot_camera_changed())); - - // Clipboard handler. - m_clipboard_handler.reset(new RenderClipboardHandler(m_light_paths_widget, m_light_paths_widget)); -} - -} // namespace studio -} // namespace appleseed - -#include "mainwindow/rendering/moc_cpp_lightpathstab.cxx" diff --git a/src/appleseed.studio/mainwindow/rendering/lightpathsviewporttoolbar.cpp b/src/appleseed.studio/mainwindow/rendering/lightpathsviewporttoolbar.cpp new file mode 100644 index 0000000000..f6f166a74a --- /dev/null +++ b/src/appleseed.studio/mainwindow/rendering/lightpathsviewporttoolbar.cpp @@ -0,0 +1,235 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2018 Francois Beaune, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// Interface header. +#include "lightpathsviewporttoolbar.h" + +// appleseed.studio headers. +#include "mainwindow/rendering/lightpathspickinghandler.h" +#include "mainwindow/rendering/lightpathslayer.h" +#include "mainwindow/rendering/viewporttab.h" +#include "utility/settingskeys.h" + +// appleseed.qtcommon headers. +#include "utility/miscellaneous.h" + +// appleseed.renderer headers. +#include "renderer/api/frame.h" +#include "renderer/api/lighting.h" +#include "renderer/api/project.h" + +// appleseed.foundation headers. +#include "foundation/image/canvasproperties.h" +#include "foundation/image/image.h" +#include "foundation/math/aabb.h" +#include "foundation/math/vector.h" + +// Qt headers. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Standard headers. +#include + +using namespace appleseed::qtcommon; +using namespace foundation; +using namespace renderer; + +namespace appleseed { +namespace studio { + +// +// LightPathsViewportToolbar class implementation. +// + +LightPathsViewportToolbar::LightPathsViewportToolbar( + ViewportTab* viewport_tab, + Project* project, + LightPathsManager& light_paths_manager) + : m_enabled(false) + , m_project(project) + , m_light_paths_manager(light_paths_manager) + , m_viewport_canvas(viewport_tab->get_viewport_canvas()) +{ + LightPathsLayer* light_paths_layer = m_viewport_canvas->get_light_paths_layer(); + + connect( + &m_light_paths_manager, &LightPathsManager::signal_light_path_selection_changed, + this, &LightPathsViewportToolbar::slot_light_path_selection_changed); + connect( + this, &LightPathsViewportToolbar::signal_display_next_light_path, + &m_light_paths_manager, &LightPathsManager::slot_select_next_light_path); + connect( + this, &LightPathsViewportToolbar::signal_display_previous_light_path, + &m_light_paths_manager, &LightPathsManager::slot_select_previous_light_path); + + create_toolbar(); + + m_viewport_canvas->installEventFilter(this); +} + +void LightPathsViewportToolbar::reset(renderer::Project* project) +{ + m_project = project; +} + +void LightPathsViewportToolbar::set_enabled(const bool enabled) +{ + m_enabled = enabled; + + if (enabled) + { + refresh_toolbar(); + m_toolbar->show(); + } + else + { + m_toolbar->hide(); + } + + m_toolbar->setDisabled(!enabled); +} + +QToolBar* LightPathsViewportToolbar::toolbar() const +{ + return m_toolbar; +} + +void LightPathsViewportToolbar::slot_light_path_selection_changed( + const bool display_light_paths, + const int selected_light_path_index, + const int total_light_paths) +{ + if (total_light_paths > 0) + { + m_prev_path_button->setEnabled(selected_light_path_index > -1); + m_next_path_button->setEnabled(selected_light_path_index < total_light_paths - 1); + } + else + { + m_prev_path_button->setEnabled(false); + m_next_path_button->setEnabled(false); + } +} + +void LightPathsViewportToolbar::slot_save_light_paths() +{ + m_light_paths_manager.save_all_light_paths(m_toolbar); +} + +void LightPathsViewportToolbar::create_toolbar() +{ + // Create the render toolbar. + m_toolbar = new QToolBar(); + m_toolbar->setObjectName("light_paths_viewport_toolbar"); + m_toolbar->setIconSize(QSize(18, 18)); + + // Save Light Paths button. + m_save_light_paths_button = new QToolButton(); + m_save_light_paths_button->setIcon(load_icons("lightpaths_toolbar_save_light_paths")); + connect( + m_save_light_paths_button , &QToolButton::clicked, + this, &LightPathsViewportToolbar::slot_save_light_paths); + m_toolbar->addWidget(m_save_light_paths_button); + + m_toolbar->addSeparator(); + + // Previous Light Path button. + m_prev_path_button = new QToolButton(); + m_prev_path_button->setIcon(load_icons("lightpaths_toolbar_prev_light_path")); + m_prev_path_button->setToolTip("Display previous light path"); + m_prev_path_button->setEnabled(false); + connect( + m_prev_path_button, &QToolButton::clicked, + &m_light_paths_manager, &LightPathsManager::slot_select_previous_light_path); + m_toolbar->addWidget(m_prev_path_button); + + // Next Light Path button. + m_next_path_button = new QToolButton(); + m_next_path_button->setIcon(load_icons("lightpaths_toolbar_next_light_path")); + m_next_path_button->setToolTip("Display next light path"); + m_next_path_button->setEnabled(false); + connect( + m_next_path_button, &QToolButton::clicked, + &m_light_paths_manager, &LightPathsManager::slot_select_next_light_path); + m_toolbar->addWidget(m_next_path_button); + + m_toolbar->setDisabled(true); + m_toolbar->hide(); +} + +void LightPathsViewportToolbar::refresh_toolbar() const +{ + const auto light_path_count = m_project->get_light_path_recorder().get_light_path_count(); + + m_save_light_paths_button->setToolTip( + QString("Save %1 Light Path%2...") + .arg(QString::fromStdString(pretty_uint(light_path_count))) + .arg(light_path_count > 1 ? "s" : "")); +} + +bool LightPathsViewportToolbar::eventFilter(QObject* object, QEvent* event) +{ + if (m_enabled) + { + if (event->type() == QEvent::KeyRelease) + { + const QKeyEvent* key_event = static_cast(event); + + if (!(key_event->modifiers() & (Qt::AltModifier | Qt::ShiftModifier | Qt::ControlModifier))) + { + const int key = key_event->key(); + + if (key == Qt::Key_Escape) + m_light_paths_manager.clear_light_paths(); + else if (key == Qt::Key_Left) + emit signal_display_previous_light_path(); + else if (key == Qt::Key_Right) + emit signal_display_next_light_path(); + } + } + } + + return QObject::eventFilter(object, event); +} + +} // namespace studio +} // namespace appleseed diff --git a/src/appleseed.studio/mainwindow/rendering/lightpathsviewporttoolbar.h b/src/appleseed.studio/mainwindow/rendering/lightpathsviewporttoolbar.h new file mode 100644 index 0000000000..d429c0108e --- /dev/null +++ b/src/appleseed.studio/mainwindow/rendering/lightpathsviewporttoolbar.h @@ -0,0 +1,114 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2018 Francois Beaune, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#pragma once + +// appleseed.studio headers. +#include "mainwindow/rendering/cameracontroller.h" +#include "mainwindow/rendering/lightpathsmanager.h" +#include "mainwindow/rendering/renderclipboardhandler.h" +#include "mainwindow/rendering/viewportcanvas.h" + +// appleseed.qtcommon headers. +#include "widgets/scrollareapanhandler.h" +#include "widgets/widgetzoomhandler.h" + +// appleseed.renderer headers. +#include "renderer/api/rendering.h" + +// Qt headers. +#include +#include + +// Forward declarations. +namespace appleseed { namespace studio { class ViewportTab; } } +namespace renderer { class PamArray; } +namespace renderer { class Project; } +class QAction; +class QEvent; +class QLabel; +class QPoint; +class QRect; +class QScrollArea; +class QToolBar; +class QToolButton; + +namespace appleseed { +namespace studio { + +// +// Control pannel for light paths. +// + +class LightPathsViewportToolbar + : public QObject +{ + Q_OBJECT + + public: + LightPathsViewportToolbar( + ViewportTab* viewport_tab, + renderer::Project* project, + LightPathsManager& light_paths_manager); + + void reset(renderer::Project* project); + + QToolBar* toolbar() const; + + void set_enabled(const bool enabled); + + signals: + void signal_display_next_light_path(); + void signal_display_previous_light_path(); + + private slots: + void slot_light_path_selection_changed( + const bool display_light_paths, + const int selected_light_path_index, + const int total_light_paths); + void slot_save_light_paths(); + + private: + bool m_enabled; + + renderer::Project* m_project; + LightPathsManager& m_light_paths_manager; + ViewportCanvas* m_viewport_canvas; + QToolBar* m_toolbar; + QToolButton* m_save_light_paths_button; + QToolButton* m_prev_path_button; + QToolButton* m_next_path_button; + + void create_toolbar(); + void refresh_toolbar() const; + + bool eventFilter(QObject* object, QEvent* event) override; +}; + +} // namespace studio +} // namespace appleseed diff --git a/src/appleseed.studio/mainwindow/rendering/lightpathswidget.cpp b/src/appleseed.studio/mainwindow/rendering/lightpathswidget.cpp deleted file mode 100644 index 953052b245..0000000000 --- a/src/appleseed.studio/mainwindow/rendering/lightpathswidget.cpp +++ /dev/null @@ -1,864 +0,0 @@ - -// -// This source file is part of appleseed. -// Visit https://appleseedhq.net/ for additional information and resources. -// -// This software is released under the MIT license. -// -// Copyright (c) 2018 Francois Beaune, The appleseedhq Organization -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -// Interface header. -#include "lightpathswidget.h" - -// appleseed.qtcommon headers. -#include "utility/miscellaneous.h" - -// appleseed.renderer headers. -#include "renderer/api/camera.h" -#include "renderer/api/entity.h" -#include "renderer/api/object.h" -#include "renderer/api/project.h" -#include "renderer/api/rasterization.h" -#include "renderer/api/scene.h" -#include "renderer/api/utility.h" - -// appleseed.foundation headers. -#include "foundation/image/color.h" -#include "foundation/image/colorspace.h" -#include "foundation/math/scalar.h" -#include "foundation/platform/types.h" -#include "foundation/string/string.h" -#include "foundation/utility/api/apistring.h" - -// Qt headers. -#include -#include -#include -#include - -// Standard headers. -#include -#include - -using namespace appleseed::qtcommon; -using namespace foundation; -using namespace renderer; - -namespace appleseed { -namespace studio { - -namespace -{ - // Number of floats per OpenGL vertex for a piece of scene geometry - // Vector3 position and Vector3 normal. - const size_t SceneVertexFloatStride = 6; - - // Number of bytes per OpenGL vertex for a piece of scene geometry. - const size_t SceneVertexByteStride = SceneVertexFloatStride * sizeof(float); - - // Number of floats per triangle for a piece of scene geometry. - const size_t SceneTriangleFloatStride = SceneVertexFloatStride * 3; - - // Number of floats per OpenGL vertex for a light path - // Vector3 position and Vector3 color. - const size_t LightPathVertexFloatStride = 6; - - // Number of bytes per OpenGL vertex for a piece of scene geometry. - const size_t LightPathVertexByteStride = LightPathVertexFloatStride * sizeof(float); - - // Number of floats per line for a light path. - const size_t LightPathVertexLineFloatStride = LightPathVertexFloatStride * 2; - - // Number of floats per OpenGL transform matrix. - const size_t TransformFloatStride = 16; - - // Number of bytes per OpenGL transform matrix. - const size_t TransformByteStride = TransformFloatStride * sizeof(float); - - struct OpenGLRasterizer - : public ObjectRasterizer - { - std::vector m_buffer; - size_t m_prim_count; - - void begin_object(const size_t triangle_count_hint) override - { - m_buffer.clear(); - m_buffer.reserve(triangle_count_hint * SceneTriangleFloatStride); - m_prim_count = 0; - } - - void end_object() override {} - - void rasterize(const Triangle& triangle) override - { - const float temp_store[SceneTriangleFloatStride] = - { - static_cast(triangle.m_v0[0]), static_cast(triangle.m_v0[1]), static_cast(triangle.m_v0[2]), - static_cast(triangle.m_n0[0]), static_cast(triangle.m_n0[1]), static_cast(triangle.m_n0[2]), - - static_cast(triangle.m_v1[0]), static_cast(triangle.m_v1[1]), static_cast(triangle.m_v1[2]), - static_cast(triangle.m_n1[0]), static_cast(triangle.m_n1[1]), static_cast(triangle.m_n1[2]), - - static_cast(triangle.m_v2[0]), static_cast(triangle.m_v2[1]), static_cast(triangle.m_v2[2]), - static_cast(triangle.m_n2[0]), static_cast(triangle.m_n2[1]), static_cast(triangle.m_n2[2]), - }; - m_buffer.reserve(m_buffer.size() + SceneTriangleFloatStride); - m_buffer.insert(m_buffer.end(), temp_store, temp_store + SceneTriangleFloatStride); - m_prim_count++; - } - }; -} - -LightPathsWidget::LightPathsWidget( - const Project& project, - const size_t width, - const size_t height) - : m_project(project) - , m_camera(*m_project.get_uncached_active_camera()) - , m_backface_culling_enabled(false) - , m_selected_light_path_index(-1) - , m_gl_initialized(false) -{ - setFocusPolicy(Qt::StrongFocus); - setFixedWidth(static_cast(width)); - setFixedHeight(static_cast(height)); - - const float time = m_camera.get_shutter_middle_time(); - set_transform(m_camera.transform_sequence().evaluate(time)); -} - -QImage LightPathsWidget::capture() -{ - return grabFramebuffer(); -} - -void LightPathsWidget::set_transform(const Transformd& transform) -{ - m_camera_matrix = transform.get_parent_to_local(); - m_gl_view_matrix = transpose(m_camera_matrix); -} - -void LightPathsWidget::set_light_paths(const LightPathArray& light_paths) -{ - m_light_paths = light_paths; - - if (m_light_paths.size() > 1) - { - // Sort paths by descending radiance at the camera. - const auto& light_path_recorder = m_project.get_light_path_recorder(); - std::sort( - &m_light_paths[0], - &m_light_paths[0] + m_light_paths.size(), - [&light_path_recorder](const LightPath& lhs, const LightPath& rhs) - { - LightPathVertex lhs_v; - light_path_recorder.get_light_path_vertex(lhs.m_vertex_end_index - 1, lhs_v); - - LightPathVertex rhs_v; - light_path_recorder.get_light_path_vertex(rhs.m_vertex_end_index - 1, rhs_v); - - return - sum_value(Color3f::from_array(lhs_v.m_radiance)) > - sum_value(Color3f::from_array(rhs_v.m_radiance)); - }); - } - - // Display all paths by default. - set_selected_light_path_index(-1); - load_light_paths_data(); -} - -void LightPathsWidget::set_selected_light_path_index(const int selected_light_path_index) -{ - m_selected_light_path_index = selected_light_path_index; - - dump_selected_light_path(); - update(); - - emit signal_light_path_selection_changed( - m_selected_light_path_index, - static_cast(m_light_paths.size())); -} - -void LightPathsWidget::slot_display_all_light_paths() -{ - if (m_selected_light_path_index > -1) - set_selected_light_path_index(-1); -} - -void LightPathsWidget::slot_display_previous_light_path() -{ - if (m_selected_light_path_index > -1) - set_selected_light_path_index(m_selected_light_path_index - 1); -} - -void LightPathsWidget::slot_display_next_light_path() -{ - if (m_selected_light_path_index < static_cast(m_light_paths.size()) - 1) - set_selected_light_path_index(m_selected_light_path_index + 1); -} - -void LightPathsWidget::slot_toggle_backface_culling(const bool checked) -{ - m_backface_culling_enabled = checked; - update(); -} - -void LightPathsWidget::slot_synchronize_camera() -{ - m_camera.transform_sequence().clear(); - m_camera.transform_sequence().set_transform(0.0f, - Transformd::from_local_to_parent(inverse(m_camera_matrix))); -} - -void LightPathsWidget::load_object_instance( - const ObjectInstance& object_instance, - const Matrix4f& assembly_transform_matrix) -{ - Object* object = object_instance.find_object(); - - // This would already be logged in LightPathsWidget::load_scene_data - if (object == nullptr) - return; - - const Transformd& transform = object_instance.get_transform(); - const Matrix4f& object_transform_matrix(transform.get_local_to_parent()); - const Matrix4f model_matrix = assembly_transform_matrix * object_transform_matrix; - - // Object vertex buffer data has already been loaded; just add an instance - const std::string obj_name = std::string(object->get_name()); - size_t buf_idx = m_scene_object_index_map.at(obj_name); - - const GLuint object_instances_vbo = m_scene_object_instance_vbos[buf_idx]; - const GLuint object_vao = m_scene_object_vaos[buf_idx]; - const GLsizei current_instance = m_scene_object_current_instances[buf_idx]; - m_scene_object_current_instances[buf_idx] += 1; - - m_gl->glBindVertexArray(object_vao); - m_gl->glBindBuffer(GL_ARRAY_BUFFER, object_instances_vbo); - const Matrix4f gl_matrix = transpose(model_matrix); - m_gl->glBufferSubData( - GL_ARRAY_BUFFER, - current_instance * TransformByteStride, - TransformByteStride, - reinterpret_cast(&gl_matrix[0])); -} - -void LightPathsWidget::load_assembly_instance( - const AssemblyInstance& assembly_instance, - const float time) -{ - const Assembly* assembly = assembly_instance.find_assembly(); - - // This would already be logged in LightPathsWidget::load_scene_data - if (assembly == nullptr) - return; - - const Transformd transform = assembly_instance.transform_sequence().evaluate(time); - - const Matrix4f transform_matrix(transform.get_local_to_parent()); - - for (const auto& object_instance : assembly->object_instances()) - load_object_instance(object_instance, transform_matrix); - - for (const auto& child_assembly_instance : assembly->assembly_instances()) - load_assembly_instance(child_assembly_instance, time); -} - -void LightPathsWidget::load_object_data(const Object& object) -{ - const std::string obj_name = std::string(object.get_name()); - RENDERER_LOG_DEBUG("opengl: uploading mesh data for object \"%s\"...", obj_name.c_str()); - - if (m_scene_object_index_map.count(obj_name) == 0) - { - // Object vertex buffer data has not been loaded; load it - const size_t buf_idx = m_scene_object_data_vbos.size(); - GLuint object_vao; - m_gl->glGenVertexArrays(1, &object_vao); - GLuint object_data_vbo; - m_gl->glGenBuffers(1, &object_data_vbo); - m_gl->glBindVertexArray(object_vao); - m_gl->glBindBuffer(GL_ARRAY_BUFFER, object_data_vbo); - - m_scene_object_vaos.push_back(object_vao); - m_scene_object_data_vbos.push_back(object_data_vbo); - m_scene_object_index_map[obj_name] = buf_idx; - - OpenGLRasterizer rasterizer; - object.rasterize(rasterizer); - m_scene_object_data_index_counts.push_back(static_cast(rasterizer.m_prim_count * 3)); - - m_gl->glBufferData( - GL_ARRAY_BUFFER, - rasterizer.m_buffer.size() * sizeof(float), - reinterpret_cast(&rasterizer.m_buffer[0]), - GL_STATIC_DRAW); - - m_gl->glVertexAttribPointer( - 0, - 3, - GL_FLOAT, - GL_FALSE, - SceneVertexByteStride, - reinterpret_cast(0)); - m_gl->glVertexAttribPointer( - 1, - 3, - GL_FLOAT, - GL_FALSE, - SceneVertexByteStride, - reinterpret_cast(SceneVertexByteStride / 2)); - m_gl->glEnableVertexAttribArray(0); - m_gl->glEnableVertexAttribArray(1); - } -} - -void LightPathsWidget::load_assembly_data(const Assembly& assembly) -{ - for (const auto& object : assembly.objects()) - load_object_data(object); - - for (const auto& child_assembly : assembly.assemblies()) - load_assembly_data(child_assembly); -} - -void LightPathsWidget::load_scene_data() -{ - const float time = m_camera.get_shutter_middle_time(); - - RENDERER_LOG_DEBUG("opengl: uploading scene data..."); - - // First, load all the unique object vertex buffer data into static VBOs - for (const auto& assembly : m_project.get_scene()->assemblies()) - load_assembly_data(assembly); - - // Create space for per-instance data - for (size_t i = 0; i < m_scene_object_index_map.size(); i++) - { - m_scene_object_instance_vbos.push_back(0); - m_scene_object_instance_counts.push_back(0); - m_scene_object_current_instances.push_back(0); - } - - // Generate instance buffers for each object - m_gl->glGenBuffers( - static_cast(m_scene_object_instance_vbos.size()), - &m_scene_object_instance_vbos[0]); - - // Figure out how many instances of each mesh are required for all assembly instances - for (const auto& assembly_instance : m_project.get_scene()->assembly_instances()) - { - const Assembly* assembly = assembly_instance.find_assembly(); - - if (assembly == nullptr) - { - RENDERER_LOG_ERROR( - "assembly instance \"%s\" has null base assembly reference.", - assembly_instance.get_name()); - continue; - } - - for (const auto& object_instance : assembly->object_instances()) - { - Object* object = object_instance.find_object(); - - if (object == nullptr) - { - RENDERER_LOG_ERROR( - "object instance \"%s\" has null base object reference.", - object_instance.get_name()); - continue; - } - - const std::string obj_name = std::string(object->get_name()); - const size_t buf_idx = m_scene_object_index_map[obj_name]; - m_scene_object_instance_counts[buf_idx] += 1; - } - } - - // Setup instance buffers by allocating a buffer big enough for the number - // of required instances and setting up vertex attributes - for (size_t i = 0; i < m_scene_object_instance_vbos.size(); i++) - { - const GLuint object_vao = m_scene_object_vaos[i]; - const GLuint object_instance_vbo = m_scene_object_instance_vbos[i]; - const GLsizei object_instance_count = m_scene_object_instance_counts[i]; - - m_gl->glBindVertexArray(object_vao); - m_gl->glBindBuffer(GL_ARRAY_BUFFER, object_instance_vbo); - m_gl->glBufferData( - GL_ARRAY_BUFFER, - object_instance_count * TransformByteStride, - NULL, - GL_DYNAMIC_DRAW); - - // Attributes for a 4x4 model matrix; requires four separate attributes - // to be setup, one for each column of the matrix. - for (int i = 0; i < 4; i++) - { - m_gl->glVertexAttribPointer( - 2 + i, - 4, - GL_FLOAT, - GL_FALSE, - TransformByteStride, - reinterpret_cast(sizeof(float) * 4 * i)); - m_gl->glEnableVertexAttribArray(2 + i); - m_gl->glVertexAttribDivisor(2 + i, 1); - } - } - - // Actually load the transform data for each instance into the allocated instance buffers - for (const auto& assembly_instance : m_project.get_scene()->assembly_instances()) - load_assembly_instance(assembly_instance, time); -} - -void LightPathsWidget::load_light_paths_data() -{ - m_light_paths_index_offsets.clear(); - if (!m_light_paths.empty()) - { - m_light_paths_index_offsets.push_back(0); - - const auto& light_path_recorder = m_project.get_light_path_recorder(); - - const size_t total_gl_vertex_count = 2 * (light_path_recorder.get_vertex_count() - 2) + 2; - - GLsizei total_index_count = 0; - std::vector buffer; - buffer.reserve(total_gl_vertex_count * LightPathVertexFloatStride); - for (size_t light_path_idx = 0; light_path_idx < m_light_paths.size(); light_path_idx++) - { - const auto& path = m_light_paths[light_path_idx]; - assert(path.m_vertex_end_index - path.m_vertex_begin_index >= 2); - - LightPathVertex prev; - light_path_recorder.get_light_path_vertex(path.m_vertex_begin_index, prev); - for (size_t vertex_idx = path.m_vertex_begin_index + 1; vertex_idx < path.m_vertex_end_index; vertex_idx++) - { - LightPathVertex curr; - light_path_recorder.get_light_path_vertex(vertex_idx, curr); - - auto piece_radiance = Color3f::from_array(curr.m_radiance); - piece_radiance /= piece_radiance + Color3f(1.0f); // Reinhard tone mapping - piece_radiance = linear_rgb_to_srgb(piece_radiance); - - const float temp_store[LightPathVertexLineFloatStride] = - { - prev.m_position[0], prev.m_position[1], prev.m_position[2], - piece_radiance[0], piece_radiance[1], piece_radiance[2], - - curr.m_position[0], curr.m_position[1], curr.m_position[2], - piece_radiance[0], piece_radiance[1], piece_radiance[2], - }; - buffer.insert(buffer.end(), temp_store, temp_store + 12); - - total_index_count += 2; - prev = curr; - } - m_light_paths_index_offsets.push_back(total_index_count); - } - - m_gl->glBindBuffer(GL_ARRAY_BUFFER, m_light_paths_vbo); - m_gl->glBufferData( - GL_ARRAY_BUFFER, - buffer.size() * sizeof(float), - reinterpret_cast(&buffer[0]), - GL_STATIC_DRAW); - } -} - -namespace -{ - std::string shader_kind_to_string(const GLint shader_kind) - { - switch (shader_kind) - { - case GL_VERTEX_SHADER: - return "Vertex"; - case GL_FRAGMENT_SHADER: - return "Fragment"; - default: - return "Unknown Kind"; - } - } - - void compile_shader( - QOpenGLFunctions_3_3_Core* f, - const GLuint shader, - const GLsizei count, - const GLchar** src_string, - const GLint* length) - { - f->glShaderSource(shader, count, src_string, length); - f->glCompileShader(shader); - GLint success; - f->glGetShaderiv(shader, GL_COMPILE_STATUS, &success); - - if (!success) - { - char info_log[1024]; - f->glGetShaderInfoLog(shader, 1024, NULL, info_log); - - GLint shader_kind; - f->glGetShaderiv(shader, GL_SHADER_TYPE, &shader_kind); - const std::string shader_kind_string = shader_kind_to_string(shader_kind); - - RENDERER_LOG_ERROR("opengl: %s shader compilation failed:\n%s", shader_kind_string.c_str(), info_log); - } - } - - void link_shader_program( - QOpenGLFunctions_3_3_Core* f, - const GLuint program, - const GLuint vert, - const GLuint frag) - { - f->glAttachShader(program, vert); - f->glAttachShader(program, frag); - f->glLinkProgram(program); - - GLint success; - f->glGetProgramiv(program, GL_LINK_STATUS, &success); - - if (!success) - { - char info_log[1024]; - f->glGetProgramInfoLog(program, 1024, NULL, info_log); - RENDERER_LOG_ERROR("opengl: shader program linking failed:\n%s", info_log); - } - } - - void create_shader_program( - QOpenGLFunctions_3_3_Core* f, - GLuint& program, - const QByteArray& vert_source, - const QByteArray& frag_source) - { - GLuint vert = f->glCreateShader(GL_VERTEX_SHADER); - GLuint frag = f->glCreateShader(GL_FRAGMENT_SHADER); - - auto gl_vert_source = static_cast(vert_source.constData()); - auto gl_vert_source_length = static_cast(vert_source.size()); - - auto gl_frag_source = static_cast(frag_source.constData()); - auto gl_frag_source_length = static_cast(frag_source.size()); - - compile_shader(f, vert, 1, &gl_vert_source, &gl_vert_source_length); - compile_shader(f, frag, 1, &gl_frag_source, &gl_frag_source_length); - - program = f->glCreateProgram(); - link_shader_program(f, program, vert, frag); - - f->glDeleteShader(vert); - f->glDeleteShader(frag); - } -} - -void LightPathsWidget::initializeGL() -{ - m_gl = QOpenGLContext::currentContext()->versionFunctions(); - // If there was already previous data, clean up - LightPathsWidget::cleanup_gl_data(); - - const auto qgl_format = format(); - - if (!m_gl->initializeOpenGLFunctions()) - { - const int major_version = qgl_format.majorVersion(); - const int minor_version = qgl_format.minorVersion(); - RENDERER_LOG_ERROR( - "opengl: could not load required gl functions: loaded version %d.%d, required version 3.3.", - major_version, - minor_version); - m_gl_initialized = false; - return; - } - - glEnable(GL_DEPTH_TEST); - - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - - create_shader_program( - m_gl, - m_scene_shader_program, - load_gl_shader("scene.vert"), - load_gl_shader("scene.frag")); - - create_shader_program( - m_gl, - m_light_paths_shader_program, - load_gl_shader("lightpaths.vert"), - load_gl_shader("lightpaths.frag")); - - m_scene_view_mat_location = m_gl->glGetUniformLocation(m_scene_shader_program, "u_view"); - m_scene_proj_mat_location = m_gl->glGetUniformLocation(m_scene_shader_program, "u_proj"); - - m_light_paths_view_mat_location = m_gl->glGetUniformLocation(m_light_paths_shader_program, "u_view"); - m_light_paths_proj_mat_location = m_gl->glGetUniformLocation(m_light_paths_shader_program, "u_proj"); - - const float z_near = 0.01f; - const float z_far = 1000.0f; - - const auto& rc = m_camera.get_rasterization_camera(); - - const float fy = std::tan(rc.m_hfov / rc.m_aspect_ratio * 0.5) * z_near; - const float fx = fy * rc.m_aspect_ratio; - - const float shift_x = rc.m_shift_x * 2.0 * fx; - const float shift_y = rc.m_shift_y * 2.0 * fy; - - const float left = -fx + shift_x; - const float right = fx + shift_x; - const float top = -fy + shift_y; - const float bottom = fy + shift_y; - - // Top and bottom are flipped because QOpenGLWidget draws to a framebuffer object and then blits - // from the FBO to the default framebuffer, which flips the image. - m_gl_proj_matrix = transpose(Matrix4f::make_frustum(top, bottom, left, right, z_near, z_far)); - - GLuint temp_light_paths_vao = 0; - GLuint temp_light_paths_vbo = 0; - m_gl->glGenVertexArrays(1, &temp_light_paths_vao); - m_gl->glGenBuffers(1, &temp_light_paths_vbo); - m_light_paths_vao = temp_light_paths_vao; - m_light_paths_vbo = temp_light_paths_vbo; - - m_gl->glBindVertexArray(m_light_paths_vao); - m_gl->glBindBuffer(GL_ARRAY_BUFFER, m_light_paths_vbo); - m_gl->glVertexAttribPointer( - 0, - 3, - GL_FLOAT, - GL_FALSE, - LightPathVertexByteStride, - reinterpret_cast(0)); - m_gl->glVertexAttribPointer( - 1, - 3, - GL_FLOAT, - GL_FALSE, - LightPathVertexByteStride, - reinterpret_cast(LightPathVertexByteStride / 2)); - m_gl->glEnableVertexAttribArray(0); - m_gl->glEnableVertexAttribArray(1); - - m_gl->glBindVertexArray(0); - - load_scene_data(); - load_light_paths_data(); - - m_gl_initialized = true; -} - -void LightPathsWidget::cleanup_gl_data() -{ - if (!m_scene_object_vaos.empty()) - { - m_gl->glDeleteVertexArrays( - static_cast(m_scene_object_vaos.size()), - &m_scene_object_vaos[0]); - m_scene_object_vaos.clear(); - } - if (!m_scene_object_data_vbos.empty()) - { - m_gl->glDeleteBuffers( - static_cast(m_scene_object_data_vbos.size()), - &m_scene_object_data_vbos[0]); - m_scene_object_data_vbos.clear(); - } - if (!m_scene_object_instance_vbos.empty()) - { - m_gl->glDeleteBuffers( - static_cast(m_scene_object_instance_vbos.size()), - &m_scene_object_instance_vbos[0]); - m_scene_object_instance_vbos.clear(); - } - if (m_scene_shader_program != 0) - { - m_gl->glDeleteProgram(m_scene_shader_program); - } - if (m_light_paths_shader_program != 0) - { - m_gl->glDeleteProgram(m_light_paths_shader_program); - } - m_scene_object_index_map.clear(); - m_scene_object_data_index_counts.clear(); - m_scene_object_instance_counts.clear(); - m_scene_object_current_instances.clear(); -} - -void LightPathsWidget::resizeGL(int w, int h) -{ - glViewport(0, 0, static_cast(w), static_cast(h)); -} - -void LightPathsWidget::paintGL() -{ - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - if (!m_gl_initialized) - return; - - if (m_backface_culling_enabled) - glEnable(GL_CULL_FACE); - else glDisable(GL_CULL_FACE); - - render_geometry(); - render_light_paths(); -} - -void LightPathsWidget::keyPressEvent(QKeyEvent* event) -{ - switch (event->key()) - { - // Home key: display all paths. - case Qt::Key_Home: - slot_display_all_light_paths(); - break; - - // Left key: display previous path. - case Qt::Key_Left: - slot_display_previous_light_path(); - break; - - // Right key: display next path. - case Qt::Key_Right: - slot_display_next_light_path(); - break; - - default: - QOpenGLWidget::keyPressEvent(event); - break; - } -} - -void LightPathsWidget::render_geometry() -{ - m_gl->glUseProgram(m_scene_shader_program); - m_gl->glUniformMatrix4fv( - m_scene_view_mat_location, - 1, - false, - const_cast(&m_gl_view_matrix[0])); - m_gl->glUniformMatrix4fv( - m_scene_proj_mat_location, - 1, - false, - const_cast(&m_gl_proj_matrix[0])); - - for (size_t i = 0; i < m_scene_object_data_vbos.size(); i++) - { - GLuint vao = m_scene_object_vaos[i]; - int index_count = m_scene_object_data_index_counts[i]; - int instance_count = m_scene_object_instance_counts[i]; - - m_gl->glBindVertexArray(vao); - - m_gl->glDrawArraysInstanced( - GL_TRIANGLES, - 0, - index_count, - instance_count); - } -} - -void LightPathsWidget::render_light_paths() -{ - if (m_light_paths_index_offsets.size() > 1) - { - m_gl->glUseProgram(m_light_paths_shader_program); - m_gl->glUniformMatrix4fv( - m_light_paths_view_mat_location, - 1, - false, - const_cast(&m_gl_view_matrix[0])); - m_gl->glUniformMatrix4fv( - m_light_paths_proj_mat_location, - 1, - false, - const_cast(&m_gl_proj_matrix[0])); - - m_gl->glBindVertexArray(m_light_paths_vao); - - GLint first; - GLsizei count; - assert(m_selected_light_path_index >= -1); - if (m_selected_light_path_index == -1) - { - first = 0; - count = m_light_paths_index_offsets[m_light_paths_index_offsets.size() - 1]; - } - else - { - first = static_cast(m_light_paths_index_offsets[m_selected_light_path_index]); - count = m_light_paths_index_offsets[m_selected_light_path_index + 1] - first; - } - glDrawArrays(GL_LINES, first, count); - } -} - -void LightPathsWidget::dump_selected_light_path() const -{ - if (m_selected_light_path_index == -1) - { - if (m_light_paths.empty()) - RENDERER_LOG_INFO("no light path to display."); - else - { - RENDERER_LOG_INFO("displaying all %s light path%s.", - pretty_uint(m_light_paths.size()).c_str(), - m_light_paths.size() > 1 ? "s" : ""); - } - } - else - { - RENDERER_LOG_INFO("displaying light path %s:", - pretty_int(m_selected_light_path_index + 1).c_str()); - - const auto& light_path_recorder = m_project.get_light_path_recorder(); - const auto& path = m_light_paths[m_selected_light_path_index]; - - for (size_t i = path.m_vertex_begin_index; i < path.m_vertex_end_index; ++i) - { - LightPathVertex v; - light_path_recorder.get_light_path_vertex(i, v); - - const std::string entity_name = - v.m_entity != nullptr - ? foundation::format("\"{0}\"", v.m_entity->get_path().c_str()) - : "n/a"; - - RENDERER_LOG_INFO(" vertex " FMT_SIZE_T ": entity: %s - position: (%f, %f, %f) - radiance: (%f, %f, %f) - total radiance: %f", - i - path.m_vertex_begin_index + 1, - entity_name.c_str(), - v.m_position[0], v.m_position[1], v.m_position[2], - v.m_radiance[0], v.m_radiance[1], v.m_radiance[2], - v.m_radiance[0] + v.m_radiance[1] + v.m_radiance[2]); - } - } -} - -} // namespace studio -} // namespace appleseed diff --git a/src/appleseed.studio/mainwindow/rendering/openglviewporttab.cpp b/src/appleseed.studio/mainwindow/rendering/openglviewporttab.cpp new file mode 100644 index 0000000000..17ea2bdf4b --- /dev/null +++ b/src/appleseed.studio/mainwindow/rendering/openglviewporttab.cpp @@ -0,0 +1,255 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2020 Kevin Masson, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// Interface header. +#include "openglviewporttab.h" + +// appleseed.renderer headers. +#include "renderer/api/frame.h" +#include "renderer/api/project.h" + +// appleseed.qtcommon headers. +#include "utility/miscellaneous.h" + +// Qt headers. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace appleseed::qtcommon; +using namespace foundation; +using namespace renderer; +namespace OCIO = OCIO_NAMESPACE; + +namespace appleseed { +namespace studio { + +OpenGLViewportTab::OpenGLViewportTab( + Project& project, + LightPathsManager& light_paths_manager, + OCIO::ConstConfigRcPtr ocio_config) + : ViewportTab(project) + , m_light_paths_manager(light_paths_manager) + , m_ocio_config(ocio_config) +{ + setObjectName("opengl_viewport_tab"); + setLayout(new QGridLayout()); + layout()->setSpacing(0); + layout()->setMargin(0); + + create_viewport_canvas(); + create_toolbar(); + create_scrollarea(); + + recreate_handlers(); + + layout()->addWidget(m_toolbar); + layout()->addWidget(m_light_paths_viewport_toolbar->toolbar()); + layout()->addWidget(m_scroll_area); + + m_camera_controller->set_enabled(true); +} + +ViewportCanvas* OpenGLViewportTab::get_viewport_canvas() const +{ + return m_viewport_canvas; +} + +void OpenGLViewportTab::render_began() +{ + ViewportTab::render_began(); + + m_light_paths_viewport_toolbar.get()->reset(&m_project); +} + +void OpenGLViewportTab::on_tab_selected() +{ + const bool display_light_paths = m_light_paths_manager.should_display_light_paths(); + m_light_paths_viewport_toolbar->set_enabled(display_light_paths); + m_light_paths_toggle_button->setChecked(display_light_paths); +} + +void OpenGLViewportTab::slot_camera_changed() +{ + m_viewport_canvas->get_light_paths_layer()->set_transform(m_camera_controller->get_transform()); + m_viewport_canvas->get_gl_scene_layer()->set_transform(m_camera_controller->get_transform()); + m_viewport_canvas->update(); +} + +void OpenGLViewportTab::slot_viewport_canvas_context_menu(const QPoint& point) +{ + emit signal_viewport_canvas_context_menu(m_viewport_canvas->mapToGlobal(point)); +} + +void OpenGLViewportTab::create_viewport_canvas() +{ + const CanvasProperties& props = m_project.get_frame()->image().properties(); + + m_viewport_canvas = + new ViewportCanvas( + m_project, + props.m_canvas_width, + props.m_canvas_height, + m_ocio_config, + m_light_paths_manager, + ViewportCanvas::BaseLayer::OpenGL, + this); + + m_viewport_canvas->setContextMenuPolicy(Qt::CustomContextMenu); + + connect( + m_viewport_canvas, &ViewportCanvas::customContextMenuRequested, + this, &OpenGLViewportTab::slot_viewport_canvas_context_menu); + + m_viewport_canvas->setMouseTracking(true); +} + +void OpenGLViewportTab::create_toolbar() +{ + // Create the light path toolbar. + m_light_paths_viewport_toolbar.reset( + new LightPathsViewportToolbar( + this, + &m_project, + m_light_paths_manager)); + + // Create the render toolbar. + m_toolbar = new QToolBar(); + m_toolbar->setObjectName("render_toolbar"); + m_toolbar->setIconSize(QSize(18, 18)); + + // Display Light Paths button. + m_light_paths_toggle_button = new QToolButton(); + m_light_paths_toggle_button->setText("Display Light Paths Overlay"); + m_light_paths_toggle_button->setCheckable(true); + connect( + m_light_paths_toggle_button, &QToolButton::toggled, + this, &OpenGLViewportTab::slot_toggle_light_paths); + m_toolbar->addWidget(m_light_paths_toggle_button); + + m_toolbar->addSeparator(); + + // Reset Zoom button. + QToolButton* reset_zoom_button = new QToolButton(); + reset_zoom_button->setIcon(load_icons("rendertab_reset_zoom")); + reset_zoom_button->setShortcut(Qt::Key_Asterisk); + reset_zoom_button->setToolTip(combine_name_and_shortcut("Reset Zoom", reset_zoom_button->shortcut())); + connect( + reset_zoom_button, &QToolButton::clicked, + this, &OpenGLViewportTab::slot_reset_zoom); + m_toolbar->addWidget(reset_zoom_button); + + m_toolbar->addSeparator(); + + // Toggle Backface Culling button. + QToolButton* backface_culling_button = new QToolButton(); + backface_culling_button->setIcon(load_icons("opengl_viewport_tab_toggle_backface_culling")); + backface_culling_button->setToolTip("Show/hide backfacing surfaces"); + backface_culling_button->setCheckable(true); + backface_culling_button->setChecked(false); + connect( + backface_culling_button, &QToolButton::toggled , + this, &OpenGLViewportTab::slot_toggle_backface_culling); + m_toolbar->addWidget(backface_culling_button); + + // Synchronize Camera button. + QToolButton* sync_camera_button = new QToolButton(); + sync_camera_button->setIcon(load_icons("opengl_viewport_tab_synchronize_camera")); + sync_camera_button->setToolTip("Synchronize the rendering camera with this camera"); + connect( + sync_camera_button, &QToolButton::clicked, + m_viewport_canvas->get_light_paths_layer(), &LightPathsLayer::slot_synchronize_camera); + m_toolbar->addWidget(sync_camera_button); +} + +void OpenGLViewportTab::create_scrollarea() +{ + // Encapsulate the render widget into another widget that adds a margin around it. + QWidget* render_widget_wrapper = new QWidget(); + render_widget_wrapper->setObjectName("render_widget_wrapper"); + render_widget_wrapper->setLayout(new QGridLayout()); + render_widget_wrapper->layout()->setSizeConstraint(QLayout::SetFixedSize); + render_widget_wrapper->layout()->setContentsMargins(20, 20, 20, 20); + render_widget_wrapper->layout()->addWidget(m_viewport_canvas); + + // Wrap the render widget in a scroll area. + m_scroll_area = new QScrollArea(); + m_scroll_area->setObjectName("render_widget_scrollarea"); + m_scroll_area->setAlignment(Qt::AlignCenter); + m_scroll_area->setWidget(render_widget_wrapper); +} + +void OpenGLViewportTab::recreate_handlers() +{ + // Handler for zooming the render widget in and out with the keyboard or the mouse wheel. + m_zoom_handler.reset( + new WidgetZoomHandler( + m_scroll_area, + m_viewport_canvas)); + + // Handler for panning the render widget with the mouse. + m_pan_handler.reset( + new ScrollAreaPanHandler( + m_scroll_area)); + + // Camera handler. + m_camera_controller.reset( + new CameraController( + m_viewport_canvas, + m_project)); + + connect( + m_camera_controller.get(), &CameraController::signal_camera_changed, + this, &OpenGLViewportTab::slot_camera_changed); + + // Clipboard handler. + m_clipboard_handler.reset(new RenderClipboardHandler(m_viewport_canvas, m_viewport_canvas)); +} + +void OpenGLViewportTab::slot_toggle_light_paths(const bool checked) +{ + m_light_paths_viewport_toolbar->set_enabled(checked); + m_light_paths_manager.display_light_paths(checked); +} + +void OpenGLViewportTab::slot_toggle_backface_culling(const bool checked) +{ + m_viewport_canvas->get_gl_scene_layer()->toggle_backface_culling(checked); + m_viewport_canvas->update(); +} + +} // namespace studio +} // namespace appleseed + diff --git a/src/appleseed.studio/mainwindow/rendering/lightpathstab.h b/src/appleseed.studio/mainwindow/rendering/openglviewporttab.h similarity index 53% rename from src/appleseed.studio/mainwindow/rendering/lightpathstab.h rename to src/appleseed.studio/mainwindow/rendering/openglviewporttab.h index f3a961e7d8..0faa4c8684 100644 --- a/src/appleseed.studio/mainwindow/rendering/lightpathstab.h +++ b/src/appleseed.studio/mainwindow/rendering/openglviewporttab.h @@ -5,7 +5,7 @@ // // This software is released under the MIT license. // -// Copyright (c) 2018 Francois Beaune, The appleseedhq Organization +// Copyright (c) 2020 Kevin Masson, The appleseedhq Organization // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -30,16 +30,16 @@ // appleseed.studio headers. #include "mainwindow/rendering/cameracontroller.h" -#include "mainwindow/rendering/lightpathspickinghandler.h" +#include "mainwindow/rendering/lightpathsmanager.h" +#include "mainwindow/rendering/lightpathsviewporttoolbar.h" #include "mainwindow/rendering/renderclipboardhandler.h" +#include "mainwindow/rendering/viewportcanvas.h" +#include "mainwindow/rendering/viewportregionselectionhandler.h" +#include "mainwindow/rendering/viewporttab.h" -// appleseed.qtcommon headers. -#include "widgets/mousecoordinatestracker.h" -#include "widgets/scrollareapanhandler.h" -#include "widgets/widgetzoomhandler.h" - -// appleseed.renderer headers. -#include "renderer/api/rendering.h" +// OpenColorIO headers. +#include +namespace OCIO = OCIO_NAMESPACE; // Qt headers. #include @@ -49,9 +49,9 @@ #include // Forward declarations. -namespace appleseed { namespace studio { class LightPathsWidget; } } -namespace renderer { class ParamArray; } +namespace renderer { class Entity; } namespace renderer { class Project; } +class QComboBox; class QLabel; class QPoint; class QRect; @@ -62,56 +62,52 @@ class QToolButton; namespace appleseed { namespace studio { -// -// A tab providing an hardware-accelerated visualization of recorded light paths. -// - -class LightPathsTab - : public QWidget +class OpenGLViewportTab + : public ViewportTab { Q_OBJECT public: - LightPathsTab( - renderer::Project& project, - renderer::ParamArray& settings); + OpenGLViewportTab( + renderer::Project& project, + LightPathsManager& light_paths_manager, + OCIO::ConstConfigRcPtr ocio_config); + + ViewportCanvas* get_viewport_canvas() const override; - public slots: - void slot_entity_picked(const renderer::ScenePicker::PickingResult& result); - void slot_rectangle_selection(const QRect& rect); + void render_began() override; + void on_tab_selected() override; + + signals: + void signal_reset_zoom(); + void signal_viewport_canvas_context_menu(const QPoint& point); private slots: - void slot_light_path_selection_changed( - const int selected_light_path_index, - const int total_light_paths) const; - void slot_context_menu(const QPoint& point); - void slot_save_light_paths(); void slot_camera_changed(); + void slot_viewport_canvas_context_menu(const QPoint& point); + void slot_toggle_light_paths(const bool checked); + void slot_toggle_backface_culling(const bool checked); private: - renderer::Project& m_project; - renderer::ParamArray& m_settings; - LightPathsWidget* m_light_paths_widget; + LightPathsManager& m_light_paths_manager; + OCIO::ConstConfigRcPtr m_ocio_config; + + ViewportCanvas* m_viewport_canvas; QScrollArea* m_scroll_area; QToolBar* m_toolbar; - QToolButton* m_prev_path_button; - QToolButton* m_next_path_button; - QLabel* m_info_label; + QToolButton* m_light_paths_toggle_button; - std::unique_ptr m_zoom_handler; - std::unique_ptr m_pan_handler; - std::unique_ptr m_mouse_tracker; std::unique_ptr m_camera_controller; - std::unique_ptr m_screen_space_paths_picking_handler; - std::unique_ptr m_world_space_paths_picking_handler; std::unique_ptr m_clipboard_handler; - void create_light_paths_widget(); + std::unique_ptr m_light_paths_viewport_toolbar; + + void create_viewport_canvas(); void create_toolbar(); void create_scrollarea(); void recreate_handlers(); - }; } // namespace studio } // namespace appleseed + diff --git a/src/appleseed.studio/mainwindow/rendering/qttilecallback.cpp b/src/appleseed.studio/mainwindow/rendering/qttilecallback.cpp index 4a3d5f15f8..ac87f498b1 100644 --- a/src/appleseed.studio/mainwindow/rendering/qttilecallback.cpp +++ b/src/appleseed.studio/mainwindow/rendering/qttilecallback.cpp @@ -31,7 +31,8 @@ #include "qttilecallback.h" // appleseed.studio headers. -#include "mainwindow/rendering/renderwidget.h" +#include "mainwindow/rendering/renderlayer.h" +#include "mainwindow/rendering/viewportcanvas.h" // Qt headers. #include @@ -60,12 +61,12 @@ namespace Q_OBJECT public: - explicit QtTileCallback(RenderWidget* render_widget) - : m_render_widget(render_widget) + explicit QtTileCallback(ViewportCanvas* viewport_canvas) + : m_render_layer(viewport_canvas->get_render_layer()) { connect( this, SIGNAL(signal_update()), - m_render_widget, SLOT(update()), + viewport_canvas, SLOT(repaint()), Qt::QueuedConnection); } @@ -81,9 +82,8 @@ namespace const size_t thread_index, const size_t thread_count) override { - assert(m_render_widget); - m_render_widget->highlight_tile(*frame, tile_x, tile_y, thread_index, thread_count); - + assert(m_render_layer); + m_render_layer->highlight_tile(*frame, tile_x, tile_y, thread_index, thread_count); emit signal_update(); } @@ -92,9 +92,8 @@ namespace const size_t tile_x, const size_t tile_y) override { - assert(m_render_widget); - m_render_widget->blit_tile(*frame, tile_x, tile_y); - + assert(m_render_layer); + m_render_layer->blit_tile(*frame, tile_x, tile_y); emit signal_update(); } @@ -105,9 +104,8 @@ namespace const double /*samples_per_pixel*/, const std::uint64_t /*samples_per_second*/) override { - assert(m_render_widget); - m_render_widget->blit_frame(frame); - + assert(m_render_layer); + m_render_layer->blit_frame(frame); emit signal_update(); } @@ -115,7 +113,7 @@ namespace void signal_update(); private: - RenderWidget* m_render_widget; + RenderLayer* m_render_layer; }; } @@ -124,8 +122,8 @@ namespace // QtTileCallbackFactory class implementation. // -QtTileCallbackFactory::QtTileCallbackFactory(RenderWidget* render_widget) - : m_render_widget(render_widget) +QtTileCallbackFactory::QtTileCallbackFactory(ViewportCanvas* viewport_canvas) + : m_viewport_canvas(viewport_canvas) { } @@ -136,7 +134,7 @@ void QtTileCallbackFactory::release() ITileCallback* QtTileCallbackFactory::create() { - return new QtTileCallback(m_render_widget); + return new QtTileCallback(m_viewport_canvas); } } // namespace studio diff --git a/src/appleseed.studio/mainwindow/rendering/qttilecallback.h b/src/appleseed.studio/mainwindow/rendering/qttilecallback.h index 1366a341f5..587ee7090b 100644 --- a/src/appleseed.studio/mainwindow/rendering/qttilecallback.h +++ b/src/appleseed.studio/mainwindow/rendering/qttilecallback.h @@ -36,7 +36,7 @@ #include "foundation/platform/compiler.h" // Forward declarations. -namespace appleseed { namespace studio { class RenderWidget; } } +namespace appleseed { namespace studio { class ViewportCanvas; } } namespace appleseed { namespace studio { @@ -46,7 +46,7 @@ class QtTileCallbackFactory { public: // Constructor. - explicit QtTileCallbackFactory(RenderWidget* render_widget); + explicit QtTileCallbackFactory(ViewportCanvas* viewport_canvas); // Delete this instance. void release() override; @@ -55,7 +55,7 @@ class QtTileCallbackFactory renderer::ITileCallback* create() override; private: - RenderWidget* m_render_widget; + ViewportCanvas* m_viewport_canvas; }; } // namespace studio diff --git a/src/appleseed.studio/mainwindow/rendering/renderingmanager.cpp b/src/appleseed.studio/mainwindow/rendering/renderingmanager.cpp index 2c34708b21..542cdf1651 100644 --- a/src/appleseed.studio/mainwindow/rendering/renderingmanager.cpp +++ b/src/appleseed.studio/mainwindow/rendering/renderingmanager.cpp @@ -32,9 +32,9 @@ // appleseed.studio headers. #include "mainwindow/rendering/cameracontroller.h" +#include "mainwindow/rendering/finalrenderviewporttab.h" #include "mainwindow/rendering/qttilecallback.h" -#include "mainwindow/rendering/rendertab.h" -#include "mainwindow/rendering/renderwidget.h" +#include "mainwindow/rendering/viewportcanvas.h" #include "mainwindow/statusbar.h" // appleseed.common headers. @@ -122,7 +122,7 @@ namespace RenderingManager::RenderingManager(StatusBar& status_bar) : m_status_bar(status_bar) , m_project(nullptr) - , m_render_tab(nullptr) + , m_final_render_viewport_tab(nullptr) { Application::initialize_resource_search_paths(m_resource_search_paths); @@ -189,25 +189,30 @@ RenderingManager::~RenderingManager() clear_sticky_actions(); } +RenderingManager::RenderingMode RenderingManager::get_rendering_mode() const +{ + return m_rendering_mode; +} + void RenderingManager::start_rendering( Project* project, const ParamArray& params, const RenderingMode rendering_mode, - RenderTab* render_tab) + FinalRenderViewportTab* final_render_viewport_tab) { m_project = project; m_params = params; m_rendering_mode = rendering_mode; - m_render_tab = render_tab; + m_final_render_viewport_tab = final_render_viewport_tab; - m_render_tab->get_render_widget()->start_render(); + m_final_render_viewport_tab->get_viewport_canvas()->get_render_layer()->start_render(); TileCallbackCollectionFactory* tile_callback_collection_factory = new TileCallbackCollectionFactory(); tile_callback_collection_factory->insert( new QtTileCallbackFactory( - m_render_tab->get_render_widget())); + m_final_render_viewport_tab->get_viewport_canvas())); tile_callback_collection_factory->insert( new ProgressTileCallbackFactory( @@ -434,7 +439,7 @@ void RenderingManager::slot_rendering_begin() run_scheduled_actions(); if (m_rendering_mode == RenderingMode::InteractiveRendering) - m_render_tab->get_camera_controller()->set_enabled(true); + m_final_render_viewport_tab->get_camera_controller()->set_enabled(true); m_has_camera_changed = false; @@ -461,10 +466,10 @@ void RenderingManager::slot_rendering_end() { // Disable camera interaction. if (m_rendering_mode == RenderingMode::InteractiveRendering) - m_render_tab->get_camera_controller()->set_enabled(false); + m_final_render_viewport_tab->get_camera_controller()->set_enabled(false); - // Save the controller target point into the camera. - m_render_tab->get_camera_controller()->save_camera_target(); + // Save the controller target point into the camera when rendering ends. + m_final_render_viewport_tab->get_camera_controller()->save_camera_target(); // Stop printing rendering time in the status bar. m_status_bar.stop_rendering_time_display(); @@ -484,10 +489,10 @@ void RenderingManager::slot_rendering_failed() { // Disable camera interaction. if (m_rendering_mode == RenderingMode::InteractiveRendering) - m_render_tab->get_camera_controller()->set_enabled(false); + m_final_render_viewport_tab->get_camera_controller()->set_enabled(false); - // Save the controller target point into the camera. - m_render_tab->get_camera_controller()->save_camera_target(); + // Save the controller target point into the camera when rendering ends. + m_final_render_viewport_tab->get_camera_controller()->save_camera_target(); // Stop printing rendering time in the status bar. m_status_bar.stop_rendering_time_display(); @@ -498,7 +503,7 @@ void RenderingManager::slot_frame_begin() // Update the scene's camera before rendering the frame. if (m_has_camera_changed) { - m_render_tab->get_camera_controller()->update_camera_transform(); + m_final_render_viewport_tab->get_camera_controller()->update_camera_transform(); m_has_camera_changed = false; } } @@ -506,7 +511,7 @@ void RenderingManager::slot_frame_begin() void RenderingManager::slot_frame_end() { // Ensure that the render widget is up-to-date. - m_render_tab->get_render_widget()->update(); + m_final_render_viewport_tab->get_viewport_canvas()->update(); } void RenderingManager::slot_camera_change_begin() diff --git a/src/appleseed.studio/mainwindow/rendering/renderingmanager.h b/src/appleseed.studio/mainwindow/rendering/renderingmanager.h index 8fa8995dce..c1d34bae9f 100644 --- a/src/appleseed.studio/mainwindow/rendering/renderingmanager.h +++ b/src/appleseed.studio/mainwindow/rendering/renderingmanager.h @@ -55,7 +55,7 @@ #include // Forward declarations. -namespace appleseed { namespace studio { class RenderTab; } } +namespace appleseed { namespace studio { class FinalRenderViewportTab; } } namespace appleseed { namespace studio { class StatusBar; } } namespace foundation { class IAbortSwitch; } namespace renderer { class Frame; } @@ -87,7 +87,10 @@ class RenderingManager renderer::Project* project, const renderer::ParamArray& params, const RenderingMode rendering_mode, - RenderTab* render_tab); + FinalRenderViewportTab* final_render_viewport_tab); + + // Get current rendering mode + RenderingMode get_rendering_mode() const; // Return true if currently rendering, false otherwise. bool is_rendering() const; @@ -165,7 +168,7 @@ class RenderingManager renderer::ParamArray m_params; foundation::SearchPaths m_resource_search_paths; RenderingMode m_rendering_mode; - RenderTab* m_render_tab; + FinalRenderViewportTab* m_final_render_viewport_tab; std::unique_ptr m_tile_callback_factory; diff --git a/src/appleseed.studio/mainwindow/rendering/renderwidget.cpp b/src/appleseed.studio/mainwindow/rendering/renderlayer.cpp similarity index 64% rename from src/appleseed.studio/mainwindow/rendering/renderwidget.cpp rename to src/appleseed.studio/mainwindow/rendering/renderlayer.cpp index c10a6b4e42..b83158c2ee 100644 --- a/src/appleseed.studio/mainwindow/rendering/renderwidget.cpp +++ b/src/appleseed.studio/mainwindow/rendering/renderlayer.cpp @@ -28,7 +28,7 @@ // // Interface header. -#include "renderwidget.h" +#include "renderlayer.h" // appleseed.renderer headers. #include "renderer/api/frame.h" @@ -39,6 +39,8 @@ #include "foundation/image/nativedrawing.h" #include "foundation/image/tile.h" #include "foundation/math/scalar.h" +#include "foundation/platform/types.h" +#include "utility/gl.h" // Qt headers. #include @@ -47,6 +49,9 @@ #include #include #include +#include +#include +#include #include // Standard headers. @@ -61,62 +66,115 @@ namespace appleseed { namespace studio { // -// RenderWidget class implementation. +// RenderLayer class implementation. // -RenderWidget::RenderWidget( - const size_t width, - const size_t height, +RenderLayer::RenderLayer( + const std::size_t width, + const std::size_t height, OCIO::ConstConfigRcPtr ocio_config, QWidget* parent) : QWidget(parent) , m_mutex(QMutex::Recursive) , m_ocio_config(ocio_config) + , m_gl_initialized(false) + , m_refresh_gl_texture(false) { setFocusPolicy(Qt::StrongFocus); + setFixedWidth(static_cast(width)); + setFixedHeight(static_cast(height)); setAutoFillBackground(false); setAttribute(Qt::WA_OpaquePaintEvent, true); - resize(width, height); + m_gl_tex = new QOpenGLTexture(QOpenGLTexture::Target2D); + + m_image = + QImage( + static_cast(width), + static_cast(height), + QImage::Format_RGB888); + + clear(); const char* display_name = m_ocio_config->getDefaultDisplay(); const char* default_transform = m_ocio_config->getDefaultView(display_name); - slot_display_transform_changed(default_transform); + set_display_transform(default_transform); setAcceptDrops(true); } -QImage RenderWidget::capture() +void RenderLayer::draw(GLuint empty_vao, bool paths_display_active) { QMutexLocker locker(&m_mutex); - return m_image.copy(); + if (m_refresh_gl_texture) + { + m_gl_tex->destroy(); + m_gl_tex->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Nearest); + m_gl_tex->setData(m_image, QOpenGLTexture::MipMapGeneration::DontGenerateMipMaps); + m_refresh_gl_texture = false; + } + + m_gl->glUseProgram(m_shader_program); + + GLfloat mult = paths_display_active ? 0.6 : 1.0; + m_gl->glUniform1f(m_mult_loc, mult); + + m_gl->glActiveTexture(GL_TEXTURE0); + m_gl_tex->bind(); + m_gl->glDisable(GL_DEPTH_TEST); + m_gl->glDepthMask(GL_FALSE); + m_gl->glBindVertexArray(empty_vao); + m_gl->glDrawArrays(GL_TRIANGLES, 0, 3); + m_gl->glDepthMask(GL_TRUE); } -void RenderWidget::resize( - const size_t width, - const size_t height) +void RenderLayer::init_gl(QSurfaceFormat format) { - QMutexLocker locker(&m_mutex); + if (!m_gl) + { + RENDERER_LOG_ERROR("Attempted to initialize GL without first setting GL functions"); + return; + } - setFixedWidth(static_cast(width)); - setFixedHeight(static_cast(height)); + auto vertex_shader = load_gl_shader("fullscreen_tri.vert"); + auto fragment_shader = load_gl_shader("final_render.frag"); - m_image = - QImage( - static_cast(width), - static_cast(height), - QImage::Format_RGB888); + m_shader_program = create_shader_program( + m_gl, + &vertex_shader, + &fragment_shader); - clear(); + m_mult_loc = m_gl->glGetUniformLocation(m_shader_program, "u_mult"); + + m_gl_initialized = true; +} + +void RenderLayer::set_gl_functions(QOpenGLFunctions_4_1_Core* functions) +{ + m_gl = functions; +} + +QImage RenderLayer::capture() +{ + QMutexLocker locker(&m_mutex); + + return m_image.copy(); } -void RenderWidget::clear() +void RenderLayer::darken() +{ + multiply(0.2f); +} + +void RenderLayer::clear() { QMutexLocker locker(&m_mutex); m_image.fill(QColor(0, 0, 0)); m_image_storage.reset(); + + m_refresh_gl_texture = true; } namespace @@ -127,59 +185,61 @@ namespace } inline std::uint8_t* get_image_pointer( - QImage& image, - const size_t x, - const size_t y) + QImage& image, + const std::size_t x, + const std::size_t y) { std::uint8_t* scanline = static_cast(image.scanLine(static_cast(y))); return scanline + x * image.depth() / 8; } } -void RenderWidget::start_render() +void RenderLayer::start_render() { // Clear the image storage. if (m_image_storage) m_image_storage->clear(Color4f(0.0f)); } -void RenderWidget::multiply(const float multiplier) +void RenderLayer::multiply(const float multiplier) { QMutexLocker locker(&m_mutex); assert(multiplier >= 0.0f && multiplier <= 1.0f); - const size_t image_width = static_cast(m_image.width()); - const size_t image_height = static_cast(m_image.height()); - const size_t dest_stride = static_cast(m_image.bytesPerLine()); + const auto image_width = static_cast(m_image.width()); + const auto image_height = static_cast(m_image.height()); + const auto dest_stride = static_cast(m_image.bytesPerLine()); std::uint8_t* dest = get_image_pointer(m_image); - for (size_t y = 0; y < image_height; ++y) + for (std::size_t y = 0; y < image_height; ++y) { std::uint8_t* row = dest + y * dest_stride; - for (size_t x = 0; x < image_width * 3; ++x) + for (std::size_t x = 0; x < image_width * 3; ++x) row[x] = truncate(row[x] * multiplier); } + + m_refresh_gl_texture = true; } namespace { void draw_bracket( std::uint8_t* dest, - const size_t dest_width, - const size_t dest_height, - const size_t dest_stride, - const size_t bracket_extent, + const std::size_t dest_width, + const std::size_t dest_height, + const std::size_t dest_stride, + const std::size_t bracket_extent, const std::uint8_t* pixel, - const size_t pixel_size) + const std::size_t pixel_size) { const int w = static_cast(std::min(bracket_extent, dest_width)); const int h = static_cast(std::min(bracket_extent, dest_height)); - const size_t right = (dest_width - 1) * pixel_size; - const size_t bottom = (dest_height - 1) * dest_stride; + const std::size_t right = (dest_width - 1) * pixel_size; + const std::size_t bottom = (dest_height - 1) * dest_stride; // Top-left corner. NativeDrawing::draw_hline(dest, w, pixel, pixel_size); @@ -199,28 +259,28 @@ namespace } } -void RenderWidget::highlight_tile( - const Frame& frame, - const size_t tile_x, - const size_t tile_y, - const size_t thread_index, - const size_t thread_count) +void RenderLayer::highlight_tile( + const Frame& frame, + const std::size_t tile_x, + const std::size_t tile_y, + const std::size_t thread_index, + const std::size_t thread_count) { QMutexLocker locker(&m_mutex); // Retrieve tile bounds. const Image& frame_image = frame.image(); const CanvasProperties& frame_props = frame_image.properties(); - const size_t x = tile_x * frame_props.m_tile_width; - const size_t y = tile_y * frame_props.m_tile_height; + const std::size_t x = tile_x * frame_props.m_tile_width; + const std::size_t y = tile_y * frame_props.m_tile_height; const Tile& tile = frame_image.tile(tile_x, tile_y); - const size_t width = tile.get_width(); - const size_t height = tile.get_height(); + const std::size_t width = tile.get_width(); + const std::size_t height = tile.get_height(); // Retrieve destination image information. - APPLESEED_UNUSED const size_t image_width = static_cast(m_image.width()); - APPLESEED_UNUSED const size_t image_height = static_cast(m_image.height()); - const size_t dest_stride = static_cast(m_image.bytesPerLine()); + APPLESEED_UNUSED const auto image_width = static_cast(m_image.width()); + APPLESEED_UNUSED const auto image_height = static_cast(m_image.height()); + const auto dest_stride = static_cast(m_image.bytesPerLine()); // Clipping is not supported. assert(x < image_width); @@ -241,7 +301,7 @@ void RenderWidget::highlight_tile( BracketColor[2] = 128 + static_cast(127.5f * (-0.5f * cos_t + 0.866f * sin_t)); // Draw a bracket around the tile. - const size_t BracketExtent = 5; + const std::size_t BracketExtent = 5; draw_bracket( dest, width, @@ -250,12 +310,14 @@ void RenderWidget::highlight_tile( BracketExtent, BracketColor, sizeof(BracketColor)); + + m_refresh_gl_texture = true; } -void RenderWidget::blit_tile( - const Frame& frame, - const size_t tile_x, - const size_t tile_y) +void RenderLayer::blit_tile( + const Frame& frame, + const std::size_t tile_x, + const std::size_t tile_y) { QMutexLocker locker(&m_mutex); @@ -263,9 +325,11 @@ void RenderWidget::blit_tile( blit_tile_no_lock(frame, tile_x, tile_y); update_tile_no_lock(tile_x, tile_y); + + m_refresh_gl_texture = true; } -void RenderWidget::blit_frame(const Frame& frame) +void RenderLayer::blit_frame(const Frame& frame) { QMutexLocker locker(&m_mutex); @@ -273,41 +337,39 @@ void RenderWidget::blit_frame(const Frame& frame) allocate_working_storage(frame_props); - for (size_t y = 0; y < frame_props.m_tile_count_y; ++y) + for (std::size_t y = 0; y < frame_props.m_tile_count_y; ++y) { - for (size_t x = 0; x < frame_props.m_tile_count_x; ++x) + for (std::size_t x = 0; x < frame_props.m_tile_count_x; ++x) { blit_tile_no_lock(frame, x, y); update_tile_no_lock(x, y); } } + + m_refresh_gl_texture = true; } -void RenderWidget::slot_display_transform_changed(const QString& transform) +void RenderLayer::set_display_transform(const QString& transform) { - { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - OCIO::DisplayTransformRcPtr transform_ptr = OCIO::DisplayTransform::Create(); - transform_ptr->setInputColorSpaceName(OCIO::ROLE_SCENE_LINEAR); - transform_ptr->setDisplay(m_ocio_config->getDefaultDisplay()); - transform_ptr->setView(transform.toStdString().c_str()); + OCIO::DisplayTransformRcPtr transform_ptr = OCIO::DisplayTransform::Create(); + transform_ptr->setInputColorSpaceName(OCIO::ROLE_SCENE_LINEAR); + transform_ptr->setDisplay(m_ocio_config->getDefaultDisplay()); + transform_ptr->setView(transform.toStdString().c_str()); - OCIO::ConstContextRcPtr context = m_ocio_config->getCurrentContext(); - m_ocio_processor = m_ocio_config->getProcessor(context, transform_ptr, OCIO::TRANSFORM_DIR_FORWARD); + OCIO::ConstContextRcPtr context = m_ocio_config->getCurrentContext(); + m_ocio_processor = m_ocio_config->getProcessor(context, transform_ptr, OCIO::TRANSFORM_DIR_FORWARD); - if (m_image_storage) + if (m_image_storage) + { + const CanvasProperties& frame_props = m_image_storage->properties(); + for (std::size_t y = 0; y < frame_props.m_tile_count_y; ++y) { - const CanvasProperties& frame_props = m_image_storage->properties(); - for (size_t y = 0; y < frame_props.m_tile_count_y; ++y) - { - for (size_t x = 0; x < frame_props.m_tile_count_x; ++x) - update_tile_no_lock(x, y); - } + for (std::size_t x = 0; x < frame_props.m_tile_count_x; ++x) + update_tile_no_lock(x, y); } } - - update(); } namespace @@ -330,7 +392,7 @@ namespace } } -void RenderWidget::allocate_working_storage(const CanvasProperties& frame_props) +void RenderLayer::allocate_working_storage(const CanvasProperties& frame_props) { if (!m_image_storage || !is_compatible(*m_image_storage, frame_props)) { @@ -365,10 +427,10 @@ void RenderWidget::allocate_working_storage(const CanvasProperties& frame_props) } } -void RenderWidget::blit_tile_no_lock( - const Frame& frame, - const size_t tile_x, - const size_t tile_y) +void RenderLayer::blit_tile_no_lock( + const Frame& frame, + const std::size_t tile_x, + const std::size_t tile_y) { // Retrieve the source tile. const Tile& src_tile = frame.image().tile(tile_x, tile_y); @@ -378,7 +440,7 @@ void RenderWidget::blit_tile_no_lock( dst_tile.copy_from(src_tile); } -void RenderWidget::update_tile_no_lock(const size_t tile_x, const size_t tile_y) +void RenderLayer::update_tile_no_lock(const std::size_t tile_x, const std::size_t tile_y) { // Retrieve the source tile. const Tile& src_tile = m_image_storage->tile(tile_x, tile_y); @@ -398,7 +460,7 @@ void RenderWidget::update_tile_no_lock(const size_t tile_x, const size_t tile_y) m_ocio_processor->apply(image_desc); // Convert the tile to 8-bit RGB for display. - static const size_t shuffle_table[4] = { 0, 1, 2, Pixel::SkipChannel }; + static const std::size_t shuffle_table[4] = { 0, 1, 2, Pixel::SkipChannel }; Tile uint8_rgb_tile( float_tile, PixelFormatUInt8, @@ -406,14 +468,14 @@ void RenderWidget::update_tile_no_lock(const size_t tile_x, const size_t tile_y) m_uint8_tile_storage->get_storage()); // Retrieve destination image information. - APPLESEED_UNUSED const size_t image_width = static_cast(m_image.width()); - APPLESEED_UNUSED const size_t image_height = static_cast(m_image.height()); - const size_t dest_stride = static_cast(m_image.bytesPerLine()); + APPLESEED_UNUSED const auto image_width = static_cast(m_image.width()); + APPLESEED_UNUSED const auto image_height = static_cast(m_image.height()); + const auto dest_stride = static_cast(m_image.bytesPerLine()); // Compute the coordinates of the first destination pixel. const CanvasProperties& frame_props = m_image_storage->properties(); - const size_t x = tile_x * frame_props.m_tile_width; - const size_t y = tile_y * frame_props.m_tile_height; + const std::size_t x = tile_x * frame_props.m_tile_width; + const std::size_t y = tile_y * frame_props.m_tile_height; // Clipping is not supported. assert(x < image_width); @@ -428,40 +490,5 @@ void RenderWidget::update_tile_no_lock(const size_t tile_x, const size_t tile_y) NativeDrawing::blit(dest, dest_stride, uint8_rgb_tile); } -void RenderWidget::paintEvent(QPaintEvent* event) -{ - QMutexLocker locker(&m_mutex); - - m_painter.begin(this); - m_painter.drawImage(rect(), m_image); - m_painter.end(); -} - -void RenderWidget::dragEnterEvent(QDragEnterEvent* event) -{ - if (event->mimeData()->hasFormat("text/plain")) - event->acceptProposedAction(); -} - -void RenderWidget::dragMoveEvent(QDragMoveEvent* event) -{ - if (pos().x() <= event->pos().x() && pos().y() <= event->pos().y() - && event->pos().x() < pos().x() + width() && event->pos().y() < pos().y() + height()) - { - event->accept(); - } - else - event->ignore(); -} - -void RenderWidget::dropEvent(QDropEvent* event) -{ - emit signal_material_dropped( - Vector2d( - static_cast(event->pos().x()) / width(), - static_cast(event->pos().y()) / height()), - event->mimeData()->text()); -} - } // namespace studio } // namespace appleseed diff --git a/src/appleseed.studio/mainwindow/rendering/renderwidget.h b/src/appleseed.studio/mainwindow/rendering/renderlayer.h similarity index 68% rename from src/appleseed.studio/mainwindow/rendering/renderwidget.h rename to src/appleseed.studio/mainwindow/rendering/renderlayer.h index 4f82ca88ba..6488048efb 100644 --- a/src/appleseed.studio/mainwindow/rendering/renderwidget.h +++ b/src/appleseed.studio/mainwindow/rendering/renderlayer.h @@ -44,6 +44,7 @@ namespace OCIO = OCIO_NAMESPACE; // Qt headers. #include #include +#include #include #include @@ -58,7 +59,10 @@ namespace renderer { class Frame; } class QDragEnterEvent; class QDragMoveEvent; class QDropEvent; +class QOpenGLFunctions_4_1_Core; +class QOpenGLTexture; class QPaintEvent; +class QRect; namespace appleseed { namespace studio { @@ -67,7 +71,7 @@ namespace studio { // A render widget based on QImage. // -class RenderWidget +class RenderLayer : public QWidget , public ICapturableWidget { @@ -75,9 +79,9 @@ class RenderWidget public: // Constructor. - RenderWidget( - const size_t width, - const size_t height, + RenderLayer( + const std::size_t width, + const std::size_t height, OCIO::ConstConfigRcPtr ocio_config, QWidget* parent = nullptr); @@ -85,9 +89,7 @@ class RenderWidget QImage capture() override; // Thread-safe. - void resize( - const size_t width, - const size_t height); + void darken(); // Thread-safe. void clear(); @@ -100,72 +102,77 @@ class RenderWidget // Thread-safe. void highlight_tile( - const renderer::Frame& frame, - const size_t tile_x, - const size_t tile_y, - const size_t thread_index, - const size_t thread_count); + const renderer::Frame& frame, + const std::size_t tile_x, + const std::size_t tile_y, + const std::size_t thread_index, + const std::size_t thread_count); // Thread-safe. void blit_tile( - const renderer::Frame& frame, - const size_t tile_x, - const size_t tile_y); + const renderer::Frame& frame, + const std::size_t tile_x, + const std::size_t tile_y); // Thread-safe. - void blit_frame(const renderer::Frame& frame); + void blit_frame( + const renderer::Frame& frame); + + // Thread-safe. + void set_display_transform( + const QString& transform); + + void draw( + GLuint empty_vao, + bool paths_display_active); + + void init_gl(QSurfaceFormat format); + void set_gl_functions(QOpenGLFunctions_4_1_Core* functions); // Direct access to internals for high-performance drawing. QMutex& mutex(); QImage& image(); - signals: - void signal_material_dropped( - const foundation::Vector2d& drop_pos, - const QString& material_name); - - public slots: - void slot_display_transform_changed(const QString& transform); - private: mutable QMutex m_mutex; QImage m_image; - QPainter m_painter; std::unique_ptr m_float_tile_storage; std::unique_ptr m_uint8_tile_storage; std::unique_ptr m_image_storage; + QOpenGLFunctions_4_1_Core* m_gl; + QOpenGLTexture* m_gl_tex; + GLuint m_shader_program; + GLint m_mult_loc; + bool m_gl_initialized; + bool m_refresh_gl_texture; + OCIO::ConstConfigRcPtr m_ocio_config; OCIO::ConstProcessorRcPtr m_ocio_processor; void allocate_working_storage(const foundation::CanvasProperties& frame_props); void blit_tile_no_lock( - const renderer::Frame& frame, - const size_t tile_x, - const size_t tile_y); + const renderer::Frame& frame, + const std::size_t tile_x, + const std::size_t tile_y); void update_tile_no_lock( - const size_t tile_x, - const size_t tile_y); - - void paintEvent(QPaintEvent* event) override; - void dragEnterEvent(QDragEnterEvent* event) override; - void dragMoveEvent(QDragMoveEvent* event) override; - void dropEvent(QDropEvent* event) override; + const std::size_t tile_x, + const std::size_t tile_y); }; // -// RenderWidget class implementation. +// RenderLayer class implementation. // -inline QMutex& RenderWidget::mutex() +inline QMutex& RenderLayer::mutex() { return m_mutex; } -inline QImage& RenderWidget::image() +inline QImage& RenderLayer::image() { return m_image; } diff --git a/src/appleseed.studio/mainwindow/rendering/scenepickinghandler.cpp b/src/appleseed.studio/mainwindow/rendering/scenepickinghandler.cpp index 328b866e5b..f9866ccb54 100644 --- a/src/appleseed.studio/mainwindow/rendering/scenepickinghandler.cpp +++ b/src/appleseed.studio/mainwindow/rendering/scenepickinghandler.cpp @@ -122,7 +122,7 @@ bool ScenePickingHandler::eventFilter(QObject* object, QEvent* event) { if (m_enabled) { - if (event->type() == QEvent::MouseButtonPress) + if (event->type() == QEvent::MouseButtonRelease) { const QMouseEvent* mouse_event = static_cast(event); if (!(mouse_event->modifiers() & (Qt::AltModifier | Qt::ShiftModifier | Qt::ControlModifier))) diff --git a/src/appleseed.studio/mainwindow/rendering/viewportcanvas.cpp b/src/appleseed.studio/mainwindow/rendering/viewportcanvas.cpp new file mode 100644 index 0000000000..313adbc40b --- /dev/null +++ b/src/appleseed.studio/mainwindow/rendering/viewportcanvas.cpp @@ -0,0 +1,402 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2019 Gray Olson, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// Interface header. +#include "viewportcanvas.h" + +// appleseed.studio headers. +#include "utility/gl.h" + +// appleseed.renderer headers. +#include "renderer/api/frame.h" + +// appleseed.foundation headers. +#include "foundation/image/canvasproperties.h" +#include "foundation/image/image.h" +#include "foundation/image/nativedrawing.h" +#include "foundation/image/tile.h" +#include "foundation/math/scalar.h" +#include "foundation/platform/types.h" + +// Qt headers. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Standard headers. +#include +#include + +using namespace foundation; +using namespace renderer; +using namespace std; + +namespace appleseed { +namespace studio { + +// +// ViewportCanvas class implementation. +// + +ViewportCanvas::ViewportCanvas( + const renderer::Project& project, + const size_t width, + const size_t height, + OCIO::ConstConfigRcPtr ocio_config, + const LightPathsManager& light_paths_manager, + const ViewportCanvas::BaseLayer layer, + QWidget* parent) + : QOpenGLWidget(parent) + , m_project(project) + , m_draw_light_paths(false) + , m_active_base_layer(layer) + , m_resolve_program(0) + , m_accum_loc(0) + , m_accum_tex(0) + , m_revealage_loc(0) + , m_revealage_tex(0) + , m_accum_revealage_fb(0) +{ + setFocusPolicy(Qt::StrongFocus); + setFixedWidth(static_cast(width)); + setFixedHeight(static_cast(height)); + setAutoFillBackground(false); + setAttribute(Qt::WA_OpaquePaintEvent, true); + + connect( + &light_paths_manager, &LightPathsManager::signal_light_path_selection_changed, + this, &ViewportCanvas::slot_light_path_selection_changed); + + create_render_layer(ocio_config, width, height); + create_gl_scene_layer(width, height); + create_light_paths_layer(light_paths_manager, width, height); + + setAcceptDrops(true); +} + +ViewportCanvas::~ViewportCanvas() +{ + m_gl->glDeleteProgram(m_resolve_program); + m_gl->glDeleteTextures(1, &m_accum_tex); + m_gl->glDeleteTextures(1, &m_revealage_tex); + m_gl->glDeleteTextures(1, &m_color_tex); + m_gl->glDeleteTextures(1, &m_depth_tex); + m_gl->glDeleteFramebuffers(1, &m_accum_revealage_fb); + m_gl->glDeleteFramebuffers(1, &m_main_fb); +} + +void ViewportCanvas::create_render_layer( + OCIO::ConstConfigRcPtr ocio_config, + const std::size_t width, + const std::size_t height) +{ + m_render_layer = + std::unique_ptr(new RenderLayer( + width, + height, + ocio_config)); +} + +void ViewportCanvas::create_gl_scene_layer( + const std::size_t width, + const std::size_t height) +{ + m_gl_scene_layer = + std::unique_ptr(new GLSceneLayer( + m_project, + width, + height)); +} + +void ViewportCanvas::create_light_paths_layer( + const LightPathsManager& light_paths_manager, + const std::size_t width, + const std::size_t height) +{ + m_light_paths_layer = + std::unique_ptr(new LightPathsLayer( + m_project, + light_paths_manager, + width, + height)); +} + +RenderLayer* ViewportCanvas::get_render_layer() +{ + return m_render_layer.get(); +} + +LightPathsLayer* ViewportCanvas::get_light_paths_layer() +{ + return m_light_paths_layer.get(); +} + +GLSceneLayer* ViewportCanvas::get_gl_scene_layer() +{ + return m_gl_scene_layer.get(); +} + +QImage ViewportCanvas::capture() +{ + return grabFramebuffer(); +} + +void ViewportCanvas::initializeGL() +{ + RENDERER_LOG_DEBUG("initializing OpenGL context..."); + + m_gl = QOpenGLContext::currentContext()->versionFunctions(); + + const QSurfaceFormat qs_format = format(); + if (!m_gl->initializeOpenGLFunctions()) + { + const int major_version = qs_format.majorVersion(); + const int minor_version = qs_format.minorVersion(); + + RENDERER_LOG_ERROR( + "could not load required OpenGL functions: loaded version %d.%d, required version 4.2.", + major_version, + minor_version); + + return; + } + + m_gl->glGenVertexArrays(1, &m_empty_vao); + m_gl->glBindVertexArray(m_empty_vao); + m_gl->glGenBuffers(1, &m_empty_vbo); + m_gl->glBindBuffer(GL_ARRAY_BUFFER, m_empty_vbo); + float vals[3]{ 0.0f, 0.0f, 0.0f }; + m_gl->glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 3, static_cast(vals), GL_STATIC_DRAW); + m_gl->glVertexAttribPointer(0, sizeof(float), GL_FLOAT, GL_FALSE, sizeof(float), static_cast(0)); + m_gl->glEnableVertexAttribArray(0); + + auto vertex_shader = load_gl_shader("fullscreen_tri.vert"); + auto fragment_shader = load_gl_shader("oit_resolve.frag"); + + m_resolve_program = create_shader_program( + m_gl, + &vertex_shader, + &fragment_shader); + + m_accum_loc = m_gl->glGetUniformLocation(m_resolve_program, "u_accum_tex"); + m_revealage_loc = m_gl->glGetUniformLocation(m_resolve_program, "u_revealage_tex"); + + GLuint temp_fbs[2]; + m_gl->glGenFramebuffers(2, temp_fbs); + + m_main_fb = temp_fbs[0]; + m_accum_revealage_fb = temp_fbs[1]; + + GLuint temp_texs[4]; + m_gl->glGenTextures(4, temp_texs); + + m_color_tex = temp_texs[0]; + m_depth_tex = temp_texs[1]; + m_accum_tex = temp_texs[2]; + m_revealage_tex = temp_texs[3]; + + resizeGL(width(), height()); + + m_gl->glBindFramebuffer(GL_FRAMEBUFFER, m_main_fb); + m_gl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_color_tex, 0); + m_gl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_depth_tex, 0); + + m_gl->glBindFramebuffer(GL_FRAMEBUFFER, m_accum_revealage_fb); + m_gl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_accum_tex, 0); + m_gl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, m_revealage_tex, 0); + m_gl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_depth_tex, 0); + + m_gl->glBindFramebuffer(GL_FRAMEBUFFER, 0); + + m_gl->glUseProgram(m_resolve_program); + m_gl->glUniform1i(m_accum_loc, 0); + m_gl->glUniform1i(m_revealage_loc, 1); + + m_render_layer->set_gl_functions(m_gl); + m_render_layer->init_gl(qs_format); + m_gl_scene_layer->set_gl_functions(m_gl); + m_gl_scene_layer->init_gl(qs_format); + m_light_paths_layer->set_gl_functions(m_gl); + m_light_paths_layer->init_gl(qs_format); +} + +void ViewportCanvas::resizeGL(int width, int height) +{ + m_light_paths_layer->resize(width, height); + + m_gl->glBindTexture(GL_TEXTURE_2D, m_color_tex); + m_gl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + m_gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + m_gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + m_gl->glBindTexture(GL_TEXTURE_2D, m_depth_tex); + m_gl->glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, width, height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL); + m_gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + m_gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + m_gl->glBindTexture(GL_TEXTURE_2D, m_accum_tex); + m_gl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_HALF_FLOAT, NULL); + m_gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + m_gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + m_gl->glBindTexture(GL_TEXTURE_2D, m_revealage_tex); + m_gl->glTexImage2D(GL_TEXTURE_2D, 0, GL_R16F, width, height, 0, GL_RED, GL_HALF_FLOAT, NULL); + m_gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + m_gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + m_gl->glBindTexture(GL_TEXTURE_2D, 0); +} + +void ViewportCanvas::paintGL() +{ + double dpr = static_cast(m_render_layer->image().devicePixelRatio()); + GLsizei w = static_cast(width() * dpr); + GLsizei h = static_cast(height() * dpr); + m_gl->glViewport(0, 0, w, h); + + // Clear the main framebuffers + m_gl->glBindFramebuffer(GL_FRAMEBUFFER, m_main_fb); + GLfloat main_clear[]{ 0.0, 0.0, 0.0, 0.0 }; + m_gl->glClearBufferfv(GL_COLOR, 0, main_clear); + m_gl->glClearBufferfi(GL_DEPTH_STENCIL, 0, 1.0, 0); + + if (m_active_base_layer == BaseLayer::FinalRender) + m_render_layer->draw(m_empty_vao, m_draw_light_paths); + + QOpenGLContext *ctx = const_cast(QOpenGLContext::currentContext()); + + if (m_active_base_layer == BaseLayer::OpenGL || m_draw_light_paths) + m_gl_scene_layer->draw_depth_only(); + + if (m_active_base_layer == BaseLayer::OpenGL) + m_gl_scene_layer->draw(); + + if (m_draw_light_paths) + { + // Bind accum/revealage framebuffer + m_gl->glBindFramebuffer(GL_FRAMEBUFFER, m_accum_revealage_fb); + + // Set both attachments as active draw buffers + const GLenum buffers[]{ GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; + m_gl->glDrawBuffers(2, buffers); + + // Clear the buffers + GLfloat accum_clear_col[]{ 0.0, 0.0, 0.0, 0.0 }; + m_gl->glClearBufferfv(GL_COLOR, 0, accum_clear_col); + GLfloat revealage_clear_col[]{ 1.0, 0.0, 0.0, 0.0 }; + m_gl->glClearBufferfv(GL_COLOR, 1, revealage_clear_col); + + // Enable proper blending for each + m_gl->glEnable(GL_BLEND); + m_gl->glBlendFunci(0, GL_ONE, GL_ONE); + m_gl->glBlendFunci(1, GL_ZERO, GL_ONE_MINUS_SRC_COLOR); + m_gl->glEnable(GL_DEPTH_TEST); + m_gl->glDepthMask(GL_FALSE); + m_gl->glDepthFunc(GL_LESS); + + if (m_active_base_layer == BaseLayer::FinalRender) + m_light_paths_layer->draw_render_camera(); + else + m_light_paths_layer->draw(); + + m_gl->glUseProgram(m_resolve_program); + + // Set default framebuffer object + m_gl->glBindFramebuffer(GL_FRAMEBUFFER, m_main_fb); + m_gl->glDrawBuffer(GL_COLOR_ATTACHMENT0); + + m_gl->glBindVertexArray(m_empty_vao); + + m_gl->glActiveTexture(GL_TEXTURE0); + m_gl->glBindTexture(GL_TEXTURE_2D, m_accum_tex); + m_gl->glActiveTexture(GL_TEXTURE1); + m_gl->glBindTexture(GL_TEXTURE_2D, m_revealage_tex); + + m_gl->glDisable(GL_DEPTH_TEST); + m_gl->glDepthMask(GL_FALSE); + m_gl->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + m_gl->glDrawArrays(GL_TRIANGLES, 0, 3); + + m_gl->glActiveTexture(GL_TEXTURE1); + m_gl->glBindTexture(GL_TEXTURE_2D, 0); + m_gl->glActiveTexture(GL_TEXTURE0); + m_gl->glBindTexture(GL_TEXTURE_2D, 0); + m_gl->glDepthMask(GL_TRUE); + } + + m_gl->glBindFramebuffer(GL_READ_FRAMEBUFFER, m_main_fb); + m_gl->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, ctx->defaultFramebufferObject()); + m_gl->glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); +} + +void ViewportCanvas::dragEnterEvent(QDragEnterEvent* event) +{ + if (event->mimeData()->hasFormat("text/plain")) + event->acceptProposedAction(); +} + +void ViewportCanvas::dragMoveEvent(QDragMoveEvent* event) +{ + if (pos().x() <= event->pos().x() && pos().y() <= event->pos().y() + && event->pos().x() < pos().x() + width() && event->pos().y() < pos().y() + height()) + { + event->accept(); + } + else + event->ignore(); +} + +void ViewportCanvas::dropEvent(QDropEvent* event) +{ + emit signal_material_dropped( + Vector2d( + static_cast(event->pos().x()) / width(), + static_cast(event->pos().y()) / height()), + event->mimeData()->text()); +} + +void ViewportCanvas::slot_light_path_selection_changed( + const bool display_light_paths, + const int selected_light_path_index, + const int total_light_paths) +{ + m_draw_light_paths = display_light_paths && total_light_paths > 0; + update(); +} + +} // namespace studio +} // namespace appleseed diff --git a/src/appleseed.studio/mainwindow/rendering/viewportcanvas.h b/src/appleseed.studio/mainwindow/rendering/viewportcanvas.h new file mode 100644 index 0000000000..3c7cd43ef8 --- /dev/null +++ b/src/appleseed.studio/mainwindow/rendering/viewportcanvas.h @@ -0,0 +1,166 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2019 Gray Olson, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#pragma once + +// appleseed.studio headers. +#include "mainwindow/rendering/lightpathslayer.h" +#include "mainwindow/rendering/lightpathsmanager.h" +#include "mainwindow/rendering/glscenelayer.h" +#include "mainwindow/rendering/renderclipboardhandler.h" +#include "mainwindow/rendering/renderlayer.h" + + +// appleseed.foundation headers. +#include "foundation/image/image.h" +#include "foundation/image/tile.h" +#include "foundation/math/vector.h" + +// Qt headers. +#include +#include +#include +#include +#include + +// OpenColorIO headers. +#include +namespace OCIO = OCIO_NAMESPACE; + +// Standard headers. +#include +#include +#include + +// Forward declarations. +namespace foundation { class CanvasProperties; } +namespace renderer { class Frame; } +class QDragEnterEvent; +class QDragMoveEvent; +class QDropEvent; +class QPaintEvent; +class QOpenGLFunctions_4_1_Core; + +namespace appleseed { +namespace studio { + +// +// A render widget based on QImage. +// + +class ViewportCanvas + : public QOpenGLWidget + , public ICapturableWidget +{ + Q_OBJECT + + public: + enum BaseLayer { + FinalRender, + OpenGL, + BASE_LAYER_MAX_VALUE + }; + + // Constructor. + ViewportCanvas( + const renderer::Project& project, + const size_t width, + const size_t height, + OCIO::ConstConfigRcPtr ocio_config, + const LightPathsManager& lith_paths_manager, + const BaseLayer layer, + QWidget* parent = nullptr); + + ~ViewportCanvas(); + + // Thread-safe. + QImage capture() override; + + RenderLayer* get_render_layer(); + GLSceneLayer* get_gl_scene_layer(); + LightPathsLayer* get_light_paths_layer(); + + signals: + void signal_material_dropped( + const foundation::Vector2d& drop_pos, + const QString& material_name); + + public slots: + void slot_light_path_selection_changed( + const bool display_light_paths, + const int selected_light_path_index, + const int total_light_paths); + + private: + const renderer::Project& m_project; + + QOpenGLFunctions_4_1_Core* m_gl; + QPainter m_painter; + + GLuint m_depth_tex; + GLuint m_color_tex; + GLuint m_accum_tex; + GLuint m_revealage_tex; + GLuint m_main_fb; + GLuint m_accum_revealage_fb; + GLuint m_empty_vao; + GLuint m_empty_vbo; + + GLuint m_resolve_program; + GLint m_accum_loc; + GLint m_revealage_loc; + + std::unique_ptr m_render_layer; + std::unique_ptr m_gl_scene_layer; + std::unique_ptr m_light_paths_layer; + + bool m_draw_light_paths; + BaseLayer m_active_base_layer; + + void create_light_paths_layer( + const LightPathsManager& light_paths_manager, + const std::size_t width, + const std::size_t height); + void create_gl_scene_layer( + const std::size_t width, + const std::size_t height); + void create_render_layer( + OCIO::ConstConfigRcPtr ocio_config, + const std::size_t width, + const std::size_t height); + + void initializeGL() override; + void resizeGL(int width, int height) override; + void paintGL() override; + void dragEnterEvent(QDragEnterEvent* event) override; + void dragMoveEvent(QDragMoveEvent* event) override; + void dropEvent(QDropEvent* event) override; +}; + +} // namespace studio +} // namespace appleseed diff --git a/src/appleseed.studio/mainwindow/rendering/renderregionhandler.cpp b/src/appleseed.studio/mainwindow/rendering/viewportregionselectionhandler.cpp similarity index 84% rename from src/appleseed.studio/mainwindow/rendering/renderregionhandler.cpp rename to src/appleseed.studio/mainwindow/rendering/viewportregionselectionhandler.cpp index 2affd76d12..66159dffa5 100644 --- a/src/appleseed.studio/mainwindow/rendering/renderregionhandler.cpp +++ b/src/appleseed.studio/mainwindow/rendering/viewportregionselectionhandler.cpp @@ -28,7 +28,7 @@ // // Interface header. -#include "renderregionhandler.h" +#include "viewportregionselectionhandler.h" // appleseed.qtcommon headers. #include "widgets/mousecoordinatestracker.h" @@ -54,7 +54,7 @@ using namespace foundation; namespace appleseed { namespace studio { -RenderRegionHandler::RenderRegionHandler( +ViewportRegionSelectionHandler::ViewportRegionSelectionHandler( QWidget* widget, const MouseCoordinatesTracker& mouse_tracker) : m_widget(widget) @@ -66,22 +66,22 @@ RenderRegionHandler::RenderRegionHandler( m_widget->installEventFilter(this); } -RenderRegionHandler::~RenderRegionHandler() +ViewportRegionSelectionHandler::~ViewportRegionSelectionHandler() { m_widget->removeEventFilter(this); } -void RenderRegionHandler::set_enabled(const bool enabled) +void ViewportRegionSelectionHandler::set_enabled(const bool enabled) { m_enabled = enabled; } -void RenderRegionHandler::set_mode(const Mode mode) +void ViewportRegionSelectionHandler::set_mode(const Mode mode) { m_mode = mode; } -bool RenderRegionHandler::eventFilter(QObject* object, QEvent* event) +bool ViewportRegionSelectionHandler::eventFilter(QObject* object, QEvent* event) { if (m_enabled) { @@ -112,12 +112,19 @@ bool RenderRegionHandler::eventFilter(QObject* object, QEvent* event) { const QMouseEvent* mouse_event = static_cast(event); - if (mouse_event->button() == Qt::LeftButton) + if (mouse_event->button() == Qt::LeftButton && + !(mouse_event->modifiers() & (Qt::AltModifier | Qt::ShiftModifier | Qt::ControlModifier))) { m_rubber_band->hide(); + const QPoint& pos = mouse_event->pos(); + + // Don't consider a click as a selection. + if (m_origin == pos) + break; + const Vector2i p0 = m_mouse_tracker.widget_to_pixel(m_origin); - const Vector2i p1 = m_mouse_tracker.widget_to_pixel(mouse_event->pos()); + const Vector2i p1 = m_mouse_tracker.widget_to_pixel(pos); const int x0 = std::max(std::min(p0.x, p1.x), 0); const int y0 = std::max(std::min(p0.y, p1.y), 0); @@ -129,6 +136,8 @@ bool RenderRegionHandler::eventFilter(QObject* object, QEvent* event) if (m_mode == RectangleSelectionMode) emit signal_rectangle_selection(QRect(x0, y0, w, h)); else emit signal_render_region(QRect(x0, y0, w, h)); + + return true; } break; diff --git a/src/appleseed.studio/mainwindow/rendering/renderregionhandler.h b/src/appleseed.studio/mainwindow/rendering/viewportregionselectionhandler.h similarity index 95% rename from src/appleseed.studio/mainwindow/rendering/renderregionhandler.h rename to src/appleseed.studio/mainwindow/rendering/viewportregionselectionhandler.h index c3ae751899..4d8424c0b2 100644 --- a/src/appleseed.studio/mainwindow/rendering/renderregionhandler.h +++ b/src/appleseed.studio/mainwindow/rendering/viewportregionselectionhandler.h @@ -43,18 +43,18 @@ class QWidget; namespace appleseed { namespace studio { -class RenderRegionHandler +class ViewportRegionSelectionHandler : public QObject { Q_OBJECT public: // Default mode is RectangleSelectionMode. - RenderRegionHandler( + ViewportRegionSelectionHandler( QWidget* widget, const qtcommon::MouseCoordinatesTracker& mouse_tracker); - ~RenderRegionHandler() override; + ~ViewportRegionSelectionHandler() override; void set_enabled(const bool enabled); diff --git a/src/appleseed.studio/mainwindow/rendering/viewporttab.cpp b/src/appleseed.studio/mainwindow/rendering/viewporttab.cpp new file mode 100644 index 0000000000..965a4d3b88 --- /dev/null +++ b/src/appleseed.studio/mainwindow/rendering/viewporttab.cpp @@ -0,0 +1,96 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2010-2013 Francois Beaune, Jupiter Jazz Limited +// Copyright (c) 2014-2018 Francois Beaune, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// Interface header. +#include "viewporttab.h" + +// appleseed.renderer headers. +#include "renderer/api/frame.h" +#include "renderer/api/project.h" + +// appleseed.foundation headers. +#include "foundation/image/canvasproperties.h" + +using namespace appleseed::qtcommon; +using namespace foundation; +using namespace renderer; + +namespace appleseed { +namespace studio { + +// +// ViewportTab class implementation. +// + +ViewportTab::ViewportTab(Project& project) + : m_project(project) +{ +} + +void ViewportTab::clear() +{ + ViewportCanvas* viewport_canvas = get_viewport_canvas(); + viewport_canvas->get_render_layer()->clear(); + viewport_canvas->update(); +} + +void ViewportTab::render_began() +{ + ViewportCanvas* viewport_canvas = get_viewport_canvas(); + viewport_canvas->get_render_layer()->darken(); + viewport_canvas->get_light_paths_layer()->update_render_camera_transform(); +} + +void ViewportTab::on_tab_selected() +{ +} + +ViewportTab::State ViewportTab::save_state() const +{ + State state; + state.m_zoom_handler_state = m_zoom_handler->save_state(); + state.m_pan_handler_state = m_pan_handler->save_state(); + return state; +} + +void ViewportTab::load_state(const State& state) +{ + // The order matters here. + m_zoom_handler->load_state(state.m_zoom_handler_state); + m_pan_handler->load_state(state.m_pan_handler_state); +} + +void ViewportTab::slot_reset_zoom() +{ + m_zoom_handler->reset_zoom(); +} + +} // namespace studio +} // namespace appleseed + diff --git a/src/appleseed.studio/mainwindow/rendering/viewporttab.h b/src/appleseed.studio/mainwindow/rendering/viewporttab.h new file mode 100644 index 0000000000..f5a977b1cb --- /dev/null +++ b/src/appleseed.studio/mainwindow/rendering/viewporttab.h @@ -0,0 +1,92 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2010-2013 Francois Beaune, Jupiter Jazz Limited +// Copyright (c) 2014-2018 Francois Beaune, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#pragma once + +// appleseed.studio headers. +#include "mainwindow/rendering/viewportcanvas.h" + +// appleseed.qtcommon headers. +#include "widgets/scrollareapanhandler.h" +#include "widgets/widgetzoomhandler.h" + +// Qt headers. +#include +#include + +// Standard headers. +#include + +// Forward declarations. +namespace renderer { class Project; } + +namespace appleseed { +namespace studio { + +// +// A tab wrapping a render widget and its toolbar. +// + +class ViewportTab + : public QWidget +{ + Q_OBJECT + + public: + ViewportTab(renderer::Project& project); + + virtual ViewportCanvas* get_viewport_canvas() const = 0; + + void clear(); + + virtual void render_began(); + + virtual void on_tab_selected(); + + struct State + { + qtcommon::WidgetZoomHandler::State m_zoom_handler_state; + qtcommon::ScrollAreaPanHandler::State m_pan_handler_state; + }; + + State save_state() const; + void load_state(const State& state); + + protected slots: + void slot_reset_zoom(); + + protected: + renderer::Project& m_project; + + std::unique_ptr m_zoom_handler; + std::unique_ptr m_pan_handler; +}; + +} // namespace studio +} // namespace appleseed diff --git a/src/appleseed.studio/resources/resources.qrc b/src/appleseed.studio/resources/resources.qrc index 9dc95f7f95..3ee211c50b 100644 --- a/src/appleseed.studio/resources/resources.qrc +++ b/src/appleseed.studio/resources/resources.qrc @@ -1,8 +1,11 @@ images/appleseed-logo-256.png + shaders/final_render.frag + shaders/fullscreen_tri.vert shaders/lightpaths.frag shaders/lightpaths.vert + shaders/oit_resolve.frag shaders/scene.frag shaders/scene.vert diff --git a/src/appleseed.studio/resources/shaders/final_render.frag b/src/appleseed.studio/resources/shaders/final_render.frag new file mode 100644 index 0000000000..a8bd7a4c2d --- /dev/null +++ b/src/appleseed.studio/resources/shaders/final_render.frag @@ -0,0 +1,42 @@ +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2019 Gray Olson, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#version 420 + +in vec2 f_uv; + +uniform sampler2D u_render_tex; +uniform float u_mult; + +out vec4 Target0; + +void main() +{ + const vec3 col = texture(u_render_tex, f_uv, 0).rgb; + + Target0 = vec4(col * u_mult, 1.0); +} diff --git a/src/appleseed.studio/resources/shaders/fullscreen_tri.vert b/src/appleseed.studio/resources/shaders/fullscreen_tri.vert new file mode 100644 index 0000000000..76ad297127 --- /dev/null +++ b/src/appleseed.studio/resources/shaders/fullscreen_tri.vert @@ -0,0 +1,48 @@ +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2019 Gray Olson, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#version 420 + +const vec2 verts[3] = vec2[]( + vec2(3.0, 1.0), + vec2(-1.0, -3.0), + vec2(-1.0, 1.0) +); + +const vec2 uvs[3] = vec2[]( + vec2(2.0, 0.0), + vec2(0.0, 2.0), + vec2(0.0, 0.0) +); + +out vec2 f_uv; + +void main() +{ + f_uv = uvs[gl_VertexID]; + gl_Position = vec4(verts[gl_VertexID], 0.0, 1.0); +} diff --git a/src/appleseed.studio/resources/shaders/lightpaths.frag b/src/appleseed.studio/resources/shaders/lightpaths.frag index f67540108f..cc9e990fcb 100644 --- a/src/appleseed.studio/resources/shaders/lightpaths.frag +++ b/src/appleseed.studio/resources/shaders/lightpaths.frag @@ -25,14 +25,40 @@ // THE SOFTWARE. // -#version 330 +#version 420 #extension GL_ARB_separate_shader_objects : enable -layout(location = 0) in vec3 v_color; +in float f_aa_norm; +flat in vec4 f_color; +flat in float f_thickness; +flat in float f_total_thickness; +flat in float f_aspect_expansion_len; -out vec4 Target0; +uniform vec2 u_res; // resolution of the frame. +// Output requires 2 values to be able to overlay correctly paths over a layer. +// The first output is for paths color and the second output is for paths opacity. +layout(location = 0) out vec4 accum_target; +layout(location = 1) out float revealage_target; + +void write_pixel(vec4 premultiplied_col, vec3 transmit) { + premultiplied_col.a *= 1.0 - clamp((transmit.r + transmit.g + transmit.b) * (1.0 / 3.0), 0, 1); + + const float a = min(1.0, premultiplied_col.a) * 8.0 + 0.01; + const float b = -gl_FragCoord.z * 0.95 + 1.0; + + const float w = clamp(a * a * a * 1e8 * b * b * b, 1e-2, 3e2); + + accum_target = premultiplied_col * w; + revealage_target = premultiplied_col.a; +} void main() { - Target0 = vec4(v_color, 1.0); + const float dist = abs(f_aa_norm) * f_total_thickness - 0.5 / f_aspect_expansion_len; + const float eps = fwidth(dist); + float a = 1.0 - smoothstep(f_thickness - eps, f_thickness + eps, dist); + a *= f_color.a; + + const vec4 premult = vec4(f_color.rgb * a, a); + write_pixel(premult, vec3(0.0)); } diff --git a/src/appleseed.studio/resources/shaders/lightpaths.vert b/src/appleseed.studio/resources/shaders/lightpaths.vert index 6664383094..94364dda12 100644 --- a/src/appleseed.studio/resources/shaders/lightpaths.vert +++ b/src/appleseed.studio/resources/shaders/lightpaths.vert @@ -25,19 +25,130 @@ // THE SOFTWARE. // -#version 330 -#extension GL_ARB_separate_shader_objects : enable +#version 420 -layout(location = 0) in vec3 a_pos; -layout(location = 1) in vec3 a_color; +const float AA_BUFFER_SIZE = 1.0; + +layout(location = 0) in vec3 v_previous; +layout(location = 1) in vec3 v_position; +layout(location = 2) in vec3 v_next; +layout(location = 3) in int v_bitmask; // flag defining which part of a light path is currently drawn. +layout(location = 4) in vec3 v_color; +layout(location = 5) in vec3 v_surface_normal; -uniform mat4 u_view; uniform mat4 u_proj; +uniform mat4 u_view; +uniform vec2 u_res; // resolution of the frame. + +uniform int u_first_selected; +uniform int u_last_selected; + +flat out vec4 f_color; +out float f_aa_norm; +flat out float f_thickness; +flat out float f_total_thickness; +flat out float f_aspect_expansion_len; -layout(location = 0) out vec3 v_color; +// Light path vertex world space position is +// moved away from the surface to avoid clipping. +const float CLIPPING_PREVENTION_FACTOR = 0.0001; + +// Light path thickness in screen space. +const float LINE_THICKNESS = 1.0; + +const float SELECTED_PATH_TRANSPARENCY = 1.0; +const float NON_SELECTED_PATH_TRANSPARENCY = 0.05; + +// +// Reference: +// - https://mattdesl.svbtle.com/drawing-lines-is-hard#screenspace-projected-lines_2 +// + +// Compute current line direction +// depending on which part of the path we +// are drawing (start, middle or end). +vec2 get_line_direction(vec2 curr_screen, vec2 prev_screen, vec2 next_screen) +{ + return + (v_bitmask & 2) == 2 + ? normalize(curr_screen - prev_screen) + : normalize(next_screen - curr_screen); +} + +// Each point on the light path is duplicated to render a real line. +// The duplicated vertex of each light path point is flagged. +bool is_second_point() +{ + return ((v_bitmask & 1) == 1); +} void main() { - v_color = a_color; - gl_Position = u_proj * u_view * vec4(a_pos, 1.0); + // Aspect ratio correction is applied on + // screen points (only on the X axis). + const vec2 aspect_correction = vec2(u_res.x / u_res.y, 1.0); + + // Project points. + // The currently drawn point is offset + // from the surface to ensure path + // doesn't go through it. + const mat4 vp = u_proj * u_view; + const vec4 curr_proj = vp * vec4(v_position + v_surface_normal * CLIPPING_PREVENTION_FACTOR, 1.0); + const vec4 prev_proj = vp * vec4(v_previous, 1.0); + const vec4 next_proj = vp * vec4(v_next, 1.0); + + // Project points in screenspace and apply aspect ratio correction. + const vec2 curr_screen = (curr_proj.xy / curr_proj.w) * aspect_correction; + const vec2 prev_screen = (prev_proj.xy / prev_proj.w) * aspect_correction; + const vec2 next_screen = (next_proj.xy / next_proj.w) * aspect_correction; + + const bool is_second_point = is_second_point(); + + const vec2 line_direction = get_line_direction(curr_screen, prev_screen, next_screen); + + // Compute the normal of the line. + const vec2 line_normal = vec2(-line_direction.y, line_direction.x); + + vec4 normal_clip = vp * vec4(v_surface_normal, 0.0); + normal_clip.x *= aspect_correction.x; + normal_clip = normalize(normal_clip); + + const vec2 tang_clip = vec2(-normal_clip.y, normal_clip.x); + + const float tdp = dot(tang_clip, line_normal); + // it uses tangent to surface unless the tangent to surface is too much aligned with the line direction, + // in which case it uses the line normal as fallback. it does that to reduce the amount of 'pokethrough' of the lines through the geometry. + // if you always expand along line normal then depending on the angle of the line and camera the edges of the rectangle will poke through i the backside of the surface + vec2 expansion = + tdp > 0.05 + ? tang_clip / tdp + : line_normal; + + const vec2 norm_exp = normalize(expansion); + const vec2 res_exp_line_direction = vec2(norm_exp.x * u_res.x, norm_exp.y * u_res.y); + f_aspect_expansion_len = length(res_exp_line_direction); + + const float half_thickness = LINE_THICKNESS * 0.5; + f_thickness = half_thickness / f_aspect_expansion_len; + + f_total_thickness = f_thickness + AA_BUFFER_SIZE / f_aspect_expansion_len; + + expansion *= f_total_thickness; + + // Reverse expansion line_directionection for the duplicated point. + if (is_second_point) expansion *= -1.0; + + vec2 screen_pos = curr_screen + expansion; + screen_pos /= aspect_correction; + gl_Position = vec4(screen_pos * curr_proj.w, curr_proj.z /* curr_proj.w*/, curr_proj.w); + f_aa_norm = is_second_point ? 1.0 : -1.0; + + const bool is_selected = gl_VertexID >= u_first_selected && gl_VertexID < u_last_selected; + + const float alpha = + is_selected + ? SELECTED_PATH_TRANSPARENCY + : NON_SELECTED_PATH_TRANSPARENCY; + + f_color = vec4(v_color, alpha); } diff --git a/src/appleseed.studio/resources/shaders/oit_resolve.frag b/src/appleseed.studio/resources/shaders/oit_resolve.frag new file mode 100644 index 0000000000..e752e91d47 --- /dev/null +++ b/src/appleseed.studio/resources/shaders/oit_resolve.frag @@ -0,0 +1,57 @@ +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2019 Gray Olson, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#version 420 + +uniform sampler2D u_accum_tex; +uniform sampler2D u_revealage_tex; + +out vec4 Target0; + +float max4(vec4 v) +{ + return max(max(max(v.x, v.y), v.z), v.w); +} + +void main() +{ + const ivec2 coord = ivec2(gl_FragCoord.xy); + + const float revealage = texelFetch(u_revealage_tex, coord, 0).r; + + vec4 accum = texelFetch(u_accum_tex, coord, 0); + + // Suppress overflow. + if (isinf(max4(abs(accum)))) + { + accum.rgb = vec3(accum.a); + } + + const vec3 averageColor = accum.rgb / max(accum.a, 0.00001); + + Target0 = vec4(averageColor, 1.0 - revealage); +} diff --git a/src/appleseed.studio/resources/shaders/scene.frag b/src/appleseed.studio/resources/shaders/scene.frag index 81ba78f5cb..edd7aaffca 100644 --- a/src/appleseed.studio/resources/shaders/scene.frag +++ b/src/appleseed.studio/resources/shaders/scene.frag @@ -25,7 +25,7 @@ // THE SOFTWARE. // -#version 330 +#version 420 #extension GL_ARB_separate_shader_objects : enable layout(location = 0) in vec3 frag_pos; diff --git a/src/appleseed.studio/resources/shaders/scene.vert b/src/appleseed.studio/resources/shaders/scene.vert index d1fa1f82e3..3f8185229f 100644 --- a/src/appleseed.studio/resources/shaders/scene.vert +++ b/src/appleseed.studio/resources/shaders/scene.vert @@ -25,7 +25,7 @@ // THE SOFTWARE. // -#version 330 +#version 420 #extension GL_ARB_separate_shader_objects : enable layout(location = 0) in vec3 a_pos; diff --git a/src/appleseed.studio/utility/gl.cpp b/src/appleseed.studio/utility/gl.cpp new file mode 100644 index 0000000000..9d59f84af2 --- /dev/null +++ b/src/appleseed.studio/utility/gl.cpp @@ -0,0 +1,175 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2019 Gray Olson, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// Interface header. +#include "gl.h" + +// appleseed.studio headers. +#include "renderer/api/utility.h" + +// appleseed.qtcommon headers. +#include "utility/miscellaneous.h" + +// Qt headers. +#include +#include +#include + +// Standard headers. +#include + +using namespace appleseed::qtcommon; +using namespace renderer; + +namespace appleseed { +namespace studio { + +namespace +{ + // Get a string from an OpenGL shader kind value. + const std::string shader_kind_to_string(const GLint shader_kind) + { + switch (shader_kind) + { + case GL_VERTEX_SHADER: return "Vertex"; + case GL_FRAGMENT_SHADER: return "Fragment"; + default: return "Unknown Kind"; + } + } + + // Compile an OpenGL shader. + void compile_shader( + QOpenGLFunctions_4_1_Core* f, + const GLuint shader, + const GLsizei count, + const GLchar** src_string, + const GLint* length) + { + f->glShaderSource(shader, count, src_string, length); + f->glCompileShader(shader); + + // Check compilation status. + GLint success; + f->glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + + if (!success) + { + char info_log[1024]; + f->glGetShaderInfoLog(shader, sizeof(info_log), NULL, info_log); + + GLint shader_kind; + f->glGetShaderiv(shader, GL_SHADER_TYPE, &shader_kind); + + const std::string shader_kind_string = shader_kind_to_string(shader_kind); + + RENDERER_LOG_ERROR( + "%s OpenGL shader compilation failed:\n%s", + shader_kind_string.c_str(), + info_log); + } + } + + // Link an OpenGL shader program with a vertex and optional fragment shader object. + void link_shader_program( + QOpenGLFunctions_4_1_Core* f, + const GLuint program, + const GLuint vert, + const GLuint frag) + { + f->glAttachShader(program, vert); + + if (frag != 0) + f->glAttachShader(program, frag); + + f->glLinkProgram(program); + + // Check linking status. + GLint success; + f->glGetProgramiv(program, GL_LINK_STATUS, &success); + + if (!success) + { + char info_log[1024]; + f->glGetProgramInfoLog(program, sizeof(info_log), NULL, info_log); + + RENDERER_LOG_ERROR( + "OpenGL shader program linking failed:\n%s", + info_log); + } + } +} + +GLuint create_shader_program( + QOpenGLFunctions_4_1_Core* f, + const QByteArray* vert_source, + const QByteArray* frag_source) +{ + assert(vert_source != 0); + const bool has_frag_shader = frag_source != 0; + + const GLuint vert = f->glCreateShader(GL_VERTEX_SHADER); + const GLuint frag = has_frag_shader ? f->glCreateShader(GL_FRAGMENT_SHADER) : 0; + + // Compile the vertex shader. + const GLchar* gl_vert_source = static_cast(vert_source->constData()); + const GLint gl_vert_source_length = static_cast(vert_source->size()); + + compile_shader(f, vert, 1, &gl_vert_source, &gl_vert_source_length); + + // Compile the fragment shader. + if (has_frag_shader) + { + const GLchar* gl_frag_source = static_cast(frag_source->constData()); + const GLint gl_frag_source_length = static_cast(frag_source->size()); + compile_shader(f, frag, 1, &gl_frag_source, &gl_frag_source_length); + } + + // Compile both shaders together. + const GLuint program = f->glCreateProgram(); + link_shader_program(f, program, vert, frag); + + f->glDeleteShader(vert); + + if (has_frag_shader) + f->glDeleteShader(frag); + + return program; +} + +QByteArray load_gl_shader(const QString& base_name) +{ + const QString resource_path(QString(":/shaders/%1").arg(base_name)); + + QFile file(resource_path); + file.open(QFile::ReadOnly); + + return file.readAll(); +} + +} // namespace studio +} // namespace appleseed diff --git a/src/appleseed.studio/utility/gl.h b/src/appleseed.studio/utility/gl.h new file mode 100644 index 0000000000..e04fa9c5b7 --- /dev/null +++ b/src/appleseed.studio/utility/gl.h @@ -0,0 +1,52 @@ + +// +// This source file is part of appleseed. +// Visit https://appleseedhq.net/ for additional information and resources. +// +// This software is released under the MIT license. +// +// Copyright (c) 2019 Gray Olson, The appleseedhq Organization +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#pragma once + +// Qt headers. +#include + +// Forward declarations. +class QByteArray; +class QString; + +namespace appleseed { +namespace studio { + +// Create an OpenlGL shader program with a vertex and optional fragment shader source code. +GLuint create_shader_program( + QOpenGLFunctions_4_1_Core* f, + const QByteArray* vert_source, + const QByteArray* frag_source = 0); + +// Load a GLSL shader from file into a QByteArray. +QByteArray load_gl_shader(const QString& base_name); + +} // namespace studio +} // namespace appleseed + diff --git a/src/appleseed/renderer/kernel/lighting/directlightingintegrator.cpp b/src/appleseed/renderer/kernel/lighting/directlightingintegrator.cpp index 23a486935b..2b93e47adf 100644 --- a/src/appleseed/renderer/kernel/lighting/directlightingintegrator.cpp +++ b/src/appleseed/renderer/kernel/lighting/directlightingintegrator.cpp @@ -468,6 +468,7 @@ void DirectLightingIntegrator::add_emitting_shape_sample_contribution( light_path_stream->sampled_emitting_shape( *sample.m_shape, sample.m_point, + sample.m_geometric_normal, material_value.m_beauty, edf_value); } @@ -547,6 +548,7 @@ void DirectLightingIntegrator::add_non_physical_light_sample_contribution( light_path_stream->sampled_non_physical_light( *light, emission_position, + m_material_sampler.get_shading_point().get_geometric_normal(), material_value.m_beauty, light_value); } diff --git a/src/appleseed/renderer/kernel/lighting/lightpathrecorder.cpp b/src/appleseed/renderer/kernel/lighting/lightpathrecorder.cpp index 864ed35516..d3ebd97784 100644 --- a/src/appleseed/renderer/kernel/lighting/lightpathrecorder.cpp +++ b/src/appleseed/renderer/kernel/lighting/lightpathrecorder.cpp @@ -236,7 +236,10 @@ void LightPathRecorder::query( { for (size_t x = x0; x <= x1; ++x) { - const auto& index_entry = impl->m_index[y * impl->m_render_width + x]; + const std::size_t pixel_index = y * impl->m_render_width + x; + assert(pixel_index < impl->m_index.size()); + + const auto& index_entry = impl->m_index[pixel_index]; for (size_t p = index_entry.m_begin_path; p < index_entry.m_end_path; ++p) { @@ -275,6 +278,10 @@ void LightPathRecorder::get_light_path_vertex( result.m_radiance[0] = source_vertex.m_radiance[0]; result.m_radiance[1] = source_vertex.m_radiance[1]; result.m_radiance[2] = source_vertex.m_radiance[2]; + + result.m_surface_normal[0] = source_vertex.m_surface_normal[0]; + result.m_surface_normal[1] = source_vertex.m_surface_normal[1]; + result.m_surface_normal[2] = source_vertex.m_surface_normal[2]; } bool LightPathRecorder::write(const char* filename) const diff --git a/src/appleseed/renderer/kernel/lighting/lightpathrecorder.h b/src/appleseed/renderer/kernel/lighting/lightpathrecorder.h index cebcebc3d9..93e094c774 100644 --- a/src/appleseed/renderer/kernel/lighting/lightpathrecorder.h +++ b/src/appleseed/renderer/kernel/lighting/lightpathrecorder.h @@ -68,6 +68,7 @@ struct LightPathVertex const Entity* m_entity; float m_position[3]; float m_radiance[3]; + float m_surface_normal[3]; }; diff --git a/src/appleseed/renderer/kernel/lighting/lightpathstream.cpp b/src/appleseed/renderer/kernel/lighting/lightpathstream.cpp index 128c3d3dcd..4cc112e3e9 100644 --- a/src/appleseed/renderer/kernel/lighting/lightpathstream.cpp +++ b/src/appleseed/renderer/kernel/lighting/lightpathstream.cpp @@ -77,7 +77,8 @@ void LightPathStream::clear() void LightPathStream::begin_path( const PixelContext& pixel_context, const Camera* camera, - const Vector3d& camera_vertex_position) + const Vector3d& camera_vertex_position, + const Vector3d& camera_vertex_normal) { assert(m_events.empty()); assert(m_hit_reflector_data.empty()); @@ -90,6 +91,7 @@ void LightPathStream::begin_path( m_camera = camera; m_camera_vertex_position = Vector3f(camera_vertex_position); + m_camera_vertex_normal = Vector3f(camera_vertex_normal); m_pixel_coords = pixel_context.get_pixel_coords(); m_sample_position = Vector2f(pixel_context.get_sample_position()); } @@ -107,6 +109,7 @@ void LightPathStream::hit_reflector(const PathVertex& vertex) HitReflectorData data; data.m_object_instance = &vertex.m_shading_point->get_object_instance(); data.m_vertex_position = Vector3f(vertex.get_point()); + data.m_surface_normal = Vector3f(vertex.get_geometric_normal()); data.m_path_throughput = vertex.m_throughput.to_rgb(g_std_lighting_conditions); m_hit_reflector_data.push_back(data); } @@ -126,6 +129,7 @@ void LightPathStream::hit_emitter( HitEmitterData data; data.m_object_instance = &vertex.m_shading_point->get_object_instance(); data.m_vertex_position = Vector3f(vertex.get_point()); + data.m_surface_normal = Vector3f(vertex.get_geometric_normal()); data.m_path_throughput = vertex.m_throughput.to_rgb(g_std_lighting_conditions); data.m_emitted_radiance = emitted_radiance.to_rgb(g_std_lighting_conditions); m_hit_emitter_data.push_back(data); @@ -134,6 +138,7 @@ void LightPathStream::hit_emitter( void LightPathStream::sampled_emitting_shape( const EmittingShape& shape, const Vector3d& emission_position, + const Vector3d& emission_normal, const Spectrum& material_value, const Spectrum& emitted_radiance) { @@ -147,6 +152,7 @@ void LightPathStream::sampled_emitting_shape( shape.get_assembly_instance()->get_assembly().object_instances().get_by_index( shape.get_object_instance_index()); data.m_vertex_position = Vector3f(emission_position); + data.m_surface_normal = Vector3f(emission_normal); data.m_material_value = material_value.to_rgb(g_std_lighting_conditions); data.m_emitted_radiance = emitted_radiance.to_rgb(g_std_lighting_conditions); m_sampled_emitter_data.push_back(data); @@ -155,6 +161,7 @@ void LightPathStream::sampled_emitting_shape( void LightPathStream::sampled_non_physical_light( const Light& light, const Vector3d& emission_position, + const Vector3d& emission_normal, const Spectrum& material_value, const Spectrum& emitted_radiance) { @@ -166,6 +173,7 @@ void LightPathStream::sampled_non_physical_light( SampledEmitterData data; data.m_entity = &light; data.m_vertex_position = Vector3f(emission_position); + data.m_surface_normal = Vector3f(emission_normal); data.m_material_value = material_value.to_rgb(g_std_lighting_conditions); data.m_emitted_radiance = emitted_radiance.to_rgb(g_std_lighting_conditions); m_sampled_emitter_data.push_back(data); @@ -244,6 +252,7 @@ void LightPathStream::create_path_from_hit_emitter(const size_t emitter_event_in StoredPathVertex emitter_vertex; emitter_vertex.m_entity = hit_emitter_data.m_object_instance; emitter_vertex.m_position = hit_emitter_data.m_vertex_position; + emitter_vertex.m_surface_normal = hit_emitter_data.m_surface_normal; emitter_vertex.m_radiance = hit_emitter_data.m_emitted_radiance; m_vertices.push_back(emitter_vertex); @@ -264,6 +273,7 @@ void LightPathStream::create_path_from_hit_emitter(const size_t emitter_event_in StoredPathVertex reflector_vertex; reflector_vertex.m_entity = event_data.m_object_instance; reflector_vertex.m_position = event_data.m_vertex_position; + reflector_vertex.m_surface_normal = event_data.m_surface_normal; reflector_vertex.m_radiance = current_radiance; m_vertices.push_back(reflector_vertex); @@ -288,6 +298,7 @@ void LightPathStream::create_path_from_hit_emitter(const size_t emitter_event_in StoredPathVertex camera_vertex; camera_vertex.m_entity = m_camera; camera_vertex.m_position = m_camera_vertex_position; + camera_vertex.m_surface_normal = m_camera_vertex_normal; camera_vertex.m_radiance = current_radiance; m_vertices.push_back(camera_vertex); @@ -319,6 +330,7 @@ void LightPathStream::create_path_from_sampled_emitter(const size_t emitter_even StoredPathVertex emitter_vertex; emitter_vertex.m_entity = sampled_emitter_data.m_entity; emitter_vertex.m_position = sampled_emitter_data.m_vertex_position; + emitter_vertex.m_surface_normal = sampled_emitter_data.m_surface_normal; emitter_vertex.m_radiance = sampled_emitter_data.m_emitted_radiance; m_vertices.push_back(emitter_vertex); @@ -339,6 +351,7 @@ void LightPathStream::create_path_from_sampled_emitter(const size_t emitter_even StoredPathVertex reflector_vertex; reflector_vertex.m_entity = event_data.m_object_instance; reflector_vertex.m_position = event_data.m_vertex_position; + reflector_vertex.m_surface_normal = event_data.m_surface_normal; reflector_vertex.m_radiance = current_radiance; m_vertices.push_back(reflector_vertex); @@ -363,6 +376,7 @@ void LightPathStream::create_path_from_sampled_emitter(const size_t emitter_even StoredPathVertex camera_vertex; camera_vertex.m_entity = m_camera; camera_vertex.m_position = m_camera_vertex_position; + camera_vertex.m_surface_normal = m_camera_vertex_normal; camera_vertex.m_radiance = current_radiance; m_vertices.push_back(camera_vertex); @@ -394,6 +408,7 @@ void LightPathStream::create_path_from_sampled_environment(const size_t env_even StoredPathVertex emitter_vertex; emitter_vertex.m_entity = sampled_env_data.m_environment_edf; emitter_vertex.m_position = last_reflector_data.m_vertex_position + m_scene_diameter * sampled_env_data.m_emission_direction; + emitter_vertex.m_surface_normal = -sampled_env_data.m_emission_direction; emitter_vertex.m_radiance = sampled_env_data.m_emitted_radiance; m_vertices.push_back(emitter_vertex); @@ -414,6 +429,7 @@ void LightPathStream::create_path_from_sampled_environment(const size_t env_even StoredPathVertex reflector_vertex; reflector_vertex.m_entity = event_data.m_object_instance; reflector_vertex.m_position = event_data.m_vertex_position; + reflector_vertex.m_surface_normal = event_data.m_surface_normal; reflector_vertex.m_radiance = current_radiance; m_vertices.push_back(reflector_vertex); @@ -438,6 +454,7 @@ void LightPathStream::create_path_from_sampled_environment(const size_t env_even StoredPathVertex camera_vertex; camera_vertex.m_entity = m_camera; camera_vertex.m_position = m_camera_vertex_position; + camera_vertex.m_surface_normal = m_camera_vertex_normal; camera_vertex.m_radiance = current_radiance; m_vertices.push_back(camera_vertex); diff --git a/src/appleseed/renderer/kernel/lighting/lightpathstream.h b/src/appleseed/renderer/kernel/lighting/lightpathstream.h index 8c008e3b20..e838880243 100644 --- a/src/appleseed/renderer/kernel/lighting/lightpathstream.h +++ b/src/appleseed/renderer/kernel/lighting/lightpathstream.h @@ -67,7 +67,8 @@ class LightPathStream void begin_path( const PixelContext& pixel_context, const Camera* camera, - const foundation::Vector3d& camera_vertex_position); + const foundation::Vector3d& camera_vertex_position, + const foundation::Vector3d& camera_vertex_normal); void hit_reflector( const PathVertex& vertex); @@ -79,12 +80,14 @@ class LightPathStream void sampled_emitting_shape( const EmittingShape& shape, const foundation::Vector3d& emission_position, + const foundation::Vector3d& emission_normal, const Spectrum& material_value, const Spectrum& emitted_radiance); void sampled_non_physical_light( const Light& light, const foundation::Vector3d& emission_position, + const foundation::Vector3d& emission_normal, const Spectrum& material_value, const Spectrum& emitted_radiance); @@ -117,6 +120,7 @@ class LightPathStream { const ObjectInstance* m_object_instance; // object instance that was hit foundation::Vector3f m_vertex_position; // world space position of the hit point on the reflector + foundation::Vector3f m_surface_normal; // world space normal of the surface of the reflector foundation::Color3f m_path_throughput; // cumulative path throughput up to but excluding this vertex, in reverse order (i.e. in the order from camera to light source) }; @@ -130,6 +134,7 @@ class LightPathStream { const Entity* m_entity; // object instance or non-physical light that was sampled foundation::Vector3f m_vertex_position; // world space position of the emitting point on the emitter + foundation::Vector3f m_surface_normal; // world space normal of the surface of the emitter foundation::Color3f m_material_value; // BSDF value at the previous vertex foundation::Color3f m_emitted_radiance; // emitted radiance in W.sr^-1.m^-2 }; @@ -157,6 +162,7 @@ class LightPathStream const Entity* m_entity; // object instance or non-physical light foundation::Vector3f m_position; // world space position of this vertex foundation::Color3f m_radiance; // radiance arriving at this vertex, in W.sr^-1.m^-2 + foundation::Vector3f m_surface_normal; // world space normal of the surface at this vertex }; // Scene. @@ -168,6 +174,7 @@ class LightPathStream foundation::Vector2i m_pixel_coords; foundation::Vector2f m_sample_position; foundation::Vector3f m_camera_vertex_position; + foundation::Vector3f m_camera_vertex_normal; // Scattering events (transient). std::vector m_events; diff --git a/src/appleseed/renderer/kernel/lighting/pt/ptlightingengine.cpp b/src/appleseed/renderer/kernel/lighting/pt/ptlightingengine.cpp index bb217ec5d7..6f87dc8b88 100644 --- a/src/appleseed/renderer/kernel/lighting/pt/ptlightingengine.cpp +++ b/src/appleseed/renderer/kernel/lighting/pt/ptlightingengine.cpp @@ -168,7 +168,8 @@ namespace m_light_path_stream->begin_path( pixel_context, shading_point.get_scene().get_render_data().m_active_camera, - shading_point.get_ray().m_org); + shading_point.get_ray().m_org, + shading_point.get_ray().m_dir); } if (m_params.m_next_event_estimation) diff --git a/src/appleseed/renderer/modeling/camera/orthographiccamera.cpp b/src/appleseed/renderer/modeling/camera/orthographiccamera.cpp index f5f3eb207a..519f02bea3 100644 --- a/src/appleseed/renderer/modeling/camera/orthographiccamera.cpp +++ b/src/appleseed/renderer/modeling/camera/orthographiccamera.cpp @@ -80,6 +80,15 @@ namespace const ParamArray& params) : Camera(name, params) { + // Extract the film dimensions from the camera parameters. + m_film_dimensions = extract_film_dimensions(); + + // Extract the abscissa of the near plane from the camera parameters. + m_near_z = extract_near_z(); + + // Precompute reciprocals of film dimensions. + m_rcp_film_width = 1.0 / m_film_dimensions[0]; + m_rcp_film_height = 1.0 / m_film_dimensions[1]; } void release() override @@ -125,19 +134,9 @@ namespace if (!Camera::on_render_begin(project, parent, recorder, abort_switch)) return false; - // Extract the film dimensions from the camera parameters. - m_film_dimensions = extract_film_dimensions(); - - // Extract the abscissa of the near plane from the camera parameters. - m_near_z = extract_near_z(); - // Retrieve the scene diameter that will be used to position the camera. m_safe_scene_diameter = project.get_scene()->get_render_data().m_safe_diameter; - // Precompute reciprocals of film dimensions. - m_rcp_film_width = 1.0 / m_film_dimensions[0]; - m_rcp_film_height = 1.0 / m_film_dimensions[1]; - // Precompute pixel area. const size_t pixel_count = project.get_frame()->image().properties().m_pixel_count; m_rcp_pixel_area = static_cast(pixel_count / (m_film_dimensions[0] * m_film_dimensions[1])); diff --git a/src/appleseed/renderer/modeling/camera/perspectivecamera.cpp b/src/appleseed/renderer/modeling/camera/perspectivecamera.cpp index 769065d018..ce96322154 100644 --- a/src/appleseed/renderer/modeling/camera/perspectivecamera.cpp +++ b/src/appleseed/renderer/modeling/camera/perspectivecamera.cpp @@ -54,6 +54,21 @@ namespace renderer PerspectiveCamera::PerspectiveCamera(const char* name, const ParamArray& params) : Camera(name, params) { + // Extract the film dimensions from the camera parameters. + m_film_dimensions = extract_film_dimensions(); + + // Extract the focal length from the camera parameters. + m_focal_length = extract_focal_length(m_film_dimensions[0]); + + // Extract the abscissa of the near plane from the camera parameters. + m_near_z = extract_near_z(); + + // Extract the shift from the camera parameters. + m_shift = extract_shift(); + + // Precompute reciprocals of film dimensions. + m_rcp_film_width = 1.0 / m_film_dimensions[0]; + m_rcp_film_height = 1.0 / m_film_dimensions[1]; } const foundation::Vector2d& PerspectiveCamera::get_film_dimensions() const @@ -80,22 +95,6 @@ bool PerspectiveCamera::on_render_begin( if (!Camera::on_render_begin(project, parent, recorder, abort_switch)) return false; - // Extract the film dimensions from the camera parameters. - m_film_dimensions = extract_film_dimensions(); - - // Extract the focal length from the camera parameters. - m_focal_length = extract_focal_length(m_film_dimensions[0]); - - // Extract the abscissa of the near plane from the camera parameters. - m_near_z = extract_near_z(); - - // Extract the shift from the camera parameters. - m_shift = extract_shift(); - - // Precompute reciprocals of film dimensions. - m_rcp_film_width = 1.0 / m_film_dimensions[0]; - m_rcp_film_height = 1.0 / m_film_dimensions[1]; - // Precompute pixel area. const size_t pixel_count = project.get_frame()->image().properties().m_pixel_count; m_pixel_area = m_film_dimensions[0] * m_film_dimensions[1] / pixel_count;