Shaders Have a shader affect an image multiple times?

Y

yyyy

Guest
Hello! I am making a cellular automata shader in Game Maker studio. The shader, I think, works properly. The problem I'm having is it only performs the rules of the automata once on the image. Is there a good way to have game maker run the shader every frame, and continually change the last frame of the image?

I have tried to output the shader onto a surface, then input said surface into the shader. And continually do that every frame, but it wasnt working correctly. Is this the right approach?

Sorry if the question is confusing, I am not exactly sure how to word it. Do you understand what I am asking about?
 
Y

yyyy

Guest
I was just getting a black screen. I assumed this was because game maker was getting messed up from writing and reading the surface in the same frame. But that was just a guess lol
 

CMAllen

Member
For this to work, you'd need two surfaces. Essentially, you need to create a new surface every time you draw something, because shaders don't actually change the source information. So you'd 'draw' the surface from the previous event to a newly created surface, free the old surface, and hand the ID of the surface you just finished drawing to into the variable that held the ID from the surface just released. You can then draw your completed surface to wherever it's needed and have it waiting for the next round of drawing.

However, because surfaces are volatile and can disappear at any time, it does pose a problem as to how you could recover the contents of that surface if were to get released (assuming that having it start over from nothing isn't an option). One possibility is to drop the information contained in a given surface into a buffer as a safety net (using a process similar to the above by releasing old buffers and passing a new buffer ID into the variable). From my experience, you have to reconstitute the data from a buffer into a surface manually (by drawing the pixels one by one), because the reverse functions of converting a buffer to a surface don't work properly. But still, the important thing is have that information stored somewhere, in a non-volatile form so it's always available.
 
M

Multimagyar

Guest
Hello! I am making a cellular automata shader in Game Maker studio. The shader, I think, works properly. The problem I'm having is it only performs the rules of the automata once on the image. Is there a good way to have game maker run the shader every frame, and continually change the last frame of the image?

I have tried to output the shader onto a surface, then input said surface into the shader. And continually do that every frame, but it wasnt working correctly. Is this the right approach?
Before I would jump into conclusions of creating 60 surfaces a second or store them in buffers every time after I generated them harvesting the data from the texture memory every frame update even multiple times, in case I need to load them back, something about this statements gets me. first I'm fairly sure you cannot use a surface as ain input and an output in the same time.
You say you run the shader and it only does the effect once? did you make your effect in the shader code in some way static?
Or you used to draw it simply on to one surface then you use that one surface?
 
Y

yyyy

Guest
For this to work, you'd need two surfaces. Essentially, you need to create a new surface every time you draw something, because shaders don't actually change the source information. So you'd 'draw' the surface from the previous event to a newly created surface, free the old surface, and hand the ID of the surface you just finished drawing to into the variable that held the ID from the surface just released. You can then draw your completed surface to wherever it's needed and have it waiting for the next round of drawing.

However, because surfaces are volatile and can disappear at any time, it does pose a problem as to how you could recover the contents of that surface if were to get released (assuming that having it start over from nothing isn't an option). One possibility is to drop the information contained in a given surface into a buffer as a safety net (using a process similar to the above by releasing old buffers and passing a new buffer ID into the variable). From my experience, you have to reconstitute the data from a buffer into a surface manually (by drawing the pixels one by one), because the reverse functions of converting a buffer to a surface don't work properly. But still, the important thing is have that information stored somewhere, in a non-volatile form so it's always available.
Hmmmm interesting. Do you think this is going to be quick enough to do automata over a large area? I assume creating and destroying surfaces constantly will really ruin my fps. I'll give it a shot and report back though.

Before I would jump into conclusions of creating 60 surfaces a second or store them in buffers every time after I generated them harvesting the data from the texture memory every frame update even multiple times, in case I need to load them back, something about this statements gets me. first I'm fairly sure you cannot use a surface as ain input and an output in the same time.
You say you run the shader and it only does the effect once? did you make your effect in the shader code in some way static?
Or you used to draw it simply on to one surface then you use that one surface?
Sorry, english isnt my first language and I had a slightly hard time understanding what you said.
psuedo code, hopefully this can help you understand my idea.

surface_set_target(surf)
shader_set(shade)
draw_surface(surf)
shader_reset()
surface_reset_target()
draw_surface(surf)

The shader, is to my knowledge, in no way static. It just does the effect on the image once, like it should (it applies its rules once), but i need it to affect the same image over and over and over again(applies it rules to the image it already applied its rules too). If that makes sense

I'll be back home shortly and can post my real code for everything, if that would help.
Thanks a lot everyone!
 
Last edited by a moderator:
M

Multimagyar

Guest
well not the surface static but rather the effect. itself when I make such shaders that need to change over time I usually just use delta time. Of course surfaces are not static you could convert them to sprites but that would be costy... like really costy.
 
Y

yyyy

Guest
I considered delta time, but I don't think it will work for me, just because of the nature of cellular automata. And sorry, I meant the shader is to my knowledge not static.
 
M

Multimagyar

Guest
Shader output can generate the same effect everytime unless you have random factors or anything that changes it run time.
 

CMAllen

Member
Hmmmm interesting. Do you think this is going to be quick enough to do automata over a large area? I assume creating and destroying surfaces constantly will really ruin my fps. I'll give it a shot and report back though.
I suppose that depends on the size of the surface. But I've worked with creating and destroying surfaces at 1920x1080 in size without any notable change in draw_event execution times. In fact, on more than one occasion, I've created a surface, completed whatever I need to do with it, drawn it over to the application surface, and immediately released it, repeating the entire process again the next draw cycle. I feel reasonably confident that the performance impact of creating surfaces is pretty small. Manipulating the data that surface contains is where the majority of its performance impact comes in.
 
Y

yyyy

Guest
Shader output can generate the same effect everytime unless you have random factors or anything that changes it run time.
Heres the code for my shader.

Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform vec2 size;

void main()
{
    vec2 realPos = v_vTexcoord * size;

    vec4 oc = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord);

    vec4 tl = texture2D( gm_BaseTexture, vec2(realPos.x-1.0,realPos.y-1.0)/size );
    vec4 tc = texture2D( gm_BaseTexture, vec2(realPos.x,realPos.y-1.0)/size );
    vec4 tr = texture2D( gm_BaseTexture, vec2(realPos.x+1.0,realPos.y-1.0)/size );
 
    vec4 cl = texture2D( gm_BaseTexture, vec2(realPos.x-1.0,realPos.y)/size );
    vec4 cc = texture2D( gm_BaseTexture, vec2(realPos.x,realPos.y)/size );
    vec4 cr = texture2D( gm_BaseTexture, vec2(realPos.x+1.0,realPos.y)/size );
 
    vec4 bl = texture2D( gm_BaseTexture, vec2(realPos.x-1.0,realPos.y+1.0)/size );
    vec4 bc = texture2D( gm_BaseTexture, vec2(realPos.x,realPos.y+1.0)/size );
    vec4 br = texture2D( gm_BaseTexture, vec2(realPos.x+1.0,realPos.y+1.0)/size );
 
 
    float count = tl.r + cl.r + bl.r + tc.r + bc.r + tr.r + cr.r + br.r;
    vec4 finalCol;
 
    // Death and life
    if (count < 2.0 || count > 3.0)
    finalCol = vec4(0.0,0.0,0.0,1.0);
    else if (count == 3.0)
    finalCol=vec4(1.0,1.0,1.0,1.0);
    else
    finalCol = vec4(cc.r,cc.g,cc.b,1.0);

    gl_FragColor = finalCol;
}

It does generate the same effect every single time, thats kind of the problem, I need it to do the effect the an image it just did the effect to.
 
Last edited by a moderator:
Y

yyyy

Guest
I suppose that depends on the size of the surface. But I've worked with creating and destroying surfaces at 1920x1080 in size without any notable change in draw_event execution times. In fact, on more than one occasion, I've created a surface, completed whatever I need to do with it, drawn it over to the application surface, and immediately released it, repeating the entire process again the next draw cycle. I feel reasonably confident that the performance impact of creating surfaces is pretty small. Manipulating the data that surface contains is where the majority of its performance impact comes in.
Got it working. You are a god among men, from the bottom of my heart I thank you. Ill put the code up sometime after Im done optimizing and and working on it a little bit more. Hopefully its cool, and maybe people can learn something from it.
 

DukeSoft

Member
I suppose that depends on the size of the surface. But I've worked with creating and destroying surfaces at 1920x1080 in size without any notable change in draw_event execution times. In fact, on more than one occasion, I've created a surface, completed whatever I need to do with it, drawn it over to the application surface, and immediately released it, repeating the entire process again the next draw cycle. I feel reasonably confident that the performance impact of creating surfaces is pretty small. Manipulating the data that surface contains is where the majority of its performance impact comes in.
I disagree with that - It might depend on your GPU, but reserving the memory in the GPU (e.g. creating a surface) takes up quite a bit of time, when compared to drawing on it (e.g. manipulating the data).

If you need the data from the surface raw (as in your transforming it to a background / saving to an image) then yeah, that takes up a lot of time as well.

In percentages, I'd guess (to the gut feeling I had while working with them):

1% Data manipulation by drawing / applying shaders:
9% Creating / destroying them
90% Changing them to backgrounds / getting pixel information / saving to images
 

DukeSoft

Member
So, to come with a single, simple solution:

your object's create event:
Code:
extra_surface = surface_create(surface_get_width(application_surface), surface_get_height(application_surface));
Draw event:
Code:
surface_set_target(extra_surface)
shader_set(shader);
draw_surface(application_surface, 0, 0)
shader_reset();
surface_reset_target();

//Draw result (its drawn on the application_surface anyways so no need to change that)
draw_surface(extra_surface, 0, 0)
I'm not even sure if you need the extra surface, feels like using the application_surface could work (haven't tested)
Draw:
Code:
surface_set_target(application_surface)
shader_set(shader);
draw_surface(application_surface, 0, 0)
shader_reset();
surface_reset_target();
 

Juju

Member
I feel reasonably confident that the performance impact of creating surfaces is pretty small.
Creating and freeing a 1920x1080 surface takes a lot of time. It takes ~6ms on my old laptop (745m) and ~4ms on my new laptop (1070). This is a significant chunk of a frame.
 

CMAllen

Member
Creating and freeing a 1920x1080 surface takes a lot of time. It takes ~6ms on my old laptop (745m) and ~4ms on my new laptop (1070). This is a significant chunk of a frame.
Good to know. But you STILL need two surfaces. One CAN be the application surface, but ONLY if nothing else is drawn to that surface AND it is not used in any other fashion either. If you're drawing GUI elements or have other objects with their own draw events, then the application surface can't be used, because you'll be passing those parts of the finished surface into the shader on the next draw pass. In which case, the solution is to just swap the two surfaces back and forth:

Code:
var temp_surf = draw_surf;
draw_surf = done_surf;
done_surf = temp_surf;
 
Top