• 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!
  • Hello [name]! Thanks for joining the GMC. Before making any posts in the Tech Support forum, can we suggest you read the forum rules? These are simple guidelines that we ask you to follow so that you can get the best help possible for your issue.

Android sprite_create_from_surface() is broken in July builds 1.4.1767 and 1.99.551

acidemic

Member
After updating to 1.4.1767 and 1.99.551 Early Access (both of them released recently in July) the function sprite_create_from_surface() which I use to create a background image for the pause screen in my game now creates a distorted image when I try to draw that sprite back to the screen. Problem appears only on Android, (Windows 7 and 10 are OK, but I haven't tested other platforms). It's not the scaling problem of my game it's how the sprite is now created. See the examples below.

When user presses the pause/menu button, I create the game screenshot sprite using the sprite_create_from_surface() function and then draw it as a menu background using the black & white shader.

How in works in 1.4.1767 and 1.99.551 builds (broken):


How it worked in the previous build Version 1.4.1763 (correct):
 
Last edited:

MaxRock

Member
In the latest builds of GMS, they disabled scissoring testing of surfaces on mobiles which should give performance boost on some devices.
This means that now when you create a surface, or a sprite/background from a surface (especially when you target mobile platforms), the size of it should be closest to the value of power of 2, for example:
  • sprite = sprite_create_from_surface(application_surface, 0, 0, 512, 512, 0, 0, 0, 0);
  • bgd = background_create_from_surface(application_surface, 120, 30, 256, 256, 0, 0);
Assuming the size of the sprite you create is 1920 x 1080, you need to write it like so:
  • greysprite = sprite_create_from_surface(application_surface, 0, 0, 2048, 2048, 0, 0, 0, 0);
 
Last edited:

acidemic

Member
Thanks for the detailed explanation! I have suspected that this problem is connected with the scissoring test fix but did not figure out the right solution. So as I understand now functions surface_get_width() and surface_get_height() are useless on Android as they still return old 1920 x 1080 instead of new 2048 x 2048 pixels.

Here is the code which I was using in my game to create the background sprite for the pause screen:
Code:
spr_pause = sprite_create_from_surface(application_surface, view_xview,view_yview, surface_get_width(application_surface),surface_get_height(application_surface), 0, 0, 0, 0);
I suppose now I now have to change the code to something similar:
Code:
spr_pause = sprite_create_from_surface(application_surface, view_xview,view_yview, 2048, 2048, 0, 0, 0, 0);
Will try to compile my game for Android taking into consideration the new size of the application surface on Android. Can't do it for now as after updating Android SDK yesterday Android YYC compiler fails to build APK due to some "gradlew" errors, LOL. Trying to figure out how to fix it now.
 

klys

Member
Thanks for the detailed explanation! I have suspected that this problem is connected with the scissoring test fix but did not figure out the right solution. So as I understand now functions surface_get_width() and surface_get_height() are useless on Android as they still return old 1920 x 1080 instead of new 2048 x 2048 pixels.

Here is the code which I was using in my game to create the background sprite for the pause screen:
Code:
spr_pause = sprite_create_from_surface(application_surface, view_xview,view_yview, surface_get_width(application_surface),surface_get_height(application_surface), 0, 0, 0, 0);
I suppose now I now have to change the code to something similar:
Code:
spr_pause = sprite_create_from_surface(application_surface, view_xview,view_yview, 2048, 2048, 0, 0, 0, 0);
Will try to compile my game for Android taking into consideration the new size of the application surface on Android. Can't do it for now as after updating Android SDK yesterday Android YYC compiler fails to build APK due to some "gradlew" errors, LOL. Trying to figure out how to fix it now.
The gradlew errors and sometimes solve as following:

* Cleaning cache
* Restarting Game Maker Studio
* Checking Internet Connection (Gradlew download on demands things)
* Updating Android SDK or NDK (Check requires SDK)
 

Jack S

Member
In the latest builds of GMS, they disabled scissoring testing of surfaces on mobiles which should give performance boost on some devices.
This means that now when you create a surface, or a sprite/background from a surface (especially when you target mobile platforms), the size of it should be closest to the value of power of 2, for example:
  • sprite = sprite_create_from_surface(application_surface, 0, 0, 512, 512, 0, 0, 0, 0);
  • bgd = background_create_from_surface(application_surface, 120, 30, 256, 256, 0, 0);
Assuming the size of the sprite you create is 1920 x 1080, you need to write it like so:
  • greysprite = sprite_create_from_surface(application_surface, 0, 0, 2048, 2048, 0, 0, 0, 0);
So .... I have a very similar issue with spite_add() ... they are all being distorted and showing up oddly only on Android. Pretty much the exact same visual result as above. Would I be looking at needing to redo all the sprites / canvases for loading into power of 2 for all my external sprites?
 

MaxRock

Member
So .... I have a very similar issue with spite_add() ... they are all being distorted and showing up oddly only on Android. Pretty much the exact same visual result as above. Would I be looking at needing to redo all the sprites / canvases for loading into power of 2 for all my external sprites?
I personally don't use the sprite_add() function in my projects, but that may be it in your case, because from what I remember GMS create a separate new texture page for each single loaded/created dynamic graphical resource, and the texture pages size must be always power of 2.
 

acidemic

Member
Looks as if I managed to fix the functionality of the sprite_create_from_surface() in my game which has native resolution of 1920x1080 by changing to
Code:
spr_pause = sprite_create_from_surface(pause_surface, 0,0, 2048, 2048, 0, 0, 0, 0);
Important that the "pause_surface", to which I draw stuff from "application_surface" in order to make it blurred and desaturated must be also same size.. ie:
Code:
pause_surface = surface_create(2048,2048);
 
This means that now when you create a surface, or a sprite/background from a surface (especially when you target mobile platforms), the size of it should be closest to the value of power of 2
Not really. The runner has always generated power-of-2 textures under the hood. What we are seeing in this version is actually a bug.
I have done thorough testing and can confirm that this is 100% correct information:

Before this update, the way the runner handled dynamic sprite generation (this includes web/storage-based asynchronous sprite loading and creating sprites from surfaces) was the following:
1. Create a texture that is a power-of-2 size in both width and height, where the dimensions are the first powers of 2 that are bigger than the dimensions of the original image.
For example, if we were to load rarestPepe.png of size 230x320 pixels, the GM runner would first create in video memory a texture of dimensions 256x512.
2. Render the image onto the texture at its native size.
In our example, this would render a 230x320 image over a 256x512 texture.
3. Set the newly created texture's meta-data to reflect how much of the sprite actually occupies the texture. The functions texture_get_width and texture_get_height actually return that meta-data.
In the above case, texture_get_width would return 230/256 = 0.8984375 and texture_get_height would return 320/512 = 0.625. The runner actually reads this data automatically when drawing sprites, in order to know where to stop when mapping texture coordinates to primitives (as also evidenced by the docs for the two functions), i.e. in this case, when drawing the full sprite (NOT with draw_sprite_part, which TRIMS the sprite), the runner will draw a quad and map the sprite's texture to it, going on the horizontal axis from texels 0 through 0.8984375 (maybe this number gets truncated a bit), and on the vertical axis from texels 0 through 0.625.

Now that we've got the Game Maker Runner Texture Management Theory™ out of the way, let's get to what's changed:
After the update, the way the runner handles creating dynamic sprites has changed just a little bit (and not everywhere necessary):
1. [this step is 100% identical to the one in the previous version]
2. Render the image onto the texture scaled to fit the entire texture.
This is the 1st change. In order to not use scissoring anymore on any dynamic textures (dynamic sprites are that too, save for being volatile), they are now filling the entire texture. By doing this, the textures never need to be "snipped", since the entire surface of the textures is useful data. This also does not use any additional memory, as blank pixels in a texture still take up space.
3. This is where they forgot to do something. Normally, since now they are always using the entire effective pixel size of a texture for dynamically-allocated textures, when setting the texture's useful width and useful height meta data, they should set the two properties to 1.0 and 1.0 respectively. However, they aren't. They forgot to add this condition for the Android runner, which still computes the proportions in the same exact way as presented in point 3. above. I have confirmed this by using texture_get_width/height on a dynamically-loaded 320x320 sprite on Android. To everyone's "surprise", the functions are reporting 0.625 and 0.625 respectively, which accounts to a sprite of 320x320 occupying a texture of 512x512 (when in fact, the sprite occupies the entirety of the 512x512 texture here). A simple oversight, essentially just forgetting to set two properties to 1, 1 instead of performing a certain calculation in the Android runner is what breaks the dynamic sprites.

It is excusable for them to forget this, as it does not affect the pre-baked sprites. These sprites all get written into texture atlases ("texture pages") but then get their very own "textures" with widths and heights always set to 1, which actually point to regions in the texture pages. This makes them draw correctly all the time, since the runner always maps them from 0, 0 to 1, 1. (In case you were wondering why this issue does not affect pre-compiled sprites).

tl;dr This is a bug where they forgot to set two values to 1, 1 in the Android runner when loading sprites from external sources (even VRAM).

Another important point to understand is related to:
So as I understand now functions surface_get_width() and surface_get_height() are useless on Android as they still return old 1920 x 1080 instead of new 2048 x 2048 pixels.
They are, in fact, not useless. This also goes back to how the GM runner actually draws stuff to the screen. The sizes that something_get_width, something_get_height (sprites, backgrounds, surfaces) return are, in fact, the original image's size, NOT the one stored in memory!
When you load up a 24x24 sprite from an external source (or create a surface, but let's stick to sprites here in order to remain somewhat consistent. The mechanism applies for all dynamic textures anyway.), it gets loaded up into a 32x32 texture, BUT when you draw it on the screen, you know you want a 24x24 image, not some other size that the computer chose for you. This is why when you create a sprite/surface/background asset, GM keeps the original size in memory so that it knows what sizes to use when drawing the polygons onto the screen.
As such, when drawing your shiny 24x24 sprite, GM will draw 4 vertices where the top-left one is at the current x and y position, and the bottom-right one is at the current x and y position off-set by 24 pixels on each axis, and the texel coordinate is 0.75, 0.75 (24 divided by 32). This, of course, should be 1.0, 1.0 on Android in the new version of the runner, but because of the bug, it is still 0.75, 0.75, making the sprite appear blown up. But, although the sprite is magnified and cropped as a result of the wrong texel coordinates set when loading the sprite under the new version of the runner, if you check the space is occupies on the screen, you will notice the pixel count is in fact the correct 24x24 pixels (or whatever the sprite size is in your case).
The only thing that is wrong here is setting the texture "width" and "height" meta-data correctly when loading dynamic sprites on Android in the new runner. Everything else works exactly as it should.

Also: The docs for texture_get_width and texture_get_height state that they return the "width/height of the texture page" -- this is rather confusing. A better statement would be that they return the proportion of texels actually being used in the given texture (which is precisely what these functions return), since the real width or height of said texture is actually a power of 2, not a sub-unitary number.

For when staff reads this: Hi guys. This is an extremely simple fix. Literally just replacing two math calls to just "= 1;". Please make this fix part of the next EA. Thank you!
 
D

Dennis Lenz

Guest
Sorry for bumping and old thread but i did have similar problems (on OSX, not on Windows). Thus i downgraded to v1.4.1763 - that worked.
Now Steam did just upgrade the GM:S again AUTOMATICALLY, thus i am having the problem again. Sigh!! And it looks like i can't downgrade this time since older versions and betas aren't available anymore. This is unbelievable.

So has this been fixed yet? In any version of GM:S? v1.4.1804 or v1.4.9999? Or do i need to learn V2 of GM:S?
This is really annoying and one would think that's a rather small bug to fix.

Any ideas?
 
Top