Dev:Ref/Requests/Plugin-System

提供: wiki
< Dev:Ref‎ | Requests
2018年6月29日 (金) 02:51時点におけるYamyam (トーク | 投稿記録)による版 (1版 をインポートしました)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)
移動先: 案内検索

Please keep in mind, that this is still work in progress. Not all APIs are written. API is in use for the upcoming render API. Check mosani's builds.

Current State

The current plugin system has the following features / short-comings:

  • supports only sequencer effects and custom materials
  • has the idea of one plugin per file
  • each plugin is loaded by the file selector, no fancy menu entries
  • mixes versioning with capabilities (look at float / byte-buffer hassle in sequencer plugins)
  • has very limited support for parameters / gui control (no ipos, parameters have all to be sliders in a row)
  • plugin instance data is more a hack than a feature.
  • plugin data is split all over the place (take a look at "effectdata", "PluginSeq" for sequencer effects)
  • is only somewhat backward compatible. The plugin has no chance to know, which Blender version is used to load it. If we use function, that is not implemented dlopen will fail.

Proposal

The idea is to make a generic plugin interface, which overcomes these limitations and adding the following benefits:

  • make input and output also pluggable, thereby removing ffmpeg and OpenAL from the core.
  • build a base, so that further things can be made pluggable a lot easier

Let's dive into the

API

Current development version of the new plugin-system can be found here: plugin-system-20070729.tgz

(Warning! There is a fake renderAPI.h file included to make it compile without renderapi. But if you want to place this into the renderAPI-branch, please remove renderAPI.h from blenpluginapi!)

also needs this patch to work: id-property-patch

blender core side

Header file to use: "PLU_core_api.h"

Let's take a look at the problem from blender core side: the first limitation I want to remove, is the "choose plugin by file idea". This is broken usability. Therefore, plugins should be kept (just like python scripts) in a seperate directory, which is scanned. Looking at the gimp plugin system, the best way to do this (for speed reasons): scan once and cache the result until the plugin filestamp changes again. To add plugins during a session, there should be a "plugin rescan" button somewhere though. In the current proposal, this is done on "plugin_enumerate" and "plugin_get_by_name" implicitly.

This scan results in several linked lists of so called "plugin_descriptors":

 typedef struct plugin_descriptor {
   struct plugin_descriptor *next, *prev;
   
   plugin_type_t plugin_type;
   
   unsigned long long capabilities;
    
   const char* name;
   const char* plugin_filename;
   const char* menu_entry;
    
   const char* author;
   const char* copyright;
   const char* help;
   const char* date;
   
   const char* file_pattern;
   
   /* fields below this place are initialized by plugin_load! */
   
   int interface_version; /* =0 if interface not loaded ! */
   
   plugin_private_descriptor_t * desc;
   
   plugin_interface_common_t common;
   plugin_interface_gui_t * gui;
   
   union {
      plugin_interface_video_input_t video_in;
      plugin_interface_video_effect_t video_eff;
      plugin_interface_video_output_t video_out;
      
      plugin_interface_audio_input_t audio_in;
      plugin_interface_audio_effect_t audio_eff;
      plugin_interface_audio_output_t audio_out;
      
      plugin_interface_subtitle_input_t subtitle_in;
      plugin_interface_subtitle_effect_t subtitle_eff;
      plugin_interface_subtitle_output_t subtitle_out;
      
      plugin_interface_mux_output_t mux_output;
      plugin_interface_material_t material;
      plugin_interface_compositor_node_t compositor_node;
      plugin_interface_material_node_t material_node;
      
      plugin_interface_video_point_filter_t point_filter;
      
      /* add more as need arises ... ;-) */
   } i;
 } plugin_descriptor_t;

Querying of descriptors is done by the following functions:

 plugin_descriptor_t * plugin_enumerate(plugin_type_t type, int force_refresh);
 plugin_descriptor_t * plugin_get_by_name(plugin_type_t type, const char* name);

As you might have already guessed, each type of plugin (video input, output, etc.) has it's own linked list of plugin_descriptors and uses it's own interface which the union above suggests.

Access to plugins is now done by plugin-name rather by file which makes it a lot easier to say: "give me the jpeg-input plugin". (which could be integrated in a larger plugin-pack shared object btw.)

OK. We are now able to build our menu entries fast at startup and we can show somewhere, which plugins are available and who wrote them, but we can't do anything with our plugin.

Let's introduce the video input plugin as an example, how to use this system: (just ignore everything beyond the free-method, we come to these functions later)

  typedef struct plugin_interface_video_input {
        /*
          the following properties are always set by the core
          
          "filename"    filename to open (string)
          
          "xoffset"     crop xoffset (int)
          "yoffset"     crop yoffset (int)
          "width"       crop width (int)
          "height"      crop height (int)
          
          the following properties are used by the core, if exposed
          
          "frame"       frame returned by next call to get() (float)
          "duration"    duration in frames (float)
          "framerate"   framerate (float)
          */
          
        struct ImBuf * (*get)(plugin_control_t* c);
  } plugin_interface_video_input_t;

Looks pretty simple (at least, if you know how to read C-function pointers... ;-)? Properties are persistent values stored along with every plugin using the well known ID Properties system. It will be extended to allow binding properties to session data structures, but this shown below on the "plugin side of the world". As you can see, all data (including the data needed for construction) are stored within properties.

Now say, we want to open a certain file from the filesystem and find the appropriate plugin to do the job.

  plugin_control_t * open_video_file(const char * filename) {
        plugin_control_t * rv;
   
        plugin_descriptor_t * p = plugin_enumerate(PLUGIN_TYPE_VIDEO_INPUT);
    
        while (p) {
             if (file_matches_pattern(filename, p->file_pattern)) {
                   rv = plugin_init_control(p, 0,
                                            PLP_STRING, "filename", filename,
                                            PLP_END);
                   if (rv) {
                        return rv;
                   }
             }
   
             p = p->next;
        }
   
        return 0;
  }

At this point, we have a perfectly initialized plugin instance. The init-function used for the plugins is hidden within the plugin_interface_common_t, so no need to worry about. Since init-arguments are simply properties, no problem here.

For completeness sake, the cruel details of plugin_control are presented below:

 typedef struct plugin_control {
        plugin_type_t plugin_type;         /* plugin type written to blend file */
        char name[64];                     /* written to blend file, to find
                                              plugin on reload 
                                              This is _not_ the filename, but
                                              the name, the plugin used to
                                              register itself!
                                            */
        plugin_descriptor_t * descriptor;  /* session descriptor pointer */
        plugin_instance_t* instance;       /* session instance pointer */
        struct IDProperty p_data;          /* written to blend file */
        plugin_gui_t *gui;                 /* session gui instance pointer */
        void * uplink;                     /* session uplink to core structure
                                              (Sequence*) e.g. */
 } plugin_control_t;

This structure is written to blend-file, as well is the data included in p_data. Don't worry about initializing this beast. It all done automagically within

  plugin_control_t * plugin_init_control(plugin_descriptor_t * desc, void * uplink,
                                         int prop_type, const char * name, void * data, ...);

Only thing left to do: save the plugin_control-structure somewhere (e.g. Sequence) and you are done. Oh wait, we missed the restore part of the story, when the blend-file is read. But this is as easy as:

  void plugin_load_control(plugin_control_t * c, void * uplink);

You can call this function as often as you want. It will only initialize the control-structure once, after load, but not more often. So lazy evaluation works here.

Assume, we want to use the plugin functions within core. As I told above, the only thing to store, is the plugin_control_t. So here we go:

  plugin_control_t * c;
  
  float frame_rate;
  
  if (plugin_get_property(c, PLP_FLOAT, "framerate", &frame_rate)) {
      /* plugin can tell us a frame rate */
  }

So what about extra parameters and GUI? And what is this little plugin_gui_t pointer doing above? Well first of all, you shouldn't care about plugin_gui_t. Period. It is used by the GUI system internally and is neither intended for core nor for plugin users.

But there is another plugin_descriptor-field, we haven't talked about:

  typedef struct plugin_interface_gui {
     void (*init)(plugin_control_t * control);
     void (*load)(plugin_control_t * control);
     void (*free)(plugin_control_t * control);
     void (*draw)(plugin_control_t * control);
     void (*callback)(plugin_control_t * control, short button);
  } plugin_interface_gui_t;

If (and only if!), there is a gui field set within the plugin_descriptor, the plugin has GUI code, that can be used.

Since all persistent data is stored in properties, even different input plugins can share the same data. Maybe you opened up a movie-file with blender on your Linux box using ffmpeg. The ffmpeg-input plugin-name is therefore stored within the plugin_control structure. Then you might want to open the same blend-file on MacOSX, which only has a quicktime plugin in place. Now we have a _real_ restore problem. The code could now be clever enough, to alternatively try to open the same file using quicktime.

The use of the GUI and private data is best shown from the plugin side. (blender core is rather boring, call draw, call callback in scene-handler, enough said.)

plugin side

Header file to use: PLU_api.h

Well plugin side, easy side of the world. So here we go: the only function exposed to the outside world of a new type plugin is:

  void plugin_query();

That was simple ;-)

And not quite correct. One of the problems of the old version was, that it made it somewhat hard to build the plugins on Windows and that it had some problems with versioning. This was caused by the fact, that the plugin does import symbols directly from the core.

So before we can do anything usefull within our plugin, we have to talk about

APIs and API versioning

Instead of simply importing APIs directly, we do it a little bit different. Don't be afraid, you don't have to learn that much new stuff :) (Trust me, I know what I'm doing...)

You may be accustomed to write

  #include <foo.h>

to import interfaces. Since we want to import interfaces dynamically and probably with a certain version, this wont be enough. To keep all the gory details away from you, three convenience macros are provided:

Those are called PLU_IMPORT_API, PLU_IMPORT_API_VERSION and PLU_IMPORT_API_ADVANCED.

   PLU_IMPORT_API(foo)

or

   PLU_IMPORT_API_VERSION(foo, VERSION)

and then in an arbitrary function use

   PLU_foo->api_function(blurb);

or

   struct plugin_api_foo_v_VERSION * myvar = NULL;
   PLU_IMPORT_API_ADVANCED(foo,VERSION,myvar)

to make it store a certain version into a particular place. All three macros expand to something semantically equivalent to this (which can be done by hand, if one wants to do customizing):

   struct plugin_api_foo_v_VERSION * PLU_foo = NULL;
   
   #if defined(c_plusplus) || defined(__cplusplus)
   extern "C" {
   #endif
   
   int plugin_store_api_foo(
         struct plugin_api_foo_v_VERSION_t * (*get_api)(int)) {
          
 	 struct plugin_api_foo_v_VERSION * p = get_api(VERSION);
         
         if (!p) {
                 return FALSE;
         } else {
                 PLU_foo = p;
                 return TRUE;
         }
   }
   
   #if defined(c_plusplus) || defined(__cplusplus)
   }
   #endif

Before the plugin_query() function is called, all plugin_store_api_ functions are called, to initialize the used APIs.

If they return FALSE, a fatal problem is assumed, and the plugin is _not_ loaded! By default, not finding an API of the correct version in the core is considered fatal, but you are free to write different plugin_store_api-functions, that are a little bit more clever and try several versions before they fail.

All good and fine, but what APIs are available, and how should they help?

Currently, there is "register", "register_gui", "gui", "util", "prop", "sequencer", "audio" and "imbuf".

To get started, we will need "register", "register_gui", "gui", "prop" (for properties) and "util" (for memory allocation).

  PLU_IMPORT_API(register)
  PLU_IMPORT_API(register_gui)
  PLU_IMPORT_API(prop)
  PLU_IMPORT_API(util)
  PLU_IMPORT_API(gui)

Now we can get back to

plugin_query

The new code works like this: check for plugin_query, if this symbol is present, call it and let the plugin register itself. If this does not work, try plugin_getinfo and set up wrapper structures, to support old plugins.

Anyways. Here are the registration functions, that can be called from the plugin. For each type of plugin, there is a seperate registration function for type safety reasons. (Trust me, type safety is a good thing...)

  plugin_descriptor_t * PLU_register->video_input(
     const char* name, 
     
     const char* author,
     const char* copyright,
     const char* help,
     const char* date,
     
     const char* file_pattern,
     
     unsigned long long capabilities,
     
     plugin_private_descriptor_t * desc,
     
     plugin_instance_t* (*init)(plugin_control_t * c),
     plugin_instance_t* (*load)(plugin_control_t * c),
     void (*free)(plugin_instance_t* This),
     	
     struct ImBuf * (*get)(plugin_control_t* c));

So basically this is the concatenation of the beginning of the plugin_descriptor structure, the plugin_common interface structure and the video_input interface structure. We could have used the structures directly, but that would lead to plugins, that forget to initialize one or the other function pointer... Since we need only one registration function per plugin_type, that's ok. Considering the fact, that plugin-code gets much cleaner then. You might have noticed, that there is no version parameter. This is completely handled by the API versioning system explained above. That way, nobody ever gets (the now obsolete) B_PLUGIN_VERSION wrong anymore...

With capabilities you can tell the core, which input data is accepted by your plugin. Right now this is only used for Imbufs:

  #define	PLUGIN_CAP_IMBUF_FLOAT (1 << 0)
  #define	PLUGIN_CAP_IMBUF_BYTE  (1 << 1)
  #define	PLUGIN_CAP_IMBUF_MULTILAYER  (1 << 2)

In plugin-scan mode, plugin_register_video_input-function will only consider the first part and when plugin_query() finished, we will simply unload the plugin. No big deal. If we want to really _use_ the plugin, we store additionally the function pointers.

We most probably also want to register a gui, so here we go:

  void PLU_register_gui->gui(
        plugin_descriptor_t * desc,
         
        void (*draw)(plugin_control_t * control),
        void (*callback)(plugin_control_t * control, short button));

or for guis, that need custom initialisation _independent_ of plugin initialisation. (generic guis written for several different plugin types):

  void PLU_register_gui->gui_custom_init(
       	plugin_descriptor_t * desc,
        
        void (*init)(plugin_control_t * control);
        void (*load)(plugin_control_t * control);
        void (*free)(plugin_control_t * control);
        
        void (*draw)(plugin_control_t * control),
        void (*callback)(plugin_control_t * control, short button));

Too much hassle for you? You wanted your old VarStruct array and that's it. There is a convenience function, that does the conversion trick:

  void PLU_register_gui->gui_simple(
        plugin_descriptor_t * desc,
        
        VarStruct * vstruct,
        unsigned int num_elems,
        unsigned int offset_p_data /* offset into plugin_instance_t,
                                      where we should store the
                                      pointer to the persitent data array
                                      (formerly known as Cast)
                                    */
       );

Now create a simple instance_data-structure, that just holds one pointer named "Cast*" and define your Cast-structure as usual. World is saved, we can at least do as much as we could do last time ;-)

But maybe you want to use the new features. Here comes the small interface for gui_draw:

  void PLU_gui->draw_slider(plugin_control_t * control,
                              int type, const char* name,
                              float def, float min, float max,
                              const char* tip, 
                              float * data, int xpos, int ypos);

Only one function for now, but enough to place the slider at some arbitrary place ;-) We will add more functions later, which is pretty easy. Especially popup-menus are needed, otherwise we won't be able to build the video-output dialog of ffmpeg.

A small little function usefull for init() and load():

  void PLU_gui->bind_ipo_curve(plugin_control_t * control, const char * icu_name, float * data);
</source

Whoopsy, and now we have IPO-control for all parameters... ;-)

Complete example of a video input plugin (without gui and bells and whizzles):

<source lang="cpp">
  #include "PLU_api.h"
  
  PLU_IMPORT_API(register)
  PLU_IMPORT_API(prop)
  PLU_IMPORT_API(util)
  PLU_IMPORT_API(imbuf)
  
  struct plugin_instance {
        int xoffset;
        int yoffset;
        
        int crop_width;
        int crop_height;
        
        jpeghandle_t handle;
  };
  
  plugin_instance_t* my_init(plugin_control_t * c) {
        plugin_instance_t * rv = 0;
        char* filename;
        jpeg_handle_t handle = 0;
        
        if (!PLU_prop->get(c, PLP_STRING, "filename", &filename)) {
              PLU_prop->set(c, PLP_STRING, "error", "core went nuts");
     	      return 0;
        }
        
        ... try jpeg open, that returns handle ...
        
        if (!handle) {
              PLU_prop->set(c, PLP_STRING, "error", failure_notice);
              return 0;
        }
        
        rv = mallocN(sizeof(struct plugin_instance), "jpeg-instance");
         
        PLU_prop->bind_many(c, 
                               PLP_INT, "xoffset", &rv->xoffset,
                               PLP_INT, "yoffset", &rv->yoffset,
                               PLP_INT, "width", &rv->crop_width,
                               PLP_INT, "height", &rv->crop_height,
                               PLP_END);
        
        rv->handle = handle;
        
        return rv;
  }
  
  void my_free(plugin_instance_t* This) {
        ... nothing to be done here, maybe some jpeg lib needs some deinit?
  }
      
  struct ImBuf * my_get(plugin_control_t* c) {
        struct ImBuf * rv = PLU_imbuf->new_from_control(c);
        
        unsigned int * rect = PLU_imbuf->get_rect(rv);
        
        ... jpeg load ...
        
        return rv;        
  }
  
  void plugin_query() {
        PLU_register->video_input("jpeg", 
                                    "Joe Sixpack",
                                    "Copyright (C) 2007 Joe Sixpack",
                                    "Opens JPEG files using jpeglib",
                                    "2007-04-01",
                                    "*.jpg;*.jpeg",
             
                                    PLUGIN_CAP_IMBUF_BYTE,
         
                                    0,
                                    my_init, my_init, my_free,
                                    
                                    my_get);
   }