Asset - Extension AsyncImage - Asynchronously Load Png Files

Mystborn

Member
Hello everyone!

This extension allows GM applications to load png files in the background without blocking the main thread.

Typically this will be useful for loading sprites/other images during a loading screen.

Marketplace

Features:
  • Free!
  • Asynchronously load png images.
  • Open Source! See the code over on Github
  • Currently Supported: Here and Discord
In order to use the library, go ahead and import the AsyncImage.extension.gmx file. If you'd like an example, go ahead and import everything.

Edit: Once I get some free time, I'll fully document this project as I usually do. This was just a fun project to show a relatively practical use of this tutorial, but if you haven't watched it, the code might be confusing.

For now, I'll just outline the data returned in the async_load map:

li_load_image_async:
  • "type" : "image_loaded"
  • "handle" : The value returned by li_load_image_async. Used to determine which image was loaded.
  • "image" : The id of the loaded image, used in further operations.
  • "error" : 0 if there was no error. Otherwise an error was thrown. You can get the error message via li_get_error_message.
li_load_image_data_async:
  • "type" : "buffer_loaded"
  • "buffer" : The id of the buffer that was loaded.
  • "image" : The id of the image that was loaded.
  • "handle" : The value that was returned by li_load_image_data_async.

If you have any questions, feature requests, or other feedback, please feel free to post it!
 
Last edited:

kupo15

Member
Hello everyone!

This extension allows GM applications to load png files in the background without blocking the main thread.
Very cool, can't wait to try it out after work! I can't check it out until I get home but does this also have unload sprite functionality as well? Is this for external sprite (sprite_add/delete) and/or tex_flush and tex_prefetch?
 

Mystborn

Member
Very cool, can't wait to try it out after work! I can't check it out until I get home but does this also have unload sprite functionality as well? Is this for external sprite (sprite_add/delete) and/or tex_flush and tex_prefetch?
The extension doesn't do any sprite creation, it just loads a png file, decodes it, then loads the data into a buffer. You'll have to turn that into a sprite yourself, at which point you can also easily unload the sprite. The example packaged with it shows how to create the sprite.

In theory, you just create a surface that's the same size as the image, load the buffer into the surface (buffer_set_surface), and create a sprite using that surface (sprite_create_from_surface). Or if you needed a really temporary image, you could just use the surface, but it will probably disappear if the screen loses focus, so it's not the best solution.

Unfortunately, I was having trouble with buffer_set_surface, so instead in the example, I just draw each pixel to the surface individually, but if you can get the function to work you should absolutely use it.
 

kupo15

Member
The extension doesn't do any sprite creation, it just loads a png file, decodes it, then loads the data into a buffer. You'll have to turn that into a sprite yourself, at which point you can also easily unload the sprite. The example packaged with it shows how to create the sprite.
Ok got it. I asked about the deletion process because that is something else that also causes a stutter that would be great to be handled by a second thread as to not interrupt gameplay during the unloading process. I still haven't had a chance to check this out yet (which I will do tonight) so maybe it'll be easy for me to take what you've done so far and figure out how to use the second thread to handle that if that functionality isn't already there. I'll ask more functionality questions if I have any after I get a chance to use it and see what its capable of doing.

I'll look to see if I can help with figuring out the buffer_set_surface issue but I'm still not that familiar with buffers. Perhaps @Fel666 would be able to figure out the issue?
 

GMWolf

aka fel666
I'll look to see if I can help with figuring out the buffer_set_surface issue but I'm still not that familiar with buffers. Perhaps @Fel666 would be able to figure out the issue?
Nah, buffer_set_surface is bugged or something. Never found a solution myself.
I settled on drawing it per pixel to a surface before getting a sprite from it.
Other solutions include using a shader and passing an array to it.
 

kupo15

Member
Nah, buffer_set_surface is bugged or something. Never found a solution myself.
I settled on drawing it per pixel to a surface before getting a sprite from it.
Other solutions include using a shader and passing an array to it.
Ah ok, I wonder if it has been filed as a bug yet. Is buffer_set_surface a faster process than drawing it or just more convenient like the prefetch functions being more convenient?
 

trg601

Member
Super cool extension!

I was able to use buffer_set_surface, but I noticed the colors were in the wrong order. I wonder if it is possible to correct the color order for GM inside the DLL?
 
Last edited:

Mystborn

Member
Awesome! How were the colors in the wrong order? Were the pixels in the wrong order, or were the rbga values in the wrong order? Gamemaker surfaces are supposed to be in BGRA format for some reason, so that's how the buffer is filled. If that's incorrect, it's an easy fix.
 

Bingdom

Googledom
Hey, this is a cool project you got going on here.

Maybe I'm blind, but is there a way to get some sort of id? The problem I'm having is being unable to differentiate between different buffers. Let's say I wanted to load up a walk animation. How would I be able to know which one is frame 1, 2, etc? I'm saying this because I'm assuming that not all resources will be loaded in order.

I ran a benchmark to see if there were any benefits to doing this.
Code:
100x test
sprite is 192x192
buffer size is 192x192x4
not using sprite_delete, but tests are independent (so no unfair memory disadvantages)

sprite_add
447124
397834
405370

(buffer random)
sprite_add_from_surface
fill surface with loops (using scripts from OP)
9918730
10651272
10299068

(buffer random)
buffer_set_surface
sprite_set_from_surface
546985
536873
560052

Here's the code to try it yourself.
Code:
sur = surface_create(192,192);
spr = -1;
buff=buffer_create(192*192*4,buffer_fixed,1);
buffer_seek(buff,buffer_seek_start,0);
for(var i = 0; i<192; i++) {
    for (var j=0; j<192; j++) {
        buffer_write(buff,buffer_u8,irandom(255));
        buffer_write(buff,buffer_u8,irandom(255));
        buffer_write(buff,buffer_u8,irandom(255));
        buffer_write(buff,buffer_u8,irandom(255));
    }
}

time = get_timer();
repeat(100) {
    //Test 1
    spr = sprite_add("192x192sprite.png",0,false,false,0,0);
   
    //Test 2
    /*
    surface_set_target(sur);
    buffer_seek(buff, buffer_seek_start, 0);
    var alpha = 0;
    for(var h = 0; h < 192; h++) {
        for(var w = 0; w < 192; w++) {
            // Read the colour values in the correct order: BGRA
            var b = buffer_read(buff, buffer_u8);
            var g = buffer_read(buff, buffer_u8);
            var r = buffer_read(buff, buffer_u8);
            var temp_alpha = buffer_read(buff, buffer_u8);
            if(temp_alpha != alpha) {
                alpha = temp_alpha;
                draw_set_alpha(alpha / 255);
            }
            //No reason to draw on blank pixels.
            if(alpha != 0)
                draw_point_colour(w, h, make_colour_rgb(r, g, b));
        }
    }
    surface_reset_target();
    spr = sprite_create_from_surface(sur,0,0,192,192,false,false,0,0);
    */
   
    //Test 3
    /*buffer_set_surface(buff, sur, 0, 0, 0);
    spr = sprite_create_from_surface(sur,0,0,192,192,false,false,0,0);*/
}
show_debug_message(get_timer() - time);
Unfortunately, this isn't a performance gain as much as I hoped it would be (if the benchmark is correct). It's still a cool project though.

I have drawn the sprite and it appears that buffer_set_surface seems to be working on my end (I didn't check the order of colour channels though). I think I remember reading a discussion about it behaving differently on different hardware.
GMS 1.4.1773
 

Mystborn

Member
Hey, this is a cool project you got going on here.

Maybe I'm blind, but is there a way to get some sort of id? The problem I'm having is being unable to differentiate between different buffers. Let's say I wanted to load up a walk animation. How would I be able to know which one is frame 1, 2, etc? I'm saying this because I'm assuming that not all resources will be loaded in order.
<Snip>
Thanks!

I didn't realize how difficult that was until your message. I've created an updated version that adds the thread handle that you get when you call an async function inside of the async_load map now.

So when you call li_load_image_async, save the value that's returned, and compare it against the value returned by async_load[? "handle"] in order to differentiate between images. At this point it's up to you to reorder them.

Those benchmarks are definitely a little unfortunate. I wonder how much time is spent just setting the surface data. Unfortunately, for something like this I don't think it's possible to eek out much performance without being able to access any lower level constructs.

On the other hand, I do appreciate the work you put into testing it!
 
Top