Draw stuff from other thread

Posted By: Superku

Draw stuff from other thread - 07/19/18 21:25

Hello!
For my level loading screen I used to have a LPD3DXEFFECT->SetFloat, bmap_process + draw_quad combination in my on_level event (and beyond because there's more stuff to setup after level_load). This faked a "smooth" transition between levels but was obviously chunky and juddery, putting most of the people off nowadays.

As an improvement I've tried to do multithreaded rendering instead.
I use CreateThread, then in the worker function loop I have code as follows:
Code:
LPDIRECT3DDEVICE9 pd3dDevKu = (LPDIRECT3DDEVICE9)pd3ddev;
if( SUCCEEDED( pd3dDevKu->BeginScene() ) )
{
	either old code from on_level
	or just a plain draw_quad
	or DrawPrimitiveUP
	or nothing at all, no draw instructions

	pd3dDevKu->EndScene();
	pd3dDevKu->Present(NULL, NULL, NULL, NULL);
}


Before level_load in the main thread I set a variable to 1 that encapsulates the previous code. I make sure to not draw anything manually while that secondary thread is running (I put a WaitForSingleObject(MUTEX) around it, just to be sure).

It's working to some degree, all 3 different draw approaches mentioned in the code above (although bmap_process seems to be crashing it easily), however after some level_loads and thus multithreaded drawing the game breaks.
It either gives an error (for bmap_process), closes the game after a 1sec freeze, messes up 2d panels (or rather draw_quad and draw_text, which is what I use) or just shows a frozen screen while the game continues to run normally.

So I assume it's either the fact that I'm *beginning* and *ending* a scene which might conflict (randomly, because of the 2 separate threads) with the engine's rendering process, or *presenting* it to the screen.
I've thought about using let's say render_sky to halt the main rendering process (with a MUTEX) when my other thread is rendering but I think it's already too late at that point (and there's no function that is called after the 2D elements, right?).




Is there any light at the end of my loading screen tunnel, can this be done with the current state of A8?

Thanks.
Posted By: jcl

Re: Draw stuff from other thread - 07/20/18 13:51

Yes, this will probably cause a problem if BeginScene() and EndScene() of different threads overlap. One of the threads might at some point render on while the scene is not open. I see no quick solution for this at the moment.
Posted By: Superku

Re: Draw stuff from other thread - 07/20/18 15:27

Hmm okay.
Could I replace the BeginScene() and EndScene() functions with my own somehow?
What I'm at right now: Those functions are defined as COM IDirect3DDevice9 interface functions. pd3ddev holds a pointer to that device/ interface ("d3d9.h") at runtime. The functions are stored in a lpVtbl table but I can ignore that in lite-C.
The function address is ((LPDIRECT3DDEVICE9)pd3ddev)->BeginScene (?). I can save that and replace it with my own function (?). This leads me to a code as follows:
Click to reveal..
Code:
HRESULT BeginSceneOld(void *This);
HRESULT EndSceneOld(void *This);
HRESULT EndSceneKu(void *This);
HRESULT BeginSceneKu(void *This)
{
	cprintf1("nBeginSceneKu at frame %d",(int)total_frames);
	LPDIRECT3DDEVICE9 pd3dDevKu = (LPDIRECT3DDEVICE9)pd3ddev;
	return BeginSceneOld(pd3dDevKu); //pd3dDevKu->lpVtbl->
}

HRESULT EndSceneKu(void *This)
{
	cprintf1("nEndSceneKu at frame %d",(int)total_frames);
	LPDIRECT3DDEVICE9 pd3dDevKu = (LPDIRECT3DDEVICE9)pd3ddev;
	return EndSceneOld(pd3dDevKu);
}

void main()
{
	fps_max = 60;
	level_load(NULL);
	wait(1);
	LPDIRECT3DDEVICE9 pd3dDevKu = (LPDIRECT3DDEVICE9)pd3ddev;
	BeginSceneOld = pd3dDevKu->BeginScene;
	pd3dDevKu->BeginScene = BeginSceneKu;
	EndSceneOld = pd3dDevKu->EndScene;
	pd3dDevKu->EndScene = EndSceneKu;
}


Probably an absolute mess to everyone who knows what they are doing. This leads to a script crash after the first execution of BeginSceneKu (does not close the program) - returning the old BeginSceneOld() (with or without parameter) is correct, right?
I'm guessing the fact that I'm dealing with an interface is leading to a crash ("this" not working right for me/ ...).

I can code it even worse though, that is switch around function pointers in every Begin/EndScene call:
Click to reveal..
Code:
HRESULT BeginSceneKu(void *This)
{
	cprintf1("nBeginSceneKu at frame %d",(int)total_frames);
	LPDIRECT3DDEVICE9 pd3dDevKu = (LPDIRECT3DDEVICE9)pd3ddev;
	pd3dDevKu->EndScene = EndSceneKu;
	pd3dDevKu->BeginScene = BeginSceneOld;
	return pd3dDevKu->BeginScene();
}

HRESULT EndSceneKu(void *This)
{
	cprintf1("nEndSceneKu at frame %d",(int)total_frames);
	LPDIRECT3DDEVICE9 pd3dDevKu = (LPDIRECT3DDEVICE9)pd3ddev;
	pd3dDevKu->BeginScene = BeginSceneKu;
	pd3dDevKu->EndScene = EndSceneOld;
	return pd3dDevKu->EndScene();
}


This prints both functions to the console "at frame 1", then waits a second and closes the program without error message.

Could I easily hook those functions somehow (to let's say put a WaitForSingleObject(MUTEX) in BeginScene and a release in EndScene)?

Thanks!

EDIT: If I wait for more than 1 frame at the start of the main function my pointer shenanigans don't seem to have any effect, the game runs as normal but doesn't print anything to the console either. confused
Posted By: Superku

Re: Draw stuff from other thread - 07/22/18 20:00

I was missing a __stdcall prefix in the function declarations.

pd3dDev->BeginScene and similar functions seem to be reset internally at various occasions (where does the old/ initial pointer come from? QueryInterface does not seem to get called). You can ""hook"" them in lite-C if you replace the function pointer each frame but that's a recipe for disaster, and even then I don't think it will go anywhere.

Apparently, after all, you need to create the device with D3DCREATE_MULTITHREADED though, which is out of reach.
© 2024 lite-C Forums