Native GML Multithreading in GameMaker - No DLLs/Networking

M

MishMash

Guest
Hey, had a bit of inspiration today and made a quick little test to see if it were possible to create a simple multithreading system in GameMaker. I know it's a feature that many people want, and it took me about 10 minutes to get this working:

Goals and Features:
  • Multithreading and concurrency of GML code itself without the need to use an external DLL or Networking, as these rely on moving data around and incur additional overhead.
  • Systems for managing threads, checking completion status etc;
  • Non-blocking modification of any GM variable/datastructure
  • Access to a good number of GM functions
  • Simple integration and utilisation. Currently functions as a replacement for script_execute called script_execute_async which can return a thread_id that could be used in other functions.
  • Goal is to allow the offloading of heavy tasks to separate threads as to not block the main thread. Or improve quality of life by having routines such as level generation not impact performance
  • Ability to set maximum number of cores an application can use to avoid consuming too many system resources

Video:
This video is a discussion and pratical example of the system

Method:
By injecting a small amount of C++ code into the YYGML.h header which gets included in every GM YYC project, and a few replacement C++ scripts for generated GM script files, we can achieve this system with relative ease, taking advantage of the simplicity and clean interface of C++ 11 threads.

Pretty excited for the potential and simplicity of this. Naturally, GM doesn't enforce any concurrent code design patterns such as locks, so it would be entirely up to the user to ensure their code was thread-safe. Though, given the nature of threading, most processing related things just work, though you naturally will get bugs if two things try to use/modify the same thing. It is however ideal for Embarrassingly Parallel problems where the data being modified in each instance is completely independent between threads.

In order to have a good idea of what is going on, I recommend watching the video.

Disclaimer:
As this involves modifying generated C++ code, it doesn't fall under any support for GM so if you do try any of this out, do so at your own risk.

I'd like to try and start a discussion about the utilisation of this, and whether it is something people would consider using, or ways in which this system could be designed differently to cover a better use case. Note that given how this is achieved, there is a user practicality limit, so I would want to limit the system functions such that:
- Generated GM script files do not need to change once compiled once. (No change as a reuslt of unrelated changes elsewhere)
- Minimal modifications to shared GM header, which do not have any impact on ordinary execution.
 
I

icuurd12b42

Guest
Interesting hack. the issue I see is reading data you write inside the script from outside, like your code is doing reading of the grid in the draw is not safe. if the 2 thread are trying to access the same data there will be a problem...

Thread synchronisation would be an issue long story short. but it could be resolved using old school thread read sync trick since you dont have thread locking/blocking mechanism

global.ready = 0;
global.data = 0;
in thread
//do struff to data
repeat(10000)
{
global.data+=1;
}
global.ready = 1;


in regular code
if(global.ready !=0) (may be 1 or garbage if thread is mid write), either way it's ready
{
var thereadyData = global.data;
}

I'm not sure by today's compilers if there is precautions in the compiled code to prevent 2 threads accessing the same memory at the same time that would cause an exception. we did not have thread safe stuff in the old days and that's how we did it... but this method may no work if the compiler has thread safety mechanisms. I don't know is what I'm saying.

@Mike
 
Last edited by a moderator:
M

MishMash

Guest
@icuurd12b42 From my experience with C++ 11, with regard to actual data modification, or modifying of overlapping values, i've never actually had any objective issue. No crashes, just whichever thread happens to run second will overwrite any previous result. Cache's are also very robust these days. C/C++ are also very raw and they tend to let you do whatever, it's just up to you to make algorithmic guarantees about the safety of your code. For example, as you suggested, just imlpementing a ready flag to serve as a lock.
I think a potential use case here is simply for when threads all do their own things and are quite disjoint. i.e. there is no overlapping data, but it is simply a way to speed up processes, or offload them.

The potential for crashes would come more from thread abuse, i.e. having one thread modify the contents of a list or remove/add elements whilst another is iterating through. Though all of these things can be avoided if you organise your code well and exercise discipline.

In my project for example, chunk generation is contained within a function, and that function only writes out data to a single grid. This grid cannot be used by anything else in the game until it is fully generated, at which point a generated flag is set to true. The only other data is read-only input data, so in that case, it should be okay.

I'm not sure by today's compilers if there is precautions in the compiled code to prevent 2 threads accessing the same memory at the same time that would cause an exception. we did not have thread safe stuff in the old days and that's how we did it... but this method may no work if the compiler has thread safety mechanisms. I don't know is what I'm saying.
At university, I had a High Performance Computing module which had heavy focus on making code run as fast as possible, using a variety of techniques, mostly OpenMP which was a wrapper to simplify the process of distributing code across different threads automatically. In practise, I have also written applications using "unsafe" multithreading which I know myself to be okay, and generally, there don't seem to be exceptions, it just tries to do what it thinks, so if there is a value, it'll return the value it finds at that moment, however it can mean if multiple threads are trying to increment a value, the end result will be nonsense as the value stored in registers will be different for each and they'll end up overwriting each other. Though I don't think modern C++ actually crashes, there may be a flag set, or it may depend on the datastructure.
 
L

Lonewolff

Guest
I'm not sure by today's compilers if there is precautions in the compiled code to prevent 2 threads accessing the same memory at the same time that would cause an exception.
Yeah, if I remember rightly, there is still issues around this if you don't lock the variables.

Things get funky at that kind of level as you are playing with things in the CPU cache and not the RAM itself (if my understanding is correct). So, while you think you have changed a variable to something else, the other thread can still be playing with another different value.

I had all sorts of issues with this a while back and the only way I got around it was to lock the variables. Had no crashes or anything, just not the results I was expecting.
 
M

MishMash

Guest
Just did a bit of extra reading and apparently (as I thought) the program wont crash, but you will just get race conditions and garbage values as expected if you try to write to the same value at the same time. People only talk about safety with regard to expectation of how something works, rather than how it actually works.

So, as long as you are careful with when/how you modify values, you should be okay. This may mean if you are trying to do a sum in parallel, each thread maintains its own sum value, and then once both threads have finished, you sum up those sums in parallel.

Though, I will throw in a tutorial on how to create a lock/concurrent programming techniques in general, as it'll likely be useful and not too difficult to achieve in GM itself.

(The same may not be true for mobile platforms, but currently, I'm only interested in desktop applications).
 
L

Lonewolff

Guest
Just did a bit of extra reading and apparently (as I thought) the program wont crash, but you will just get race conditions and garbage values as expected if you try to write to the same value at the same time. People only talk about safety with regard to expectation of how something works, rather than how it actually works.

So, as long as you are careful with when/how you modify values, you should be okay. This may mean if you are trying to do a sum in parallel, each thread maintains its own sum value, and then once both threads have finished, you sum up those sums in parallel.

(The same may not be true for mobile platforms, but currently, I'm only interested in desktop applications).
Yep, pretty much as I found in practise :)
 
I

icuurd12b42

Guest
from my experience. as long as one thread is doing all the writing and the other is doing only reading, the only issue is that the reader may get garbage data upon reading, hence the flag to synchronize, which you dont care if the flag is truely set to 1 or garbage; mid write
 

GMWolf

aka fel666
A simple lock will not be enough: the lock itself could get race conditions.
I dont 100% remember my OS course, but there was talk of using Semaphores to solve the problem.
They could easily be written in c++ (or use the standard library) and wrapped for use in GML.

The important thing is to make sure that operations on the lock are atomic. A simple boolean variable will not be atomic
Code:
if (!lock)
   lock = true;
   dostuff();
   lock = false;
}
A checks lock. => open
B checks lock => open
A closes lock and does stuff
B closes lock and does stuff.
With multitreading, this can totally occur.
 
L

Lonewolff

Guest
A simple lock will not be enough: the lock itself could get race conditions.
I dont 100% remember my OS course, but there was talk of using Semaphores to solve the problem.
They could easily be written in c++ (or use the standard library) and wrapped for use in GML.
Yeah, I use semaphore variables quite a bit.
 
M

MishMash

Guest
A simple lock will not be enough: the lock itself could get race conditions.
I dont 100% remember my OS course, but there was talk of using Semaphores to solve the problem.
They could easily be written in c++ (or use the standard library) and wrapped for use in GML.

The important thing is to make sure that operations on the lock are atomic. A simple boolean variable will not be atomic
Code:
if (!lock)
   lock = true;
   dostuff();
   lock = false;
}
A checks lock. => open
B checks lock => open
A closes lock and does stuff
B closes lock and does stuff.
With multitreading, this can totally occur.
I can't actually remember what they were formally called, but in my Concurrent Computing/operating systems unit we had something similar, though I remember the dining philosphers problem which involved a similar resource allocation problem, and in this case, the solution was to avoid a deadlock, however the solution applies. In this case, you introduced a third party (a waiter) who would see who had expressed interest in the resources (forks on the table) and then allocated them. So no thread took responsibility itself for wanting a variable, it would first express interest in a variable, and then wait until it had that variable acquired.

For example (PSEUDO CODE):
Code:
var lock_id = lock("some_var"); // Express interest in some variable
while(true){ // Block/keep trying
    if( global_lock("some_var") == lock_id ){ // If the global system has allocated that variable to us
          // do stuff
          release_lock("some_var", lock_id); // Say we are no longer interested in that variable
          break;
    }
}
Since none of the threads control how global lock is allocated directly, there is no overlapping of locking. (As a practical solution, one thread would be given responsibility of being the resource manager, lets say thread index 0, if you didn't want to allocate a specific separate thread for managing resources.)
 

GMWolf

aka fel666
I can't actually remember what they were formally called, but in my Concurrent Computing/operating systems unit we had something similar
I remember semaphores and mutex.

I think what you describe is known as a spin lock, but i'm not sure.

I still have my OS notes from last year, I'll have to read through them again.
 
I

icuurd12b42

Guest
OK, now make some code where the movement is in a thread and the draw in another :)
 
M

MishMash

Guest
OK, now make some code where the movement is in a thread and the draw in another :)
Image:
So, given that the while loop runs stupidly fast I initially used the _pressed variant, and given GM resets the keyboard state/polls for it on its own accord, keyboard_clear needs to be specifically called to create the same behaviour.

However, I think it is important to determine a list of practical/non practical use cases for a system like this. As an initial guess, i'd imagine a good example of things NOT to mess with would be:
- Any internal GM system that maintains its own internal state and has its own processing , Audio, images, networking, etc; OR any GM system that has background code running between events that the user does not see. This includes collisions.
- Anything related to the graphics pipeline and/or rendering to the screen. DirectX 9 does not support multi-threading natively.

Though all of these caveats would be the same for any engine. Though it'll be important to outline any use case outside of data manipulation, and without employing good concurrent principles, could lead to side effects within your code. For me personally, i'd simply limit this use case to heavy lifting scripts that tend to take a read-only/simple input and return an output, or fill in some independent data.

Good examples of use cases:
- Pathfinding (Path calculations on static grid)
- Simulations, such as fog of war, fluid dynamics.
- AI thinking, for games with more complex AI such as chess, so that the player can continue interacting with the game or doing other things whilst the AI is making its move
- Compression/conversion of datastructures for networking (so that if you have a particularly large clump of data that needs sending, you can build it into a buffer without blocking up your whole game)
--- Similarly, this can apply for saving and loading (wouldn't recommend actually calling file IO from a different thread, but you could build the buffers in a different thread and then simply save them from the main, as the saving is generally the fast part.
- World/Level generation -- This would again allow it to avoid having an impact on player performance and could similarly allow you to start the game earlier whilst continuing to generate things afterwards.
- distributing similar algorithmic tasks (that could have an impact on performance) such as sorting.

Generally, games wont have loads of threads. If your game is running so slowly that it needs threads for common game features like enemies, then there is a problem with the design. However, the main benefit I see is more to do with offloading infrequent tasks, and just allowing a game to utilise more of a players machine. This can be especially relevant on weaker machines which may have multiple cores, but a low per-core clock speed. Another example in my project is that in order to make pathfinding fast, I had to build a pathfinding request system where objects submit requests for paths and a general pathing control object processes paths continuously, though limiting the number of operations per frame as to avoid lagging the game. An object will then wait (normally only a few frames) to get its path.
 
L

Lonewolff

Guest
- Anything related to the graphics pipeline and/or rendering to the screen. DirectX 9 does not support multi-threading natively.
While DX11 on GMS is single threaded, I have found that it is trivial to turn on multi-threading on the graphics pipeline and play around with it (if you are particularly careful).
 

GMWolf

aka fel666
While DX11 on GMS is single threaded, I have found that it is trivial to turn on multi-threading on the graphics pipeline and play around with it (if you are particularly careful).
I though dx11 was inherently single threaded, and dx12 was needed for paralelle draw calls.
 
L

Lonewolff

Guest
I though dx11 was inherently single threaded, and dx12 was needed for paralelle draw calls.
Even DX9 had the capability to be multi-threaded, it just wasn't very good.

DX11 handles MT much better, but for absolute control DX12 is the go (but massive overkill for most people - myself included).
 
M

MishMash

Guest
wait, x+=10... the instance context is maintained?
Yessir, given how YYC works, "pSelf" and "pOther", the pointers to the GM instances, are always passed into any function/script that gets called, giving you access to the context of the instances. Whatever code you can run in a normal GM script, you can get to run in a seperate thread. (Whether that code then works safely is a different matter, as i expect certain functions to behave oddly, such as collision check ones may come out wonky as those will depend on some processing being done by the main thread, and having that run whilst the underlying collision map is being updated might break something)

So basically any script in GM has two implicit arguments that are self and other, and all non "var" declared local variables are assumed to belong to the "self" instance.
 
A

Ampersand

Guest
It is always fascinating to learn bits like this about how the IDE and the compiler itself actually work... Wish I could contribute anything to the discussion, but what I do understand is only giving me vivid flashbacks of an Intro to Operating Systems class I sat in on -- it's really making me wish I had dove in rather than shying away from this sort of stuff.

One has to feel a bit like a frontiersman exploring ideas like this!
 
I

icuurd12b42

Guest
collision functions should work, at least if you dont have the fast collision system enabled as they basically rely on the position scale and angle and stuff... Hmm. I have a step emulator I wrote almost a decade ago which basically uses event_perform to simulate stepping in a loop. I wonder. basically

loop{
with(all) {xprevious = x; yprevious = y; event_perform(step, step begin)}
with(all) event_perform(step, step)
with(all) with(all) if(intance_place(other.id...)) {event perform twice, once with this and once with the other involved)}
with(all) event_perform(step, end step)
}

not sure what would happen but it's something you crazy kids can try...
 
M

MishMash

Guest
collision functions should work, at least if you dont have the fast collision system enabled as they basically rely on the position scale and angle and stuff... Hmm. I have a step emulator I wrote almost a decade ago which basically uses event_perform to simulate stepping in a loop. I wonder. basically

loop{
with(all) {xprevious = x; yprevious = y; event_perform(step, step begin)}
with(all) event_perform(step, step)
with(all) with(all) if(intance_place(other.id...)) {event perform twice, once with this and once with the other involved)}
with(all) event_perform(step, end step)
}

not sure what would happen but it's something you crazy kids can try...
I was unsure whether collisions were made faster by some internally updated grid. I know when I wrote my own game engine in the past, the only way I could achieve fast collisions was to maintain a quad-tree which was updated at the start of each step using the bounding boxes, or bounding spheres of instances. This reduced collision complexity by instantly being able to only test against things in the corresponding quadtree sections you needed to test, rather than the whole game world. I imagined GM may be doing something similar internally, and that functions like place_meeting used this to reduce the number of instances needed to test against. (Given that it may need to build this quadtree to fire off certain collision events anyway). I guess the only way to know is to try it out :p

Could probably also get away with creating an event_perform_async function, but I think that's asking to be abused :p

Edit: On a progressive note, i'm going to have a look at finishing up and uploading this first little test tomorrow once i've finished my work for the day. I don't expect it to be practically useful for a bit, though I think time is needed to experiment with things like this. The only long-term intention I want from this is to be able to do include some additional optimisation for my own project nearer to its final release.
 

Tthecreator

Your Creator!
Wow I love this. Great work!
Though I guess this only works on windows YYC?

Anyways, I could see this used mostly for having a script with some intensive process be separate of the normal thread used for drawing.
Like for example you have to generate a world and want to show the user a loading bar that runs at 60 fps.
Previously you had to just put your script in a step event and just hope it wouldn't take longer than 1/60th of a second.
This system would make just implementing some of those things really easy.
Using it in such a way doesn't give you any race conditions or anything. But it only makes life easier instead of giving a big performance benefit.(since the entire task is still in one thread)

This is going to lead to a lot of problems for people who don't know what they are doing.
Therefore, it probably wouldn't be fully safe for commercial projects if one just started out using threads without having dealt with these kind of threading related bugs before.
Still this is extremely useful to those who do know how to use this properly.

I've actually got this game idea and if I can't figure out the maths I'll have to simulate everything instead. In the latter case this would be so useful.
 

kupo15

Member
First off this seems fantastic and exciting! Great stuff!

As an initial guess, i'd imagine a good example of things NOT to mess with would be:
- Any internal GM system that maintains its own internal state and has its own processing , Audio, images, networking, etc; OR any GM system that has background code running between events that the user does not see. This includes collisions.
- Anything related to the graphics pipeline and/or rendering to the screen. DirectX 9 does not support multi-threading natively.
This would be what I would be using this for, graphics mainly. What kind of graphics things are you recommending against with this? I would like to use MT to load new graphics and unload no longer needed graphics in the background to avoid loading screens altogether. Surely this should be an ok thing to do, right? Just make sure not to reference any new graphics until the thread is finished loading everything. And I would imagine unloading in the background is also safe because you wouldn't be referencing anything you aren't using anyway
 
M

MishMash

Guest
First off this seems fantastic and exciting! Great stuff!
This would be what I would be using this for, graphics mainly. What kind of graphics things are you recommending against with this? I would like to use MT to load new graphics and unload no longer needed graphics in the background to avoid loading screens altogether. Surely this should be an ok thing to do, right? Just make sure not to reference any new graphics until the thread is finished loading everything. And I would imagine unloading in the background is also safe because you wouldn't be referencing anything you aren't using anyway
Rendering will break because GM manages batching and other things internally. The reason loading of resources on a separate thread is dodgy is because internally, DirectX builds an object for that graphic and submits it to the GPU. Given that GM manages this, there is no means of using GM's specific systems (i.e. what it does internally) on a separate thread. It might work, it may not, however the important thing to remember is that its not just loading things into memory, its the transfer between CPU and GPU memory + vice versa, and this is managed by the directX pipeline, which in DX9 is primarily single threaded, and calling pipeline functions from another thread (which GM will do) causes a crash.

For resource loading, if you represent your filepath as a URL, you can load sprite asynchronously already: https://docs.yoyogames.com/index.ht...reference/game assets/sprites/sprite_add.html
Many other asynchronous functions already exist in GMS to save/load resources and data for buffers, therefore you are probably better off using those for that sort of purpose.

The main benefit of threading in this context is likely going to be algorithmic improvements. This does include data preperation, for example the building of a vertex buffer could likely be done in a separate thread.
 

kupo15

Member
For resource loading, if you represent your filepath as a URL, you can load sprite asynchronously already: https://docs.yoyogames.com/index.ht...reference/game assets/sprites/sprite_add.html
Many other asynchronous functions already exist in GMS to save/load resources and data for buffers, therefore you are probably better off using those for that sort of purpose.
Wait, so you are saying GM already supports the ability to prefetch sprites into VRAM without the stutter you experience from doing so using the async events? But that it has to be retrieved from a website instead of the included folder or IDE? What if I want to load sprites from the included folder and prefetch them into VRAM and do so without the stutter? Or what if I want to do a texture flush or prefetch assets from the IDE without having a stutter occur? I thought that is what the MT was needed for....
 
M

MishMash

Guest
Wait, so you are saying GM already supports the ability to prefetch sprites into VRAM without the stutter you experience from doing so using the async events? But that it has to be retrieved from a website instead of the included folder or IDE? What if I want to load sprites from the included folder and prefetch them into VRAM and do so without the stutter? Or what if I want to do a texture flush or prefetch assets from the IDE without having a stutter occur? I thought that is what the MT was needed for....
Into VRAM, no. As said before, DirectX 9 is single threaded, and regardless, there is a fixed pipeline communicating between the CPU and GPU. However, upon importing a sprite, it still has to get processed on the CPU first. The loading process of a PNG for example involves converting it into an array of pixels. This is managed internally. Regarding how long it takes things to be sent to the GPU, this depends. Normally, modern GPUs have incredibly large bandwidths (of the order of 200GB/s+) so sending graphics to the GPU should never be too slow, unless you have loads and loads of graphics, at which point a stutter may occur.

Think of it this way, the process is split into two steps:

1) A "sprite" resource is loaded from HDD into CPU RAM
2) When you want to render a sprite, if it is not already loaded onto the GPU, it will be sent from CPU RAM -> GPU VRAM. Once it is in VRAM it can be quickly rendered.

The loading of a sprite only deals with step 1. Step 2 is not necessarily executed until your first render, though this depends on your project settings, and the settings may differ for other sprites.

Whilst the asynchronous functions only work with file requests, I think you can get a sprite to load asynchronously by using file:/// in front of the filename. However it is worth noting that asynchronous functions aren't necessarily multi-threaded, they just make up events in an event loop. It just means that it doesn't stop execution of your current task. (Though GM doesn't provide specific information on this).

I don't want to diverge this thread too much, but sprite_prefetch and sprite_flush only control when the data is sent to the GPU. A stutter from this is unavoidable, because its to do with a pipeline bottleneck in the rendering thread.
 

GMWolf

aka fel666
I don't want to diverge this thread too much, but sprite_prefetch and sprite_flush only control when the data is sent to the GPU. A stutter from this is unavoidable, because its to do with a pipeline bottleneck in the rendering thread.
The key is that stuttering during a loading screen doesn't matter, but stuttering during game play does.
 
M

MishMash

Guest
The key is that stuttering during a loading screen doesn't matter, but stuttering during game play does.
Yeah I know, but kupo seemed to know about the existence of this functionality and was talking about how dynamically loading/unloading of resources mid-game was causing problems.
 

kupo15

Member
Sorry if this is derailing it, but I think this still ties back into learning the limitations of MT and at least can held debunk any myths other users might have with it like I do

1) A "sprite" resource is loaded from HDD into CPU RAM
2) When you want to render a sprite, if it is not already loaded onto the GPU, it will be sent from CPU RAM -> GPU VRAM. Once it is in VRAM it can be quickly rendered.

The loading of a sprite only deals with step 1. Step 2 is not necessarily executed until your first render, though this depends on your project settings, and the settings may differ for other sprites.
Ok true, things don't have to be sent to the VRAM straight away and the two parts is a good visual. So if loading a sprite into memory is purely a CPU function only, does that mean that this step in the process only could be handled via MT?

A stutter from this is unavoidable, because its to do with a pipeline bottleneck in the rendering thread.
So then how do consoles manage to swap out textures during gameplay without a stutter like Wind Waker for example? Or does WW still stutter during the VRAM process but its so tiny its not noticeable? I thought this was MT at work...

On a side note, are animated loading screens actually disguised stutters? As in its the main thread that is stuttered swapping out resources but the second thread is using the draw event to draw an animated texture that was already loaded into memory instead of what I thought which was using the second thread to swap resources while the main thread remains unstuttered? Kinda like how you showed off you can draw moving the text around while still doing things in the background? Can you play the game on the second thread with resources that are already loaded while you load new resources on the main thread?

EDIT: A better example would be how does Breath of the Wild have zero load screens during the massive overworld yet no stuttering or perceived stuttering? Surely it must be swapping out textures throughout instead of holding all the textures in the overworld at one time
 
Last edited:
M

MishMash

Guest
Sorry if this is derailing it, but I think this still ties back into learning the limitations of MT and at least can held debunk any myths other users might have with it like I do


Ok true, things don't have to be sent to the VRAM straight away and the two parts is a good visual. So if loading a sprite into memory is purely a CPU function only, does that mean that this step in the process only could be handled via MT?


So then how do consoles manage to swap out textures during gameplay without a stutter like Wind Waker for example? Or does WW still stutter during the VRAM process but its so tiny its not noticeable?

On a side note, are animated loading screens actually disguised stutters? As in its the main thread that is stuttered swapping out resources but the second thread is using the draw event to draw an animated texture that was already loaded into memory instead of what I thought which was using the second thread to swap resources while the main thread remains unstuttered? Kinda like how you showed off you can draw moving the text around while still doing things in the background?
To take a slightly different approach, there are many answers to this question:

First of all, different graphics API's have different restrictions on what can/can't happen. DirectX 11 for example does support multi-threading, and DirectX 12 makes multithreaded functionality very accessible (This is part of what contributes to its large speed up over DX11).

Secondly, most games inherently have a number of independently running threads from the get go. Normally, it is common to separate out the simulation thread from the rendering thread. You may also throw in a resource manager thread too. Having a setup like that instantly gives you more flexibility because all of the stages are disjoint. (Whereas in GM, the limitations come from the fact that both simulation and rendering happen in the same thread, so you run into issues when trying to separate the two). So in AAA games, the simulation thread never touches resources, similarly, the resource management thread handles dynamic loading of assets, and the rendering thread would only then be allowed to use a resource once it has been fully loaded.

In my experience, it also seems to take a lot to get the game to stutter from the loading of textures onto the GPU. Generally, it only seems to be that initial load that is really slow (HDD to CPU), but this makes sense in a way, as the interface between a CPU and GPU is 1000x faster than that between a CPU and a HDD. Which is good news, because the loading to CPU should be something we can get around. Though using the multi-threading for this may not work given that sprite_add would need to manipulate some internal GM sprite datastructure, and having two threads mingling with the same data could be messy. So i'd say I don't think this specific Multi-threading will work without a stutter given that the resource management and rendering both share a same thread, and I can't do anything to change that without use of a separate DLL for rendering.

I'd give the async loading thing a try though. I've never done it, but that seems like the best bet.

Edit: Regarding loading screens, normally they will hide stutters, however again, in conventional games, the rendering thread is free to do what it wants and will rarely block. It will simply display the progress being made by a resource management thread. AAA games also are not perfect. GTA V can stutter a fair bit if you are going really fast, Watchdogs was worse for it. Most openworld games end up having quite slow movement however, so they can safely distribute loading evenly as they aren't forced to load 100s of textures in a single frame.
 

kupo15

Member
Secondly, most games inherently have a number of independently running threads from the get go. Normally, it is common to separate out the simulation thread from the rendering thread. You may also throw in a resource manager thread too. Having a setup like that instantly gives you more flexibility because all of the stages are disjoint. (Whereas in GM, the limitations come from the fact that both simulation and rendering happen in the same thread, so you run into issues when trying to separate the two). So in AAA games, the simulation thread never touches resources, similarly, the resource management thread handles dynamic loading of assets, and the rendering thread would only then be allowed to use a resource once it has been fully loaded.
Oh I see, that explains why you said its impossible to avoid a stutter from VRAM loading. Not that its impossible in general but in GM it is because the architecture of how GM's Engine is built prohibits that naively. So that means we will never be able to get to see a true GTA style GMS game without stutters unless you use a DLL. That's the difference between what you are trying to do and the other MT thread I saw that uses a DLL. His application would be more in line with this sort of thing for me then.

In my experience, it also seems to take a lot to get the game to stutter from the loading of textures onto the GPU. Generally, it only seems to be that initial load that is really slow (HDD to CPU), but this makes sense in a way, as the interface between a CPU and GPU is 1000x faster than that between a CPU and a HDD.
And this HDD to CPU only occurs from external files I believe, not if you already include them in the IDE which makes external loading slightly longer than IDE resources unless I'm mistaken. My game is one where it takes a lot so stuttering is unavoidable on GMs native single core design. Luckily my game doesn't require seamless loading, it just would be nice to have. Waiting 3 seconds to load a match (currently) isn't that bad to wait. It might end up being closer to 5-6 when the game is done unless I come up with tricks to cut that down. (one character's sprites alone takes about 2 seconds currently)

Thanks so much for taking the time to explain this to me!
 

GMWolf

aka fel666
So that means we will never be able to get to see a true GTA style GMS game without stutters unless you use a DLL
There is a reason why companies still develop in house engines. Even engines like Unreal still focus a lot on the idea of Artists using them, not programmers.
You would be surpirsed at the stuff you can do when you dont abstract DX or OpenGL away!
 
Last edited:

kupo15

Member
There is a reason why companies still develop in house engines. Even engines like Unreal still focus a lot on the idea of Artists using them, not programmers.
You would be surpirsed at the stuff you can do when you dont abstract DX or OpenGL away!
Ah I see, that makes sense. I didn't know that even Unreal is just as limited in some ways
 

GMWolf

aka fel666
Ah I see, that makes sense. I didn't know that even Unreal is just as limited in some ways
The thing with Unreal is that it *does* allow you to have access to lower level API's. But at that point you are better off with a bespoke solution.

Back on topic though:
How does MT behave with instance and asset creation in GM?
I'm guessing that if you are not careful, to simultaneous calls to ds_*_create could return the same ID?
 
H

HammerOn

Guest
Isn't it against eula's 4.ii term?
It would be fun to play with but if I can't publish something with it...
 
L

Lonewolff

Guest
Your own game is neither "Publisher Property" or a "YYG Platform" which is what is stated in the EULA 4.ii clause.
The exe is the YYG runner. Which is very much "Publisher Property".

Even if it were your own game, as such. You are still not allowed to decompile or reverse engineer it, as it is not your engine.
 

rIKmAN

Member
The exe is the YYG runner. Which is very much "Publisher Property".

Even if it were your own game, as such. You are still not allowed to decompile or reverse engineer it, as it is not your engine.
Yeah fair point re: the runner, can PIX be called a reverse engineering / decompiling tool?

Everything I've seen is to do with profiling, performance tuning and debugging, which isn't really the same thing as what the EULA is talking about (in practical terms) and which is what Mikes article also refers to.

It'd be a bit odd to have a blog article giving a tutorial on how to break the EULA, but as we know the EULA can be a little ambiguous at times.
 
L

Lonewolff

Guest
Yeah fair point re: the runner, can PIX be called a reverse engineering / decompiling tool?
For sure.

You can strip out the compiled shaders, geometry, and all sorts of gear in there.

90% of what you see in the Pix output is the GMS internal 'secrets', very little in the render cycle actually belongs to the code that you type in.
 

rIKmAN

Member
For sure.

You can strip out the compiled shaders, geometry, and all sorts of gear in there.

90% of what you see in the Pix output is the GMS internal 'secrets', very little in the render cycle actually belongs to the code that you type in.
So technically the blog is just using the profiling tools in PIX, and so isn't breaking the EULA (even if that's possible with it)?

I'll have to check it out sometime, I remember reading an old thread where you dissected the the render cycle with Russell that was a bit over my head, but an interesting read.
 
L

Lonewolff

Guest
So technically the blog is just using the profiling tools in PIX.
Technically yes. But you have to disassemble/reverse engineer, in order to do this.

Either way, I am sure that YYG would be loving that people are pushing all sorts of boundaries with GMS. What MishMash has going on here is magic!
 

GMWolf

aka fel666
Either way, I am sure that YYG would be loving that people are pushing all sorts of boundaries with GMS. What MishMash has going on here is magic!
I made a tool to extend the GML language for GMS1.4 and got shutdown.
Probably because it would have made GMS2 redundant.
 
L

Lonewolff

Guest
I made a tool to extend the GML language for GMS1.4 and got shutdown.
Probably because it would have made GMS2 redundant.
Geez, really? Where you told to stop?
 
Last edited by a moderator:
L

Lonewolff

Guest
It messed with the GM compiler so I asked if they where ok with me releasing it..and if it was tevhntechni against the EULA.
I should not have asked.
That's pretty rough. If someone wanted to improve/expand things, I would have thought they'd be supportive that you are pushing the engine to greater limits.
 
Top