-
Notifications
You must be signed in to change notification settings - Fork 329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Expose checkpoint option and save/restore renders in cli #2120
Conversation
4752d68
to
355cdd3
Compare
8b58fa1
to
b145095
Compare
b145095
to
ddcc35b
Compare
89a342f
to
b9a6b15
Compare
Will need to be rebased. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code is very well written and looks extremely clean.
parser().add_option_handler( | ||
&m_checkpoint | ||
.add_name("--checkpoint") | ||
.set_description("save a rendering checkpoint on each passes or resume rendering with a previous checkpoint")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
write a rendering checkpoint after each pass, or resume rendering from the last checkpoint
// Boost headers. | ||
#include "boost/filesystem.hpp" | ||
|
||
namespace bf = boost::filesystem; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No namespace aliases in header files.
@@ -56,6 +56,19 @@ BBox compute_union(const Iterator begin, const Iterator end); | |||
template <typename BBox, typename Iterator> | |||
BBox interpolate(const Iterator begin, const Iterator end, const double time); | |||
|
|||
template <typename BBox, typename Tile, typename Int> | |||
BBox compute_tile_image_space_bbox( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe compute_tile_bbox_image_space()
would be clearer.
const Int tile_y) | ||
{ | ||
BBox tile_bbox; | ||
const Int tile_origin_x = tile_x * static_cast<Int>(tile.get_width()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is incorrect. A tile may have smaller dimensions than others because it's on the border of a frame. For instance a rightmost tile may have dimensions 50x64 while all tiles on its left have dimensions 64x64.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC the box is then intersected with the frame. I think i tested that but I will check again.
|
||
// When resuming a render, first pass index should be | ||
// the number of the resumed render's pass + 1. | ||
// Otherwise it' 0. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's
} | ||
|
||
private: | ||
size_t m_invalid_sample_count; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We align data members among themselves, not necessarily with the unrelated surrounding stuff.
const Frame& frame, | ||
ITileRendererFactory* tile_renderer_factory, | ||
IShadingResultFrameBufferFactory* framebuffer_factory, | ||
ITileCallbackFactory* tile_callback_factory, // may be 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually the comments here should say may be nullptr
.
const size_t tile_y, | ||
Tile* output_tile); | ||
|
||
bool choose_subimage(const size_t subimage) const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment. Move right above the two read_tile()
methods.
@@ -139,6 +141,7 @@ struct GenericProgressiveImageFileReader::Impl | |||
static_cast<size_t>(spec.nchannels), | |||
pixel_format); | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revert.
UnfilteredAOVAccumulator( | ||
foundation::Image& image, | ||
foundation::Image& filter_image); | ||
UnfilteredAOVAccumulator(foundation::Image& image); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make constructor explicit now that it has only one argument
@@ -110,12 +93,12 @@ ColorAOV::ColorAOV(const char* name, const ParamArray& params) | |||
|
|||
size_t ColorAOV::get_channel_count() const | |||
{ | |||
return 3; | |||
return 4; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure about this change. Most of our color AOVs are 3 channel.
Why did you change it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the SadingBuffer is doing 4 * aov_count. So wether we change the shading buffer to use the exact amount of channels which can be difficult or we set all AOV to 4 channels if they are used in the shading buffer.
49055f4
to
691a514
Compare
691a514
to
5156950
Compare
8198a19
to
4cdb4fd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent code.
Made some remarks.
Thanks!
} | ||
|
||
} // namespace renderer | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove redundant blank line. The file should simply end with a \n
.
@@ -62,6 +62,7 @@ namespace foundation { class Tile; } | |||
namespace renderer { class BaseGroup; } | |||
namespace renderer { class DenoiserAOV; } | |||
namespace renderer { class ImageStack; } | |||
namespace renderer { class PermanentShadingResultFrameBufferFactory; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Order alphabetically.
std::unique_ptr<ISampleRendererFactory> m_sample_renderer_factory; | ||
std::unique_ptr<ISampleGeneratorFactory> m_sample_generator_factory; | ||
std::unique_ptr<IPixelRendererFactory> m_pixel_renderer_factory; | ||
foundation::auto_release_ptr<IShadingResultFrameBufferFactory> m_shading_result_framebuffer_factory; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the ptr is not unique anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The master renderer use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not a valid argument :) foundation::auto_release_ptr<>
is like the now-deprecated std::auto_ptr<>
, which is the ancestor of std::unique_ptr<>
. All three are "unique pointers".
The only relevant difference between foundation::auto_release_ptr<>
and std::unique_ptr<>
is that the former calls a release()
method to destroy the object while the later calls the destructor.
In this context, either points would work. I don't see any reason to change the type in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't knew that :o The names aren't really helping.
@@ -461,13 +462,24 @@ struct MasterRenderer::Impl | |||
// Updating the trace context causes ray tracing acceleration structures to be updated or rebuilt. | |||
m_project.update_trace_context(); | |||
|
|||
// Load the checkpoint if any. | |||
Frame& frame = *m_project.get_frame(); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this blank line and the one below so that the comment applies to the whole block.
// Load the checkpoint if any. | ||
Frame& frame = *m_project.get_frame(); | ||
|
||
PermanentShadingResultFrameBufferFactory* buffer_factory = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What guarantees that the shading result framebuffer is a permanent one here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code that generate the renderer and the buffer :D You think I should dynamic cast and test the result ?
|
||
// Save denoiser checkpoint. | ||
if (denoiser_aov != nullptr) | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove braces around single line statement.
} | ||
} | ||
|
||
RENDERER_LOG_INFO("wrote checkpoint for pass %s.", pretty_uint(pass + 1).c_str()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's mention the checkpoint file path.
wrote checkpoint file %s for pass %s.
public: | ||
ShadingBufferCanvas( | ||
const Frame& frame, | ||
PermanentShadingResultFrameBufferFactory* buffer_factory) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you really need a PermanentShadingResultFrameBufferFactory
? What for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes because EphemeralShadingResultFrameBufferFactory
always create a new tile. But here I want the tiles that was already created and filled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the ShadingBufferCanvas
would work with any IShadingResultFrameBufferFactory
, then you wouldn't need casts or any special cases anywhere.
const CanvasProperties& props = image.properties(); | ||
|
||
// Compute the image space bounding box of the tile. | ||
const int tile_origin_x = static_cast<int>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Single line.
// Compute the image space bounding box of the tile. | ||
const int tile_origin_x = static_cast<int>( | ||
props.m_tile_width * tile_x); | ||
const int tile_origin_y = static_cast<int>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Single line.
Thanks for the review @dictoon, really appreciated. |
b306f72
to
2705c01
Compare
2705c01
to
8bc8cba
Compare
// Create channel names. | ||
const size_t shading_channel_count = weight_canvas.properties().m_channel_count; | ||
vector<string> shading_channel_names; // for shading buffer | ||
vector<const char *> shading_channel_names_cr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does the _cr
suffix mean here? What about _cstr
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was for char
. but _cstr
is better indeed
writer.set_image_attributes(image_attributes); | ||
|
||
writer.append_image(&weight_canvas); | ||
image_attributes.insert("image_name", "appleseed:RenderingBuffer"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reusing image_attributes
is confusing and potentially dangerous: you might end up writing attributes you didn't intend to. For instance, right now the main image and all AOV images have a appleseed:LastPass
attribute, is that intended?
If possible, I would suggest create a new ImageAttributes
object every time. If necessary, you can wrap each block in braces ({}
) in order to isolate them from each other.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not it wasn't intended, thanks for pointing it out
writer.set_image_attributes(image_attributes); | ||
|
||
// Add AOV layers. | ||
for (size_t i = 0, e = aovs().size(); i < e; ++i) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whenever possible, we should use range-based for
loops:
for (const AOV& aov : aovs())
{
...
}
{ | ||
const AOV* aov = internal_aovs().get_by_index(i); | ||
|
||
const DenoiserAOV* denoiser_aov = dynamic_cast<const DenoiserAOV*>(aov); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line belongs to the block below:
// Save denoiser checkpoint.
const DenoiserAOV* denoiser_aov = dynamic_cast<const DenoiserAOV*>(aov);
if (denoiser_aov != nullptr)
save_denoiser_checkpoint(impl->m_checkpoint_create_path, denoiser_aov);
std::unique_ptr<ISampleRendererFactory> m_sample_renderer_factory; | ||
std::unique_ptr<ISampleGeneratorFactory> m_sample_generator_factory; | ||
std::unique_ptr<IPixelRendererFactory> m_pixel_renderer_factory; | ||
foundation::auto_release_ptr<IShadingResultFrameBufferFactory> m_shading_result_framebuffer_factory; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not a valid argument :) foundation::auto_release_ptr<>
is like the now-deprecated std::auto_ptr<>
, which is the ancestor of std::unique_ptr<>
. All three are "unique pointers".
The only relevant difference between foundation::auto_release_ptr<>
and std::unique_ptr<>
is that the former calls a release()
method to destroy the object while the later calls the destructor.
In this context, either points would work. I don't see any reason to change the type in this PR.
public: | ||
ShadingBufferCanvas( | ||
const Frame& frame, | ||
PermanentShadingResultFrameBufferFactory* buffer_factory) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the ShadingBufferCanvas
would work with any IShadingResultFrameBufferFactory
, then you wouldn't need casts or any special cases anywhere.
@@ -316,11 +323,17 @@ namespace | |||
{ | |||
set_current_thread_name("pass_manager"); | |||
|
|||
const size_t start_pass = m_frame.get_initial_pass(); | |||
|
|||
PermanentShadingResultFrameBufferFactory* buffer_factory = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my other comment in frame.cpp
. We want to avoid those potentially dangerous casts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will work without casting but the buffer factory needs to be permanent. Can I point that out in a comment and forget about the cast ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does it need to be permanent? What happens if it's not (and assuming you don't cast anymore)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's not permanent, it means it's ephemeral and it will create an empty tile. So there no point in saving this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand, but wouldn't be a correct behavior? If an ephemeral buffer is used, then you get empty checkpoints, no problem, no special case. It's kind of the same as saying, if there are no lights in the scene, the render will be black, so we should abort instead of rendering. We don't, there's no special case for a scene without any light.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed
m_frame.image().properties().m_canvas_height, | ||
m_frame.image().properties().m_tile_width, | ||
m_frame.image().properties().m_tile_height, | ||
(1 + m_frame.aov_images().size()) * 4 + 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does that expression come from? Is it the responsability of ShadingBufferCanvas
to compute that number of channels, are you trying to match the number of channels computed elsewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed I'm trying to match the shading buffer number of channels.
https://github.com/appleseedhq/appleseed/blob/master/src/appleseed/renderer/kernel/rendering/shadingresultframebuffer.cpp#L53
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the additional + 1
?
Can you instead query the number of channels in the ShadingResultFrameBuffer instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 Because i save the beauty image, just in case you want to correctly visualize the checkpoint
- Expose a checkpoint option in cli - Create a checkpoint file after each pass if "--checkpoint" is on - Restore from a checkpoint file if "--checkpoint" is on - Handle denoiser when saving and restoring a checkpoint file (informations are not saved in the same checkpoint file) - Fix passes option path in cli and studio - Minor changes
8bc8cba
to
4a1d191
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks a lot better!
It's almost ready for merging. The few remaining comments are all small details and it should only take a few minutes to address them. Once these are addressed, we can (finally) merge this PR.
Thanks for the excellent work!
foundation::IAbortSwitch* abort_switch) const; | ||
|
||
// Open the checkpoint if checkpoint is enabled and the file located | ||
// at checkpoint_path exists and is valid. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the comment up-to-date here? What's checkpoint_path
?
@@ -80,9 +80,17 @@ class ShadingResultFrameBuffer | |||
foundation::Tile& tile, | |||
TileStack& aov_tiles) const; | |||
|
|||
static inline size_t get_total_channel_count(const size_t aov_count); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove inline
here.
Move static method before non-static ones.
private: | ||
const size_t m_aov_count; | ||
std::vector<float> m_scratch; | ||
}; | ||
|
||
size_t ShadingResultFrameBuffer::get_total_channel_count(const size_t aov_count) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add inline
here.
@@ -485,6 +490,7 @@ struct MasterRenderer::Impl | |||
recorder.on_render_end(m_project); | |||
|
|||
const CanvasProperties& props = m_project.get_frame()->image().properties(); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revert.
image_attributes.insert("red_xy_chromaticity", Vector2f(0.64f, 0.33f)); | ||
image_attributes.insert("green_xy_chromaticity", Vector2f(0.30f, 0.60f)); | ||
image_attributes.insert("blue_xy_chromaticity", Vector2f(0.15f, 0.06f)); | ||
// The beauty image plus the shadding result framebuffer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shading
if (extension != ".exr") | ||
{ | ||
// Only .exr files are allowed. | ||
RENDERER_LOG_ERROR("checkpoint file must be an \".exr\" file, disabling checkpoint creation"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
End message with a period (.
).
if (extension != ".exr") | ||
{ | ||
// Only .exr files are allowed. | ||
RENDERER_LOG_ERROR("checkpoint file must be an \".exr\" file, disabling checkpoint resuming"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
End message with a period (.
).
{ | ||
string path = m_params.get_optional<string>("checkpoint_create_path", ""); | ||
const bool use_default_path = path.empty(); | ||
if (use_default_path) path = m_params.get_required<string>("output_path"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move if
body to separate line.
{ | ||
string path = m_params.get_optional<string>("checkpoint_resume_path", ""); | ||
const bool use_default_path = path == ""; | ||
if (use_default_path) path = m_params.get_required<string>("output_path"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move if
body to separate line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
.exr
files for checkpointsFuture Work
#2355