Skip to content
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

Merged
merged 6 commits into from
Feb 14, 2019

Conversation

oktomus
Copy link
Member

@oktomus oktomus commented Jul 12, 2018

  • Correctly write buffer weights in the checkpoint EXR
  • Correctly load weights from the checkpoint
  • Correctly save filtered AOVs
  • Correctly save un-filtered AOVs
  • Correctly load filtered AOVs
  • Correctly load un-filtered AOVs
  • Allow only .exr files for checkpoints
  • Add custom message when a checkpoint is invalid
  • Compatibility: Check for layer count
  • Color AOVs: don't recreate the same image multiple times
  • Support for denoiser AOV
  • Reading: use a map containing for each subimage the layer name
  • After render finished, rename previously created checkpoint file to not override it
  • Restore and save from the frame

Future Work

  • Expose option in studio
  • Save denoiser checkpoint in the same exr file
  • Allow to resume progressive rendering
  • Suggested by @est77 : Add checkpoint paths in the project file
  • Handle interruption happening while writing a checkpoint file (a solution could be to stop Ctrl-C during that moment).

#2355

@dictoon dictoon added the PR | Squash This PR must be squashed when merged. label Jul 18, 2018
@oktomus oktomus force-pushed the cli-checkpoint-renders branch 11 times, most recently from 4752d68 to 355cdd3 Compare July 31, 2018 08:03
@oktomus oktomus force-pushed the cli-checkpoint-renders branch 7 times, most recently from 8b58fa1 to b145095 Compare August 1, 2018 12:13
@oktomus oktomus added PR | Rebase This PR must be rebased when merged. and removed PR | Squash This PR must be squashed when merged. labels Aug 1, 2018
@oktomus oktomus changed the title [WIP] Expose checkpoint option and save/restore renders in cli Expose checkpoint option and save/restore renders in cli Aug 1, 2018
@oktomus oktomus force-pushed the cli-checkpoint-renders branch 4 times, most recently from 89a342f to b9a6b15 Compare August 2, 2018 14:25
@dictoon
Copy link
Member

dictoon commented Aug 29, 2018

Will need to be rebased.

Copy link
Member

@dictoon dictoon left a 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"));
Copy link
Member

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

src/appleseed/CMakeLists.txt Show resolved Hide resolved
// Boost headers.
#include "boost/filesystem.hpp"

namespace bf = boost::filesystem;
Copy link
Member

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(
Copy link
Member

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());
Copy link
Member

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.

Copy link
Member Author

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.
Copy link
Member

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;
Copy link
Member

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
Copy link
Member

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;
Copy link
Member

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);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert.

est77
est77 previously requested changes Aug 30, 2018
UnfilteredAOVAccumulator(
foundation::Image& image,
foundation::Image& filter_image);
UnfilteredAOVAccumulator(foundation::Image& image);
Copy link
Member

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;
Copy link
Member

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?

Copy link
Member Author

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.

@oktomus oktomus force-pushed the cli-checkpoint-renders branch 2 times, most recently from 49055f4 to 691a514 Compare September 16, 2018 10:33
@oktomus oktomus removed the WIP ⚠ label Dec 4, 2018
@oktomus oktomus force-pushed the cli-checkpoint-renders branch 2 times, most recently from 8198a19 to 4cdb4fd Compare December 11, 2018 18:21
Copy link
Member

@dictoon dictoon left a 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

Copy link
Member

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; }
Copy link
Member

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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change?

Copy link
Member Author

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.

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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();

Copy link
Member

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 =
Copy link
Member

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?

Copy link
Member Author

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)
{
Copy link
Member

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());
Copy link
Member

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)
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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>(
Copy link
Member

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>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Single line.

@oktomus
Copy link
Member Author

oktomus commented Dec 19, 2018

Thanks for the review @dictoon, really appreciated.

// 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;
Copy link
Member

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?

Copy link
Member Author

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");
Copy link
Member

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.

Copy link
Member Author

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)
Copy link
Member

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);
Copy link
Member

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;
Copy link
Member

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)
Copy link
Member

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 =
Copy link
Member

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.

Copy link
Member Author

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 ?

Copy link
Member

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)?

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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,
Copy link
Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

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?

Copy link
Member Author

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
Copy link
Member

@dictoon dictoon left a 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.
Copy link
Member

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);
Copy link
Member

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)
Copy link
Member

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();

Copy link
Member

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.
Copy link
Member

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");
Copy link
Member

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");
Copy link
Member

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");
Copy link
Member

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");
Copy link
Member

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.

Copy link
Member

@dictoon dictoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@dictoon dictoon added PR | Squash This PR must be squashed when merged. and removed PR | Rebase This PR must be rebased when merged. labels Feb 14, 2019
@dictoon dictoon merged commit a9a25a8 into appleseedhq:master Feb 14, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Component | appleseed.cli PR | Squash This PR must be squashed when merged.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants