Difference between revisions of "Dev:Adding a Render progress bar"

From Synfig Studio :: Documentation
Jump to: navigation, search
(2nd part)
(Done)
Line 1: Line 1:
 
Now let's implement a Render Progress Bar!<br />
 
Now let's implement a Render Progress Bar!<br />
 
It has been requested several time:<br />
 
It has been requested several time:<br />
[https://github.com/synfig/synfig/issues/383| Indication needed that rendering is in progress. #383]<br />
+
[https://github.com/synfig/synfig/issues/383 Indication needed that rendering is in progress. #383]<br />
[https://github.com/synfig/synfig/issues/626| Feature request: Give feedback when rendering is happening and complete #626]<br />
+
[https://github.com/synfig/synfig/issues/626 Feature request: Give feedback when rendering is happening and complete #626]<br />
also in [https://github.com/synfig/synfig/issues/464| Default render parameter are bad #464]
+
also in [https://github.com/synfig/synfig/issues/464 Default render parameter are bad #464]
  
 
== Things to take into account ==
 
== Things to take into account ==
Line 17: Line 17:
 
'''App''' ([https://github.com/synfig/synfig/blob/master/synfig-studio/src/gui/app.h .h][https://github.com/synfig/synfig/blob/master/synfig-studio/src/gui/app.cpp .cpp]) is the root of everything and used to store globals.<br />
 
'''App''' ([https://github.com/synfig/synfig/blob/master/synfig-studio/src/gui/app.h .h][https://github.com/synfig/synfig/blob/master/synfig-studio/src/gui/app.cpp .cpp]) is the root of everything and used to store globals.<br />
 
But we start in fact from '''CanvasView''' ([https://github.com/synfig/synfig/blob/master/synfig-studio/src/gui/canvasview.h .h][https://github.com/synfig/synfig/blob/master/synfig-studio/src/gui/canvasview.cpp .cpp]) which contains the call to display the '''RenderSettings''' Dialog ([https://github.com/synfig/synfig/blob/master/synfig-studio/src/gui/render.h .h][https://github.com/synfig/synfig/blob/master/synfig-studio/src/gui/render.cpp .cpp]).<br />
 
But we start in fact from '''CanvasView''' ([https://github.com/synfig/synfig/blob/master/synfig-studio/src/gui/canvasview.h .h][https://github.com/synfig/synfig/blob/master/synfig-studio/src/gui/canvasview.cpp .cpp]) which contains the call to display the '''RenderSettings''' Dialog ([https://github.com/synfig/synfig/blob/master/synfig-studio/src/gui/render.h .h][https://github.com/synfig/synfig/blob/master/synfig-studio/src/gui/render.cpp .cpp]).<br />
Then we press on render button which leads to execute an '''AsyncRender''' ([https://github.com/synfig/synfig/blob/master/synfig-studio/src/gui/asyncrenderer.h .h][https://github.com/synfig/synfig/blob/master/synfig-studio/src/gui/asyncrenderer.cpp .cpp]) (or 2, sequentially, if we have a second pass for Alpha extraction).<br />
+
Then we press on render button which leads to execute an '''AsyncRenderer''' ([https://github.com/synfig/synfig/blob/master/synfig-studio/src/gui/asyncrenderer.h .h][https://github.com/synfig/synfig/blob/master/synfig-studio/src/gui/asyncrenderer.cpp .cpp]) (or 2, sequentially, if we have a second pass for Alpha extraction).<br />
 
'''AsyncRenderer''' can have 4 types of targets, ''AsyncTarget_Cairo'', AsyncTarget_Cairo_Tile, ''AsyncTarget_Scanline'', AsyncTarget_Tile.<br />
 
'''AsyncRenderer''' can have 4 types of targets, ''AsyncTarget_Cairo'', AsyncTarget_Cairo_Tile, ''AsyncTarget_Scanline'', AsyncTarget_Tile.<br />
 
Only ''AsyncTarget_Cairo'' and ''AsyncTarget_Scanline'' have a ''frame_ready()'' function that we will use to implement our call to update to the Render ProgressBar.<br />
 
Only ''AsyncTarget_Cairo'' and ''AsyncTarget_Scanline'' have a ''frame_ready()'' function that we will use to implement our call to update to the Render ProgressBar.<br />
  
 
Note that we can have 2 passes, this has to be considered when displaying the percents of accomplished render.<br />
 
Note that we can have 2 passes, this has to be considered when displaying the percents of accomplished render.<br />
 +
 +
The render itself is done writing the data to the target, then the selected codec is fed through a pipe (for example ffmpeg).
 +
 +
=== Where to do calls and implementation? ===
 +
In different files, as it is a "multi-level" task.<br />
 +
The details are described in each file touched in the next section.
  
 
== Implementation ==
 
== Implementation ==
Line 52: Line 58:
  
 
In Dock_Info(), at the end:
 
In Dock_Info(), at the end:
 +
''just before table->show_all();''
 
  //Render Progress Bar
 
  //Render Progress Bar
 
  table->attach(*manage(new Gtk::Label(_("Render Progress: "))),0,1,5,6,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK|Gtk::FILL);
 
  table->attach(*manage(new Gtk::Label(_("Render Progress: "))),0,1,5,6,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK|Gtk::FILL);
Line 61: Line 68:
 
  //Another spacer
 
  //Another spacer
 
  table->attach(*manage(new Gtk::Label),0,5,7,8);
 
  table->attach(*manage(new Gtk::Label),0,5,7,8);
 
''just before table->show_all();''
 
  
 
''and after add(*table);''
 
''and after add(*table);''
Line 99: Line 104:
 
100% of pass 2 while be displayed as 100%.<br />
 
100% of pass 2 while be displayed as 100%.<br />
 
If we had 3 passes, it would be 33.3%, 66.6% and 100.0%<br />
 
If we had 3 passes, it would be 33.3%, 66.6% and 100.0%<br />
 +
 +
=== synfig-studio/src/gui/docks/app.h ===
 +
* Lets add our ''static dock_info_''. A ''studio::dock_info'' is already defined in the .cpp but we need to access it as ''App::dock_info_''!
 +
Inside the static declarations:
 +
 +
static Dock_Info* dock_info_; //For Render ProgressBar
 +
=== synfig-studio/src/gui/docks/app.cpp ===
 +
* Let's declare the static
 +
Inside the declare of statics
 +
Dock_Info* App::dock_info_            = 0;
 +
 +
At the end of the constructor App()
 +
App::dock_info_ = dock_info;
 +
''It looks like some kind of alias!''
 +
 +
=== synfig-studio/src/gui/docks/render.cpp ===
 +
* Here the things start to become serious
 +
In the #include section:
 +
#include "docks/dockmanager.h"
 +
#include "docks/dock_info.h"
 +
 +
In RenderSettings::on_render_pressed<br />
 +
''Just before submit_next_render_pass();''
 +
App::dock_info_->set_n_passes_requested(render_passes.size());
 +
App::dock_info_->set_n_passes_pending(render_passes.size());
 +
App::dock_info_->set_render_progress(0.0);
 +
App::dock_manager->find_dockable("info").present(); //Bring Dock_Info to front
 +
''We initialized our ProgressBar with its default parameters to display 0.0%''<br />
 +
''Note that the Dock_Info will be brought to front to show the progression... It's its goal!''
 +
 +
In RenderSettings::submit_next_render_pass()
 +
''Just after render_passes.pop_back();''
 +
App::dock_info_->set_n_passes_pending(render_passes.size()); //! Decrease until 0
 +
App::dock_info_->set_render_progress(0.0); //For this pass
 +
''We reinitialized the parameters for this specific pass!''
 +
 +
'''Doing tests I noticed that with extract alpha option on, we have 2 passes and therefore the render done sound was played 2 times!<br />
 +
Let's correct this bad behaviour'''
 +
In RenderSettings::on_finished(), around submit_next_render_pass();
 +
bool really_finished = (render_passes.size() == 0); //Must be checked BEFORE submit_next_render_pass();
 +
 +
submit_next_render_pass();
 +
 +
//Sound effect - RenderDone (-1 : play on first free channel, 0 : no repeat)
 +
if (App::use_render_done_sound) Mix_PlayChannel( -1, App::gRenderDone, 0 );
 +
if (really_finished) { //Because of multi-pass render
 +
    if (App::use_render_done_sound) Mix_PlayChannel( -1, App::gRenderDone, 0 );
 +
    App::dock_info_->set_render_progress(1.0);
 +
}
 +
 +
''This way, it will play only once the full render has occured!''
 +
 +
=== synfig-studio/src/gui/docks/asyncrender.cpp ===
 +
* Now the deepest part
 +
In #include section:
 +
#include <docks/dock_info.h>
 +
 +
In the beginning of AsyncRenderer::start()
 +
App::dock_info_->set_render_progress(0.0);
 +
 +
 +
In ''void frame_ready()'' '''of both''' ''AsyncTarget_Cairo'' and ''AsyncTarget_Scanline''<br />
 +
''After ready_next=true;''
 +
int n_total_frames_to_render = warm_target->desc.get_frame_end()        //120
 +
                              - warm_target->desc.get_frame_start()      //0
 +
                              + 1;                                      //->121
 +
int current_rendered_frames_count = warm_target->curr_frame_
 +
                                  - warm_target->desc.get_frame_start();
 +
float r = (float) current_rendered_frames_count
 +
        / (float) n_total_frames_to_render;
 +
App::dock_info_->set_render_progress(r);
 +
 +
Here the current progress is calculated according the starting, ending and current frame, in a range of 0.0 to 1.0<br />
 +
The pass (or target) thinks it is the only one in the world but it is compensated in the display :)
 +
 +
Hoping this will help you to come and join the effort in development of Synfig :)

Revision as of 23:53, 3 January 2019

Now let's implement a Render Progress Bar!
It has been requested several time:
Indication needed that rendering is in progress. #383
Feature request: Give feedback when rendering is happening and complete #626
also in Default render parameter are bad #464

Things to take into account

  • Where to place the Progress Bar?
  • How does really a render work?
  • Where to do calls and implementation?

Where to place the Progress Bar?

I chose to implement it in the Dock_Info panel.
After all, this is a kind of information!

How does really a render work?

App (.h.cpp) is the root of everything and used to store globals.
But we start in fact from CanvasView (.h.cpp) which contains the call to display the RenderSettings Dialog (.h.cpp).
Then we press on render button which leads to execute an AsyncRenderer (.h.cpp) (or 2, sequentially, if we have a second pass for Alpha extraction).
AsyncRenderer can have 4 types of targets, AsyncTarget_Cairo, AsyncTarget_Cairo_Tile, AsyncTarget_Scanline, AsyncTarget_Tile.
Only AsyncTarget_Cairo and AsyncTarget_Scanline have a frame_ready() function that we will use to implement our call to update to the Render ProgressBar.

Note that we can have 2 passes, this has to be considered when displaying the percents of accomplished render.

The render itself is done writing the data to the target, then the selected codec is fed through a pipe (for example ffmpeg).

Where to do calls and implementation?

In different files, as it is a "multi-level" task.
The details are described in each file touched in the next section.

Implementation

synfig-studio/src/gui/docks/dock_info.h

  • Declare the components and members

In #include section:

#include <gtkmm/progressbar.h>

In private section:

Gtk::ProgressBar render_progress;

//! Number of passes request - 1 or 2 (if alpha)
int              n_passes_requested;
//! Number of passes pending - 2,1,0
int              n_passes_pending;

In public section:

//! Current render progress - 0.0 to 1.0
//  depends on n_passes_requested and current_pass
void set_render_progress   (float value);
void set_n_passes_requested(int   value);
void set_n_passes_pending  (int   value);

synfig-studio/src/gui/docks/dock_info.cpp

  • Here we will implement the UI and members

In #include section:

#include "app.h"
#include <gtkmm/progressbar.h>

It will permit to access our App::dock_info_ as a static from anywhere in the application

In Dock_Info(), at the end: just before table->show_all();

//Render Progress Bar
table->attach(*manage(new Gtk::Label(_("Render Progress: "))),0,1,5,6,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK|Gtk::FILL);
table->attach(render_progress,                                0,5,6,7,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK|Gtk::FILL);
render_progress.set_show_text(true);
render_progress.set_text(strprintf("%.1f%%", 0.0));
render_progress.set_fraction(0.0);
//Another spacer
table->attach(*manage(new Gtk::Label),0,5,7,8);

and after add(*table);

//Render progress
set_n_passes_requested(1); //Default
set_n_passes_pending  (1); //Default
set_render_progress (0.0); //Default, 0.0%

Then at the end of the file, we add these 3 functions:

void studio::Dock_Info::set_n_passes_requested(int value)
{
    n_passes_requested = value;
}
 	
void studio::Dock_Info::set_n_passes_pending(int value)
{
    n_passes_pending = value;
}
void studio::Dock_Info::set_render_progress(float value)
{
 float coeff        = (1.000 / (float)n_passes_requested);  //% of fraction for 1 pass if more than 1 pass
 float already_done = coeff * (float)(n_passes_requested - n_passes_pending -1); 
 float r = ( coeff * value ) + already_done;

 render_progress.set_text( strprintf( "%.1f%%", r*100 ));
 render_progress.set_fraction(r);
}

The 2 first ones are obvious, the last one does the calculation for the display of the current percents of the WHOLE TASK.
If we have only 1 pass, value will be reflected directly.
In case of 2 (or more, who knows what will be implemented later!), each pass will still continue to send its progress as if it was the only one in the world; we will do the adjustments here.
100% of pass 1 while be displayed as 50%.
100% of pass 2 while be displayed as 100%.
If we had 3 passes, it would be 33.3%, 66.6% and 100.0%

synfig-studio/src/gui/docks/app.h

  • Lets add our static dock_info_. A studio::dock_info is already defined in the .cpp but we need to access it as App::dock_info_!

Inside the static declarations:

static Dock_Info* dock_info_; //For Render ProgressBar

synfig-studio/src/gui/docks/app.cpp

  • Let's declare the static

Inside the declare of statics

Dock_Info* App::dock_info_            = 0;

At the end of the constructor App()

App::dock_info_ = dock_info;

It looks like some kind of alias!

synfig-studio/src/gui/docks/render.cpp

  • Here the things start to become serious

In the #include section:

#include "docks/dockmanager.h"
#include "docks/dock_info.h"

In RenderSettings::on_render_pressed
Just before submit_next_render_pass();

App::dock_info_->set_n_passes_requested(render_passes.size());
App::dock_info_->set_n_passes_pending(render_passes.size());
App::dock_info_->set_render_progress(0.0);
App::dock_manager->find_dockable("info").present(); //Bring Dock_Info to front

We initialized our ProgressBar with its default parameters to display 0.0%
Note that the Dock_Info will be brought to front to show the progression... It's its goal!

In RenderSettings::submit_next_render_pass() Just after render_passes.pop_back();

App::dock_info_->set_n_passes_pending(render_passes.size()); //! Decrease until 0
App::dock_info_->set_render_progress(0.0); //For this pass

We reinitialized the parameters for this specific pass!

Doing tests I noticed that with extract alpha option on, we have 2 passes and therefore the render done sound was played 2 times!
Let's correct this bad behaviour In RenderSettings::on_finished(), around submit_next_render_pass();

bool really_finished = (render_passes.size() == 0); //Must be checked BEFORE submit_next_render_pass();

submit_next_render_pass();

//Sound effect - RenderDone (-1 : play on first free channel, 0 : no repeat)
if (App::use_render_done_sound) Mix_PlayChannel( -1, App::gRenderDone, 0 );
if (really_finished) { //Because of multi-pass render
    if (App::use_render_done_sound) Mix_PlayChannel( -1, App::gRenderDone, 0 );
    App::dock_info_->set_render_progress(1.0);
}

This way, it will play only once the full render has occured!

synfig-studio/src/gui/docks/asyncrender.cpp

  • Now the deepest part

In #include section:

#include <docks/dock_info.h>

In the beginning of AsyncRenderer::start()

App::dock_info_->set_render_progress(0.0);


In void frame_ready() of both AsyncTarget_Cairo and AsyncTarget_Scanline
After ready_next=true;

int n_total_frames_to_render = warm_target->desc.get_frame_end()        //120
                             - warm_target->desc.get_frame_start()      //0
                             + 1;                                       //->121
int current_rendered_frames_count = warm_target->curr_frame_
                                  - warm_target->desc.get_frame_start();
float r = (float) current_rendered_frames_count 
        / (float) n_total_frames_to_render;
App::dock_info_->set_render_progress(r);

Here the current progress is calculated according the starting, ending and current frame, in a range of 0.0 to 1.0
The pass (or target) thinks it is the only one in the world but it is compensated in the display :)

Hoping this will help you to come and join the effort in development of Synfig :)