/// <summary>
		/// Construct the texture downsampler
		/// </summary>
		/// <param name="source">Source texture to read</param>
		/// <param name="target">Target texture to write to</param>
		/// <param name="intermediate">Intermediate texture (if null, will be created as required)</param>
		/// <param name="intermediate2">Second intermediate texture (if null, will be created as required)</param>
		/// <param name="targetWidth">target width to downsample to</param>
		/// <param name="targetHeight">target height to downsample to</param>
		/// <param name="shaderProvider">Optional provider for downsample shaders</param>
		public TextureDownsample(DrawTargetTexture2D source, DrawTargetTexture2D target, ref DrawTargetTexture2D intermediate, ref DrawTargetTexture2D intermediate2, int targetWidth, int targetHeight,
					IDownsampleShaderProvider shaderProvider)
		{
			if (source == null ||
				target == null)
				throw new ArgumentNullException();

			if (targetWidth <= 0 ||
				targetHeight <= 0)
				throw new ArgumentException("Invalid target size");

			if (DrawTarget.FormatChannels(source.SurfaceFormat) != DrawTarget.FormatChannels(target.SurfaceFormat))
			    throw new ArgumentException("source.SurfaceFormat has a different number of channels than target.SurfaceFormat");

			if (targetWidth > target.Width ||
				targetHeight > target.Height)
				throw new ArgumentException("Size is larger than target");

			if (targetWidth > source.Width ||
				targetHeight > source.Height)
				throw new ArgumentException("Size is larger than source");

			if (intermediate != null)
			{
				if (target.SurfaceFormat != intermediate.SurfaceFormat)
					throw new ArgumentException("target.SurfaceFormat != intermediate.SurfaceFormat");
				if (intermediate == intermediate2)
					throw new ArgumentException("intermediate == intermediate2");
			}
			if (intermediate2 != null)
			{
				if (target.SurfaceFormat != intermediate2.SurfaceFormat)
					throw new ArgumentException("target.SurfaceFormat != intermediate2.SurfaceFormat");
			}

			int w = source.Width;
			int h = source.Height;

			int targetMultipleWidth = targetWidth;
			int targetMultipleHeight = targetHeight;

			while (targetMultipleWidth * 2 <= w)
				targetMultipleWidth *= 2;
			while (targetMultipleHeight * 2 <= h)
				targetMultipleHeight *= 2;

			DrawTargetTexture2D current = null;
			Rectangle sRegion = new Rectangle(0,0,0,0);

			//first pass may require that the source is sized down to a multiple of the target size

			if ((double)targetWidth / (double)w <= 0.5 &&
				(double)targetHeight / (double)h <= 0.5 &&
				(targetMultipleWidth != w || targetMultipleHeight != h))
			{
				DrawTargetTexture2D go = this.PickRT(ref intermediate, ref intermediate2, source, targetMultipleWidth, targetMultipleHeight,target.SurfaceFormat);

				Vector2 size = new Vector2((float)targetMultipleWidth, (float)targetMultipleHeight);

				TexturedElement te = new TexturedElement(source, size, false);
				te.TextureCrop = new Rectangle(0,0,w,h);

				go.Add(te);
				passes.Add(go);
				current = go;
				w = targetMultipleWidth;
				h = targetMultipleHeight;
			}

			//downsample on the larger axis, either 2x, 4x or 8x downsampling, until reached the target size

			while (target.Equals(current) == false)
			{
				DrawTargetTexture2D localSource = current ?? source;

				double difW = (double)targetWidth / (double)w;
				double difH = (double)targetHeight / (double)h;

				sRegion.Width = w;
				sRegion.Height = h;
				sRegion.Y = localSource.Height - h;

				//both width/height difference are less than 50% smaller, so a linear interpolation will do fine
				if (difW > 0.5 &&
					difH > 0.5)
				{
					//write directly to the target
					DrawTargetTexture2D go = target.Clone(false, false, false);
					Vector2 te_size = new Vector2((float)targetWidth, (float)targetHeight);
					TexturedElement te = new TexturedElement(localSource, te_size, false);

					go.AddModifier(new ScissorModifier(0, go.Height - targetHeight, targetWidth, go.Height, go));
					te.TextureCrop = sRegion;

					go.Add(te);
					passes.Add(go);
					current = go;

					continue;
				}

				bool horizontal = difW < difH;
				double dif = Math.Min(difW, difH);
				int size = horizontal ? w : h;

				Vector2 dir = new Vector2(0,0);
				if (horizontal)
					dir.X = 1.0f / localSource.Width;
				else
					dir.Y = 1.0f / localSource.Height;

				if (dif > 0.25) // cutoff for using 2 samples
				{
					DrawTargetTexture2D go;
					int new_width = w;
					int new_height = h;
					if (horizontal)
						new_width /= 2;
					else
						new_height /= 2;

					if (new_width == targetWidth && new_height == targetHeight)
						go = target.Clone(false, false, false);
					else
						go = PickRT(ref intermediate, ref intermediate2, localSource, new_width, new_height, target.SurfaceFormat);

					Vector2 se_size = new Vector2((float)new_width, (float)new_height);
					ShaderElement se = new ShaderElement((shaderProvider ?? this).DownsampleShader2, se_size, false);

					go.AddModifier(new ScissorModifier(0, go.Height - new_height, new_width, go.Height, go));

					se.TextureCrop = sRegion;

					go.Add(new Drawer(dir,se,localSource));
					passes.Add(go);

					w = new_width;
					h = new_height;

					current = go;
					continue;
				}

				if (dif > 0.125) // cutoff for using 4 samples
				{
					DrawTargetTexture2D go;
					int new_width = w;
					int new_height = h;
					if (horizontal)
						new_width /= 4;
					else
						new_height /= 4;

					if (new_width == targetWidth && new_height == targetHeight)
						go = target.Clone(false, false, false);
					else
						go = PickRT(ref intermediate, ref intermediate2, localSource, new_width, new_height, target.SurfaceFormat);

					Vector2 se_size = new Vector2((float)new_width, (float)new_height);
					ShaderElement se = new ShaderElement((shaderProvider ?? this).DownsampleShader4, se_size, false);

					go.AddModifier(new ScissorModifier(0, go.Height - new_height, new_width, go.Height, go));

					se.TextureCrop = sRegion;

					go.Add(new Drawer(dir, se, localSource));
					passes.Add(go);

					w = new_width;
					h = new_height;

					current = go;
					continue;
				}

				// cutoff for using 8 samples
				{
					DrawTargetTexture2D go;
					int new_width = w;
					int new_height = h;
					if (horizontal)
						new_width /= 8;
					else
						new_height /= 8;
					
					if (new_width == targetWidth && new_height == targetHeight)
						go = target.Clone(false, false, false);
					else
						go = PickRT(ref intermediate, ref intermediate2, localSource, new_width, new_height, target.SurfaceFormat);

					Vector2 se_size = new Vector2((float)new_width, (float)new_height);
					ShaderElement se = new ShaderElement((shaderProvider ?? this).DownsampleShader8, se_size, false);

					go.AddModifier(new ScissorModifier(0, go.Height - new_height, new_width, go.Height, go));

					se.TextureCrop = sRegion;

					go.Add(new Drawer(dir, se, localSource));
					passes.Add(go);

					w = new_width;
					h = new_height;

					current = go;
					continue;
				}
			}
		}
		internal SinglePassTextureFilter(DrawTargetTexture2D source, DrawTargetTexture2D target)
		{
			if (source == null || target == null)
				throw new ArgumentNullException();
			if (source.SurfaceFormat != target.SurfaceFormat)
				throw new ArgumentException("source.SurfaceFormat != target.SurfaceFormat");
			if (source == target)
				throw new ArgumentException("source == target is invalid");
			if (source.Width > target.Width ||
				source.Height > target.Height)
				throw new ArgumentException("source is larger than target");

			this.source = source;
			this.targetClone = target.Clone(false,false,false);

			this.targetClone.ClearBuffer.Enabled = false;

			this.element = new ShaderElement(null, source.Width, source.Height,new Vector2(1,1),true);
			this.targetClone.Add(element);

			if (target.Width != source.Width ||
				target.Height != source.Height)
			{
				this.targetClone.AddModifier(new Xen.Graphics.Modifier.ScissorModifier(0, 0, Math.Min(1, (source.Width) / target.Width), Math.Min(1, (source.Height) / target.Height)));
				this.element.TextureCrop = new Rectangle(0, 0, Math.Min(this.source.Width, this.targetClone.Width), Math.Min(this.source.Height, this.targetClone.Height));
			}
		}