Page 1 of 1

Canvas breaking shaders

Posted: Sat Jan 21, 2017 9:43 pm
by lucasm
I have a shader that turns the whole screen black-and-white and I use it on my Game Over overlay.

My code for this overlay is as follows:

Code: Select all

         
local lg = love.graphics
lg.setShader()
lg.setFont(fontBig)
local fontHeight = fontBig:getHeight()
lg.printf({{180,0,0},'Game Over'}, 0, (screenHeight/2)-(fontHeight/2), screenWidth, 'center')
lg.setShader(args.shader)
And it used to work fine until I started to draw my game in a canvas

Code: Select all

love.graphics.setCanvas(canvas)
love.graphics.clear()
self.level:draw()
love.graphics.setCanvas()
love.graphics.draw(canvas, 0, 0, 0, scale, scale)
This is how my game over screen looked like before using canvas. Note how 'Game Over' is in red and the Hud (upper left corner) is dimmed by the shader
love_2017_01_21_19_15_08_207.png
love_2017_01_21_19_15_08_207.png (6.02 KiB) Viewed 5363 times
And this is what happens when I wrap my draw code `self.level:draw()` in a canvas
love_2017_01_21_19_14_14_258.png
love_2017_01_21_19_14_14_258.png (15.9 KiB) Viewed 5363 times
Any ideas?

There are no other shaders nor canvas on my entire game.
I tried to remove love.graphics.clear() but nothing changed.

Re: Canvas breaking shaders

Posted: Sat Jan 21, 2017 11:35 pm
by MrFariator
My assumption is that you set the shader in a way that it affects everything that's drawn on the canvas (can't say without seeing how the shader is set in the second piece of code). Perhaps when you draw the canvas with love.graphics.draw(), you still have the shader active? If so, just put love.graphics.setShader() on the line before it.

Alternatively, disable the shader when you draw the "Game Over" or other elements that you don't want to the shader touch.

Re: Canvas breaking shaders

Posted: Sun Jan 22, 2017 12:18 am
by lucasm
The current flow is as follows

Code: Select all

setCanvas(canvas)
self.level:draw()
--Those are inside self.level:draw()
   background:draw()
   actors:draw()
   hud:draw()
   overlay:draw()
-- inside overlay:draw()...
     setShader() -- empty shader
     print('Game Over') -- in red
     setShader(shader) -- it gets the r,g,b of every pixel and divides the value by 3
--flow returns to main
setCanvas()
draw(canvas)
Shader code

Code: Select all

lg.newShader([[
								vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords ){
									vec4 pixel = Texel(texture, texture_coords );//This is the current pixel color
									number average = (pixel.r+pixel.b+pixel.g)/3.0;
									pixel.r = average;
									pixel.g = average;
									pixel.b = average;
									return pixel;
								}
							]])
The weird part is that if I comment ONLY the four lines related to canvas and nothing else, it works perfectly

Re: Canvas breaking shaders

Posted: Sun Jan 22, 2017 1:22 am
by MrFariator
Try the following:

Code: Select all

love.graphics.setCanvas()
love.graphics.setShader()
love.graphics.draw(canvas, 0, 0, 0, scale, scale)
A canvas, like any other love2d drawable object, will be affected by the current active shader when you call the love.graphics.draw() on it. The overlay:draw() portion in your code suggests you don't disable the shader after the print() call.

Edit:
never mind, it seems your shader might hang in there until the next render cycle as drunken_munki suggests.

Re: Canvas breaking shaders

Posted: Sun Jan 22, 2017 1:25 am
by drunken_munki
lucasm wrote:The current flow is as follows

Code: Select all

setCanvas(canvas)
self.level:draw()
--Those are inside self.level:draw()
   background:draw()
   actors:draw()
   hud:draw()
   overlay:draw()
-- inside overlay:draw()...
     setShader() -- empty shader
     print('Game Over') -- in red
     setShader(shader) -- it gets the r,g,b of every pixel and divides the value by 3
--flow returns to main
setCanvas()
draw(canvas)

Looking at that code you just posted it looks like the shader may still be active by the next render cycle?

After you have finished with the shader use the setShader() to disable it just to be sure.

Mayhaps try this quickly?

Code: Select all

setCanvas(canvas)

setShader(shader) -- it gets the r,g,b of every pixel and divides the value by 3
self.level:draw()
background:draw()
actors:draw()
hud:draw()
overlay:draw()
setShader() -- empty shader

print('Game Over') -- in red

setCanvas()
draw(canvas)

Re: Canvas breaking shaders

Posted: Sun Jan 22, 2017 1:13 pm
by lucasm
Looking at that code you just posted it looks like the shader may still be active by the next render cycle?
That's exactly what I intended. The overlay is supposed to be the last drawn layer of my game because it is where messages like "Paused", "Game Over", or "World x-y" will be displayed. So, what my code is doing is

**Current Frame Cycle
Draw everything
Disable shaders
Print colored message
Enable shaders

**Next Frame Cycle
Draw everything (shader is still active, so the desired effect will be applied)
Disable shaders
Print colored message
Enable shader

**Repeat until the specified overlay duration


To do what you suggested, I had to monkey patch the code so the shaders are enabled when the cycle starts and disabled just before the message is printed and it worked but I'm not very happy with the solution code-wise.

What I suspect is that when I don't have a Canvas, the active shader will only be applied to the things drawn on the next frame, but when I have a Canvas the engine will apply the active shader to the canvas when drawing it to the screen during the love.draw() cycle.