[SOLVED] Threaded content loading in MG 3.6

Hi,

I use this kind of content loading in general:

        static public bool _loadingDone;

        void LoadContent()
        {
            var loading = Task.Factory.StartNew(() =>
            {
                  // Load all your content here.
                  // NOTE: Be sure to catch any exceptions in here and handle them.
                  
                  _loadingDone = true;
            });
        }

Yesterday I updated my project to MG 3.6 (both Desktop GL and Android) and this method became so much inefficient - 11x worse exactly (ive got measurements in my project). Moreover I tried using different ways of Tasks, Threads and ThreadPool (QueueUserWorkItem) - all the same issue.

So, what happened? Can anyone explain? What its funny is that executing the loading method synchronously works super fast like before - its the new thread that slows down.

There is also a possibility that I don’t understand how threading in MG works so instruct me too please :slight_smile:

You might want to mark the _loadingDone field as volatile, since it looks as if it’ll be accessed by more than one thread at a time. See the official documentation for the volatile keyword here.

I don’t know if that will change anything, but it’s worth a try. I don’t know much about multithreading, but it sounds as if your main thread is stuck waiting on the loading thread even though the loading thread is already done.

You must be checking loadingdone too fast in the part waiting for the load to be effectively done
Better use a manual or autoresetevent to ‘notify’ the loading has ended.
Or use threads with a callback overload to do things in the main thread when the load is done
Or use a timer to check the state but thats the worst solution.

Thanks guys for the tips, Im sure that volatile keyword and checking the loadingStatus variable matter and can optimize multithreaded code even more. However, few hours later, I seem to get closer to my case.
It looks like those lines below have asynchronous problem.

cells[n][i].AnimationFrame = new Texture2D(graphicsDevice, ani[n][i].sizex, ani[n][i].sizey);
cells[n][i].AnimationFrame.SetData<uint>(ani[n][i].celldata);

So:

  1. synchronously, entire loading goes in 900ms (read from disc, unpack and decompress, create Texture2D objects for every animation frame (like 200 Textures2D)
  2. this method threw on a thread asynchronously executes in 11000ms!!!
  3. entire loading with removed only Texture2D creation and asynchronously - again a flash 900ms

Sooo… any ideas? :smiley:
I might just add that the GraphicsDevice object is a static variable for a singleton of an ‘AssetLoader’.
Also I wouldn’t drill the topic if not for fact that it used to work differently clearly.
I see now that DesktopGL uses sdl2 with nvoglv64.dll on windows instead of openTK in 32bit… I dont know if that even matters…

Still need help guys.

MonoGame only creates one OpenGL context which can only be used on the thread it was created from. When you try to load graphical content from another thread, MonoGame doesn’t actually immediately load it. Instead the load is queued and when the next iteration of the game loop starts on the main thread the queued operations are executed. I think MonoGame previously created a context specifically for loading textures, but that was lost when the switch from OpenTK to SDL was made. I don’t know the specifics though.

Oh. So I can delete my async loading code for textures.

Why not create textures before loading data into celldata, ie: as a texture file ?

Moved Texture2D construction to the method in the main thread executed just after the end of the task with the data reader,unpacker and all. All takes just a flash of the loading bar now. Measuring the creation of 200 Textures2D (with setData) in main thread - takes 24ms :open_mouth:
I don’t know if its the proper architecture - sure is for MG!

Thank you all. Topic solved.

1 Like

The right architecture is the ine that fits the needs (at least at the moment needed but thats another story) :wink:

OpenGL drivers are notoriously bad at multithreaded contexts. Contexts can only be used on one thread at a time, and sharing resources between shared contexts is not well implemented. Enabling a multithreaded context also has performance implications for rendering by the addition of synchronization.

The XNA ContentManager API was designed for synchronous operation, so the graphics resource is expected to be completely loaded and ready to use by the time the Load() method returns. This allows for only one file I/O then wait for the GPU resource to be constructed on the main thread. To better support asynchronous loading, we need to add a LoadAsync() method that returns a waitable handle. This way we can queue multiple GPU resources per frame and continue with file I/O for subsequent assets on the other thread.