From 7a90af7b5a50bac5bb9fa6bf8ec785dac145a90c Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Sat, 12 Jan 2013 10:59:13 +0000 Subject: [PATCH] Packed and generated images support for Cycles This commit adds support of packed and generated images for Cycles when using SVM backend. Movies are still not supported. This changes also doesn't touch OSL which is much less trivial to adopt for any images which are not saved to disk. Implementation details: - When adding images to Image Manager is now possible to mark image as builtin. Builtin images will bypass OIIO loader and will use special loading callbacks. - Callbacks are set by Blender Session and they're using C++ RNA interface to obtain needed data (pixels, dimensions, is_float flag). - Image Manager assumes file path is used as reference to a builtin images, but in fact currently image datablock name is used for reference. This makes it easy to find an image in BlendData database. - Added some extra properties to Image RNA: * channels, which denotes actual number of channels in ImBuf. This is needed to treat image's pixels correct (before it wasn't possible because API used internal number of channels for pixels which is in fact doesn't correlate with image depth) * is_float, which is truth if image is stored in float buffer of ImBuf. - Implemented string lookup for C++ RNA collections for cases there's no manual lookup function. OSL is not supported because it used own image loading and filtering routines and there's seems to be no API to feed pre-loaded pixels directly to the library. Think we'll either need to add some API to support such kind of feeding or consider OSL does not have support of packed images at all. Movies are not supported at this moment because of lack of RNA API to load specified frame. It's not difficult to solve, just need to consider what to best here: * Either write some general python interface for ImBuf and use it via C++ API, or * Write a PY API function which will return pixels for given frame, or * Use bad-level BKE_* call Anyway, small steps, further improvements later. Reviewed by Brecht, thanks! --- intern/cycles/blender/blender_session.cpp | 69 ++++++++++ intern/cycles/blender/blender_session.h | 4 + intern/cycles/blender/blender_shader.cpp | 32 ++++- intern/cycles/render/image.cpp | 153 ++++++++++++++------- intern/cycles/render/image.h | 10 +- intern/cycles/render/nodes.cpp | 14 +- intern/cycles/render/nodes.h | 2 + source/blender/makesrna/intern/makesrna.c | 23 +++- source/blender/makesrna/intern/rna_image.c | 46 +++++++ 9 files changed, 288 insertions(+), 65 deletions(-) diff --git a/intern/cycles/blender/blender_session.cpp b/intern/cycles/blender/blender_session.cpp index d0c7c06cbb3..60dfe37c9f8 100644 --- a/intern/cycles/blender/blender_session.cpp +++ b/intern/cycles/blender/blender_session.cpp @@ -109,6 +109,11 @@ void BlenderSession::create_session() session->reset(buffer_params, session_params.samples); b_engine.use_highlight_tiles(session_params.progressive_refine == false); + + /* setup callbacks for builtin image support */ + scene->image_manager->builtin_image_info_cb = function_bind(&BlenderSession::builtin_image_info, this, _1, _2, _3, _4, _5); + scene->image_manager->builtin_image_pixels_cb = function_bind(&BlenderSession::builtin_image_pixels, this, _1, _2); + scene->image_manager->builtin_image_float_pixels_cb = function_bind(&BlenderSession::builtin_image_float_pixels, this, _1, _2); } void BlenderSession::reset_session(BL::BlendData b_data_, BL::Scene b_scene_) @@ -607,5 +612,69 @@ void BlenderSession::test_cancel() session->progress.set_cancel("Cancelled"); } +void BlenderSession::builtin_image_info(const string &name, bool &is_float, int &width, int &height, int &channels) +{ + BL::Image b_image = b_data.images[name]; + + if(b_image) { + is_float = b_image.is_float(); + width = b_image.size()[0]; + height = b_image.size()[1]; + channels = b_image.channels(); + } + else { + is_float = false; + width = 0; + height = 0; + channels = 0; + } +} + +bool BlenderSession::builtin_image_pixels(const string &name, unsigned char *pixels) +{ + BL::Image b_image = b_data.images[name]; + + if(b_image) { + int width = b_image.size()[0]; + int height = b_image.size()[1]; + int channels = b_image.channels(); + + BL::DynamicArray pixels_array = b_image.pixels(); + float *float_pixels = pixels_array.data; + + /* a bit of shame, but Py API currently only returns float array, + * which need to be converted back to char buffer + */ + unsigned char *cp = pixels; + float *fp = float_pixels; + for(int i = 0; i < channels * width * height; i++, cp++, fp++) { + *cp = *fp * 255; + } + + return true; + } + + return false; +} + +bool BlenderSession::builtin_image_float_pixels(const string &name, float *pixels) +{ + BL::Image b_image = b_data.images[name]; + + if(b_image) { + int width = b_image.size()[0]; + int height = b_image.size()[1]; + int channels = b_image.channels(); + + BL::DynamicArray pixels_array = b_image.pixels(); + + memcpy(pixels, pixels_array.data, width * height * channels * sizeof(float)); + + return true; + } + + return false; +} + CCL_NAMESPACE_END diff --git a/intern/cycles/blender/blender_session.h b/intern/cycles/blender/blender_session.h index 7f3973ae873..dfa487bd244 100644 --- a/intern/cycles/blender/blender_session.h +++ b/intern/cycles/blender/blender_session.h @@ -93,6 +93,10 @@ public: protected: void do_write_update_render_result(BL::RenderResult b_rr, BL::RenderLayer b_rlay, RenderTile& rtile, bool do_update_only); void do_write_update_render_tile(RenderTile& rtile, bool do_update_only); + + void builtin_image_info(const string &name, bool &is_float, int &width, int &height, int &channels); + bool builtin_image_pixels(const string &name, unsigned char *pixels); + bool builtin_image_float_pixels(const string &name, float *pixels); }; CCL_NAMESPACE_END diff --git a/intern/cycles/blender/blender_shader.cpp b/intern/cycles/blender/blender_shader.cpp index ddbd7f935e4..3bb02bbfe74 100644 --- a/intern/cycles/blender/blender_shader.cpp +++ b/intern/cycles/blender/blender_shader.cpp @@ -511,9 +511,24 @@ static ShaderNode *add_node(Scene *scene, BL::BlendData b_data, BL::Scene b_scen BL::ShaderNodeTexImage b_image_node(b_node); BL::Image b_image(b_image_node.image()); ImageTextureNode *image = new ImageTextureNode(); - /* todo: handle generated/builtin images */ + /* todo: handle movie images */ if(b_image && b_image.source() != BL::Image::source_MOVIE) { - image->filename = image_user_file_path(b_image_node.image_user(), b_image, b_scene.frame_current()); + /* builtin images will use callback-based reading because + * they could only be loaded correct from blender side + */ + bool is_builtin = b_image.packed_file() || + b_image.source() == BL::Image::source_GENERATED; + + if(is_builtin) { + /* for builtin images we're using image datablock name to find an image to read pixels from later */ + image->filename = b_image.name(); + image->is_builtin = true; + } + else { + image->filename = image_user_file_path(b_image_node.image_user(), b_image, b_scene.frame_current()); + image->is_builtin = false; + } + image->animated = b_image_node.image_user().use_auto_refresh(); } image->color_space = ImageTextureNode::color_space_enum[(int)b_image_node.color_space()]; @@ -528,8 +543,17 @@ static ShaderNode *add_node(Scene *scene, BL::BlendData b_data, BL::Scene b_scen BL::Image b_image(b_env_node.image()); EnvironmentTextureNode *env = new EnvironmentTextureNode(); if(b_image && b_image.source() != BL::Image::source_MOVIE) { - env->filename = image_user_file_path(b_env_node.image_user(), b_image, b_scene.frame_current()); - env->animated = b_env_node.image_user().use_auto_refresh(); + bool is_builtin = b_image.packed_file() || + b_image.source() == BL::Image::source_GENERATED; + + if(is_builtin) { + env->filename = b_image.name(); + env->is_builtin = true; + } + else { + env->filename = image_user_file_path(b_env_node.image_user(), b_image, b_scene.frame_current()); + env->animated = b_env_node.image_user().use_auto_refresh(); + } } env->color_space = EnvironmentTextureNode::color_space_enum[(int)b_env_node.color_space()]; env->projection = EnvironmentTextureNode::projection_enum[(int)b_env_node.projection()]; diff --git a/intern/cycles/render/image.cpp b/intern/cycles/render/image.cpp index 230a12f9ff2..e6f8ab4a5d9 100644 --- a/intern/cycles/render/image.cpp +++ b/intern/cycles/render/image.cpp @@ -85,11 +85,21 @@ bool ImageManager::set_animation_frame_update(int frame) return false; } -bool ImageManager::is_float_image(const string& filename) +bool ImageManager::is_float_image(const string& filename, bool is_builtin) { - ImageInput *in = ImageInput::create(filename); bool is_float = false; + if(is_builtin) { + if(builtin_image_info_cb) { + int width, height, channels; + builtin_image_info_cb(filename, is_float, width, height, channels); + } + + return is_float; + } + + ImageInput *in = ImageInput::create(filename); + if(in) { ImageSpec spec; @@ -113,13 +123,13 @@ bool ImageManager::is_float_image(const string& filename) return is_float; } -int ImageManager::add_image(const string& filename, bool animated, bool& is_float) +int ImageManager::add_image(const string& filename, bool is_builtin, bool animated, bool& is_float) { Image *img; size_t slot; /* load image info and find out if we need a float texture */ - is_float = (pack_images)? false: is_float_image(filename); + is_float = (pack_images)? false: is_float_image(filename, is_builtin); if(is_float) { /* find existing image */ @@ -150,6 +160,7 @@ int ImageManager::add_image(const string& filename, bool animated, bool& is_floa /* add new image */ img = new Image(); img->filename = filename; + img->is_builtin = is_builtin; img->need_load = true; img->animated = animated; img->users = 1; @@ -184,6 +195,7 @@ int ImageManager::add_image(const string& filename, bool animated, bool& is_floa /* add new image */ img = new Image(); img->filename = filename; + img->is_builtin = is_builtin; img->need_load = true; img->animated = animated; img->users = 1; @@ -197,12 +209,12 @@ int ImageManager::add_image(const string& filename, bool animated, bool& is_floa return slot; } -void ImageManager::remove_image(const string& filename) +void ImageManager::remove_image(const string& filename, bool is_builtin) { size_t slot; for(slot = 0; slot < images.size(); slot++) { - if(images[slot] && images[slot]->filename == filename) { + if(images[slot] && images[slot]->filename == filename && images[slot]->is_builtin == is_builtin) { /* decrement user count */ images[slot]->users--; assert(images[slot]->users >= 0); @@ -220,7 +232,7 @@ void ImageManager::remove_image(const string& filename) if(slot == images.size()) { /* see if it's in a float texture slot */ for(slot = 0; slot < float_images.size(); slot++) { - if(float_images[slot] && float_images[slot]->filename == filename) { + if(float_images[slot] && float_images[slot]->filename == filename && float_images[slot]->is_builtin == is_builtin) { /* decrement user count */ float_images[slot]->users--; assert(float_images[slot]->users >= 0); @@ -242,27 +254,43 @@ bool ImageManager::file_load_image(Image *img, device_vector& tex_img) if(img->filename == "") return false; - /* load image from file through OIIO */ - ImageInput *in = ImageInput::create(img->filename); + ImageInput *in = NULL; + int width, height, components; - if(!in) - return false; + if(!img->is_builtin) { + /* load image from file through OIIO */ + in = ImageInput::create(img->filename); - ImageSpec spec; + if(!in) + return false; - if(!in->open(img->filename, spec)) { - delete in; - return false; + ImageSpec spec; + + if(!in->open(img->filename, spec)) { + delete in; + return false; + } + + width = spec.width; + height = spec.height; + components = spec.nchannels; + } + else { + /* load image using builtin images callbacks */ + if(!builtin_image_info_cb || !builtin_image_pixels_cb) + return false; + + bool is_float; + builtin_image_info_cb(img->filename, is_float, width, height, components); } /* we only handle certain number of components */ - int width = spec.width; - int height = spec.height; - int components = spec.nchannels; - if(!(components == 1 || components == 3 || components == 4)) { - in->close(); - delete in; + if(in) { + in->close(); + delete in; + } + return false; } @@ -270,14 +298,19 @@ bool ImageManager::file_load_image(Image *img, device_vector& tex_img) uchar *pixels = (uchar*)tex_img.resize(width, height); int scanlinesize = width*components*sizeof(uchar); - in->read_image(TypeDesc::UINT8, - (uchar*)pixels + (height-1)*scanlinesize, - AutoStride, - -scanlinesize, - AutoStride); + if(in) { + in->read_image(TypeDesc::UINT8, + (uchar*)pixels + (height-1)*scanlinesize, + AutoStride, + -scanlinesize, + AutoStride); - in->close(); - delete in; + in->close(); + delete in; + } + else { + builtin_image_pixels_cb(img->filename, pixels); + } if(components == 3) { for(int i = width*height-1; i >= 0; i--) { @@ -304,27 +337,42 @@ bool ImageManager::file_load_float_image(Image *img, device_vector& tex_ if(img->filename == "") return false; - /* load image from file through OIIO */ - ImageInput *in = ImageInput::create(img->filename); + ImageInput *in = NULL; + int width, height, components; - if(!in) - return false; + if(!img->is_builtin) { + /* load image from file through OIIO */ + in = ImageInput::create(img->filename); - ImageSpec spec; + if(!in) + return false; - if(!in->open(img->filename, spec)) { - delete in; - return false; + ImageSpec spec; + + if(!in->open(img->filename, spec)) { + delete in; + return false; + } + + /* we only handle certain number of components */ + width = spec.width; + height = spec.height; + components = spec.nchannels; + } + else { + /* load image using builtin images callbacks */ + if(!builtin_image_info_cb || !builtin_image_float_pixels_cb) + return false; + + bool is_float; + builtin_image_info_cb(img->filename, is_float, width, height, components); } - /* we only handle certain number of components */ - int width = spec.width; - int height = spec.height; - int components = spec.nchannels; - if(!(components == 1 || components == 3 || components == 4)) { - in->close(); - delete in; + if(in) { + in->close(); + delete in; + } return false; } @@ -332,14 +380,19 @@ bool ImageManager::file_load_float_image(Image *img, device_vector& tex_ float *pixels = (float*)tex_img.resize(width, height); int scanlinesize = width*components*sizeof(float); - in->read_image(TypeDesc::FLOAT, - (uchar*)pixels + (height-1)*scanlinesize, - AutoStride, - -scanlinesize, - AutoStride); + if(in) { + in->read_image(TypeDesc::FLOAT, + (uchar*)pixels + (height-1)*scanlinesize, + AutoStride, + -scanlinesize, + AutoStride); - in->close(); - delete in; + in->close(); + delete in; + } + else { + builtin_image_float_pixels_cb(img->filename, pixels); + } if(components == 3) { for(int i = width*height-1; i >= 0; i--) { diff --git a/intern/cycles/render/image.h b/intern/cycles/render/image.h index 4d177174971..e39ac14b60f 100644 --- a/intern/cycles/render/image.h +++ b/intern/cycles/render/image.h @@ -51,9 +51,9 @@ public: ImageManager(); ~ImageManager(); - int add_image(const string& filename, bool animated, bool& is_float); - void remove_image(const string& filename); - bool is_float_image(const string& filename); + int add_image(const string& filename, bool is_builtin, bool animated, bool& is_float); + void remove_image(const string& filename, bool is_builtin); + bool is_float_image(const string& filename, bool is_builtin); void device_update(Device *device, DeviceScene *dscene, Progress& progress); void device_free(Device *device, DeviceScene *dscene); @@ -65,6 +65,9 @@ public: bool need_update; + boost::function builtin_image_info_cb; + boost::function builtin_image_pixels_cb; + boost::function builtin_image_float_pixels_cb; private: int tex_num_images; int tex_num_float_images; @@ -74,6 +77,7 @@ private: struct Image { string filename; + bool is_builtin; bool need_load; bool animated; diff --git a/intern/cycles/render/nodes.cpp b/intern/cycles/render/nodes.cpp index 14ef3c68ad3..d1297c9dc94 100644 --- a/intern/cycles/render/nodes.cpp +++ b/intern/cycles/render/nodes.cpp @@ -142,6 +142,7 @@ ImageTextureNode::ImageTextureNode() slot = -1; is_float = -1; filename = ""; + is_builtin = false; color_space = ustring("Color"); projection = ustring("Flat");; projection_blend = 0.0f; @@ -155,7 +156,7 @@ ImageTextureNode::ImageTextureNode() ImageTextureNode::~ImageTextureNode() { if(image_manager) - image_manager->remove_image(filename); + image_manager->remove_image(filename, is_builtin); } ShaderNode *ImageTextureNode::clone() const @@ -176,7 +177,7 @@ void ImageTextureNode::compile(SVMCompiler& compiler) image_manager = compiler.image_manager; if(is_float == -1) { bool is_float_bool; - slot = image_manager->add_image(filename, animated, is_float_bool); + slot = image_manager->add_image(filename, is_builtin, animated, is_float_bool); is_float = (int)is_float_bool; } @@ -237,7 +238,7 @@ void ImageTextureNode::compile(OSLCompiler& compiler) tex_mapping.compile(compiler); if(is_float == -1) - is_float = (int)image_manager->is_float_image(filename); + is_float = (int)image_manager->is_float_image(filename, false); compiler.parameter("filename", filename.c_str()); if(is_float || color_space != "Color") @@ -271,6 +272,7 @@ EnvironmentTextureNode::EnvironmentTextureNode() slot = -1; is_float = -1; filename = ""; + is_builtin = false; color_space = ustring("Color"); projection = ustring("Equirectangular"); animated = false; @@ -283,7 +285,7 @@ EnvironmentTextureNode::EnvironmentTextureNode() EnvironmentTextureNode::~EnvironmentTextureNode() { if(image_manager) - image_manager->remove_image(filename); + image_manager->remove_image(filename, is_builtin); } ShaderNode *EnvironmentTextureNode::clone() const @@ -304,7 +306,7 @@ void EnvironmentTextureNode::compile(SVMCompiler& compiler) image_manager = compiler.image_manager; if(slot == -1) { bool is_float_bool; - slot = image_manager->add_image(filename, animated, is_float_bool); + slot = image_manager->add_image(filename, is_builtin, animated, is_float_bool); is_float = (int)is_float_bool; } @@ -354,7 +356,7 @@ void EnvironmentTextureNode::compile(OSLCompiler& compiler) tex_mapping.compile(compiler); if(is_float == -1) - is_float = (int)image_manager->is_float_image(filename); + is_float = (int)image_manager->is_float_image(filename, false); compiler.parameter("filename", filename.c_str()); compiler.parameter("projection", projection); diff --git a/intern/cycles/render/nodes.h b/intern/cycles/render/nodes.h index 564ceee5a5b..8b2d6a0e5c8 100644 --- a/intern/cycles/render/nodes.h +++ b/intern/cycles/render/nodes.h @@ -70,6 +70,7 @@ public: int slot; int is_float; string filename; + bool is_builtin; ustring color_space; ustring projection; float projection_blend; @@ -89,6 +90,7 @@ public: int slot; int is_float; string filename; + bool is_builtin; ustring color_space; ustring projection; bool animated; diff --git a/source/blender/makesrna/intern/makesrna.c b/source/blender/makesrna/intern/makesrna.c index 1ba50657751..2eacdfd4880 100644 --- a/source/blender/makesrna/intern/makesrna.c +++ b/source/blender/makesrna/intern/makesrna.c @@ -3544,8 +3544,27 @@ static const char *cpp_classes = "" "#define COLLECTION_PROPERTY_LOOKUP_STRING_FALSE(sname, identifier) \\\n" " inline static int sname##_##identifier##_lookup_string_wrap(PointerRNA *ptr, const char *key, PointerRNA *r_ptr) \\\n" " { \\\n" -" memset(r_ptr, 0, sizeof(*r_ptr)); \\\n" -" return 0; \\\n" +" CollectionPropertyIterator iter; \\\n" +" int found = 0; \\\n" +" PropertyRNA *item_name_prop = RNA_struct_name_property(ptr->type); \\\n" +" sname##_##identifier##_begin(&iter, ptr); \\\n" +" while (iter.valid && !found) { \\\n" +" char name_fixed[32]; \\\n" +" const char *name; \\\n" +" int name_length; \\\n" +" name = RNA_property_string_get_alloc(&iter.ptr, item_name_prop, name_fixed, sizeof(name_fixed), &name_length); \\\n" +" if (!strncmp(name, key, name_length)) { \\\n" +" *r_ptr = iter.ptr; \\\n" +" found = 1; \\\n" +" } \\\n" +" if (name_fixed != name) \\\n" +" MEM_freeN((void *) name); \\\n" +" sname##_##identifier##_next(&iter); \\\n" +" } \\\n" +" sname##_##identifier##_end(&iter); \\\n" +" if (!found) \\\n" +" memset(r_ptr, 0, sizeof(*r_ptr)); \\\n" +" return found; \\\n" " } \n" "#define COLLECTION_PROPERTY_LOOKUP_STRING_TRUE(sname, identifier) \\\n" " inline static int sname##_##identifier##_lookup_string_wrap(PointerRNA *ptr, const char *key, PointerRNA *r_ptr) \\\n" diff --git a/source/blender/makesrna/intern/rna_image.c b/source/blender/makesrna/intern/rna_image.c index 5d37f67fa93..11510b7e436 100644 --- a/source/blender/makesrna/intern/rna_image.c +++ b/source/blender/makesrna/intern/rna_image.c @@ -377,6 +377,38 @@ static void rna_Image_pixels_set(PointerRNA *ptr, const float *values) BKE_image_release_ibuf(ima, ibuf, lock); } +static int rna_Image_channels_get(PointerRNA *ptr) +{ + Image *im = (Image *)ptr->data; + ImBuf *ibuf; + void *lock; + int channels = 0; + + ibuf = BKE_image_acquire_ibuf(im, NULL, &lock); + if (ibuf) + channels = ibuf->channels; + + BKE_image_release_ibuf(im, ibuf, lock); + + return channels; +} + +static int rna_Image_is_float_get(PointerRNA *ptr) +{ + Image *im = (Image *)ptr->data; + ImBuf *ibuf; + void *lock; + int is_float = FALSE; + + ibuf = BKE_image_acquire_ibuf(im, NULL, &lock); + if (ibuf) + is_float = ibuf->rect_float != NULL; + + BKE_image_release_ibuf(im, ibuf, lock); + + return is_float; +} + #else static void rna_def_imageuser(BlenderRNA *brna) @@ -664,6 +696,10 @@ static void rna_def_image(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Duration", "Duration (in frames) of the image (1 when not a video/sequence)"); RNA_def_property_clear_flag(prop, PROP_EDITABLE); + /* NOTE about pixels/channels/is_floa: + * this properties describes how image is stored internally (inside of ImBuf), + * not how it was saved to disk or how it'll be saved on disk + */ prop = RNA_def_property(srna, "pixels", PROP_FLOAT, PROP_NONE); RNA_def_property_flag(prop, PROP_DYNAMIC); RNA_def_property_multi_array(prop, 1, NULL); @@ -671,6 +707,16 @@ static void rna_def_image(BlenderRNA *brna) RNA_def_property_dynamic_array_funcs(prop, "rna_Image_pixels_get_length"); RNA_def_property_float_funcs(prop, "rna_Image_pixels_get", "rna_Image_pixels_set", NULL); + prop = RNA_def_property(srna, "channels", PROP_INT, PROP_UNSIGNED); + RNA_def_property_int_funcs(prop, "rna_Image_channels_get", NULL, NULL); + RNA_def_property_ui_text(prop, "Channels", "Number of channels in pixels nuffer"); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + + prop = RNA_def_property(srna, "is_float", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_funcs(prop, "rna_Image_is_float_get", NULL); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, "Is Float", "True if this image is stored in float buffer"); + prop = RNA_def_property(srna, "colorspace_settings", PROP_POINTER, PROP_NONE); RNA_def_property_pointer_sdna(prop, NULL, "colorspace_settings"); RNA_def_property_struct_type(prop, "ColorManagedInputColorspaceSettings");