Changeset View
Changeset View
Standalone View
Standalone View
intern/cycles/render/tile.cpp
| Show All 10 Lines | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | * distributed under the License is distributed on an "AS IS" BASIS, | ||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| * See the License for the specific language governing permissions and | * See the License for the specific language governing permissions and | ||||
| * limitations under the License. | * limitations under the License. | ||||
| */ | */ | ||||
| #include "render/tile.h" | #include "render/tile.h" | ||||
| #include "render/pass.h" | |||||
| #include "util/util_algorithm.h" | #include "util/util_algorithm.h" | ||||
| #include "util/util_foreach.h" | #include "util/util_foreach.h" | ||||
| #include "util/util_logging.h" | |||||
| #include "util/util_path.h" | |||||
| #include "util/util_string.h" | |||||
| #include "util/util_types.h" | #include "util/util_types.h" | ||||
| CCL_NAMESPACE_BEGIN | CCL_NAMESPACE_BEGIN | ||||
| void TileManager::reset(const BufferParams ¶ms, int2 tile_size) | TileManager::TileManager() | ||||
| { | |||||
| storage_dir_ = path_temp_get(""); | |||||
| partial_filename_ = "cycles-partial.exr"; | |||||
brecht: We'll need to generate a unique name here, otherwise multiple processes will overwrite each… | |||||
| } | |||||
| TileManager::~TileManager() | |||||
| { | |||||
| } | |||||
Not Done Inline ActionsMaybe use cycles-tile-buffer otherwise it sounds a bit like the file contains one tile. brecht: Maybe use `cycles-tile-buffer` otherwise it sounds a bit like the file contains one tile. | |||||
| void TileManager::reset(const BufferParams ¶ms, const vector<Pass *> &passes, int2 tile_size) | |||||
| { | { | ||||
| tile_size_ = tile_size; | tile_size_ = tile_size; | ||||
| buffer_params_ = params; | buffer_params_ = params; | ||||
| num_tiles_x_ = divide_up(params.width, tile_size_.x); | tile_state_.num_tiles_x = divide_up(params.width, tile_size_.x); | ||||
| num_tiles_y_ = divide_up(params.height, tile_size_.y); | tile_state_.num_tiles_y = divide_up(params.height, tile_size_.y); | ||||
| tile_state_.num_tiles = tile_state_.num_tiles_x * tile_state_.num_tiles_y; | |||||
| state_.next_tile_index = 0; | tile_state_.next_tile_index = 0; | ||||
| state_.num_tiles = num_tiles_x_ * num_tiles_y_; | |||||
| state_.current_tile = Tile(); | tile_state_.current_tile = Tile(); | ||||
| close_tile_output(); | |||||
| configure_image_spec(passes); | |||||
| } | } | ||||
| bool TileManager::done() | bool TileManager::done() | ||||
| { | { | ||||
| return state_.next_tile_index == state_.num_tiles; | return tile_state_.next_tile_index == tile_state_.num_tiles; | ||||
| } | } | ||||
| bool TileManager::next() | bool TileManager::next() | ||||
| { | { | ||||
| if (done()) { | if (done()) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| tile_state_.current_tile = get_tile_for_index(tile_state_.next_tile_index); | |||||
| ++tile_state_.next_tile_index; | |||||
| return true; | |||||
| } | |||||
| Tile TileManager::get_tile_for_index(int index) const | |||||
| { | |||||
| /* TODO(sergey): Consider using hilbert spiral, or. maybe, even configurable. Not sure this | /* TODO(sergey): Consider using hilbert spiral, or. maybe, even configurable. Not sure this | ||||
| * brings a lot of value since this is only applicable to BIG tiles. */ | * brings a lot of value since this is only applicable to BIG tiles. */ | ||||
| const int tile_y = state_.next_tile_index / num_tiles_x_; | const int tile_y = index / tile_state_.num_tiles_x; | ||||
| const int tile_x = state_.next_tile_index - tile_y * num_tiles_x_; | const int tile_x = index - tile_y * tile_state_.num_tiles_x; | ||||
| state_.current_tile.x = tile_x * tile_size_.x; | Tile tile; | ||||
| state_.current_tile.y = tile_y * tile_size_.y; | |||||
| state_.current_tile.width = tile_size_.x; | |||||
| state_.current_tile.height = tile_size_.y; | |||||
| state_.current_tile.width = min(state_.current_tile.width, | |||||
| buffer_params_.width - state_.current_tile.x); | |||||
| state_.current_tile.height = min(state_.current_tile.height, | |||||
| buffer_params_.height - state_.current_tile.y); | |||||
| ++state_.next_tile_index; | tile.x = tile_x * tile_size_.x; | ||||
| tile.y = tile_y * tile_size_.y; | |||||
| tile.width = tile_size_.x; | |||||
| tile.height = tile_size_.y; | |||||
| return true; | tile.width = min(tile.width, buffer_params_.width - tile.x); | ||||
| tile.height = min(tile.height, buffer_params_.height - tile.y); | |||||
| return tile; | |||||
| } | } | ||||
| const Tile &TileManager::get_current_tile() const | const Tile &TileManager::get_current_tile() const | ||||
| { | { | ||||
| return state_.current_tile; | return tile_state_.current_tile; | ||||
| } | |||||
| void TileManager::configure_image_spec(const vector<Pass *> &passes) | |||||
| { | |||||
| static const char *component_suffixes[] = {"R", "G", "B", "A"}; | |||||
| int pass_index = 0; | |||||
| int num_channels = 0; | |||||
| std::vector<std::string> channel_names; | |||||
| for (const Pass *pass : passes) { | |||||
| const PassInfo &pass_info = pass->get_info(); | |||||
| num_channels += pass_info.num_components; | |||||
| /* EXR canonically expects first part of channel names to be sorted alphabetically, which is | |||||
| * not guaranteed to be the case with passes names. Assign a prefix based on the pass index | |||||
| * with a fixed width to ensure ordering. This makes it possible to dump existing render | |||||
| * buffers memory to disk and read it back without doing extra mapping. */ | |||||
| const string prefix = string_printf("%08d", pass_index); | |||||
| const string channel_name_prefix = prefix + string(pass->name) + "."; | |||||
| for (int i = 0; i < pass_info.num_components; ++i) { | |||||
| channel_names.push_back(channel_name_prefix + component_suffixes[i]); | |||||
| } | |||||
| ++pass_index; | |||||
| } | |||||
| partial_state_.image_spec = ImageSpec( | |||||
| buffer_params_.full_width, buffer_params_.full_height, num_channels, TypeDesc::FLOAT); | |||||
| partial_state_.image_spec.channelnames = move(channel_names); | |||||
| partial_state_.image_spec.tile_width = tile_size_.x; | |||||
| partial_state_.image_spec.tile_height = tile_size_.y; | |||||
| } | |||||
| string TileManager::get_partial_filepath() const | |||||
| { | |||||
| return path_join(storage_dir_, partial_filename_); | |||||
brechtUnsubmitted Done Inline ActionsWe might as well join these in the constructor instead of storing both. brecht: We might as well join these in the constructor instead of storing both. | |||||
| } | |||||
| bool TileManager::open_tile_output() | |||||
| { | |||||
| const string filepath = get_partial_filepath(); | |||||
| partial_state_.tile_out = ImageOutput::create(filepath); | |||||
| if (!partial_state_.tile_out) { | |||||
| LOG(ERROR) << "Error creating image output for " << filepath; | |||||
| return false; | |||||
| } | |||||
| if (!partial_state_.tile_out->supports("tiles")) { | |||||
| LOG(ERROR) << "Progress tile file format does not support tiling."; | |||||
| return false; | |||||
| } | |||||
| partial_state_.tile_out->open(filepath, partial_state_.image_spec); | |||||
| partial_state_.num_tiles_written = 0; | |||||
| VLOG(3) << "Opened partial file " << filepath; | |||||
| return true; | |||||
| } | |||||
| bool TileManager::close_tile_output() | |||||
| { | |||||
| if (!partial_state_.tile_out) { | |||||
| return true; | |||||
| } | |||||
| const bool success = partial_state_.tile_out->close(); | |||||
| partial_state_.tile_out = nullptr; | |||||
| if (!success) { | |||||
| LOG(ERROR) << "Error closing partial tile file."; | |||||
| return false; | |||||
| } | |||||
| VLOG(3) << "Tile output is closed."; | |||||
| return true; | |||||
| } | |||||
| bool TileManager::write_tile(const RenderBuffers &tile_buffers) | |||||
| { | |||||
| if (!partial_state_.tile_out) { | |||||
| if (!open_tile_output()) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| const BufferParams &tile_params = tile_buffers.params; | |||||
| vector<float> pixel_storage; | |||||
| const float *pixels = tile_buffers.buffer.data(); | |||||
| /* Tiled writing expects pixels to contain data for an entire tile. Pad the render buffers with | |||||
| * empty pixels for tiles which are on the image boundary. */ | |||||
| if (tile_params.width != tile_size_.x || tile_params.height != tile_size_.y) { | |||||
| const int64_t pass_stride = tile_params.pass_stride; | |||||
| const int64_t src_row_stride = tile_params.width * pass_stride; | |||||
| const int64_t dst_row_stride = tile_size_.x * pass_stride; | |||||
| pixel_storage.resize(dst_row_stride * tile_size_.y); | |||||
| const float *src = tile_buffers.buffer.data(); | |||||
| float *dst = pixel_storage.data(); | |||||
| pixels = dst; | |||||
| for (int y = 0; y < tile_params.height; ++y, src += src_row_stride, dst += dst_row_stride) { | |||||
| memcpy(dst, src, src_row_stride * sizeof(float)); | |||||
| } | |||||
| } | |||||
| VLOG(3) << "Write tile at " << tile_params.full_x << ", " << tile_params.full_y; | |||||
| if (!partial_state_.tile_out->write_tile( | |||||
| tile_buffers.params.full_x, tile_buffers.params.full_y, 0, TypeDesc::FLOAT, pixels)) { | |||||
| LOG(ERROR) << "Error writing tile " << partial_state_.tile_out->geterror(); | |||||
| } | |||||
| ++partial_state_.num_tiles_written; | |||||
| if (done()) { | |||||
| if (!close_tile_output()) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| return true; | |||||
| } | |||||
| void TileManager::finish_write_tiles() | |||||
| { | |||||
| if (!partial_state_.tile_out) { | |||||
| /* None of the tiles were written hence the partial file was not created. | |||||
| * Avoid creation of fully empty file since it is redundant. */ | |||||
| return; | |||||
| } | |||||
| vector<float> pixel_storage(tile_size_.x * tile_size_.y * buffer_params_.pass_stride); | |||||
| for (int tile_index = partial_state_.num_tiles_written; tile_index < tile_state_.num_tiles; | |||||
| ++tile_index) { | |||||
| const Tile tile = get_tile_for_index(tile_index); | |||||
| VLOG(3) << "Write dummy tile at " << tile.x << ", " << tile.y; | |||||
| partial_state_.tile_out->write_tile(tile.x, tile.y, 0, TypeDesc::FLOAT, pixel_storage.data()); | |||||
| } | |||||
| close_tile_output(); | |||||
| } | |||||
| bool TileManager::has_partial_tiles() const | |||||
| { | |||||
| return partial_state_.num_tiles_written != 0; | |||||
| } | |||||
| static bool check_image_spec_compatible(const ImageSpec &spec, const ImageSpec &expected_spec) | |||||
| { | |||||
| if (spec.width != expected_spec.width || spec.height != expected_spec.height || | |||||
| spec.depth != expected_spec.depth) { | |||||
| LOG(ERROR) << "Mismatched image dimension."; | |||||
| return false; | |||||
| } | |||||
| if (spec.format != expected_spec.format) { | |||||
| LOG(ERROR) << "Mismatched image format."; | |||||
| } | |||||
| if (spec.nchannels != expected_spec.nchannels) { | |||||
| LOG(ERROR) << "Mismatched number of channels."; | |||||
| return false; | |||||
| } | |||||
| if (spec.channelnames != expected_spec.channelnames) { | |||||
| LOG(ERROR) << "Mismatched channel names."; | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| bool TileManager::read_full_buffer(RenderBuffers *buffers) | |||||
| { | |||||
| const string filepath = get_partial_filepath(); | |||||
| unique_ptr<ImageInput> in(ImageInput::open(filepath)); | |||||
| if (!in) { | |||||
| LOG(ERROR) << "Error opening partial tile file " << filepath; | |||||
| return false; | |||||
| } | |||||
| const ImageSpec &spec = in->spec(); | |||||
| if (!check_image_spec_compatible(spec, partial_state_.image_spec)) { | |||||
| return false; | |||||
| } | |||||
| buffers->reset(buffer_params_); | |||||
| if (!in->read_image(TypeDesc::FLOAT, buffers->buffer.data())) { | |||||
| LOG(ERROR) << "Error reading pixels from the partial tile file " << in->geterror(); | |||||
| return false; | |||||
| } | |||||
| if (!in->close()) { | |||||
| LOG(ERROR) << "Error closing partial tile file " << in->geterror(); | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| void TileManager::remove_partial_file() const | |||||
| { | |||||
| const string filepath = get_partial_filepath(); | |||||
| VLOG(3) << "Removing partial file " << filepath; | |||||
| path_remove(filepath); | |||||
| } | } | ||||
| CCL_NAMESPACE_END | CCL_NAMESPACE_END | ||||
We'll need to generate a unique name here, otherwise multiple processes will overwrite each other's files.