Blender V4.3
session/tile.cpp
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5#include "session/tile.h"
6
7#include <atomic>
8
9#include "graph/node.h"
10#include "scene/background.h"
11#include "scene/bake.h"
12#include "scene/film.h"
13#include "scene/integrator.h"
14#include "scene/scene.h"
15#include "session/session.h"
16#include "util/algorithm.h"
17#include "util/foreach.h"
18#include "util/log.h"
19#include "util/path.h"
20#include "util/string.h"
21#include "util/system.h"
22#include "util/time.h"
23#include "util/types.h"
24
26
27/* --------------------------------------------------------------------
28 * Internal functions.
29 */
30
31static const char *ATTR_PASSES_COUNT = "cycles.passes.count";
32static const char *ATTR_PASS_SOCKET_PREFIX_FORMAT = "cycles.passes.%d.";
33static const char *ATTR_BUFFER_SOCKET_PREFIX = "cycles.buffer.";
34static const char *ATTR_DENOISE_SOCKET_PREFIX = "cycles.denoise.";
35
36/* Global counter of ToleManager object instances. */
37static std::atomic<uint64_t> g_instance_index = 0;
38
39/* Construct names of EXR channels which will ensure order of all channels to match exact offsets
40 * in render buffers corresponding to the given passes.
41 *
42 * Returns `std` data-types so that it can be assigned directly to the OIIO's `ImageSpec`. */
43static std::vector<std::string> exr_channel_names_for_passes(const BufferParams &buffer_params)
44{
45 static const char *component_suffixes[] = {"R", "G", "B", "A"};
46
47 int pass_index = 0;
48 std::vector<std::string> channel_names;
49 for (const BufferPass &pass : buffer_params.passes) {
50 if (pass.offset == PASS_UNUSED) {
51 continue;
52 }
53
54 const PassInfo pass_info = pass.get_info();
55
56 /* EXR canonically expects first part of channel names to be sorted alphabetically, which is
57 * not guaranteed to be the case with passes names. Assign a prefix based on the pass index
58 * with a fixed width to ensure ordering. This makes it possible to dump existing render
59 * buffers memory to disk and read it back without doing extra mapping. */
60 const string prefix = string_printf("%08d", pass_index);
61
62 const string channel_name_prefix = prefix + string(pass.name) + ".";
63
64 for (int i = 0; i < pass_info.num_components; ++i) {
65 channel_names.push_back(channel_name_prefix + component_suffixes[i]);
66 }
67
68 ++pass_index;
69 }
70
71 return channel_names;
72}
73
74inline string node_socket_attribute_name(const SocketType &socket, const string &attr_name_prefix)
75{
76 return attr_name_prefix + string(socket.name);
77}
78
79template<typename ValidateValueFunc, typename GetValueFunc>
81 ImageSpec *image_spec,
82 const Node *node,
83 const SocketType &socket,
84 const string &attr_name_prefix,
85 const ValidateValueFunc &validate_value_func,
86 const GetValueFunc &get_value_func)
87{
88 if (!validate_value_func(node, socket)) {
89 return false;
90 }
91
92 image_spec->attribute(node_socket_attribute_name(socket, attr_name_prefix),
93 get_value_func(node, socket));
94
95 return true;
96}
97
98static bool node_socket_to_image_spec_atttributes(ImageSpec *image_spec,
99 const Node *node,
100 const SocketType &socket,
101 const string &attr_name_prefix)
102{
103 const string attr_name = node_socket_attribute_name(socket, attr_name_prefix);
104
105 switch (socket.type) {
106 case SocketType::ENUM: {
107 const ustring value = node->get_string(socket);
108
109 /* Validate that the node is consistent with the node type definition. */
110 const NodeEnum &enum_values = *socket.enum_values;
111 if (!enum_values.exists(value)) {
112 LOG(DFATAL) << "Node enum contains invalid value " << value;
113 return false;
114 }
115
116 image_spec->attribute(attr_name, value);
117
118 return true;
119 }
120
122 image_spec->attribute(attr_name, node->get_string(socket));
123 return true;
124
125 case SocketType::INT:
126 image_spec->attribute(attr_name, node->get_int(socket));
127 return true;
128
130 image_spec->attribute(attr_name, node->get_float(socket));
131 return true;
132
134 image_spec->attribute(attr_name, node->get_bool(socket));
135 return true;
136
137 default:
138 LOG(DFATAL) << "Unhandled socket type " << socket.type << ", should never happen.";
139 return false;
140 }
141}
142
144 const SocketType &socket,
145 const ImageSpec &image_spec,
146 const string &attr_name_prefix)
147{
148 const string attr_name = node_socket_attribute_name(socket, attr_name_prefix);
149
150 switch (socket.type) {
151 case SocketType::ENUM: {
152 /* TODO(sergey): Avoid construction of `ustring` by using `string_view` in the Node API. */
153 const ustring value(image_spec.get_string_attribute(attr_name, ""));
154
155 /* Validate that the node is consistent with the node type definition. */
156 const NodeEnum &enum_values = *socket.enum_values;
157 if (!enum_values.exists(value)) {
158 LOG(ERROR) << "Invalid enumerator value " << value;
159 return false;
160 }
161
162 node->set(socket, enum_values[value]);
163
164 return true;
165 }
166
168 /* TODO(sergey): Avoid construction of `ustring` by using `string_view` in the Node API. */
169 node->set(socket, ustring(image_spec.get_string_attribute(attr_name, "")));
170 return true;
171
172 case SocketType::INT:
173 node->set(socket, image_spec.get_int_attribute(attr_name, 0));
174 return true;
175
177 node->set(socket, image_spec.get_float_attribute(attr_name, 0));
178 return true;
179
181 node->set(socket, static_cast<bool>(image_spec.get_int_attribute(attr_name, 0)));
182 return true;
183
184 default:
185 LOG(DFATAL) << "Unhandled socket type " << socket.type << ", should never happen.";
186 return false;
187 }
188}
189
190static bool node_to_image_spec_atttributes(ImageSpec *image_spec,
191 const Node *node,
192 const string &attr_name_prefix)
193{
194 for (const SocketType &socket : node->type->inputs) {
195 if (!node_socket_to_image_spec_atttributes(image_spec, node, socket, attr_name_prefix)) {
196 return false;
197 }
198 }
199
200 return true;
201}
202
204 const ImageSpec &image_spec,
205 const string &attr_name_prefix)
206{
207 for (const SocketType &socket : node->type->inputs) {
208 if (!node_socket_from_image_spec_atttributes(node, socket, image_spec, attr_name_prefix)) {
209 return false;
210 }
211 }
212
213 return true;
214}
215
216static bool buffer_params_to_image_spec_atttributes(ImageSpec *image_spec,
217 const BufferParams &buffer_params)
218{
219 if (!node_to_image_spec_atttributes(image_spec, &buffer_params, ATTR_BUFFER_SOCKET_PREFIX)) {
220 return false;
221 }
222
223 /* Passes storage is not covered by the node socket. so "expand" the loop manually. */
224
225 const int num_passes = buffer_params.passes.size();
226 image_spec->attribute(ATTR_PASSES_COUNT, num_passes);
227
228 for (int pass_index = 0; pass_index < num_passes; ++pass_index) {
229 const string attr_name_prefix = string_printf(ATTR_PASS_SOCKET_PREFIX_FORMAT, pass_index);
230
231 const BufferPass *pass = &buffer_params.passes[pass_index];
232 if (!node_to_image_spec_atttributes(image_spec, pass, attr_name_prefix)) {
233 return false;
234 }
235 }
236
237 return true;
238}
239
241 const ImageSpec &image_spec)
242{
243 if (!node_from_image_spec_atttributes(buffer_params, image_spec, ATTR_BUFFER_SOCKET_PREFIX)) {
244 return false;
245 }
246
247 /* Passes storage is not covered by the node socket. so "expand" the loop manually. */
248
249 const int num_passes = image_spec.get_int_attribute(ATTR_PASSES_COUNT, 0);
250 if (num_passes == 0) {
251 LOG(ERROR) << "Missing passes count attribute.";
252 return false;
253 }
254
255 for (int pass_index = 0; pass_index < num_passes; ++pass_index) {
256 const string attr_name_prefix = string_printf(ATTR_PASS_SOCKET_PREFIX_FORMAT, pass_index);
257
258 BufferPass pass;
259
260 if (!node_from_image_spec_atttributes(&pass, image_spec, attr_name_prefix)) {
261 return false;
262 }
263
264 buffer_params->passes.emplace_back(std::move(pass));
265 }
266
267 buffer_params->update_passes();
268
269 return true;
270}
271
272/* Configure image specification for the given buffer parameters and passes.
273 *
274 * Image channels will be strictly ordered to match content of corresponding buffer, and the
275 * metadata will be set so that the render buffers and passes can be reconstructed from it.
276 *
277 * If the tile size different from (0, 0) the image specification will be configured to use the
278 * given tile size for tiled IO. */
279static bool configure_image_spec_from_buffer(ImageSpec *image_spec,
280 const BufferParams &buffer_params,
281 const int2 tile_size = make_int2(0, 0))
282{
283 const std::vector<std::string> channel_names = exr_channel_names_for_passes(buffer_params);
284 const int num_channels = channel_names.size();
285
286 *image_spec = ImageSpec(
287 buffer_params.width, buffer_params.height, num_channels, TypeDesc::FLOAT);
288
289 image_spec->channelnames = std::move(channel_names);
290
291 if (!buffer_params_to_image_spec_atttributes(image_spec, buffer_params)) {
292 return false;
293 }
294
295 if (tile_size.x != 0 || tile_size.y != 0) {
296 DCHECK_GT(tile_size.x, 0);
297 DCHECK_GT(tile_size.y, 0);
298
299 image_spec->tile_width = min(TileManager::IMAGE_TILE_SIZE, tile_size.x);
300 image_spec->tile_height = min(TileManager::IMAGE_TILE_SIZE, tile_size.y);
301 }
302
303 return true;
304}
305
306/* --------------------------------------------------------------------
307 * Tile Manager.
308 */
309
311{
312 /* Use process ID to separate different processes.
313 * To ensure uniqueness from within a process use combination of object address and instance
314 * index. This solves problem of possible object re-allocation at the same time, and solves
315 * possible conflict when the counter overflows while there are still active instances of the
316 * class. */
317 const int tile_manager_id = g_instance_index.fetch_add(1, std::memory_order_relaxed);
319 to_string(reinterpret_cast<uintptr_t>(this)) + "-" +
320 to_string(tile_manager_id);
321}
322
324
325int TileManager::compute_render_tile_size(const int suggested_tile_size) const
326{
327 /* Must be a multiple of IMAGE_TILE_SIZE so that we can write render tiles into the image file
328 * aligned on image tile boundaries. We can't set IMAGE_TILE_SIZE equal to the render tile size
329 * because too big tile size leads to integer overflow inside OpenEXR. */
330 const int computed_tile_size = (suggested_tile_size <= IMAGE_TILE_SIZE) ?
331 suggested_tile_size :
332 align_up(suggested_tile_size, IMAGE_TILE_SIZE);
333 return min(computed_tile_size, MAX_TILE_SIZE);
334}
335
337{
338 VLOG_WORK << "Using tile size of " << tile_size;
339
341
342 tile_size_ = tile_size;
343
344 tile_state_.num_tiles_x = tile_size_.x ? divide_up(params.width, tile_size_.x) : 0;
345 tile_state_.num_tiles_y = tile_size_.y ? divide_up(params.height, tile_size_.y) : 0;
346 tile_state_.num_tiles = tile_state_.num_tiles_x * tile_state_.num_tiles_y;
347
348 tile_state_.next_tile_index = 0;
349
350 tile_state_.current_tile = Tile();
351}
352
354{
355 DCHECK_NE(params.pass_stride, -1);
356
358
359 if (has_multiple_tiles()) {
360 /* TODO(sergey): Proper Error handling, so that if configuration has failed we don't attempt to
361 * write to a partially configured file. */
363
364 const DenoiseParams denoise_params = scene->integrator->get_denoise_params();
365 const AdaptiveSampling adaptive_sampling = scene->integrator->get_adaptive_sampling();
366
368 &write_state_.image_spec, &denoise_params, ATTR_DENOISE_SOCKET_PREFIX);
369
370 /* Not adaptive sampling overscan yet for baking, would need overscan also
371 * for buffers read from the output driver. */
372 if (adaptive_sampling.use && !scene->bake_manager->get_baking()) {
373 overscan_ = 4;
374 }
375 else {
376 overscan_ = 0;
377 }
378 }
379 else {
380 write_state_.image_spec = ImageSpec();
381 overscan_ = 0;
382 }
383}
384
385void TileManager::set_temp_dir(const string &temp_dir)
386{
387 temp_dir_ = temp_dir;
388}
389
391{
392 return tile_state_.next_tile_index == tile_state_.num_tiles;
393}
394
396{
397 if (done()) {
398 return false;
399 }
400
401 tile_state_.current_tile = get_tile_for_index(tile_state_.next_tile_index);
402
403 ++tile_state_.next_tile_index;
404
405 return true;
406}
407
409{
410 /* TODO(sergey): Consider using hilbert spiral, or. maybe, even configurable. Not sure this
411 * brings a lot of value since this is only applicable to BIG tiles. */
412
413 const int tile_index_y = index / tile_state_.num_tiles_x;
414 const int tile_index_x = index - tile_index_y * tile_state_.num_tiles_x;
415
416 const int tile_window_x = tile_index_x * tile_size_.x;
417 const int tile_window_y = tile_index_y * tile_size_.y;
418
419 Tile tile;
420
421 tile.x = max(0, tile_window_x - overscan_);
422 tile.y = max(0, tile_window_y - overscan_);
423
424 tile.window_x = tile_window_x - tile.x;
425 tile.window_y = tile_window_y - tile.y;
426 tile.window_width = min(tile_size_.x, buffer_params_.width - tile_window_x);
427 tile.window_height = min(tile_size_.y, buffer_params_.height - tile_window_y);
428
429 tile.width = min(buffer_params_.width - tile.x, tile.window_x + tile.window_width + overscan_);
430 tile.height = min(buffer_params_.height - tile.y,
431 tile.window_y + tile.window_height + overscan_);
432
433 return tile;
434}
435
437{
438 return tile_state_.current_tile;
439}
440
445
447{
449 "cycles-tile-buffer-" + tile_file_unique_part_ + "-" +
450 to_string(write_state_.tile_file_index) + ".exr");
451
452 write_state_.tile_out = ImageOutput::create(write_state_.filename);
453 if (!write_state_.tile_out) {
454 LOG(ERROR) << "Error creating image output for " << write_state_.filename;
455 return false;
456 }
457
458 if (!write_state_.tile_out->supports("tiles")) {
459 LOG(ERROR) << "Progress tile file format does not support tiling.";
460 return false;
461 }
462
463 if (!write_state_.tile_out->open(write_state_.filename, write_state_.image_spec)) {
464 LOG(ERROR) << "Error opening tile file: " << write_state_.tile_out->geterror();
465 write_state_.tile_out = nullptr;
466 return false;
467 }
468
469 write_state_.num_tiles_written = 0;
470
471 VLOG_WORK << "Opened tile file " << write_state_.filename;
472
473 return true;
474}
475
477{
478 if (!write_state_.tile_out) {
479 return true;
480 }
481
482 const bool success = write_state_.tile_out->close();
483 write_state_.tile_out = nullptr;
484
485 if (!success) {
486 LOG(ERROR) << "Error closing tile file.";
487 return false;
488 }
489
490 VLOG_WORK << "Tile output is closed.";
491
492 return true;
493}
494
495bool TileManager::write_tile(const RenderBuffers &tile_buffers)
496{
497 if (!write_state_.tile_out) {
498 if (!open_tile_output()) {
499 return false;
500 }
501 }
502
503 const double time_start = time_dt();
504
506
507 const BufferParams &tile_params = tile_buffers.params;
508
509 const int tile_x = tile_params.full_x - buffer_params_.full_x + tile_params.window_x;
510 const int tile_y = tile_params.full_y - buffer_params_.full_y + tile_params.window_y;
511
512 const int64_t pass_stride = tile_params.pass_stride;
513 const int64_t tile_row_stride = tile_params.width * pass_stride;
514
515 vector<float> pixel_storage;
516 const float *pixels = tile_buffers.buffer.data() + tile_params.window_x * pass_stride +
517 tile_params.window_y * tile_row_stride;
518
519 /* If there is an overscan used for the tile copy pixels into single continuous block of memory
520 * without any "gaps".
521 * This is a workaround for bug in OIIO (https://github.com/OpenImageIO/oiio/pull/3176).
522 * Our task reference: #93008. */
523 if (tile_params.window_x || tile_params.window_y ||
524 tile_params.window_width != tile_params.width ||
525 tile_params.window_height != tile_params.height)
526 {
527 pixel_storage.resize(pass_stride * tile_params.window_width * tile_params.window_height);
528 float *pixels_continuous = pixel_storage.data();
529
530 const int64_t pixels_row_stride = pass_stride * tile_params.width;
531 const int64_t pixels_continuous_row_stride = pass_stride * tile_params.window_width;
532
533 for (int i = 0; i < tile_params.window_height; ++i) {
534 memcpy(pixels_continuous, pixels, sizeof(float) * pixels_continuous_row_stride);
535 pixels += pixels_row_stride;
536 pixels_continuous += pixels_continuous_row_stride;
537 }
538
539 pixels = pixel_storage.data();
540 }
541
542 VLOG_WORK << "Write tile at " << tile_x << ", " << tile_y;
543
544 /* The image tile sizes in the OpenEXR file are different from the size of our big tiles. The
545 * write_tiles() method expects a contiguous image region that will be split into tiles
546 * internally. OpenEXR expects the size of this region to be a multiple of the tile size,
547 * however OpenImageIO automatically adds the required padding.
548 *
549 * The only thing we have to ensure is that the tile_x and tile_y are a multiple of the
550 * image tile size, which happens in compute_render_tile_size. */
551
552 const int64_t xstride = pass_stride * sizeof(float);
553 const int64_t ystride = xstride * tile_params.window_width;
554 const int64_t zstride = ystride * tile_params.window_height;
555
556 if (!write_state_.tile_out->write_tiles(tile_x,
557 tile_x + tile_params.window_width,
558 tile_y,
559 tile_y + tile_params.window_height,
560 0,
561 1,
562 TypeDesc::FLOAT,
563 pixels,
564 xstride,
565 ystride,
566 zstride))
567 {
568 LOG(ERROR) << "Error writing tile " << write_state_.tile_out->geterror();
569 return false;
570 }
571
572 ++write_state_.num_tiles_written;
573
574 VLOG_WORK << "Tile written in " << time_dt() - time_start << " seconds.";
575
576 return true;
577}
578
580{
581 if (!write_state_.tile_out) {
582 /* None of the tiles were written hence the file was not created.
583 * Avoid creation of fully empty file since it is redundant. */
584 return;
585 }
586
587 /* EXR expects all tiles to present in file. So explicitly write missing tiles as all-zero. */
588 if (write_state_.num_tiles_written < tile_state_.num_tiles) {
590
591 for (int tile_index = write_state_.num_tiles_written; tile_index < tile_state_.num_tiles;
592 ++tile_index)
593 {
595
596 const int tile_x = tile.x + tile.window_x;
597 const int tile_y = tile.y + tile.window_y;
598
599 VLOG_WORK << "Write dummy tile at " << tile_x << ", " << tile_y;
600
601 write_state_.tile_out->write_tiles(tile_x,
602 tile_x + tile.window_width,
603 tile_y,
604 tile_y + tile.window_height,
605 0,
606 1,
607 TypeDesc::FLOAT,
608 pixel_storage.data());
609 }
610 }
611
613
616 }
617
618 VLOG_WORK << "Tile file size is "
620
621 /* Advance the counter upon explicit finish of the file.
622 * Makes it possible to re-use tile manager for another scene, and avoids unnecessary increments
623 * of the tile-file-within-session index. */
624 ++write_state_.tile_file_index;
625
626 write_state_.filename = "";
627}
628
629bool TileManager::read_full_buffer_from_disk(const string_view filename,
631 DenoiseParams *denoise_params)
632{
633 unique_ptr<ImageInput> in(ImageInput::open(filename));
634 if (!in) {
635 LOG(ERROR) << "Error opening tile file " << filename;
636 return false;
637 }
638
639 const ImageSpec &image_spec = in->spec();
640
641 BufferParams buffer_params;
643 return false;
644 }
645 buffers->reset(buffer_params);
646
648 return false;
649 }
650
651 const int num_channels = in->spec().nchannels;
652 if (!in->read_image(0, 0, 0, num_channels, TypeDesc::FLOAT, buffers->buffer.data())) {
653 LOG(ERROR) << "Error reading pixels from the tile file " << in->geterror();
654 return false;
655 }
656
657 if (!in->close()) {
658 LOG(ERROR) << "Error closing tile file " << in->geterror();
659 return false;
660 }
661
662 return true;
663}
664
int pass_stride
Definition buffers.h:94
vector< BufferPass > passes
Definition buffers.h:97
int window_y
Definition buffers.h:80
int window_height
Definition buffers.h:82
int window_width
Definition buffers.h:81
NODE_DECLARE int width
Definition buffers.h:72
int window_x
Definition buffers.h:79
void update_passes()
Definition buffers.cpp:120
device_vector< float > buffer
Definition buffers.h:160
BufferParams params
Definition buffers.h:157
BufferParams buffer_params_
void set_temp_dir(const string &temp_dir)
function< void(string_view)> full_buffer_written_cb
string temp_dir_
bool close_tile_output()
struct TileManager::@1465 tile_state_
void reset_scheduling(const BufferParams &params, int2 tile_size)
int compute_render_tile_size(const int suggested_tile_size) const
bool has_multiple_tiles() const
Tile get_tile_for_index(int index) const
void update(const BufferParams &params, const Scene *scene)
const int2 get_size() const
const Tile & get_current_tile() const
string filename
bool open_tile_output()
bool write_tile(const RenderBuffers &tile_buffers)
struct TileManager::@1466 write_state_
bool read_full_buffer_from_disk(string_view filename, RenderBuffers *buffers, DenoiseParams *denoise_params)
ImageSpec image_spec
static const int MAX_TILE_SIZE
string tile_file_unique_part_
void finish_write_tiles()
static const int IMAGE_TILE_SIZE
#define CCL_NAMESPACE_END
ccl_device_forceinline int2 make_int2(const int x, const int y)
draw_view in_light_buf[] float
static const char * to_string(const Interpolation &interp)
Definition gl_shader.cc:82
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
const int tile_index
ccl_global const KernelWorkTile * tile
#define PASS_UNUSED
#define DCHECK_EQ(a, b)
Definition log.h:59
#define VLOG_WORK
Definition log.h:75
#define DCHECK_GT(a, b)
Definition log.h:60
#define LOG(severity)
Definition log.h:33
#define DCHECK_NE(a, b)
Definition log.h:58
size_t path_file_size(const string &path)
Definition path.cpp:556
string path_join(const string &dir, const string &file)
Definition path.cpp:417
static const char * ATTR_PASS_SOCKET_PREFIX_FORMAT
static bool node_socket_generic_to_image_spec_atttributes(ImageSpec *image_spec, const Node *node, const SocketType &socket, const string &attr_name_prefix, const ValidateValueFunc &validate_value_func, const GetValueFunc &get_value_func)
static const char * ATTR_BUFFER_SOCKET_PREFIX
static std::vector< std::string > exr_channel_names_for_passes(const BufferParams &buffer_params)
static bool buffer_params_to_image_spec_atttributes(ImageSpec *image_spec, const BufferParams &buffer_params)
static const char * ATTR_DENOISE_SOCKET_PREFIX
static CCL_NAMESPACE_BEGIN const char * ATTR_PASSES_COUNT
static std::atomic< uint64_t > g_instance_index
static bool node_socket_from_image_spec_atttributes(Node *node, const SocketType &socket, const ImageSpec &image_spec, const string &attr_name_prefix)
static bool node_from_image_spec_atttributes(Node *node, const ImageSpec &image_spec, const string &attr_name_prefix)
static bool node_to_image_spec_atttributes(ImageSpec *image_spec, const Node *node, const string &attr_name_prefix)
string node_socket_attribute_name(const SocketType &socket, const string &attr_name_prefix)
static bool buffer_params_from_image_spec_atttributes(BufferParams *buffer_params, const ImageSpec &image_spec)
static bool configure_image_spec_from_buffer(ImageSpec *image_spec, const BufferParams &buffer_params, const int2 tile_size=make_int2(0, 0))
static bool node_socket_to_image_spec_atttributes(ImageSpec *image_spec, const Node *node, const SocketType &socket, const string &attr_name_prefix)
#define min(a, b)
Definition sort.c:32
_W64 unsigned int uintptr_t
Definition stdint.h:119
__int64 int64_t
Definition stdint.h:89
string string_human_readable_number(size_t num)
Definition string.cpp:255
CCL_NAMESPACE_BEGIN string string_printf(const char *format,...)
Definition string.cpp:23
bool exists(ustring x) const
Definition node_enum.h:29
int num_components
Definition pass.h:28
ustring name
Definition node_type.h:79
Type type
Definition node_type.h:80
const NodeEnum * enum_values
Definition node_type.h:83
int x
Definition types_int2.h:15
int y
Definition types_int2.h:15
uint64_t system_self_process_id()
Definition system.cpp:255
CCL_NAMESPACE_BEGIN double time_dt()
Definition time.cpp:36
float max
ccl_device_inline size_t align_up(size_t offset, size_t alignment)
Definition util/types.h:48
ccl_device_inline size_t divide_up(size_t x, size_t y)
Definition util/types.h:53
char * buffers[2]