GMS 2 Choppy Framerate When Drawing Surfaces

Hello,

I'm trying to figure out how to fix the seemingly lower framerate as well as objects jumping around a tiny bit when surfaces are being drawn. Here's an example:

Here's my code:
Draw GUI Begin:

Code:
if(tea_active == true)
{
    /// @description Draw Tea Effect
    if(!surface_exists(base_surf))
    {
        base_surf = surface_create(camera_get_view_width(view_camera[0]),camera_get_view_height(view_camera[0]));
        tea_surf = surface_create(camera_get_view_width(view_camera[0]),camera_get_view_height(view_camera[0]));
    }   
    //sets the view to be drawn in the tea surf
    if(view_surface_id[0] != base_surf)
    {
        view_surface_id[0] = base_surf;
    }
    
    surface_set_target(tea_surf);
    //clear the surface
    draw_clear_alpha(c_black,0);
    
    //draw the white tea sprite
    draw_sprite(spr_tea_vision_sprite,0,0,0);

    //set the alpha to false so you only draw on non-transparent areas
    gpu_set_colorwriteenable(true,true,true,false);
    
    if(shader_is_compiled(sh_greyscale))
    {
        shader_set(sh_greyscale);
        //draw application surface
        draw_surface(base_surf,0,0);
        shader_reset();
    }

    //reset the drawing mode
    gpu_set_colorwriteenable(true,true,true,true);


    surface_reset_target();

    //draw the regular layer
    draw_surface(base_surf,0,0);   
    draw_surface(tea_surf,0,0);

}
else
{
    if(view_surface_id[0] != -1)
    {
        view_surface_id[0] = -1;   
    }
}
Any tips or ideas would be much appreciated. I've tried moving the code around to different draw events, and splitting up when the draw_surface functions are being done, but I can't seem to figure it out.

Thanks

Dan
 

Joe Ellis

Member
I think it's just to do with the amount of rendering being done is too much for your computer. Drawing to fullscreen surfaces takes alot of time and if you're using 3+, it can easily cause lag. There's not a whole lot of way around that till computers get faster.
But, you can reduce the amount of surfaces you're using by using blend modes instead and overlaying things ontop of the application surface.
You could probably work a way to get those effects your doing with just 1 surface.
 
I think it's just to do with the amount of rendering being done is too much for your computer. Drawing to fullscreen surfaces takes alot of time and if you're using 3+, it can easily cause lag. There's not a whole lot of way around that till computers get faster.
But, you can reduce the amount of surfaces you're using by using blend modes instead and overlaying things ontop of the application surface.
You could probably work a way to get those effects your doing with just 1 surface.
Ah really? Hmm that's too bad. I suppose I'll have to be more creative with how I attempt this effect
 
No, that doesn't seem right. I have a number of full screen surfaces, even a couple full room surfaces, and I don't get lag like that. First thing, I would enable v-sync and see if that makes a difference. Second, I would open up the debugger and look at the graphics section and see how many surfaces and texture pages you have going on, make sure you aren't accidentally creating a bunch of surfaces/textures by accident and that it matches up to what you expect. The next thing I would do is check and see what your FPS is like before enabling that second surface. If your already maxing out your FPS before enabling that surface then you will need to optimize somewhere else to make room for it. Also, keep in mind the specs of whatever your testing on, if your testing on a awesome computer and your maxing out FPS that would not be good, but if your on a potato and your maxing out then your probably ok.
 

Azenris

Member
if you slow the video to 0.25 if looks like weird positoning, like things are floor or something. Ive never used view_surface_id[0], could it be releated to that ? Don't know about the FPS tho.
 
No, that doesn't seem right. I have a number of full screen surfaces, even a couple full room surfaces, and I don't get lag like that. First thing, I would enable v-sync and see if that makes a difference. Second, I would open up the debugger and look at the graphics section and see how many surfaces and texture pages you have going on, make sure you aren't accidentally creating a bunch of surfaces/textures by accident and that it matches up to what you expect. The next thing I would do is check and see what your FPS is like before enabling that second surface. If your already maxing out your FPS before enabling that surface then you will need to optimize somewhere else to make room for it. Also, keep in mind the specs of whatever your testing on, if your testing on a awesome computer and your maxing out FPS that would not be good, but if your on a potato and your maxing out then your probably ok.
So I tried messing with the vsync and didn't notice a difference.
Next I checked out the debugger to see if a bunch of surfaces were being created, but I don't think that's the case either:

upload_2020-1-25_22-31-58.png

As for the FPS its well over 1500 FPS both when the spirit vision is on and when its off.

So it doesn't seem to be a very processing-demanding action.

Any other ideas?
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
Moderator
Is there a reason you're using view_surface_id and not directly using the application_surface? Really, in GMS2, there is no reason to ever disable the app surface, and the view_surface_id stuff is for more complex drawing operations than you are doing right now - so I'd scrap the "base_surface" and just draw the app surface directly. As for the issue, I suspect the view surface is being updated a step later than the app surface and so it's drawing with that "stutter". You could try doing the setting in the draw begin phase of the draw event instead of the main draw event and see if that helps, or set the object doing the drawing to the lowest layer/depth possible then drawing the overlay surface in the draw end (although this is moot if you just use the app surface! :p ). As a side note, you should probably be freeing the surface used when you set the view_surface_id to -1, just to keep the code clean (although it's not strictly necessary and I imagine you free the surface in the clean-up event anyway).

PS: Just noticed that you aren't freeing up the tea_surface anywhere either... So, if you lose the base surface then you are re-creating the tea surface although it might already exist, so there's a memory leak right there! You should probably have two different surface_exists checks - one for each.

PPS: In the image you posted above, you set the camera position before all the rest of the code... if you are changing the xview/yview values in that code, you should probably set the camera after it.
 
Let me first say thank you to everyone who responded. I really do appreciate it and I'm sorry it's taken me so long to respond. To be honest I've been putting off coming back to address this issue, but I think it's finally time, haha.

Thanks @Nocturne for the tip about moving my camera position to AFTER xview and yview have changed. That seems to have solved an issue I'd been having for quite sometime (both related to this vision effect and just in regular gameplay). I also took the advice to stop using view_surface_id[0] and am attempting to simply use the application surface instead. I am not very familiar with view_surface_id either, and only dabbled with it due to following other somewhat-related tutorials online.

So, my modified code is as follows:
Draw Begin:
GML:
if(tea_active == true)
{
    
    /// @description Draw Tea Effect
    if(!surface_exists(tea_surf))
    {
        tea_surf = surface_create(camera_get_view_width(view_camera[0]),camera_get_view_height(view_camera[0]));   
    }
    
    surface_set_target(tea_surf);
    //clear the surface
    draw_clear_alpha(c_black,0);
    
    //draw the white tea sprite
    draw_sprite(spr_tea_vision_sprite,0,0,0);

    //set the alpha to false so you only draw on non-transparent areas
    gpu_set_colorwriteenable(true,true,true,false);
    
    if(shader_is_compiled(sh_greyscale))
    {
        shader_set(sh_greyscale);
        //draw application surface
        //draw_sprite(spr_bath_house,0,50,150);
        draw_surface(application_surface,0,0);
        shader_reset();
    }

    //reset the drawing mode
    gpu_set_colorwriteenable(true,true,true,true);


    surface_reset_target();

}
else
{
    //clear the memory
    if(surface_exists(tea_surf))
    {
        surface_free(tea_surf);   
        tea_surf = -1;
    }
}
And my Draw End event now has:

Code:
//draw the black and white tea effect
if(surface_exists(tea_surf))
{
    draw_surface(tea_surf,camera_get_view_x(view_camera[0]),camera_get_view_y(view_camera[0]));
}


I don't seem to be using the application surface correctly as it's not drawing anything (or perhaps in a different location) and the result is just seeing the green "mask"
1603227490190.png

However the effect I'm going for does seem to work when I test it out using the draw_sprite(spr_bath_house,0,50,150) line and remove the draw application surface code

1603227570783.png

Now I just need to figure out how to get it to draw my current view in black and white rather than the bath house sprite. Is there a way to draw my current view? I think that's what I was trying to do when I was dabbling with view_surface_id[0] before.

Any tips would be very appreciated, and I promise I won't take a year to respond
 

Yal

🍋 *lemon noises*
GMC Elder
Since nobody seems to have pointed this out in february, you can use the profiler to see where all your CPU power goes (it's one of the debugger features). Have it run for a while whilst doing the thing that causes the game to lag, then stop profiling and start looking through the results (they're sorted from most to least CPU usage by default). If a function call is taking up 90% of the processing power, you've probably found the issue. (You might need to break it down a few steps - the highest level entry is an object/event pair, and then there's function calls and functions those functions called, etc)

The green effect probably is whatever was drawn on the back buffer the previous step (since the alpha holes in the app surface doesn't cover the entire window now). The best way to deal with this would be to turn off automatic drawing and then draw the app surface yourself in the Post Draw event, this lets you draw whatever you want to the back buffer (so you could put a gradient or even an animated background around the part of the surface you want to draw) and also apply any effects you want to the app surface, like using a shader to distort and recolor it.

To make the image black and white, you'd have a shader that checks the rgb values of the input texture (in the fragment shader), and then sets the output rgb all to the same value (thus making them grayscale). You get different effects if you pick the minimum, maximum or average of the inputs so you might want to experiement a bit with what looks the best. If you want pure black and white (and not grayscale), check if the average color is larger or smaller than a threshold value (e.g. 0.5) and then set the output color to vec3(0.0) - i.e., black - if it's below the threshold or vec3(1.0) - white - if it's above.
 
Thanks for the response Yal. And sorry the images above are really messy!

I've come to the conclusion that the laggy effect isn't due to high CPU usage but instead an issue where the view was being adjusted and drawn a step late. I've managed to fix that part of it, but as I posted above, I've adjusted the code to not make use of the view_surface_id function, and I'm now drawing in the Draw Begin and Draw End events, rather than the Draw GUI Begin and Draw GUI End events.
The green rectangle with a faded hole punched out of the middle is actually a sprite I'm drawing named spr_tea_vision_sprite. I'm drawing it onto a surface (tea_surf), and then basically trying to draw the application surface onto the parts of tea_surf that has an alpha value above 0 (approximately).

The weird looking black and white building above is me testing out the code to see if it is working, and it is. The building is being drawn in a greyscale shader and being successfully drawn onto the opaque part of tea_surf. The only thing is, I don't want to draw that building, I want to draw the regular view the player would be looking at (giving the effect that the player and a small circle around them is colourized, but surrounding the player everything is black and white.
I've been trying to figure out how to draw the application surface using this greyscale shader, but I haven't figured that part out yet. It seems like I might need to use view_surface_id, but I'm not sure yet.
 
Update:
I've started resizing the application surface and moved the Draw events back to the Draw GUI Begin and Draw GUI End events, rather than regular Draw Begin and Draw End events. I've successfully replicated the effect I want using the application surface instead of using the view_surface_id function, however I'm still getting that stutter:


Draw GUI Begin Code:
GML:
if(tea_active == true)
{
    
    /// @description Draw Tea Effect
    if(!surface_exists(tea_surf))
    {
        tea_surf = surface_create(camera_get_view_width(view_camera[0]),camera_get_view_height(view_camera[0]));   
    }
    surface_resize(application_surface, display_get_gui_width(), display_get_gui_height());
    
    surface_set_target(tea_surf);
    //clear the surface
    draw_clear_alpha(c_black,0);
    
    //draw the white tea sprite
    draw_sprite(spr_tea_vision_sprite,0,0,0);

    //set the alpha to false so you only draw on non-transparent areas
    gpu_set_colorwriteenable(true,true,true,false);
    
    if(shader_is_compiled(sh_greyscale))
    {
        shader_set(sh_greyscale);
        //draw application surface
        //draw_sprite(spr_bath_house,0,50,150);
        if(surface_exists(application_surface))
        {
            draw_surface(application_surface,0,0);
        }
        shader_reset();
    }

    //reset the drawing mode
    gpu_set_colorwriteenable(true,true,true,true);


    surface_reset_target();

}
else
{
    //clear the memory
    if(surface_exists(tea_surf))
    {
        surface_free(tea_surf);   
        tea_surf = -1;
    }
}
Draw GUI End Code:
Code:
if(tea_active == true)
{
    //draw the black and white tea effect
    if(surface_exists(tea_surf))
    {
        draw_surface(tea_surf,0,0);
    }
}

Any ideas on how I can get the drawn greyscale application surface to line up with the regular application surface? I can't seem to figure out where/when to draw.
 
Top