• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Scaled Surfaces

Stubbjax

Member
I have a view and window size of 200x150, as well as a background image that is 400x300. The background is drawn at 50% size so that it fits within the view.

If I double the window size to 400x300, everything looks fine because the background is scaled up using its original size of 400x300. So even though the background is drawn at a scale of 50% in the code, the 200% window size results in the background being drawn at a crisp 100%.

Now on to the problem...

If I use a 200x150 surface to draw the background image on, the background will scale based on its drawn size of 50%, rather than its original size (the surface does not know about the larger image). So if the window size is 400x300, the background is drawn at 400x300 as well, but it is as if the original size of the background is 200x150. This results in pixelation and bilinear filtering, which is obviously not desirable.

The entire surface-drawing process simply sets visibility of the affected objects to false and then manually calls their draw events when the render target is changed to the surface. The view size does not scale.

Is there any easy solution to this that would not result in me having to rewrite everything? I do not think I can even afford to do that because it would not only be considerably time-consuming, but it would interfere with game logic if objects were different sizes and positions based on the window size. Hopefully there's a simple trick or workaround that I am missing. Maybe I have been doing things the wrong way the entire time and should be scaling/positioning everything based on the window size? Help would be appreciated, thanks.

I have whipped up a quick example that demonstrates the exact problem I am experiencing which can be found here (Surface.gmk - 33.9 KB). You can change the window size via the first room's creation code. At 200x150, pressing SPACE appears to make no difference. But with a window size of 400x300, you can suddenly see the problems caused by switching between default rendering and surface rendering. Hope that helps!
 
Last edited:

Stubbjax

Member
surface_resize(application_surface, window_get_width(), window_get_height());
I already resize the application surface at the start of my game. Actually, it would be good to have a solution that is backwards compatible with previous versions of Game Maker (~8.0) as well.
 
A

Aura

Guest
I guess you're having issues with scaling up the surface which results in the bad quality of the background.

If that is the issue, I'd suggest clearing the surface upon change in the size of the window and redrawing everything again.

Create:
Code:
cur_w = window_get_width();
cur_h = window_get_height();
Draw:
Code:
var w, h;
w = window_get_width();
h = window_get_height();
if (cur_w != w || cur_h != h)
{
   surface_resize(surf, w, h);
   surface_set_target(surf);
   draw_clear_alpha(c_black, 0);
   //Draw the background scaled according to the window size
   surface_reset_target();
   cur_w = w;
   cur_h = h;
}
 

Stubbjax

Member
Have you tried adapting the port dimensions?
Are you sure the surface keeps the correct size?
The view port dimensions are set to the window size upon game start. The surface size is correct and does not change.

I guess you're having issues with scaling up the surface which results in the bad quality of the background.

If that is the issue, I'd suggest clearing the surface upon change in the size of the window and redrawing everything again.
The size of the surface is set at game start and does not change; only the game window and thus view ports are variable. I might try to create a diagram to help explain the problem better.
 

jo-thijs

Member
Uhm, if the window dimensions change, then the application surface must be resized.
It is not sufficient to set it at game start if you change the window dimensions afterwards.

Also, you just said you only set the view ports at game start, but then you say they are variable.
Which of it is it?
 

Stubbjax

Member
Uhm, if the window dimensions change, then the application surface must be resized.
It is not sufficient to set it at game start if you change the window dimensions afterwards.

Also, you just said you only set the view ports at game start, but then you say they are variable.
Which of it is it?
Sorry, I guess I was not really clear there. When I said they are variable, I mean they are the only components that could potentially have different values based on the display size at the start of the game.

Below are components that are initialised once at the start of the game and never changed again:

view_wview
view_yview
view_wport
view_hport
window size
surface size
 

jo-thijs

Member
Oops, I didn't read your original post well enough.
Why are you drawing your background to a surface?
And why are you keeping that surface at the dimensions 200x150?
It's obvious that it will give those problems then.

The code Aura provided will solve this issue,
but I'm feeling like you don't even need the extra surface.
Do you?
 

Stubbjax

Member
Oops, I didn't read your original post well enough.
Why are you drawing your background to a surface?
There are a lot of dynamic elements drawn onto the background that are better off batched into a single draw call.

And why are you keeping that surface at the dimensions 200x150?
It's obvious that it will give those problems then.

The code Aura provided will solve this issue,
but I'm feeling like you don't even need the extra surface.
Do you?
Resizing the surface would have no effect because everything is still drawn at the same size. It is akin to expanding the canvas on an image, but not expanding the image itself. As I said, everything is scaled by the view port.

I feel like I'm going to have to create an example of this.
 

jo-thijs

Member
The size of your surface is 200x150?
Then it will only keep track of 200x150 pixels,
whereas you need to keep track of 400x300 pixels.

Just try giving the surface a width of 400 pixels and a height of 300 pixels.
 

Stubbjax

Member
The size of your surface is 200x150?
Then it will only keep track of 200x150 pixels,
whereas you need to keep track of 400x300 pixels.

Just try giving the surface a width of 400 pixels and a height of 300 pixels.
Yeah, if it was that simple I would not have created an entire topic about it. As I stated above, simply resizing/expanding the surface will not work. It would merely expand the canvas, with the same data being drawn to it. I suggest reading the OP again, although if you already did twice, maybe I have not explained my problem correctly...
 

jo-thijs

Member
Ok, your problem is this, right?

You have a view of 200 x 150, which doesn't change.
You you now want to draw a bunch of objects to a surface, using their draw event.
When you do this however, everything in view, will be placed inside a 200 x 150 area on the surface,
which causes quality to be lost.

Now, you want to know what the easiest way is to have them drawn to the surface so that coordinates are doubled and the quality is maintained.

EDIT:
You could easily do this through shaders.
You would just simply multiply every vertex with 2 in the vertex shader before passing it to the fragment shader.
However, this has the disadvantage that not every device supports shaders.
I'll look if there is a more convenient way.

EDIT:
I've never tried this out, but you could try using matrix_get and matrix_set to have things scaled like that.
 
Last edited:

Stubbjax

Member
That sounds like a valid solution, although as you said, compatibility issues could arise. I think I may be incorrectly using views or resizing the window, and am wondering whether there is a better or more efficient way of implementing this functionality to allow for clean surface usage. For this reason, I believe it would be beneficial to find a solution that would work in prior versions of Game Maker as well.

I have whipped up a quick example that demonstrates the exact problem I am experiencing which can be found here (Surface.gmk - 33.9 KB). You can change the window size via the first room's creation code. At 200x150, pressing SPACE appears to make no difference. But with a window size of 400x300, you can suddenly see the problems caused by switching between default rendering and surface rendering. Hope that helps!
 

jo-thijs

Member
I just tested out the matrix solution I suggested, but I couldn't get it to work.
I apparently don't understand when those marices are used.

I can't really find an other easy solution,
so you might just need to program everything drawing scaled to the surface yourself, I'm afraid.
 

Stubbjax

Member
Ah well, thanks for trying. That's the one thing I was really wanting to avoid. It's easy enough to set up by simply changing the view size to the display size (rather than set to room size), but I would effectively have to multiply every scale, position, speed and time to accomodate the increased size. And not only that, but it could lead to in-game events occurring differently based on the screen size, which is certainly not ideal.
 

jo-thijs

Member
Speeds don't need to be changed,
you only need to use other draw events, that use doubled coordinates.
You could put those events in a user defined event.
 

Stubbjax

Member
Why would the speeds not need to be changed if I'm effectively doubling the space? And needing to use alternate draw methods for different window sizes would require a lot of duplicated code that is incredibly tedious and simply bad programming practice.
 

jo-thijs

Member
Then the space would need to be doubled, but you don't instantly need to double the space.

The amount of duplicate code could be reletively small.
You don't even really need any duplicate code, as you can have a global variable that keeps track of the surface scale
and have the draw events of the objects that are drawn to the surface draw with respect to this scale.
You can also use inheritance to try and really minimize the amount of code you have to write.
 

Stubbjax

Member
I am aware that I can go and multiply or divide absolutely everything by a scale value, but as you can probably imagine, that would be incredibly tedious - particularly for a large game. I am looking for a solution that does not involve doing that (thus my reason for creating this topic in the first place).
 

jo-thijs

Member
With good use of scripts and inheritance, I don't think it would be too tedious.
How many objects are you drawing to the surface anyway?

You can also use the shader trick and tell users that don't support shaders how to get it working.

You could also try to trick GameMaker into drawing everything to an invisible view,
but I don't know how well that would work and it would make an awkward first frame,
so that's not a good solution.

I'm looking into other solutions, but so far I've got nothing else.

EDIT:

Actually, could you try this?
Using view_surface_id to have a view drawn to a surface instead of the screen.
Have only 1 object draw to it in the begin draw event (if the surface is resized or doesn't exist).
Then have the view somehow disabled for the next draw events.
You can then use this surface in the normal draw event.

EDIT:

Perhaps you can disable the next draw events by controlling the variables visible.

EDIT:

The visible trick works.
 
Last edited:

Stubbjax

Member
Wouldn't assigning the view_surface_id effectively copy everything that is displayed within the view onto the surface, which would then be drawn instead of the view? If I understand correctly and that is indeed the case, then it's not really a viable option. There are often moving elements within the view that need to be updated/drawn every frame (unlike the surface, which is not redrawn so frequently).
 

jo-thijs

Member
Well, nothing would be drawn to the view anymore, but directly to the view_surface_id surface.
It doesn't get drawn to the screen.

You can control which things are drawn to the surface by using view_current
or by using my previous suggestion of using visible.
 
Top