grimfang4 / sdl-gpu

A library for high-performance, modern 2D graphics with SDL written in C.
MIT License
1.19k stars 123 forks source link

How to make draw calls from an other thread ? #95

Open dhiahassen opened 7 years ago

dhiahassen commented 7 years ago

I am really sorry for posting many issue , but i am building a game engine with and IDE .... i am really glad for using sdl_gpu for the game engine , it is very funtional and it exports on all platforms sdl supports ....

In the engine studio the game is loaded as dll in a seperate thread , when the game performs calls using my api from it's thread , the calls are all batched and then sent to the gpu from the studio's main thread when the display is about to be fliped , i have done a search and found that i can switch opengl context from one thread to another , i saw that you are using such thing as "GPU_MakeCurrent" or something ( glMake Current() is used for the pupsose ) ... so how can i draw from a seperate thread using SDL_gpu without batching all calls ?

Current state , i made another hidden window using SDL_CreateWindow() with SDL_WINDOW_HIDDEN|SDL_OPENGL flags , then i Created a target for it using GPU_CreateTargetFromWindow() and i switch using GPU_MakeCurrent() , now that target has it's own opengl context , so i can draw to it from another thread and opengl wont face any troubles , after bliting eveything i want to copy the result to another target in my original windoow , but when i blit the target's image to a target i create in the orginal window , SDL_Gpu fails to blit , it doesn't suppot inter-context bliting , should i continue on this path and find a solution for that or there is a something better ?

grimfang4 commented 7 years ago

Hey, no problem at all. This is a great place to ask questions.

So it sounds like you're doing what I would do for multiple threads: Create a separate GL context for each thread, one thread per window. One context per thread can avoid serialized vsync issues for multiple windows (a problem I've seen on Linux). You do need a window as the basis for a GL context.

Have you tried making these shared contexts? I use SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); before GPU_CreateTargetFromWindow() to make contexts able to use some of the same GL objects. Textures are shared, for example, but FBOs are not. You should be able to blit a texture from a different context onto the current context's render targets, but you will probably have trouble trying to do it the other way around.

dhiahassen commented 7 years ago

Yes , i forgot to mention that i tried 'SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);' and i still get the gl_handle = 0 for game_target->image->data->handle , that means opengl didnt share the textures , in other words , opengl started couting again from 0 for texture ids , thats no supposed to happen i think, because both contexts are now supposed to share the id(s counter for textures , and blting does not work , sdl gpu crashes when trying to blit an image made in context A to a target in context B , if only the is a solution for that , everything else is solved

grimfang4 commented 6 years ago

I haven't gotten around to testing this on multiple threads, but I'll get to it. It'd be good to make an explicit test case for it.

Dids commented 6 years ago

Any news on this?

I'm attempting something similar, where I need to capture frames on a separate thread, which from what I can tell is only possible by creating a separate target on the separate thread.

Enabling shared contexts and creating the target in the thread itself results in an exception in glGenTextures(1, &handle), among other places.

I'm simply passing the current window handle to the thread, then creating a new target from that, but I'm assuming that's the right way to do it anyway, and the issue is elsewhere.

That said, is there any other way of capturing frames (eg. screen->image) without blocking the main thread while doing so?

dhiahassen commented 6 years ago

what i am doing is using a blit batch , i created a wrapper class for GPU_Target , then that target can be attached to an object of type 'BlitBatch' , and when a draw call gets performed on that target , if batching is 'ON' then no direct draw is made , instead of that , the draw call will be appended to the blit batch of that target , and later when the child thread is done with all draw calls of this frame , he he does this :

` .... BEGIN_DRAW_CALLS() target->GetBlitBatch()->Flush(); END_DRAW_CALLS() .... `

the flush method does finally the actual draw calls but from the parent thread ! how that is done ? the macros BEGIN_DRAW_CALLS() / END_DRAW_CALLS() wraps the draw calls and push them in a TaskList of the main thread , thread task list is constantly checked from the main thread each frame , also BEGIN_DRAW_CALLS() should stop the child thread until the tasklist is served , thefore you will need a mutex/bolean .... , i am trying to explain a complex mechanism of 700 lines of code so excuse me .. Note : you may face a synchronazation problem when bliting one target to another and the draw are not performed in thier actual order , so you can use a global draw batch for the whole engine

grimfang4 commented 6 years ago

Also, the best way I can think of to offload frame capture to another thread is only a "sort of" solution. You can't ask another thread to read the framebuffer without interfering with the main thread, but you can ask it to save data to disk.

Render your whole game to an intermediate buffer (which then is normally rendered directly to the screen target). Then, when doing a screenshot, you'd GPU_CopySurfaceFrom|Image/Target|(intermediate_screen) and pass the resulting surface to the worker thread. The worker thread can do what it needs to with the surface and free it when done.

dhiahassen commented 6 years ago

@grimfang4 you dont understand , any ANY draw call performed from a child thread will cause the app to crash , it is not possible

Dids commented 6 years ago

@grimfang4 That's definitely a good idea, and it's more or less what I'm doing right now.

However, I'm also trying to record multiple frames (say 5 seconds at 60 frames per second), which I then turn into a recording. ~Simply calling GPU_CopySurfaceFromTarget every frame seems like it's killing performance (unless it's something else I'm doing to block the main thread). Any tips on that particular scenario?~

EDIT: Never mind! Looks like calling GPU_CopySurfaceFromTarget every frame does not have a noticeable impact on performance after all, so it's a perfect solution for me. :)

grimfang4 commented 6 years ago

That's good news. Reading from the framebuffer on the GPU is going to be relatively slow, one way or another, but it sounds like it's fast enough for that purpose.

dhiahassen commented 6 years ago

@Dids a new vulkan version is in process , so this can be fixed i think because i heard that vulkan is much more multithread friendly