Missile Command Splosion Effect

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
unlimitedbacon
Prole
Posts: 6
Joined: Sun Dec 27, 2015 10:28 pm

Missile Command Splosion Effect

Post by unlimitedbacon »

Greetings,

I'm trying to do an effect sort of like the explosions in the old Atari game Missile Command, but not quite the same. This is where two explosions cancel each other out where they intersect. Essentially what I'm trying to do is XOR two circles. I thought that using a shader might be the best way to go about this. Also, it gave me an excuse to learn about shaders. This is what I came up with (full .love file is also included)

Shader

Code: Select all

extern Image explosionCanvas;
vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords ){
    vec4 pixel = Texel(texture, texture_coords );
    screen_coords.x = screen_coords.x / love_ScreenSize.x;
    screen_coords.y = screen_coords.y / love_ScreenSize.y;
    vec4 canvasPixel = Texel(explosionCanvas, screen_coords);
    if (canvasPixel.r != 0) {
        pixel.r = 0;
    } else {
        pixel.r = 1.0;
    }
    return pixel;
}
Where it gets used in love.draw()

Code: Select all

love.graphics.setColor(255,255,0)
love.graphics.setCanvas(explosionCanvas)
love.graphics.clear()
love.graphics.setBlendMode("replace")
love.graphics.setShader(explosionShader)
for _,e in pairs(explosions) do
    explosionShader:send("explosionCanvas",explosionCanvas)
    love.graphics.circle("fill", e.x, e.y, e.rad, 32)
end
love.graphics.setCanvas()
love.graphics.setShader()
love.graphics.setBlendMode("alpha")
love.graphics.draw(explosionCanvas)
The problem is it comes out looking like this:
Image

The green areas should be solid, but instead they come out garbled and super flickery. I have no idea whats going on. Obviously I am doing something horribly wrong.
Attachments
MiddleCommand.love
(6.6 KiB) Downloaded 129 times
bobbyjones
Party member
Posts: 730
Joined: Sat Apr 26, 2014 7:46 pm

Re: Missile Command Splosion Effect

Post by bobbyjones »

Can't stencils do this? With the new api anyways.
User avatar
pgimeno
Party member
Posts: 3674
Joined: Sun Oct 18, 2015 2:58 pm

Re: Missile Command Splosion Effect

Post by pgimeno »

This looks like the problem I had while attempting to scroll using a shader:
https://www.love2d.org/forums/viewtopic.php?f=4&t=81251

The problem is probably concurrency: race conditions between the parallel GPU processors . You're always sending the same canvas to the shader, not a new canvas. I can't think of a way to do it right without swapping the canvas once per circle drawn, and as I learned recently, it turns out that it's one of the most expensive operations for the GPU. (But I'm quite new to shaders, so don't take my word as gospel)
unlimitedbacon
Prole
Posts: 6
Joined: Sun Dec 27, 2015 10:28 pm

Re: Missile Command Splosion Effect

Post by unlimitedbacon »

bobbyjones wrote:Can't stencils do this? With the new api anyways.
I did not know about that. Cool.
pgimeno wrote:The problem is probably concurrency: race conditions between the parallel GPU processors . You're always sending the same canvas to the shader, not a new canvas. I can't think of a way to do it right without swapping the canvas once per circle drawn, and as I learned recently, it turns out that it's one of the most expensive operations for the GPU. (But I'm quite new to shaders, so don't take my word as gospel)
It definitely looks like a race condition, but I'd still like to understand why it is happening.
User avatar
slime
Solid Snayke
Posts: 3166
Joined: Mon Aug 23, 2010 6:45 am
Location: Nova Scotia, Canada
Contact:

Re: Missile Command Splosion Effect

Post by slime »

Your shader is accessing the stored pixel data of the same thing that you're rendering to – which produces undefined behaviour in GPUs because pixels are shaded in parallel, so new data may or may not have been written (or might only be partially written) to a pixel in the Canvas by the time the shader retrieves its value.
unlimitedbacon
Prole
Posts: 6
Joined: Sun Dec 27, 2015 10:28 pm

Re: Missile Command Splosion Effect

Post by unlimitedbacon »

So are you saying that, in the loop, it actually starts drawing the second circle before the drawing of the first circle is complete? Does this mean that I should think of love.graphics.circle() as something that initiates the drawing process, rather than something that completes the entire operation and continues when finished? If so, is there a way I can make it pause until the drawing operation is completed?
User avatar
pgimeno
Party member
Posts: 3674
Joined: Sun Oct 18, 2015 2:58 pm

Re: Missile Command Splosion Effect

Post by pgimeno »

OpenGL can reorder operations to optimize rendering, paralleling operations as much as it can to make the best possible use of the GPU. The function to force display of what has been drawn so far is love.graphics.present() but I don't think you want to go there.

OpenGL works damn well for "fire and forget" graphics: draw blindly without caring about what is already there. But if you depend on what you have just drawn in order to draw the next thing, that dependency stalls paralleling and performance drops significantly, because that's not how it's designed to work.

You can use two canvases:
  1. start with an empty first canvas;
  2. swap first and second canvas;
  3. activate the first canvas;
  4. clear;
  5. copy the second canvas to the first canvas;
  6. send the second canvas to the shader;
  7. enable the shader;
  8. draw the circle;
  9. disable the shader;
  10. if there are more circles to draw, go to step 2
but expect the performance to be low.
User avatar
s-ol
Party member
Posts: 1077
Joined: Mon Sep 15, 2014 7:41 pm
Location: Cologne, Germany
Contact:

Re: Missile Command Splosion Effect

Post by s-ol »

pgimeno wrote:OpenGL can reorder operations to optimize rendering, paralleling operations as much as it can to make the best possible use of the GPU. The function to force display of what has been drawn so far is love.graphics.present() but I don't think you want to go there.

OpenGL works damn well for "fire and forget" graphics: draw blindly without caring about what is already there. But if you depend on what you have just drawn in order to draw the next thing, that dependency stalls paralleling and performance drops significantly, because that's not how it's designed to work.

You can use two canvases:
  1. start with an empty first canvas;
  2. swap first and second canvas;
  3. activate the first canvas;
  4. clear;
  5. copy the second canvas to the first canvas;
  6. send the second canvas to the shader;
  7. enable the shader;
  8. draw the circle;
  9. disable the shader;
  10. if there are more circles to draw, go to step 2
but expect the performance to be low.
you don't need to go that far. Certain state-swaps force the GPU to flush the render queue, I think if you swap the canvas and do something else (like draw one point) it would wait for the first operation to complete. Also if you do two canvas', then don't copy-and-clear, copy-and-swap:

Code: Select all

cvs1, cvs2 = love.graphics.newCanvas(), love.graphics.newCanvas()

love.graphics.setShader(shader) -- shader should output to first canvas
for i,v in pairs(circles) do
  love.graphics.setCanvas(cvs1, cvs2)
  love.graphics.circle(v.x, v.y, v.r, 200)
  cvs1, cvs2 = cvs2, cvs1
end

love.graphics.setShader()
love.graphics.setCanvas()
love.graphics.draw(cvs2)
For something as simple as drawing a cirlce, the overhead of the API is probably higher than the drawing itself, so this could be twice as fast (or even better because afaik love.graphics.clear() is very expensive)

s-ol.nu /blog  -  p.s-ol.be /st8.lua  -  g.s-ol.be /gtglg /curcur

Code: Select all

print( type(love) )
if false then
  baby:hurt(me)
end
unlimitedbacon
Prole
Posts: 6
Joined: Sun Dec 27, 2015 10:28 pm

Re: Missile Command Splosion Effect

Post by unlimitedbacon »

Thanks S0lll0s. Thats a simple and elegant solution.
User avatar
pgimeno
Party member
Posts: 3674
Joined: Sun Oct 18, 2015 2:58 pm

Re: Missile Command Splosion Effect

Post by pgimeno »

S0lll0s wrote:For something as simple as drawing a cirlce, the overhead of the API is probably higher than the drawing itself, so this could be twice as fast (or even better because afaik love.graphics.clear() is very expensive)
love.graphics.clear() is something as simple as drawing a rectangle, isn't it? It's used by LÖVE every frame, after all. I don't see why it should be expensive.

From my understanding of slime's post that I linked upthread, I think the real bottleneck is the canvas swapping, which is no surprise because it forces serialization of an otherwise parallel process, making it take roughly N times what it takes in parallel, with N being the number of circles drawn.
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot] and 12 guests