Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,28 @@ Using the SHIFT + CTRL keys, you will trigger events on key pressed/released.
* `-a` or `--start`: if true, starts the simulation just after opening. True by default.
* `-s` or `--fullscreen`: set full screen at startup. False by default.
* `-l` or `--load`: load given plugins as a comma-separated list. Example: -l SofaPython3
* `-m` or `--msaa_samples`: set the number of samples for multisample anti-aliasing (MSAA). 0 by default.
* `-n` or `--nb_iterations`: set the number of iterations to run (batch mode). 0 (unlimited) by default.
* `--hideProgressBar`: hide the progress bar shown during a bounded run. False by default.
* `--offscreen`: render offscreen. No window is shown, but the graphics functions are still called. False by default.
The window and its OpenGL context are created as usual, they are simply never mapped on screen. Combine it with
`--nb_iterations` to get a finite run, since there is no window to close.

The `-n`/`--nbIter` option of the batch GUI is also honoured by the `glfw` and `imgui` GUIs:
`runSofa -g imgui -l SofaImGui -n 100 myScene.scn` stops after 100 iterations, and `infinite`
(the batch default) keeps running. As in the batch GUI, giving a number of iterations starts the
animation, `-a` is not needed.

A bounded run shows the same progress bar as the batch GUI, hidden with `--hideProgressBar`, and reports
the number of iterations, the elapsed time and the average FPS when it ends.

The `--offscreen` option is also available from `runSofa` with the `glfw` GUI:
`runSofa -g glfw -l SofaGLFW --offscreen myScene.scn`
(as for any GUI argument coming from a plugin, it is only listed by `runSofa --help` when the plugin is autoloaded,
but it is taken into account in all cases).

It is ignored, with a warning, by the `imgui` GUI: that GUI is the `glfw` one plus interactive widgets, so
rendering it offscreen would serve no purpose.

## Dear ImGui

Expand Down
5 changes: 5 additions & 0 deletions SofaGLFW/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ sofa_find_package(Sofa.Simulation.Common REQUIRED)
sofa_find_package(Sofa.GL REQUIRED)
sofa_find_package(Sofa.Component.Visual REQUIRED)
sofa_find_package(Sofa.GUI.Common QUIET)
sofa_find_package(Sofa.GUI.Batch QUIET)

if( UNIX AND NOT APPLE )
option(SOFAGLFW_USEX11 "Flag to force x11 for windows management" ON)
Expand Down Expand Up @@ -78,6 +79,10 @@ if(Sofa.GUI.Common_FOUND)
target_link_libraries(${PROJECT_NAME} PUBLIC Sofa.GUI.Common)
endif()

if(Sofa.GUI.Batch_FOUND)
target_link_libraries(${PROJECT_NAME} PRIVATE Sofa.GUI.Batch)
endif()

sofa_find_package(SofaPython3 QUIET)
if(SofaPython3_FOUND)
add_subdirectory(bindings)
Expand Down
48 changes: 48 additions & 0 deletions SofaGLFW/src/SofaGLFW/SofaGLFWBaseGUI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@
#include <sofa/helper/system/SetDirectory.h>
#include <sofa/helper/Utils.h>

#if SOFAGLFW_HAVE_SOFA_GUI_BATCH
#include <sofa/gui/batch/ProgressBar.h>
#endif

#include <algorithm>
#include <chrono>
#include <filesystem>
#include <map>

Expand Down Expand Up @@ -251,6 +256,15 @@ bool SofaGLFWBaseGUI::createWindow(int width, int height, const char* title, boo
return false;
}

// the hint is sticky, hence always set explicitly
glfwWindowHint(GLFW_VISIBLE, m_bOffscreen ? GLFW_FALSE : GLFW_TRUE);

if (m_bOffscreen && fullscreenAtStartup)
{
msg_warning("SofaGLFWBaseGUI") << "Fullscreen is ignored in offscreen mode.";
fullscreenAtStartup = false;
}

GLFWwindow* glfwWindow = nullptr;
if (fullscreenAtStartup)
{
Expand All @@ -275,6 +289,7 @@ bool SofaGLFWBaseGUI::createWindow(int width, int height, const char* title, boo
s_numberOfActiveWindows++;

#ifndef __APPLE__ // Apple implies Cocoa and Cocoa does not support icon for the window
if (!m_bOffscreen)
{
setWindowIcon(glfwWindow);
}
Expand Down Expand Up @@ -383,6 +398,12 @@ bool SofaGLFWBaseGUI::isFullScreen(GLFWwindow* glfwWindow) const

void SofaGLFWBaseGUI::switchFullScreen(GLFWwindow* glfwWindow, unsigned int /* screenID */)
{
if (m_bOffscreen)
{
msg_warning("SofaGLFWBaseGUI") << "Cannot switch to fullscreen in offscreen mode.";
return;
}

if (hasWindow())
{
// only manage the first window for now
Expand Down Expand Up @@ -481,6 +502,16 @@ std::size_t SofaGLFWBaseGUI::runLoop(std::size_t targetNbIterations)
std::stringstream tmpStr;
std::vector<uint8_t> pixels;

#if SOFAGLFW_HAVE_SOFA_GUI_BATCH
std::unique_ptr<sofa::gui::batch::ProgressBar> progressBar;
if (targetNbIterations > 0 && !m_bHideProgressBar)
{
progressBar = std::make_unique<sofa::gui::batch::ProgressBar>(static_cast<int>(targetNbIterations));
}
#endif

const auto startTime = std::chrono::steady_clock::now();

while (s_numberOfActiveWindows > 0 && running)
{
SIMULATION_LOOP_SCOPE
Expand Down Expand Up @@ -552,10 +583,27 @@ std::size_t SofaGLFWBaseGUI::runLoop(std::size_t targetNbIterations)
delete sofaGlfwWindow;
}

#if SOFAGLFW_HAVE_SOFA_GUI_BATCH
if (progressBar)
{
progressBar->tick();
}
#endif

currentNbIterations++;
running = (targetNbIterations > 0) ? currentNbIterations < targetNbIterations : true;
}

// measurements only make sense for a bounded run
if (targetNbIterations > 0)
{
const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - startTime).count() / 1000.0;
msg_info("SofaGLFWBaseGUI") << currentNbIterations << " iterations done in " << elapsed << " s ( "
<< (elapsed > 0.0 ? static_cast<double>(currentNbIterations) / elapsed : 0.0)
<< " FPS)." << msgendl;
}

return currentNbIterations;
}

Expand Down
11 changes: 11 additions & 0 deletions SofaGLFW/src/SofaGLFW/SofaGLFWBaseGUI.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ class SOFAGLFW_API SofaGLFWBaseGUI : public BaseViewer

bool createWindow(int width, int height, const char* title, bool fullscreenAtStartup = false);
void destroyWindow();

/// Window and OpenGL context created but never shown; graphics functions are still
/// called. To be set before createWindow().
void setOffscreen(bool offscreen) { m_bOffscreen = offscreen; }
bool isOffscreen() const { return m_bOffscreen; }

/// Hide the progress bar of a bounded run
void setHideProgressBar(bool hide) { m_bHideProgressBar = hide; }

void initVisual();
std::size_t runLoop(std::size_t targetNbIterations = 0);
void terminate();
Expand Down Expand Up @@ -156,6 +165,8 @@ class SOFAGLFW_API SofaGLFWBaseGUI : public BaseViewer

bool m_bGlfwIsInitialized{ false };
bool m_bGlewIsInitialized{ false };
bool m_bOffscreen{ false };
bool m_bHideProgressBar{ false };

std::unique_ptr<sofa::gl::DrawToolGL> m_glDrawTool;
sofa::core::visual::VisualParams* m_vparams{ nullptr };
Expand Down
82 changes: 81 additions & 1 deletion SofaGLFW/src/SofaGLFW/SofaGLFWGUI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,76 @@
#include <sofa/simulation/Node.h>
#include <sofa/simulation/Simulation.h>
#include <sofa/component/setting/ViewerSetting.h>
#include <sofa/gui/common/ArgumentParser.h>
#include <sofa/type/hardening.h>

using namespace sofa;

namespace sofaglfw
{

sofa::gui::common::ArgumentParser* SofaGLFWGUI::s_argumentParser = nullptr;

bool SofaGLFWGUI::init()
{
return m_baseGUI.init();
}

int SofaGLFWGUI::mainLoop()
{
m_baseGUI.runLoop();
const std::size_t targetNbIterations = getTargetNbIterations();
if (targetNbIterations > 0)
{
// a bounded run is meant to be computed, as in runSofaGLFW and the batch GUI
m_baseGUI.setSimulationIsRunning(true);
}

// '--hideProgressBar' is registered by the batch GUI; it is only read here
bool hideProgressBar = false;
if (s_argumentParser)
{
s_argumentParser->getValueFromKey("hideProgressBar", hideProgressBar);
}
m_baseGUI.setHideProgressBar(hideProgressBar);
m_baseGUI.runLoop(targetNbIterations);
return 0;
}

bool SofaGLFWGUI::isOffscreenRequested()
{
bool offscreen = false;
if (s_argumentParser)
{
s_argumentParser->getValueFromKey("offscreen", offscreen);
}
return offscreen;
}

std::size_t SofaGLFWGUI::getTargetNbIterations()
{
// '-n'/'--nbIter' is registered by the batch GUI; it is only read here
std::string value;
if (!s_argumentParser || !s_argumentParser->getValueFromKey("nbIter", value))
{
return 0;
}

if (value == "infinite")
{
return 0;
}

int nbIterations = 0;
if (!sofa::type::hardening::safeStrToInt(value, nbIterations) || nbIterations < 0)
{
msg_warning("SofaGLFWGUI") << "Invalid number of iterations: '" << value << "'. Ignored.";
return 0;
}

msg_info("SofaGLFWGUI") << "Computing " << nbIterations << " iterations.";
return static_cast<std::size_t>(nbIterations);
}

void SofaGLFWGUI::redraw()
{

Expand All @@ -63,6 +116,13 @@ void SofaGLFWGUI::setScene(sofa::simulation::NodeSPtr groot, const char* filenam
m_baseGUI.setSimulation(groot, strFilename);

m_baseGUI.load();
const bool interactive = isInteractive();
const bool offscreen = isOffscreenRequested();
if (interactive && offscreen)
{
msg_warning("SofaGLFWGUI") << "The '" << mGuiName << "' GUI is interactive: '--offscreen' is ignored.";
}
m_baseGUI.setOffscreen(!interactive && offscreen);
m_baseGUI.createWindow(m_baseGUI.getWindowWidth(), m_baseGUI.getWindowHeight(), std::string("SOFA - " + strFilename).c_str(), m_bCreateWithFullScreen);

// needs to be done after for background
Expand Down Expand Up @@ -140,6 +200,26 @@ sofa::gui::common::BaseGUI* SofaGLFWGUI::CreateGUI(const char* name, sofa::simul
return gui;
}

int SofaGLFWGUI::RegisterGUIParameters(sofa::gui::common::ArgumentParser* argumentParser)
{
s_argumentParser = argumentParser;

// GUIManager declares the parameters of every registered GUI: only add them once
static bool alreadyRegistered = false;
if (alreadyRegistered)
{
return 0;
}
alreadyRegistered = true;

argumentParser->addArgument(
cxxopts::value<bool>()->default_value("false"),
"offscreen",
"(only glfw) render offscreen: no window is shown but the graphics functions are still called"
);
return 0;
}

void SofaGLFWGUI::setMouseButtonConfiguration(sofa::component::setting::MouseButtonSetting *setting)
{
if (setting)
Expand Down
21 changes: 21 additions & 0 deletions SofaGLFW/src/SofaGLFW/SofaGLFWGUI.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@

#include <sofa/component/setting/ViewerSetting.h>

namespace sofa::gui::common
{
class ArgumentParser;
}

namespace sofaglfw
{

Expand All @@ -52,12 +57,28 @@ class SOFAGLFW_API SofaGLFWGUI : public sofa::gui::common::BaseGUI
void setBackgroundColor(const sofa::type::RGBAColor& color) override;
void setBackgroundImage(const std::string& image) override;
static sofa::gui::common::BaseGUI * CreateGUI(const char* name, sofa::simulation::NodeSPtr groot, const char* filename);
/// Command line arguments shared by the GLFW-based GUIs (glfw, imgui); calling it more
/// than once is a no-op.
static int RegisterGUIParameters(sofa::gui::common::ArgumentParser* argumentParser);

/// Number of iterations requested with the '-n'/'--nbIter' argument of the batch GUI;
/// 0 when unlimited.
static std::size_t getTargetNbIterations();

/// Whether '--offscreen' was given on the command line
static bool isOffscreenRequested();
void setMouseButtonConfiguration(sofa::component::setting::MouseButtonSetting *setting) override;

/// An interactive GUI ignores the arguments meant for headless rendering
virtual bool isInteractive() const { return false; }

virtual BaseViewer* getViewer() override;
protected:
SofaGLFWBaseGUI m_baseGUI;
bool m_bCreateWithFullScreen{ false };

/// duplicates GUIManager::currentArgumentParser, protected: would need an accessor in SOFA
static sofa::gui::common::ArgumentParser* s_argumentParser;
};

} // namespace sofaglfw
22 changes: 11 additions & 11 deletions SofaGLFW/src/SofaGLFW/SofaGLFWWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ void SofaGLFWWindow::close()

void SofaGLFWWindow::draw(simulation::NodeSPtr groot, core::visual::VisualParams* vparams)
{
if (!m_currentCamera)
{
msg_error("SofaGLFWGUI") << "No camera defined.";
return;
}

glClearColor(m_backgroundColor.r(), m_backgroundColor.g(), m_backgroundColor.b(), m_backgroundColor.a());
glClearDepth(1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
Expand All @@ -81,13 +87,6 @@ void SofaGLFWWindow::draw(simulation::NodeSPtr groot, core::visual::VisualParams
glDisable(GL_COLOR_MATERIAL);

// draw the scene
if (!m_currentCamera)
{
msg_error("SofaGLFWGUI") << "No camera defined.";
return;
}


if (groot->f_bbox.getValue().isValid())
{
vparams->sceneBBox() = groot->f_bbox.getValue();
Expand Down Expand Up @@ -169,14 +168,15 @@ void SofaGLFWWindow::drawBackgroundImage()
if(!m_backgrounds.contains(m_currentBackgroundFilename))
return;

glPushAttrib(GL_ALL_ATTRIB_BITS);

glDisable(GL_LIGHTING);

const auto& background = m_backgrounds[m_currentBackgroundFilename];

// before glPushAttrib(), otherwise the attribute stack leaks on every frame
if(!background.image)
return;

glPushAttrib(GL_ALL_ATTRIB_BITS);

glDisable(GL_LIGHTING);

const int imageWidth = background.image->getWidth();
const int imageHeight = background.image->getHeight();
Expand Down
1 change: 1 addition & 0 deletions SofaGLFW/src/SofaGLFW/config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#define SOFAGLFW_VERSION @PROJECT_VERSION@

#cmakedefine01 SOFAGLFW_HAVE_SOFA_GUI_COMMON
#cmakedefine01 SOFAGLFW_HAVE_SOFA_GUI_BATCH
#cmakedefine01 SOFAGLFW_HAVE_FFMPEG

#cmakedefine SOFAGLFW_USEX11_INTERNAL
Expand Down
2 changes: 1 addition & 1 deletion SofaGLFW/src/SofaGLFW/initSofaGLFW.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void init()
{
first = false;
#if SOFAGLFW_HAVE_SOFA_GUI_COMMON
sofa::gui::common::GUIManager::RegisterGUI("glfw", &sofaglfw::SofaGLFWGUI::CreateGUI,nullptr,0);
sofa::gui::common::GUIManager::RegisterGUI("glfw", &sofaglfw::SofaGLFWGUI::CreateGUI, &sofaglfw::SofaGLFWGUI::RegisterGUIParameters, 0);
#endif // SOFAGLFW_HAVE_SOFA_GUI_COMMON
}
}
Expand Down
Loading
Loading