Difference between revisions of "Item mandelbrot"

From The Foundry MODO SDK wiki
Jump to: navigation, search
Line 16: Line 16:
  
 
==Class Declarations==
 
==Class Declarations==
 +
 +
We want to create a filter with which to apply our mandelbrot pattern. First, we inherit from [[Filter_(lx-filter.hpp)#.284.29_SDK:_ILxStackFilter_interface|CLxImpl_StackFilter]] to be able to compare, query, and convert our filter. We then inherit from CLxImpl_ImageFilter to be able to actually build an image filter, as the mandelbrot pattern is ultimately an image. Finally, we inherit from CLxImpl_ImageFilterMetrics so we can can keep track of the values in the filter.
  
 
  class CMandelbrotFilter :
 
  class CMandelbrotFilter :
Line 51: Line 53:
 
  };
 
  };
  
We want to create a filter with which to apply our mandelbrot pattern. First, we inherit from [[Filter_(lx-filter.hpp)#.284.29_SDK:_ILxStackFilter_interface|CLxImpl_StackFilter]] to be able to compare, query, and convert our filter. We then inherit from CLxImpl_ImageFilter to be able to actually build an image filter, as the mandelbrot pattern is ultimately an image. Finally, we inherit from CLxImpl_ImageFilterMetrics so we can can keep track of the values in the filter.
+
We want to be able to create threads and then share our work among them, so we inherit from [[Thread_(lx-thread.hpp)#.2822.29_SDK:_ILxSharedWork_interface|CLxImpl_SharedWork]].
  
 
  class CMandlebrotWork :
 
  class CMandlebrotWork :
Line 68: Line 70:
 
  };
 
  };
  
We want to be able to create threads and then share our work among them, so we inherit from [[Thread_(lx-thread.hpp)#.2822.29_SDK:_ILxSharedWork_interface|CLxImpl_SharedWork]].
+
We want to implement our package by creating an instance of it, so we inherit from the [[Package_(lx-package.hpp)#.2825.29_SDK:_ILxPackageInstance_interface|CLxImpl_PackageInstance class]]. We also need some of the methods that [[Clip_(lx-clip.hpp)#.281.29_SDK:_ILxVideoClipItem_interface|CLxImpl_VideoClipItem]], as the superclass of all image related clips, includes so we inherit from it.
  
 
  class CMandelbrotInstance :
 
  class CMandelbrotInstance :
Line 92: Line 94:
 
  };
 
  };
  
We want to implement our package by creating an instance of it, so we inherit from the [[Package_(lx-package.hpp)#.2825.29_SDK:_ILxPackageInstance_interface|CLxImpl_PackageInstance class]]. We also need some of the methods that [[Clip_(lx-clip.hpp)#.281.29_SDK:_ILxVideoClipItem_interface|CLxImpl_VideoClipItem]], as the superclass of all image related clips, includes so we inherit from it.
+
We want to construct a package for the mandelbrot filter, so we inherit from [[Package_(lx-package.hpp)|CLxImpl_Package]].
  
 
  class CMandelbrotPackage :
 
  class CMandelbrotPackage :
Line 106: Line 108:
 
  };  
 
  };  
  
We want to construct a package for the mandelbrot filter, so we inherit from [[Package_(lx-package.hpp)|CLxImpl_Package]].
+
This class creates a [[Factory_Object|factory]] for all the exported objects that are not servers. Only CMandelBrotPackage is exported as a server in this fle. Since the polymorph object has to exist for the lifetime of any instance, the are explicitly allocated as part of the global module and freed at shutdown.  
  
 
  class CFactories {
 
  class CFactories {
Line 127: Line 129:
 
   
 
   
 
  } *pF;
 
  } *pF;
 
This class creates a [[Factory_Object|factory]] for all the exported objects that are not servers. Only CMandelBrotPackage is exported as a server in this fle. Since the polymorph object has to exist for the lifetime of any instance, the are explicitly allocated as part of the global module and freed at shutdown.
 
  
 
===[[Server_Tags|Server Tags]]===
 
===[[Server_Tags|Server Tags]]===
 +
 +
The tags here indicate that our Mandelbrot item type is a subtype of video clip meaning an image that may change over time.
  
 
  LXtTagInfoDesc CMandelbrotPackage::descInfo[] = {
 
  LXtTagInfoDesc CMandelbrotPackage::descInfo[] = {
Line 136: Line 138:
 
         { 0 }
 
         { 0 }
 
  };
 
  };
 
The tags here indicate that our Mandelbrot item type is a subtype of video clip meaning an image that may change over time.
 
  
 
===[[Initialize_(index)|Initialize]]===
 
===[[Initialize_(index)|Initialize]]===
 +
 +
This function exports a server with the package and staticdesc interfaces that is dependent on the CMandelbrotPackage. It also creates the factories using the function references in 138-164.
  
 
         void
 
         void
Line 154: Line 156:
 
  }
 
  }
  
This function exports a server with the package and staticdesc interfaces that is dependent on the CMandelbrotPackage. It also creates the factories using the function references in 138-164.
+
===Helper Function===
 +
 
 +
This function destroys the factories so they can persist while their objects are in use.  
  
===Helper Function===
 
 
 
         void
 
         void
 
  cleanup ()
 
  cleanup ()
Line 163: Line 165:
 
         delete pF;
 
         delete pF;
 
  }
 
  }
 
This function destroys the factories so they can persist while their objects are in use.
 
 
  
 
===Implementations===
 
===Implementations===
 +
 +
Here the channels for the package are created and their default values set using the [[Package_(lx-package.hpp)#.2815.29_SDK:_ILxAddChannel_interface|AddChannel]] interface and the [[Package_(lx-package.hpp)#.2815.29_SDK:_ILxAddChannel_interface|NewChannel]] utility
  
 
  #define Cs_ITERATIONS "iterations"
 
  #define Cs_ITERATIONS "iterations"
Line 215: Line 216:
 
  }
 
  }
  
Here the channels for the package are created and their default values set using the [[Package_(lx-package.hpp)#.2815.29_SDK:_ILxAddChannel_interface|AddChannel]] interface and the [[Package_(lx-package.hpp)#.2815.29_SDK:_ILxAddChannel_interface|NewChannel]] utility
+
This section uses [[Wrap_(lx-wrap.hpp)#CLxGenericPolymorph|TestInterface]] so that nexus knows what interfaces instances of this package support. This is necessary as to prevent query loops.
  
 
         LxResult
 
         LxResult
Line 224: Line 225:
 
  }
 
  }
  
This section uses [[Wrap_(lx-wrap.hpp)#CLxGenericPolymorph|TestInterface]] so that nexus knows what interfaces instances of this package support. This is necessary as to prevent query loops.
+
Here,  Attach is called to create a new instance of this item. The returned object implements a specific item of this type in the scene using the [[Wrap_(lx-wrap.hpp)#CLxPolymorph|Alloc]] method.
  
 
         LxResult
 
         LxResult
Line 236: Line 237:
 
  }
 
  }
  
Here,  Attach is called to create a new instance of this item. The returned object implements a specific item of this type in the scene using the [[Wrap_(lx-wrap.hpp)#CLxPolymorph|Alloc]] method.
+
The instance is the implementation of the item, and this set of code allocates one for each item in the scene. The instant can respond to a set of events.
  
 
         LxResult
 
         LxResult
Line 247: Line 248:
 
  }
 
  }
  
The instance is the implementation of the item, and this set of code allocates one for each item in the scene. The instant can respond to a set of events.
+
The VideoClipItem interface allows this item to function as a generic video clip. PrepFilter allows the item to select channels needed for filter allocation, and record the indices for those in a cache.
  
 
         LxResult
 
         LxResult
Line 280: Line 281:
 
  }
 
  }
  
The VideoClipItem interface allows this item to function as a generic video clip. PrepFilter allows the item to select channels needed for filter allocation, and record the indices for those in a cache.
+
AllocFilter creates the image filter for a given frame.
  
 
         LxResult
 
         LxResult
Line 320: Line 321:
 
  }
 
  }
  
AllocFilter creates the image filter for a given frame.
+
This section cleans up some data.
  
 
         void
 
         void
Line 331: Line 332:
 
  }
 
  }
  
This section cleans up some data.
+
This section places the image onto the image stack where it generates target image on the fly
  
 
         const char *
 
         const char *
Line 345: Line 346:
 
  }
 
  }
  
This section places the image onto the image stack where it generates target image on the fly
+
This uses Compare to compare a filter which has been evaluated to a new one being computed. If the size has changed we require the image to be remade from scratch. For other changes the past result is compatible with the current result so some previous work can be reused.
  
 
         unsigned
 
         unsigned
Line 375: Line 376:
 
  }
 
  }
  
This uses Compare to compare a filter which has been evaluated to a new one being computed. If the size has changed we require the image to be remade from scratch. For other changes the past result is compatible with the current result so some previous work can be reused.
+
This section will be called to convert a past evaluation to a compatible filter. We always use the existing image, and if only the color parameters have changed we can reuse the previous gradient input map.
  
 
         LxResult
 
         LxResult
Line 403: Line 404:
 
  }
 
  }
  
This section will be called to convert a past evaluation to a compatible filter. We always use the existing image, and if only the color parameters have changed we can reuse the previous gradient input map.
+
Here, generators allocate an image and fill it in. This filter maintains a reference to the image so it can be reused as needed. Filters that generate and forget would instead simply return the image.
  
 
         LxResult
 
         LxResult
Line 437: Line 438:
 
  }
 
  }
  
Here, generators allocate an image and fill it in. This filter maintains a reference to the image so it can be reused as needed. Filters that generate and forget would instead simply return the image.
+
Generate full filter(the main function in this section) will create the image filter at its full size, using the width and height from the channels on the Mandelbrot item.
  
 
         LxResult
 
         LxResult
Line 479: Line 480:
 
  }
 
  }
  
Generate full filter(the main function in this section) will create the image filter at its full size, using the width and height from the channels on the Mandelbrot item.
+
Generate small filter does the same as above, except it works on a smaller resolution, using a temporary float buffer. GenerateFBuffer() converts the buffer to the image
  
 
         LxResult
 
         LxResult
Line 506: Line 507:
 
  }
 
  }
  
Generate small filter does the same as above, except it works on a smaller resolution, using a temporary float buffer. GenerateFBuffer() converts the buffer to the image
+
We generate the buffer in the threads. We allocate a work object using Alloc(F), initialize it to the image and process it.
  
 
         void
 
         void
Line 532: Line 533:
 
  }
 
  }
  
We generate the buffer in the threads. We allocate a work object using Alloc(F), initialize it to the image and process it.
+
This section converts the raw buffered values to colors. This could be done in the workers too, since setting pixels should be thread-safe.
  
 
         void
 
         void
Line 560: Line 561:
 
  }
 
  }
  
This section converts the raw buffered values to colors. This could be done in the workers too, since setting pixels should be thread-safe.
+
Adds a metrics interface for getting size and type info.
  
 
  LxResult
 
  LxResult
Line 575: Line 576:
 
  }
 
  }
  
Adds a metrics interface for getting size and type info.
+
The class created here(CMandelbrotWork) is a way to spread the work among threads. Each work object holds a current line and number of lines remaining initialized to zero. The master work object will be initialized to the total lines in the image, and the threading system will allocate enough new work objects to keep the system's core busy.
  
 
  LxResult
 
  LxResult
Line 591: Line 592:
 
  }
 
  }
  
The class created here(CMandelbrotWork) is a way to spread the work among threads. Each work object holds a current line and number of lines remaining initialized to zero. The master work object will be initialized to the total lines in the image, and the threading system will allocate enough new work objects to keep the system's core busy.
+
Work objects that have work will be asked to share with those that don't. In this case we transfer one line from this work object to the other. This is currently the only valid split mode, although it could transfer more lines to reduce contention.
  
 
         LxResult  
 
         LxResult  
Line 611: Line 612:
 
  }
 
  }
  
Work objects that have work will be asked to share with those that don't. In this case we transfer one line from this work object to the other. This is currently the only valid split mode, although it could transfer more lines to reduce contention.
+
Evaluate(the main function here) processes one piece of work, in this case a line of the image. The floating point buffer is filled with values from -1 to 1.
  
 
         LxResult
 
         LxResult
Line 633: Line 634:
 
  }
 
  }
  
Evaluate(the main function here) processes one piece of work, in this case a line of the image. The floating point buffer is filled with values from -1 to 1.
+
Here, we compute a single pixel. This is the standard Mandelbrot loop, except that the final iteration count is scaled 0 to 1 in a log scale. Points in the set are assigned -1.
  
 
         float
 
         float
Line 653: Line 654:
 
         return -1.0;
 
         return -1.0;
 
  }
 
  }
 
Here, we compute a single pixel. This is the standard Mandelbrot loop, except that the final iteration count is scaled 0 to 1 in a log scale. Points in the set are assigned -1.
 

Revision as of 21:24, 10 September 2013

Item_mandelbrot is a basic example plugin. This wiki page is intended as a walkthrough of the code in order to help you better understand the SDK.

This plugin adds the mandelbrot pattern to modo. A mandelbrot is a shading pattern that you overlay onto whatever you wish.

You can access the pattern by going to the model tab->Shading->Add Layer->mandelbrot.

Sphereshot.png

A unit sphere before the mandelbrot patter is applied

Brat1.pngBrat2.png

The two sides of the above unit sphere after the mandelbrot pattern is applied

Code Walkthrough

Class Declarations

We want to create a filter with which to apply our mandelbrot pattern. First, we inherit from CLxImpl_StackFilter to be able to compare, query, and convert our filter. We then inherit from CLxImpl_ImageFilter to be able to actually build an image filter, as the mandelbrot pattern is ultimately an image. Finally, we inherit from CLxImpl_ImageFilterMetrics so we can can keep track of the values in the filter.

class CMandelbrotFilter :
               public CLxImpl_StackFilter,
               public CLxImpl_ImageFilter,
               public CLxImpl_ImageFilterMetrics
{
   public:
       CMandelbrotPackage	*src_pkg;
       CLxUser_GradientFilter	 g_filt[4];
       unsigned		 i_w, i_h, i_max;
       double			 x_0, x_s, y_0, y_s;
       float			 s_col[4];

       CLxUser_ImageWrite	 w_img;
       float			*f_buf;
       double			 max_log;
       bool			 use_image, gen_buffer;

       const char *	 filt_Type (void)			LXx_OVERRIDE;
       unsigned	 filt_Compare (ILxUnknownID other)	LXx_OVERRIDE;
       LxResult	 filt_Convert (ILxUnknownID other)	LXx_OVERRIDE;

       unsigned	 imf_Type (void)			LXx_OVERRIDE;
       LxResult	 imf_Generate (int width, int height, ILxUnknownID monitor, void **ppvObj) LXx_OVERRIDE;

       LxResult	 imfmet_Generate (LXtImageMetrics *metrics) LXx_OVERRIDE;
 
   private:
       LxResult	 GenerateFullFilter (void);
       LxResult	 GenerateSmallFilter (const int w, const int  h, CLxUser_ImageWrite &wimg);

       void		 GenerateFBuffer (const int w, const int h, float *fbuf);
       void		 ConvertFBufferToImage (const float *fbuf, const int w, const int h, CLxUser_ImageWrite &wimg);
};

We want to be able to create threads and then share our work among them, so we inherit from CLxImpl_SharedWork.

class CMandlebrotWork :
               public CLxImpl_SharedWork
{
   public:
       CMandelbrotFilter	*src_filt;
       float			*fbuf;
       int			 i_y, n_y, w, h;

       LxResult	 share_Evaluate ();
       LxResult	 share_Spawn (void **ppvObj);
       LxResult	 share_Share (ILxUnknownID other, unsigned int split);

       float		 Pixel (double x, double y);
};

We want to implement our package by creating an instance of it, so we inherit from the CLxImpl_PackageInstance class. We also need some of the methods that CLxImpl_VideoClipItem, as the superclass of all image related clips, includes so we inherit from it.

class CMandelbrotInstance :
               public CLxImpl_PackageInstance,
               public CLxImpl_VideoClipItem
{
   public:
       CMandelbrotPackage	*src_pkg;
       CLxUser_Item		 m_item;

       LxResult	 pins_Initialize (ILxUnknownID item, ILxUnknownID super) LXx_OVERRIDE;
       void		 pins_Cleanup (void) LXx_OVERRIDE;

       LxResult	 vclip_PrepFilter (ILxUnknownID eval, void **cache) LXx_OVERRIDE;
       LxResult	 vclip_AllocFilter (ILxUnknownID attr, void *cache, void **ppvObj) LXx_OVERRIDE;
       void		 vclip_Cleanup (void *cache) LXx_OVERRIDE;

       typedef struct st_GenData {
               unsigned	w, h, cx, cy;
               unsigned	rad, grd, col;
               unsigned	itr;
       } GenData;
};

We want to construct a package for the mandelbrot filter, so we inherit from CLxImpl_Package.

class CMandelbrotPackage :
               public CLxImpl_Package
{
   public:
       CLxUser_ImageService	 img_svc;
       static LXtTagInfoDesc	 descInfo[];

       LxResult		pkg_SetupChannels (ILxUnknownID addChan) LXx_OVERRIDE;
       LxResult		pkg_TestInterface (const LXtGUID *guid) LXx_OVERRIDE;
       LxResult		pkg_Attach (void **ppvObj) LXx_OVERRIDE;
}; 

This class creates a factory for all the exported objects that are not servers. Only CMandelBrotPackage is exported as a server in this fle. Since the polymorph object has to exist for the lifetime of any instance, the are explicitly allocated as part of the global module and freed at shutdown.

class CFactories {
   public:
       CLxPolymorph<CMandelbrotInstance>	 inst;
       CLxPolymorph<CMandelbrotFilter>		 filt;
       CLxPolymorph<CMandlebrotWork>		 work;

       CFactories ()
       {
               inst.AddInterface (new CLxIfc_PackageInstance   <CMandelbrotInstance>);
               inst.AddInterface (new CLxIfc_VideoClipItem     <CMandelbrotInstance>);

               filt.AddInterface (new CLxIfc_StackFilter       <CMandelbrotFilter>);
               filt.AddInterface (new CLxIfc_ImageFilter       <CMandelbrotFilter>);
               filt.AddInterface (new CLxIfc_ImageFilterMetrics<CMandelbrotFilter>);

               work.AddInterface (new CLxIfc_SharedWork        <CMandlebrotWork>);
       }

} *pF;

Server Tags

The tags here indicate that our Mandelbrot item type is a subtype of video clip meaning an image that may change over time.

LXtTagInfoDesc	 CMandelbrotPackage::descInfo[] = {
       { LXsPKG_SUPERTYPE,	LXsITYPE_VIDEOCLIP	},
       { 0 }
};

Initialize

This function exports a server with the package and staticdesc interfaces that is dependent on the CMandelbrotPackage. It also creates the factories using the function references in 138-164.

       void
initialize ()
{
       CLxGenericPolymorph		*srv;

       srv = new CLxPolymorph<CMandelbrotPackage>;
       srv->AddInterface (new CLxIfc_Package   <CMandelbrotPackage>);
       srv->AddInterface (new CLxIfc_StaticDesc<CMandelbrotPackage>);
       thisModule.AddServer ("mandelbrot", srv);

       pF = new CFactories;
}

Helper Function

This function destroys the factories so they can persist while their objects are in use.

       void
cleanup ()
{
       delete pF;
}

Implementations

Here the channels for the package are created and their default values set using the AddChannel interface and the NewChannel utility

#define Cs_ITERATIONS		"iterations"
#define Cs_SIZE			"size"
#define Cs_CENTER		"center"
#define Cs_RADIUS		"radius"
#define Cs_COLOR		"color"
#define Cs_SETCOL		"setColor"

       LxResult
CMandelbrotPackage::pkg_SetupChannels (
       ILxUnknownID		 addChan)
{
       CLxUser_AddChannel	 ac (addChan);
       double			 vec[4];

       ac.NewChannel  (Cs_ITERATIONS, LXsTYPE_INTEGER);
       ac.SetDefault  (0.0, 255);

       ac.NewChannel  (Cs_SIZE,   LXsTYPE_PIXEL);
       ac.SetVector   (LXsCHANVEC_XY);
       ac.SetDefault  (0.0, 512);

       ac.NewChannel  (Cs_CENTER, LXsTYPE_UVCOORD);
       ac.SetVector   (LXsCHANVEC_XY);
       vec[0] = -0.5;
       vec[1] =  0.0;
       ac.SetDefaultVec (vec);

       ac.NewChannel  (Cs_RADIUS, LXsTYPE_UVCOORD);
       ac.SetDefault  (1.0, 0);

       ac.NewChannel  (Cs_COLOR,  LXsTYPE_COLOR1);
       ac.SetVector   (LXsCHANVEC_RGBA);
       ac.SetGradient (LXsTYPE_FLOAT);
       ac.SetDefault  (1.0, 0);

       ac.NewChannel  (Cs_SETCOL, LXsTYPE_COLOR1);
       ac.SetVector   (LXsCHANVEC_RGBA);
       vec[0] = 0.0;
       vec[1] = 0.0;
       vec[2] = 0.0;
       vec[3] = 1.0;
       ac.SetDefaultVec (vec);

       return LXe_OK;
}

This section uses TestInterface so that nexus knows what interfaces instances of this package support. This is necessary as to prevent query loops.

       LxResult
CMandelbrotPackage::pkg_TestInterface (
       const LXtGUID		*guid)
{
       return (pF->inst.TestInterface (guid) ? LXe_TRUE : LXe_FALSE);
}

Here, Attach is called to create a new instance of this item. The returned object implements a specific item of this type in the scene using the Alloc method.

       LxResult
CMandelbrotPackage::pkg_Attach (
       void		       **ppvObj)
{
       CMandelbrotInstance		*brot = pF->inst.Alloc (ppvObj);

       brot->src_pkg = this;
       return LXe_OK;
}

The instance is the implementation of the item, and this set of code allocates one for each item in the scene. The instant can respond to a set of events.

       LxResult
CMandelbrotInstance::pins_Initialize (
       ILxUnknownID		 item,
       ILxUnknownID		 super)
{ 
       m_item.set (item);
       return LXe_OK;
}

The VideoClipItem interface allows this item to function as a generic video clip. PrepFilter allows the item to select channels needed for filter allocation, and record the indices for those in a cache.

       LxResult
CMandelbrotInstance::vclip_PrepFilter (
       ILxUnknownID		 evalObj,
       void		       **cache)
{
       CLxUser_Evaluation	 eval (evalObj);
       GenData			*gd;

       gd = new GenData;
       if (!gd)
               return LXe_FAILED;

       gd->itr = eval.AddChan (m_item, Cs_ITERATIONS );
       gd->w   = eval.AddChan (m_item, Cs_SIZE   ".X");
       gd->h   = eval.AddChan (m_item, Cs_SIZE   ".Y");
       gd->cx  = eval.AddChan (m_item, Cs_CENTER ".X");
       gd->cy  = eval.AddChan (m_item, Cs_CENTER ".Y");
       gd->rad = eval.AddChan (m_item, Cs_RADIUS     );
       gd->col = eval.AddChan (m_item, Cs_SETCOL ".R");
                 eval.AddChan (m_item, Cs_SETCOL ".G");
                 eval.AddChan (m_item, Cs_SETCOL ".B");
                 eval.AddChan (m_item, Cs_SETCOL ".A");
       gd->grd = eval.AddChan (m_item, Cs_COLOR  ".R");
                 eval.AddChan (m_item, Cs_COLOR  ".G");
                 eval.AddChan (m_item, Cs_COLOR  ".B");
                 eval.AddChan (m_item, Cs_COLOR  ".A");

       cache[0] = gd;
       return LXe_OK;
}

AllocFilter creates the image filter for a given frame.

       LxResult
CMandelbrotInstance::vclip_AllocFilter (
       ILxUnknownID		 attrObj,
       void			*cache,
       void		       **ppvObj)
{
       CLxUser_Attributes	 attr (attrObj);
       GenData			*gd = (GenData *) cache;
       CMandelbrotFilter	*filt;
       float			 cx, cy, rad;

       filt = pF->filt.Alloc (ppvObj);

       filt->src_pkg = src_pkg;
       filt->i_max   = attr.Int (gd->itr);
       filt->i_w     = attr.Int (gd->w);
       filt->i_h     = attr.Int (gd->h);
       filt->f_buf   = 0;

       cx  = attr.Float (gd->cx);
       cy  = attr.Float (gd->cy);
       rad = attr.Float (gd->rad);

       filt->x_0 = cx - rad;
       filt->y_0 = cy - rad;
       filt->x_s = rad * 2.0;
       filt->y_s = rad * 2.0;

       for (int i = 0; i < 4; i++) {
               filt->s_col[i] = attr.Float (gd->col + i);
               attr.ObjectRO (gd->grd + i, filt->g_filt[i]);
       }

       filt->use_image  = false;
       filt->gen_buffer = true;
       return LXe_OK;
}

This section cleans up some data.

       void
CMandelbrotInstance::vclip_Cleanup (
       void			*cache)
{
       GenData			*gd = (GenData *) cache;

       delete gd;
}

This section places the image onto the image stack where it generates target image on the fly

       const char *
CMandelbrotFilter::filt_Type (void)
{
       return "benoit_mandelbrot";
}

       unsigned
CMandelbrotFilter::imf_Type (void)
{ 
       return LXi_IMAGE_GENERATOR;
}

This uses Compare to compare a filter which has been evaluated to a new one being computed. If the size has changed we require the image to be remade from scratch. For other changes the past result is compatible with the current result so some previous work can be reused.

       unsigned
CMandelbrotFilter::filt_Compare (
       ILxUnknownID		 other)
{
       CMandelbrotFilter	*filt = pF->filt.Cast (other);
       CLxUser_Value		 val;

       if (filt->i_w != i_w || filt->i_h != i_h)
               return LXiSTACK_DIFFERENT;

       if (filt->x_0 != x_0 || filt->x_s != x_s || filt->y_0 != y_0 || filt->y_s != y_s)
               return LXiSTACK_COMPATIBLE;

       if (filt->i_max != i_max)
               return LXiSTACK_COMPATIBLE;

       for (int i = 0; i < 4; i++) {
               if (filt->s_col[i] != s_col[i])
                       return LXiSTACK_COMPATIBLE;

               val.set (g_filt[i]);
               if (val.Compare (filt->g_filt[i]))
                       return LXiSTACK_COMPATIBLE;
       }

       return LXiSTACK_IDENTICAL;
}

This section will be called to convert a past evaluation to a compatible filter. We always use the existing image, and if only the color parameters have changed we can reuse the previous gradient input map.

       LxResult
CMandelbrotFilter::filt_Convert (
       ILxUnknownID		 other)
{
       CMandelbrotFilter	*filt = pF->filt.Cast (other);

       use_image  = true;
       gen_buffer = (filt->x_0 != x_0 || filt->x_s != x_s ||
                     filt->y_0 != y_0 || filt->y_s != y_s ||
                     filt->i_max != i_max);

       x_0 = filt->x_0;
       x_s = filt->x_s;
       y_0 = filt->y_0;
       y_s = filt->y_s;

       i_max   = filt->i_max;

       for (int i = 0; i < 4; i++) {
               s_col[i] = filt->s_col[i];
               g_filt[i].set (filt->g_filt[i]);
       }

       return LXe_OK;
}

Here, generators allocate an image and fill it in. This filter maintains a reference to the image so it can be reused as needed. Filters that generate and forget would instead simply return the image.

       LxResult
CMandelbrotFilter::imf_Generate (
       int			 width,
       int			 height,
       ILxUnknownID		 monitor,
       void		       **ppvObj)
{
       LxResult		 res;

       if (width == i_w && height == i_h) {
               res = GenerateFullFilter ();
               if (LXx_OK (res))
                       res = w_img.get (ppvObj);
       }
       else if (w_img.test () && use_image && !gen_buffer && f_buf) {
               ConvertFBufferToImage (f_buf, i_w, i_h, w_img);
               res = w_img.get (ppvObj);
       }
       else {
               CLxUser_Image		 img;
               CLxUser_ImageWrite	wimg;

               src_pkg->img_svc.New (img, width, height, LXiIMP_RGBA32);
               wimg.set (img);

               res = GenerateSmallFilter (width, height, wimg);
               wimg.get (ppvObj);
       }

       return res;
}

Generate full filter(the main function in this section) will create the image filter at its full size, using the width and height from the channels on the Mandelbrot item.

       LxResult
CMandelbrotFilter::GenerateFullFilter (void)
{
       /*
        * Allocate an image if we need one. We'll hold on to the writable one.
        */
       if (!w_img.test () || !use_image) {
               CLxUser_Image	 img;
               src_pkg->img_svc.New (img, i_w, i_h, LXiIMP_RGBA32);
               w_img.set (img);

               if (f_buf) {
                       delete[] f_buf;
                       f_buf = 0;
               }
       }

       /*
        * Allocate the gradient input buffer.
        */
       if (!f_buf) {
               f_buf = new float[i_w * i_h];
               if (!f_buf)
                       return LXe_OUTOFMEMORY;
       }

       /* 
        * Unless we have a valid buffer we must compute it
        */
       if (gen_buffer)
               GenerateFBuffer (i_w, i_h, f_buf);

       /*
        * Finally, convert the buffer to the image
        */
       ConvertFBufferToImage (f_buf, i_w, i_h, w_img);

       return LXe_OK;
}

Generate small filter does the same as above, except it works on a smaller resolution, using a temporary float buffer. GenerateFBuffer() converts the buffer to the image

       LxResult
CMandelbrotFilter::GenerateSmallFilter (
       const int		 w,
       const int		 h,
       CLxUser_ImageWrite	&wimg)
{
       float			*fbuf = NULL;

       fbuf = new float[w * h];
       if (!fbuf)
               return LXe_OUTOFMEMORY;

       /* 
        * We compute the buffer
        */
       GenerateFBuffer (w, h, fbuf);

       /*
        * Finally, convert the buffer to the image
        */
       ConvertFBufferToImage (fbuf, w, h, wimg);

       return LXe_OK;
}

We generate the buffer in the threads. We allocate a work object using Alloc(F), initialize it to the image and process it.

       void
CMandelbrotFilter::GenerateFBuffer (
       const int		 w,
       const int		 h,
       float			*fbuf)
{
       CLxUser_ThreadService	 mt;
       CMandlebrotWork		*work;
       LXtObjectID		 workObj;

       work = pF->work.Alloc (&workObj);
       work->src_filt	= this;
       work->i_y	= 0;
       work->n_y	= h;
       work->w		= w;
       work->h		= h;
       work->fbuf	= fbuf;

       max_log = log ((double) i_max);

       mt.ProcessShared ((ILxUnknownID) workObj);
       lx::ObjRelease (workObj);
}

This section converts the raw buffered values to colors. This could be done in the workers too, since setting pixels should be thread-safe.

       void
CMandelbrotFilter::ConvertFBufferToImage (
       const float		*fbuf,
       const int		 w,
       const int		 h,
       CLxUser_ImageWrite	&wimg)
{
       LXtImageFloat		 pixel[4];
       unsigned		 x, y, k;
       double			 d;

       for (y = 0; y < h; y++) {
               for (x = 0; x < w; x++) {
                       d = fbuf[y * h + x];
                       if (d < 0.0)
                               for (k = 0; k < 4; k++)
                                       pixel[k] = s_col[k];
                       else
                               for (k = 0; k < 4; k++)
                                       pixel[k] = g_filt[k].Evaluate (d);

                       wimg.SetPixel (x, y, LXiIMP_RGBAFP, pixel);
               }
       }
}

Adds a metrics interface for getting size and type info.

LxResult
CMandelbrotFilter::imfmet_Generate (
       LXtImageMetrics		*metrics)
{
       metrics->maxRes[0]   = i_w;
       metrics->maxRes[1]   = i_h;
       metrics->pixelType   = LXiIMP_RGBA32;
       metrics->aspect      = 1.0;
       metrics->filename[0] = 0;
       metrics->format[0]   = 0;
       return LXe_OK;
}

The class created here(CMandelbrotWork) is a way to spread the work among threads. Each work object holds a current line and number of lines remaining initialized to zero. The master work object will be initialized to the total lines in the image, and the threading system will allocate enough new work objects to keep the system's core busy.

LxResult
CMandlebrotWork::share_Spawn (
       void		       **ppvObj)
{
       CMandlebrotWork		*work;

        work = pF->work.Alloc (ppvObj);
       *work = *this;

       work->i_y = 0;
       work->n_y = 0;
       return LXe_OK;
}

Work objects that have work will be asked to share with those that don't. In this case we transfer one line from this work object to the other. This is currently the only valid split mode, although it could transfer more lines to reduce contention.

        LxResult 
CMandlebrotWork::share_Share (
       ILxUnknownID		 other,
       unsigned int		 split)
{
       CMandlebrotWork		*take = pF->work.Cast (other);

       if (n_y <= 1)
               return LXe_FALSE;

       take->i_y = i_y;
       take->n_y = 1;

       i_y ++;
       n_y --;
       return LXe_TRUE;
}

Evaluate(the main function here) processes one piece of work, in this case a line of the image. The floating point buffer is filled with values from -1 to 1.

       LxResult
CMandlebrotWork::share_Evaluate ()
{
       double			 u, v;

       if (n_y <= 0)
               return LXe_FALSE;

       v = i_y / (double) h;
       for (int x = 0; x < w; x++) {
               u = x / (double) w;

               fbuf[i_y * h + x] = Pixel (u, v);
       }

       i_y ++;
       n_y --;
       return LXe_TRUE;
}

Here, we compute a single pixel. This is the standard Mandelbrot loop, except that the final iteration count is scaled 0 to 1 in a log scale. Points in the set are assigned -1.

       float
CMandlebrotWork::Pixel (
       double			 x,
       double			 y)
{
       std::complex<double>	 z (0.0, 0.0);
       std::complex<double>	 c (src_filt->x_0 + src_filt->x_s * x,
                                   src_filt->y_0 + src_filt->y_s * y);
       unsigned		 i;

       for (i = 1; i <= src_filt->i_max; i++) {
               z = z * z + c;
               if (abs (z) > 2.0)
                       return log ((double) i) / src_filt->max_log;
       }

       return -1.0;
}