anchor.diff:              new MapReduce<'a, 'b>(runtime, reducer, map, add)
           )
   
  +    let getImageMapReducer (map : Expr<V3i -> V4f -> 'b>) (add : Expr<'b -> 'b -> 'b>) =
  +        mapReduceCache.GetOrCreate(map, add, fun map add -> 
  +            let reducer = getReducer add
  +            new MapReduceImage<'b>(runtime, reducer, map, add)
  +        )
   
       member x.CompileScan(add : Expr<'a -> 'a -> 'a>, input : IBufferVector<'a>, output : IBufferVector<'a>) =
           let scanner = getScanner add
  
positive.code: 
  type ParallelPrimitives(runtime : IComputeRuntime) =
      
      let scanCache = ExpressionCache()
      let reduceCache = ExpressionCache()
      let mapReduceCache = ExpressionCache()
  
      let getScanner (add : Expr<'a -> 'a -> 'a>) = scanCache.GetOrCreate(add, fun add -> new Scan<'a>(runtime, add))
      let getReducer (add : Expr<'a -> 'a -> 'a>) = reduceCache.GetOrCreate(add, fun add -> new Reduce<'a>(runtime, add))
      let getMapReducer (map : Expr<int -> 'a -> 'b>) (add : Expr<'b -> 'b -> 'b>) =
  
negative.code: 
          }
   
  type private MapReduceImage<'b when 'b : unmanaged>(runtime : IComputeRuntime, reduce : Reduce<'b>, map : Expr<V3i -> V4f -> 'b>, add : Expr<'b -> 'b -> 'b>) =
    
      static let ceilDiv (v : int) (d : int) =
          if v % d = 0 then v / d
          else 1 + v / d
  
      static let ceilDiv2 (v : V2i) (d : V2i) =
          V2i(ceilDiv v.X d.X, ceilDiv v.Y d.Y)
     
      static let ceilDiv3 (v : V3i) (d : V3i) =
          V3i(ceilDiv v.X d.X, ceilDiv v.Y d.Y, ceilDiv v.Z d.Z)
     
      let mapReduce2d = runtime.CreateComputeShader (Kernels.mapReduceImageKernel2d map add)
      let mapReduce3d = runtime.CreateComputeShader (Kernels.mapReduceImageKernel3d map add)
      let reduce      = reduce.ReduceShader
  
      let rec build (args : HashSet<System.IDisposable>) (input : IBufferVector<'b>) (target : 'b[]) =
          let cnt = int input.Count
          if cnt > 1 then
              let args0 = runtime.NewInputBinding(reduce)
              
              let groupCount = ceilDiv (int input.Count) Kernels.scanSize
              let temp = runtime.CreateBuffer<'b>(groupCount)
              args0.["inputOffset"] <- input.Origin |> int
              args0.["inputDelta"] <- input.Delta |> int
              args0.["inputSize"] <- input.Count |> int
              args0.["inputData"] <- input.Buffer
              args0.["outputData"] <- temp
              args0.Flush()
  
              args.Add args0 |> ignore
              args.Add temp |> ignore
  
              let cmd =
                  [
                      ComputeCommand.Bind reduce
                      ComputeCommand.SetInput args0
                      ComputeCommand.Dispatch(ceilDiv (int input.Count) Kernels.scanSize)
                      ComputeCommand.Sync(temp.Buffer)
                  ]
  
              if temp.Count > 0 then
                  let inner = build args temp target
                  [
                      yield! cmd
                      yield! inner
                  ]
              else
                  cmd
          else
              let b = input.Buffer.Coerce<'b>()
              [
                  ComputeCommand.Copy(b.[input.Origin .. input.Origin], target)
              ]
      
      let buildTop (args : HashSet<System.IDisposable>) (input : ITextureSubResource) (target : 'b[]) =
          let dimensions =
              match input.Texture.Dimension with
                  | TextureDimension.Texture2D | TextureDimension.TextureCube -> 2
                  | TextureDimension.Texture3D -> 3
                  | d -> 
                      failwithf "cannot reduce image with dimension: %A" d
  
          match dimensions with
          | 2 -> 
              let args0 = runtime.NewInputBinding(mapReduce2d)
              
              let size = input.Size.XY
              let groupCount = ceilDiv2 size (V2i(16,8))
              let temp = runtime.CreateBuffer<'b>(groupCount.X * groupCount.Y)
  
  
              args0.["InputTexture"] <- input.Texture
              args0.["outputData"] <- temp
              args0.["numGroups"] <- groupCount
              args0.["inputSize"] <- size
              args0.["inputLevel"] <- input.Level
              args0.Flush()
  
              args.Add args0 |> ignore
              args.Add temp |> ignore
  
              let cmd =
                  [
                      ComputeCommand.Bind mapReduce2d
                      ComputeCommand.SetInput args0
                      ComputeCommand.Dispatch(groupCount)
                      ComputeCommand.Sync(temp.Buffer)
                  ]
  
              if temp.Count > 0 then
                  let inner = build args temp target
                  [
                      yield! cmd
                      yield! inner
                  ]
              else
                  cmd
          | 3 ->
              let args0 = runtime.NewInputBinding(mapReduce3d)
              
              let size = input.Size
              let groupCount = ceilDiv3 size (V3i(8,4,2))
              let temp = runtime.CreateBuffer<'b>(groupCount.X * groupCount.Y * groupCount.Z)
  
  
              args0.["InputTexture"] <- input.Texture
              args0.["outputData"] <- temp
              args0.["numGroups"] <- groupCount
              args0.["inputSize"] <- size
              args0.["inputLevel"] <- input.Level
              args0.Flush()
  
              args.Add args0 |> ignore
              args.Add temp |> ignore
  
              let cmd =
                  [
                      ComputeCommand.Bind mapReduce3d
                      ComputeCommand.SetInput args0
                      ComputeCommand.Dispatch(groupCount)
                      ComputeCommand.Sync(temp.Buffer)
                  ]
  
              if temp.Count > 0 then
                  let inner = build args temp target
                  [
                      yield! cmd
                      yield! inner
                  ]
              else
                  cmd
          | d ->  
              failwithf "cannot reduce image with dimension: %A" d
  
      member x.Run(input : ITextureSubResource) =
          let args = System.Collections.Generic.HashSet<System.IDisposable>()
          let target : 'b[] = Array.zeroCreate 1
          let cmd = buildTop args input target
          runtime.Run cmd
          for a in args do a.Dispose()
          target.[0]
          
      member x.Compile (input : ITextureSubResource) =
          let args = System.Collections.Generic.HashSet<System.IDisposable>()
          let target : 'b[] = Array.zeroCreate 1
          let cmd = buildTop args input target
          let prog = runtime.Compile cmd 
  
          { new ComputeProgram<'b>() with
              member x.Dispose() =
                  prog.Dispose()
                  for a in args do a.Dispose()
                  args.Clear()
              member x.Run() =
                  prog.Run()
                  target.[0]
  
          }
      
  type private ExpressionCache() =
      let store = System.Collections.Concurrent.ConcurrentDictionary<list<string>, obj>()
  