Page 1 of 6

How to use Framebuffer:renderTo() properly

Posted: Wed Nov 17, 2010 12:19 am
by TechnoCat
Disclaimer: A lot of the information in this thread only applies to a pre-release version of LOVE (fb's being tied to window dimensions). However, this first post refers to the release version of 0.7.0 (fb's are independent of the window size).

Code: Select all

function love.load()
  enabled = false
  translate = {x=0, y=0}
  image = love.graphics.newImage("image.png")
  fb = love.graphics.newFramebuffer(6000,6000)
  imageSet = {}
  for i=0, 10000, 1 do
    local entry = {x=math.random(6000), y=math.random(6000)}
    table.insert(imageSet, entry)
    --alternative method to draw to framebuffer
    fb:renderTo(
        function()
          love.graphics.draw(image,entry.x,entry.y)
        end
    )
    --]]
  end
  --[[method to draw to framebuffer
  love.graphics.setRenderTarget(fb)
  for _,v in ipairs(imageSet) do
    love.graphics.draw(image, v.x, v.y)
  end
  love.graphics.setRenderTarget()
  --]]
end

function love.update(dt)
  if love.keyboard.isDown("left") then
    translate.x = translate.x + 1000*dt
  elseif love.keyboard.isDown("right") then
    translate.x = translate.x - 1000*dt
  end
  if love.keyboard.isDown("up") then
    translate.y = translate.y + 1000*dt
  elseif love.keyboard.isDown("down") then
    translate.y = translate.y - 1000*dt
  end
end

function love.draw()
  love.graphics.push()
  love.graphics.translate(translate.x, translate.y)
  if enabled then
    love.graphics.draw(fb,0,0)
    love.graphics.pop()
    love.graphics.print("Framebuffer rendering enabled",0,0)
  else
    for _,v in ipairs(imageSet) do
      love.graphics.draw(image, v.x, v.y)
    end
    love.graphics.pop()
    love.graphics.print("Framebuffer rendering disabled",0,0)
  end
end

function love.keypressed(k)
  if k==" " then
    enabled = not enabled
  end
end
Disclaimer: It takes a while to generate10,000 random (x,y) pairs.

Re: How to use Framebuffer:renderTo() properly

Posted: Wed Nov 17, 2010 12:57 am
by zac352
Stupid OpenGL. T_T

Code: Select all

Error: [string "main.lua"]:6: Cannot create Framebuffer: Not supported by your O
penGL implementation
stack traceback:
        [C]: in function 'newFramebuffer'
        [string "main.lua"]:6: in function 'load'
        [string "boot.lua"]:305: in function <[string "boot.lua"]:303>
        [C]: in function 'xpcall'

Re: How to use Framebuffer:renderTo() properly

Posted: Wed Nov 17, 2010 3:39 am
by TechnoCat
Boolsheet on IRC helped me out and told me about the framebuffer openGL size limitation. Apprently, framebuffers can only be the same dimension as your primary framebuffer. So to work around that, a framebuffer array is used.

Anyways, it works now. Here it is as what I think is a good example of how to use it. Press spacebar to switch between framebuffer mode and non-framebuffer mode.

Code: Select all

function love.load()
  enabled = false
  image = love.graphics.newImage("image.png")
  
  translate = {x=0, y=0}
  screen = {}
  screen.width = love.graphics.getWidth()
  screen.height = love.graphics.getHeight()
  numBuffersX = 4
  numBuffersY = 4
  numBuffers = numBuffersX*numBuffersY
  fbs = {}
  
  --Iniitialize framebuffers
  for y = 1, numBuffersY+1 do
    fbs[y] = {}
    for x = 1, numBuffersX+1 do
      fbs[y][x] = love.graphics.newFramebuffer()
    end
  end
  
  --Create random objects placed everywhere
  imageSet = {}
  for i = 1, 5000 do
    local entry = {}
    entry.x = math.random(screen.width*(numBuffersX-1))
    entry.y = math.random(screen.height*(numBuffersY-1))
    imageSet[i] = entry
  end
  
  --Draw them to the framebuffers
  for y = 1, numBuffersY do
    for x = 1, numBuffersX do
      -- This is my preferable method
      love.graphics.setRenderTarget(fbs[y][x])
      for _,v in ipairs(imageSet) do
        love.graphics.draw(image, v.x, v.y)
      end
      --]]
      --[[ This is the other method
      fbs[y][x]:renderTo(
          function()
            for _,v in ipairs(imageSet) do
              love.graphics.draw(image, v.x, v.y)
            end
          end
      )
      --]]
      -- Shift one buffer to the right
      love.graphics.translate(-screen.width, 0)
    end
    -- Go back to the right and then down.
    love.graphics.translate(screen.width*numBuffersX, -screen.height)
  end
  love.graphics.setRenderTarget()
end


function love.update(dt)
  if love.keyboard.isDown("left") then
    translate.x = translate.x + 1000*dt
  elseif love.keyboard.isDown("right") then
    translate.x = translate.x - 1000*dt
  end
  
  if love.keyboard.isDown("up") then
    translate.y = translate.y + 1000*dt
  elseif love.keyboard.isDown("down") then
    translate.y = translate.y - 1000*dt
  end 
end

function love.draw()
  love.graphics.push()
  love.graphics.translate(translate.x, translate.y)
  if enabled then
    for y = 1, numBuffersY+1 do
      for x = 1, numBuffersX+1 do
        love.graphics.draw(fbs[y][x], (x-1)*640, (y-1)*480)
      end
    end
  else
    for _,v in ipairs(imageSet) do
      love.graphics.draw(image, v.x, v.y)
    end
  end
  love.graphics.pop()
  
  fps = love.timer.getFPS()
  if enabled then
    love.graphics.setCaption(fps.." Framebuffer rendering enabled",0,0)
  else
    love.graphics.setCaption(fps.." Framebuffer rendering disabled",0,0)
  end
end

function love.keypressed(k)
  if k==" " then
    enabled = not enabled
  end
end
DISCLAIMER: Drawing to the framebuffers could really do with some clipping algorithm. And doing clipping on the non-framebuffer mode would help too.

File removed: framebuffer arrays are not a good implementation or method of doing things.

Re: How to use Framebuffer:renderTo() properly

Posted: Wed Nov 17, 2010 9:32 am
by vrld
TechnoCat wrote: So to work around that, a framebuffer array is used.
Don't do that. Your graphics card can only support so may framebuffers...
TechnoCat wrote:

Code: Select all

for i=0, 10000, 1 do
    local entry = {x=math.random(6000), y=math.random(6000)}
    table.insert(imageSet, entry)
    --alternative method to draw to framebuffer
    fb:renderTo(
        function()
          love.graphics.draw(image,entry.x,entry.y)
        end
    )
end
That code is equivalent to:

Code: Select all

for i = 0, 10000, 1 do
    local entry = {x=math.random(6000), y=math.random(6000)}
    table.insert(imageSet, entry)
    love.graphics.setRenderTarget(fb)
    love.graphics.draw(image,entry.x,entry.y)
    love.graphics.setRenderTarget()
end
At each setRenderTarget, the framebuffer will be cleared, so what you are doing is painting an image entry, then clearing the buffer, then paining another entry, then clearing again, ...
This should work:

Code: Select all

fb:renderTo(function()
    for i = 0, 10000, 1 do
        local entry = {x=math.random(6000), y=math.random(6000)}
        table.insert(imageSet, entry)
        love.graphics.draw(image,entry.x,entry.y)
    end
end)

Re: How to use Framebuffer:renderTo() properly

Posted: Wed Nov 17, 2010 1:30 pm
by TechnoCat
vrld wrote:At each setRenderTarget, the framebuffer will be cleared, so what you are doing is painting an image entry, then clearing the buffer, then paining another entry, then clearing again, ...
That is good to know.
vrld wrote:
TechnoCat wrote: So to work around that, a framebuffer array is used.
Don't do that. Your graphics card can only support so may framebuffers...
But, what if I want to create an image off-screen that is larger than the main screen?

Re: How to use Framebuffer:renderTo() properly

Posted: Wed Nov 17, 2010 1:44 pm
by vrld
TechnoCat wrote:But, what if I want to create an image off-screen that is larger than the main screen?
I don't see a problem there.

Code: Select all

function love.load()
	fbo = love.graphics.newFramebuffer(math.pi*love.graphics.getWidth(),math.exp(1)*love.graphics.getHeight())
	love.graphics.setBackgroundColor(0,0,0)
end

function love.draw()
	love.graphics.setColor(255,255,255)
	love.graphics.setRenderTarget(fbo)
	love.graphics.rectangle('fill', 0, 0, 100, 100)
	love.graphics.setColor(40,60,150)
	love.graphics.circle('fill', 100, 100, 60, 32)
	love.graphics.setRenderTarget()
	love.graphics.setColor(255,255,255)
	love.graphics.draw(fbo, 50,50)
end
Works like a charm. But be aware that though the buffer is bigger, it does not act as a canvas. Everything you draw will be scaled by
framebuffersize/screensize. To emulate a canvas, you can use

Code: Select all

love.graphics.scale(love.graphics.getWidth()/FB_WIDTH, love.graphics.getHeight()/FB_HEIGHT)

Re: How to use Framebuffer:renderTo() properly

Posted: Wed Nov 17, 2010 4:45 pm
by kikito
vrld wrote:But be aware that though the buffer is bigger, it does not act as a canvas. Everything you draw will be scaled by
framebuffersize/screensize. To emulate a canvas, you can use

Code: Select all

love.graphics.scale(love.graphics.getWidth()/FB_WIDTH, love.graphics.getHeight()/FB_HEIGHT)
What the ... why?

Is that a bug or a feature?

Re: How to use Framebuffer:renderTo() properly

Posted: Wed Nov 17, 2010 5:23 pm
by vrld
kikito wrote:Is that a bug or a feature?
Neither. It's a constraint set by OpenGL.

To understand why, think of the framebuffer as an offscreen... screen. In a 3D world, you don't handle pixels, but only shapes (that may have some nice textures on them). These shapes are projected onto a screen, like in a shadow theater.
Now you always want to project the shapes in a way that the whole screen will be filled. If you take a bigger screen and set it up so that it will show the same shadows as the small screen, the shadows will obviously be larger.
When creating a framebuffer, OpenGL will ensure you see the same content on the buffer as you do on your main screen.

The way LÖVE handles 2D is basically to have only flat shapes in the same plane. Images are just surfaces with the image as texture on them. If you project that to a bigger screen, the image will appear to be upscaled.
The scaling with love.graphics.scale is somewhat like moving the screen further away (or closer to) from the objects that are projected onto the screen.

Re: How to use Framebuffer:renderTo() properly

Posted: Wed Nov 17, 2010 7:03 pm
by TechnoCat
If my window dimensions are 640x480, 4/3 ratio, and I want a PO2 compliant framebuffer at about 4 times the window size. How do I scale it properly? Since I can't make my framebuffer 4/3 (I think). Therefore, it always stretches the framebuffer when drawing.

Disclaimer: Framebuffer and screen size will be independent soon. http://bitbucket.org/rude/love/changeset/4deb61ae7d2c

Re: How to use Framebuffer:renderTo() properly

Posted: Wed Nov 17, 2010 8:04 pm
by Robin
TechnoCat wrote:If my window dimensions are 640x480, 4/3 ratio, and I want a PO2 compliant framebuffer at about 4 times the window size. How do I scale it properly? Since I can't make my framebuffer 4/3 (I think). Therefore, it always stretches the framebuffer when drawing.
Does it need to be PO2?