
rotation_x = 0.0
rotation_y = 0.0

texture_src= nil  -- 128k
texture_dst= nil  -- 128k

-- these functions are called once every frame (automatically) -----------------------------------------------------------

feedback={
  -- effect parameters
  rotate= 0,
  skew= 0.0, -- 0 for orthogonal textures or angle 0..90
  zoom= 100.0, -- zoom factor in percent. >100: zoom out, <100 zoom in
  stretchy= 1.0,  -- stretch along y axis (1.0 = no stretch, 2.0 = stretch to double height)
  phase= 0.0,     -- phase for bouncing wave
  bounce= 0.0,    -- bounce amplitude 
  darken= 255,    -- darken for recursive layers
  flash= 1.0,     -- flash of border
  centerx= 0,     -- center x on texture (0= center)
  centery= 0,     -- center y
  textureid= 0,

  -- runtime data (created in init)
  border= nil,       -- 128k  -- TODO: u8 enough?
  border_flash= nil, -- 128k
  mask= nil,         -- 128k  -- TODO: u8 enough?

  palette= {
  [0]= { [0]= 0x000000, 0x010101, 0x010103, 0x010301, 0x010303, 0x030303, 0x020305, 0x030507, 
              0x03070a, 0x050a0f, 0x060f16, 0x07141f, 0x081a2a, 0x082237, 0x082843, 0x082c4a, 
              0x083052, 0x04325a, 0x083459, 0x08365e, 0x063962, 0x023969, 0x0a3b65, 0x063b66, 
              0x063d6a, 0x063f6d, 0x054070, 0x044375, 0x03467b, 0x034981, 0x094b87, 0x014d88, 
              0x015291, 0x095494, 0x02589c, 0x0b5ba0, 0x055ea7, 0x0b64b0, 0x126aba, 0x1871c7, 
              0x1e77d3, 0x107eba, 0x227de1, 0x1889d4, 0x2784ee, 0x1f8ee5, 0x2e8cfa, 0x2399f1, 
              0x2e9bff, 0x17a6d1, 0x1da8e6, 0x28abf8, 0x22b8ed, 0x1fc4e4, 0x20caf4, 0x1fcfde, 
              0x21d7d9, 0x16dbd1, 0x22ddd5, 0x1fdbea, 0x23e3d0, 0x24e9ca, 0x18f4a6, 0x06fb7e },
        {}
  }
}

feedback.setparams= function(this, params)
  this.rotate=  params[2] or 0 -- 0..360
  this.skew=    params[3] or 0
  this.zoom=    params[4] or 100
  this.stretchy= params[5] or 1
  this.phase=   params[6] or 0
  this.bounce=  params[7] or 0
  this.darken=  params[8] or 94
  this.flash=   params[9] or 1
  this.centerx= params[10] or 0
  this.centery= params[11] or 0
end


feedback.load = function(this)
  local temp1= unpod( fetch("border.pod") )
  local temp2= unpod( fetch("alpha.pod") )

  this.border= userdata("i16",256,256)
  this.border_flash= userdata("i16",256,256)
  this.mask= userdata("i16",256,256)
  texture_src= userdata("i16",256,256)
  texture_dst= userdata("i16",256,256)

  for y=0,255 do
      this.border:set(0,y, temp1:get(0, y, 256))
      this.mask:set(0,y, temp2:get(0, y, 256))
  end

  -- 0..255 -> 0...127
  this.mask:shr(1, true, 0,0, 256*256, 0,0,1)
end

feedback.init = function(this)
end

feedback.update = function(this)
  pal()
  palt(0,false)
  local colors= this.palette[0]
  for i=0,63 do
    pal(i, colors[i], 2)
  end
end

feedback.draw = function(this)
  -- texture resolution
  local dst_w= 256

  local rot= this.rotate/360.0
  local skew= this.skew/360.0
  local zoomx= this.zoom*2.56
  local zoomy= this.stretchy*zoomx
  local bounce= 60 + flr(sin(this.phase)*this.bounce)
  local darken= this.darken*2.56
  local flash= this.flash*64.0
  local cx= (this.centerx+128)*256.0
  local cy= (this.centery+128)*256.0

  -- create temporary buffers
  local index_u=  userdata("i32", dst_w)   -- u-part of final uv coordinate
  local index_v=  userdata("i32", dst_w)   -- v-part of final uv coordinate
  local index_uv= userdata("i32", dst_w) -- uv coordinates
  local temp2x2=  userdata("i16", dst_w*2, 1) -- temp. scanline with 2 neighboring pixels and 2 lines of pixels for filtering 2x2 bilinear
  local temp1=    userdata("i16", dst_w*2, 1) -- temp buffer 1
  local temp2=    userdata("i16", dst_w*2, 1) -- temp buffer 2
  local temp3=    userdata("i16", dst_w, 1) -- temp buffer 3

  local index_v8= userdata("u8", index_v)

  -- copy border
  this.border:mul(flash, this.border_flash, 0,0, 256*256, 0,0,1)
  this.border_flash:shr(6, true, 0,0, 256*256, 0,0,1)
  this.border_flash:min(127, true, 0,0, 256*256, 0,0,1)

  texture_src:mul(this.mask, true, 0,0, 256*256, 0,0,1)
  texture_src:shr(7,    true, 0,0, 256*256, 0,0,1)
  texture_src:add(this.border_flash, true, 0,0, 256*256, 0,0,1)

  -- reconstruct y/y+1 pixel pairs
  texture_src:mutate("u8")
  texture_src:copy(texture_src, true, dst_w*2,1, 1, 2,2, (dst_w-1)*dst_w) 
  texture_src:copy(texture_src, true, 0,(dst_w-1)*dst_w*2+1, 1, 2,2, dst_w) 
  texture_src:mutate("i16")

  -- delta for left and right side
  local deltax, deltay= cos(rot)*zoomx, sin(rot)*zoomx
  local deltax1, deltay1= cos(rot+skew)*zoomy, sin(rot+skew)*zoomy

  -- interpolate left and right screen border from top to bottom
  local leftx= (-deltax*128 + deltay1*128) + cx
  local lefty= (-deltay*128 - deltax1*128) + cy

  for y=0,256-1 do

    -- we step through the scanline from left to right [0..dstw-1]
    -- left starts at texture coordinate u0,v0
    -- deltau,deltav step forward one pixel
    -- texture has a fixed size of 256x256

    -- linear interpolate between left and right u,v
    -- use additional integer precision for bilinear filtering
--    index_u:mutate("i32")
    index_u:mutate("i32")
    index_u[0]= leftx
    index_u[dst_w-1]= leftx + deltax*(dst_w-1)
    index_u:lerp(0,dst_w-1)

    -- same for v
    index_v:mutate("i32")
    index_v[0]= lefty
    index_v[dst_w-1]= lefty + deltay*(dst_w-1)
    index_v:lerp(0,dst_w-1)

    index_u:shr(8, index_uv, 0,0, dst_w, 0,0,1) -- copy u
    index_uv:band(255, true, 0,0, dst_w, 0,0,1) -- copy u
    -- extract subpixel coefficients for u-filtering
    index_u:band(255, true,  0,0, dst_w, 0,0,1)    -- fetch subpixel coefficients

    -- fetch subpixel coefficients for v-filtering
    index_v:mutate("u8")
    temp3:mutate("u8")
    temp3:copy(index_v,  true, 0,0, 1, 4,2, dst_w)    
    temp3:mutate("i16")

--    index_uv:mutate("u8") -- "mutate" is for free
    index_v:mutate("i32")
    index_uv:mutate("i32")
    index_v:band(0xff00, true, 0,0, dst_w)
    index_uv:bor(index_v, true, 0,0, dst_w) -- copy v
--    index_u:mutate("i32")

    -- fetch texel at positions given in "index_uv"
    -- always pick two horizontally neightbouring pixels
    -- [0..dst_w-1] are the upper texel-pairs of a 2x2 pixel block
    -- [dst_w..dst_w*2-1] are the lower texel-pairs of 2x2 pixel block
    texture_src:take(index_uv, temp2x2, 0,0, 2, 1,2, dst_w) -- fetch 2x2 pixels
--    texture:take(index_uv, temp2x2, 0,0, 2, 1,2, dst_w) -- fetch 2x2 pixels
    --  0  1    2  3   ...
    -- (c1 c3) (c2 c4)

    -- copy pixels to 16bit buffer so we can multiply without overflow
    temp1:mutate("u8")
    temp2:mutate("u8")
    temp2x2:mutate("u8")

    -- convert 8bit to 16bit
    temp1:copy(temp2x2, true, 0,0,    1, 4,4, dst_w)   -- fetch c1
    temp1:copy(temp2x2, true, 1,2,    1, 4,4, dst_w)   -- fetch c3

    temp2:copy(temp2x2, true, 2,0,    1, 4,4, dst_w)   -- fetch c2
    temp2:copy(temp2x2, true, 3,2,    1, 4,4, dst_w)   -- fetch c4
    temp2x2:mutate("i16")

    -- filtering in u:
    -- temp2[x] = temp1[x] + ((temp2[x]-temp1[x]) * temp3[x] >> 8)
    temp1:mutate("i16")
    temp2:mutate("i16")
    temp2:sub(temp1, true, 0,0, dst_w*2)
    
    temp2:mutate("i32")
    temp2:mul(index_u, true, 0,0, dst_w)
    temp2:mutate("i16")
    temp2:shr(8,     true, 0,0, dst_w*2)
    temp2:add(temp1, true, 0,0, dst_w*2)
    
    temp1:copy(temp2, true, 0,0, 1, 2,1, dst_w)
    temp2:copy(temp2, true, 1,0, 1, 2,1, dst_w)

    -- filtering in y:
    -- temp2[x] = temp1[x] + ((temp2[x]-temp1[x]) * temp3[x] >> 8)

    temp2:sub(temp1, true, 0,0, dst_w)
    temp2:mul(temp3, true, 0,0, dst_w)
    temp2:shr(8,     true, 0,0, dst_w)
    temp2:add(temp1, true, 0,0, dst_w)

    temp2:mul(darken,true, 0,0, dst_w)
    temp2:shr(8,     true, 0,0, dst_w)
    temp2:min(62, true, 0,0, dst_w, 0,0,1)

    texture_dst:copy(temp2, true, 0,y*dst_w, dst_w, 0,0, 1)

    -- here some things left to optimize
    leftx-= deltay1
    lefty+= deltax1
  end

  texture_dst:mutate("u8")
  for y=0,134 do
    -- copy result to screen
    -- convert from 16bit to 8bit output by copying every 2nd byte
    get_display():copy(texture_dst, true, (y+bounce)*512+8, y*240, 1, 2,1, dst_w)
    get_display():add(1, true, 0,y*240+(y&1), 1, 0,2, dst_w/2)
--    get_display():shr(1, true, 0,y*240, dst_w, 0,0,1)
  end
  texture_dst:mutate("i16")

  texture_src,texture_dst= texture_dst, texture_src
end

