SOLVED Surface Drawing Stuttering

Hi there,

Basically what I'm trying to achieve is this:
-have "cloud shadows" moving across the screen
-draw the cloud shadows with an alpha I can control (all with the same value at any given time)
-have the cloud shadows create a uniform shadow colour when overlapping, rather than having two alphas combine to make darker patches when overlapping

To achieve this I created a surface, made all of the cloud shadow sprites completely black (alpha 1.0) and drew them all onto a surface (cloud_surf). I then would draw the surface at an adjusted alpha, to achieve my desired effect. Here's the code (in the Draw End event):
GML:
//draw clouds
    if(!surface_exists(cloud_surf))
    {
        cloud_surf = surface_create(480,270);   
    }
    if(surface_exists(cloud_surf))
    {
        surface_set_target(cloud_surf);
        draw_clear_alpha(c_black,0);
        
        with(obj_menu_clouds)
        {
            draw_sprite_ext(sprite_index,image_index,x,y,image_xscale,image_yscale,0,c_white,1);
        }
        
        surface_reset_target();
        
        draw_surface_ext(cloud_surf,0,0,1,1,0,c_white,cloud_alpha);
    }
The problem is, though I achieve the effect I'm going for, the clouds don't appear to be moving super smoothly.
While troubleshooting, I tried just drawing the clouds in the manner I was trying to avoid: where when they overlap the overlapping areas will appear darker. This troubleshooting method avoids using a surface altogether, and though the appearance isn't exactly what I wanted, the clouds DO move smoothly.

GML:
var ca = cloud_alpha;
    with(obj_menu_clouds)
    {
        if(sprite_index != -1)
        {
            draw_sprite_ext(sprite_index,image_index,x,y,image_xscale,image_yscale,0,c_white,ca);
        }
    }
You can see what I mean in this video (the first part of the vid uses a surface, the second part doesn't)
Example in YouTube Video

Is there something I'm misunderstanding when it comes to using shaders the way I'm trying? I seem to have run into this problem before, though in a slightly different form:
Game Maker Community Unresolved Surface related Post
and I never did find a solution to that one...

Any tips or insights into something I might be misunderstanding would be greatly appreciated!
(Note I did try drawing the surface instead in the different draw events to no success)
 

dudaxan

Member
Hey, I hope this forum thread helps you:
https://forum.yoyogames.com/index.php?threads/how-to-draw-surfaces-with-subpixel-enabled.13714/

You just need to take into account the scaling differences. If your viewport is one size, and your application size is another, you need your shadow surface and everything you draw to it to account for the ratio between them. For example, if your viewport is 320x240 and your application surface is 960x720, then the scale ratio is 1:3, so when you draw to your shadow surface, it needs to be equivalently scaled. So scale up your shadow surface to 3x normal, draw your shadows at 3x normal (with their positions x3 as well), then draw your shadow surface to the application_surface at 1/3rd size (which will then be scaled back to 3x normal to match the ratio difference between your viewport and application surface).
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
Moderator
This suggests to me that the application surface is a higher resolution than the custom surface that you are drawing the clouds to. Keep in mind that the game "canvas" is a surface too, but it'll be set automatically by gamemaker to the viewport/window size. So, while your game may look 480x270 it's actually rendered on a surface that is 960x540 or something. This means that images can move in "sub-pixel" increments and look smoother. I bet if you set the application surface to 480x270 at the start of your game you'd get the same "stuttery" movement, as then everything will be at the correct lower resolution (and you can't draw half a pixel, so all movement will be done in full pixel increments). The solution would be to make the custom surface the same size as the app surface and then draw the cloud shadow sprites scaled up using the relative scale difference between "native" size and app-surface size. For example, if the app surface is indeed 2 times larger than the 480x270 camera/room size, then you'd draw the cloud sprites scaled up by 2 (and you'd need to scale the positions too).

EDIT: Link supplied above by @dudaxan is an excellent resource and explains the situation better than my post does. :)
 
Thank you both @dudaxan and @Nocturne! I really appreciate the responses, and I'm super pumped they were so quick too!
What you explained makes sense, but I will definitely check out that thread dudaxan linked.
I'm excited because this could be the reason I was experiencing stuttering when I was using surfaces to create a black and white frame in my previous GMC post (linked above). Very excited to try your solutions out tomorrow! I will update my post here with the results!

P.S. Unfortunately I couldn't look at your sample project dudaxan because my IDE is an older one, and I'm in the middle of a very big project, haha.
 

dudaxan

Member
Thank you both @dudaxan and @Nocturne! I really appreciate the responses, and I'm super pumped they were so quick too!
What you explained makes sense, but I will definitely check out that thread dudaxan linked.
I'm excited because this could be the reason I was experiencing stuttering when I was using surfaces to create a black and white frame in my previous GMC post (linked above). Very excited to try your solutions out tomorrow! I will update my post here with the results!

P.S. Unfortunately I couldn't look at your sample project dudaxan because my IDE is an older one, and I'm in the middle of a very big project, haha.
Thanks for your feedback! Sorry for taking so long to reply... I'll post the code here:

3 objects:
obj_clouds -> The cloud object (with the cloud sprite)
obj_clouds_controller -> A controller object to draw the clouds - no sprite
obj_grass -> Just a tiled background image (with a grass tile sprite)

obj_clouds Create Event:
GML:
randomize();
x = random(room_width);
y = random(room_height);
xspeed = random(0.5)+0.5;
yspeed = random(0.5)+0.5;
obj_clouds Step Event:
GML:
x += xspeed;
y += yspeed;

if x > room_width x = -(x-room_width);
if y > room_height y = -(y-room_height);
obj_clouds Draw event:
GML:
//Avoid drawing the sprite
obj_grass Draw Event:
GML:
draw_sprite_tiled(sprite_index,image_index,0,0);
obj_clouds_controller Create Event:
GML:
draw_mode = 0;
draw_modes = ["Regular","Surface","Round Regular","Round Surface","Surface Fixed"];
alpha = 0.32;
obj_clouds_controller Draw Event:
GML:
switch(draw_mode){
    case 0:
        with(obj_clouds) draw_sprite_ext(sprite_index,image_index,x,y,image_xscale,image_yscale,image_angle,c_white,other.alpha);
    break;
    case 1:
        var surf = surface_create(room_width,room_height);
        surface_set_target(surf);
        draw_clear_alpha(c_black,0);
        with(obj_clouds) draw_sprite_ext(sprite_index,image_index,x,y,image_xscale,image_yscale,image_angle,c_white,1);
        surface_reset_target();
        draw_surface_ext(surf,0,0,1,1,0,c_white,alpha);
        surface_free(surf);
    break;
    case 2:
        with(obj_clouds) draw_sprite_ext(sprite_index,image_index,floor(x),floor(y),image_xscale,image_yscale,image_angle,c_white,other.alpha);
    break;
    case 3:
        var surf = surface_create(room_width,room_height);
        surface_set_target(surf);
        draw_clear_alpha(c_black,0);
        with(obj_clouds) draw_sprite_ext(sprite_index,image_index,floor(x),floor(y),image_xscale,image_yscale,image_angle,c_white,1);
        surface_reset_target();
        draw_surface_ext(surf,0,0,1,1,0,c_white,alpha);
        surface_free(surf);
    break;
    case 4:
        var camw = camera_get_view_width(view_camera[0]);
        var camh = camera_get_view_height(view_camera[0]);
        var portw = view_get_wport(0);
        var porth = view_get_hport(0);
        var wratio = portw/camw;
        var hratio = porth/camh;
        var surf = surface_create(room_width*wratio,room_height*hratio);
        surface_set_target(surf);
        draw_clear_alpha(c_black,0);
        with(obj_clouds) draw_sprite_ext(sprite_index,image_index,x*wratio,y*hratio,image_xscale*wratio,image_yscale*hratio,image_angle,c_white,1);
        surface_reset_target();
        draw_surface_ext(surf,0,0,1/wratio,1/hratio,0,c_white,alpha);
        surface_free(surf);
    break;
}
obj_clouds_controller Draw GUI Event:
GML:
draw_set_alpha(1);

draw_set_color(c_black);
draw_text(30+1,30+1,"Draw mode: '"+draw_modes[draw_mode]+"'");
draw_set_color(c_white);
draw_text(30,30,"Draw mode: '"+draw_modes[draw_mode]+"'");
obj_clouds_controller Global Left Pressed Event:
GML:
draw_mode = (draw_mode+1) mod array_length(draw_modes);
obj_clouds_controller Global Right Pressed Event:
GML:
draw_mode = (draw_mode-1);
if draw_mode < 0 draw_mode = array_length(draw_modes)-1;
 
Thanks very much for the example project @dudaxan . It was a very simple way of summarizing everything I'd trialed and errored in the last day or so haha.
But the key thing is that case 4 (Fixed Surface) was exactly what I was going for (as you and @Nocturne suggested).

I was a bit thrown off when reading through the forum you posted earlier, as they were suggesting a ratio needed to be used based on the application surface's size and the view port sizes, however my camera sets the application surface to be the size of the viewport, so the ratio was just 1:1. Then I noticed that in the sample project's code you provided you were using the ratio between view ports and the camera size, which turned out to be the ratio I was needing.
So now I've got this Draw End code:

GML:
var camw = camera_get_view_width(view_camera[0]);
    var camh = camera_get_view_height(view_camera[0]);
    var portw = view_get_wport(0);
    var porth = view_get_hport(0);
    var wratio = portw/camw;
    var hratio = porth/camh;
    
    //draw clouds
    if(!surface_exists(cloud_surf))
    {
        cloud_surf = surface_create(camw * wratio, camh * hratio); //(480 * 4 = 1920,270 * 4 = 1080)
    }
    
    if(surface_exists(cloud_surf))
    {
        surface_set_target(cloud_surf);
        draw_clear_alpha(c_black,0);
        
        with(obj_menu_clouds)
        {
            draw_sprite_ext(sprite_index,image_index,x*wratio,y*hratio,image_xscale*wratio, image_yscale*hratio,0,c_white,1);
        }
        
        surface_reset_target();
        
        draw_surface_ext(cloud_surf,0,0,1/wratio,1/hratio,0,c_white,cloud_alpha);
    }
And I used camw * wratio and camh * hratio for the surface size, because, though the room size is the same as the camera sizes in this instance, if I wanted to try to fix my other potentially-related problem with this code then I figured I'd need to use the camera sizes here. I could be wrong though, but in this case it's working.

I also noticed that you freed the surface right after you drew it. Is this the proper way to handle surfaces? I knew that freeing them was important, but I thought that meant when the object drawing the surface was destroyed be sure to free the surface at this time. But in your example's case, if it's being freed each step then aren't you needing to also create it each step? Whereas if you didn't free it each step, then the surface is around and not needing to be created next time we draw on it. I'd have thought it would be more processing-intensive by freeing it each time?

Thank you both for your help, and I look forward to any further insights on this topic!
 

dudaxan

Member
I'm glad the project helped you!
About your question, I really prefer creating and destroying surfaces on the draw event. I'm not sure how much more processing-intensive it gets, but I can compare it and get back to you in a few minutes.
 

dudaxan

Member
So I did a short-comparison between the two scenarios and here are the results:

Single Surface - 11620 microseconds
Multiple Surfaces - 299129 microseconds

Basically, I'm processing the draw event 500 times, to see how long it takes.
Using a single surface (as you did on your project) is much more efficient and takes 30x less time to process.
 
Oh wow, that's great to know, thank you! I'd seen other people freeing the surfaces after drawing it too, so it wasn't just you doing it. I was wondering if I was missing out on some secret, haha.
Also, it seems like the solution you and Nocturne gave me was the solution to a stuttering effect I was having as well... something I came upon a year and a half ago and was just leaving hoping it would solve itself haha. So big thank you on that front too!
 
Top