Bug in DX9.0c - leaking of GDI resources on each CreateDevice in Win+L mode

Started by
5 comments, last by jpetrie 15 years, 11 months ago
We have discovered very unpleasant bug with leaking of huge number of GDI resources when our program with windowed D3D9 work in locked PC (after pressing Win+L for example). Windows XP SP2. Any video card - NVIDIA and ATI. Our program has a continiously working thread of painting and once we get devicelost we free device and try to call CreateDevice until it returns success. It always works fine, but when PC stays in locked mode (Win+L) each failed CreateDevice calls take one additional GDI resource. So if PC locked for 10 minutes our application allocs about 9000 of GDI resources! And Windows works very bad. If re-create root interface IDirect3D9 each unsuccessful call of CreateDevice it will free GDI resources. We've reproduced it with DirectX 9 only. Same test with DirectX 8 doesn't alloc such amount of GDI resources. Even the simpliest test application with timer thread each sets nil and then call CreateDevice causes this problem in Win+L mode - leakage of GDI resources.
Advertisement
Quote:
Our program has a continiously working thread of painting

Like... WM_PAINT painting? You don't need to do that with D3D. In fact, doing so can cause trouble if you're not careful.

Quote:
once we get devicelost we free device and try to call CreateDevice until it returns success

This is crude and non-idiomatic. When the device is lost, you need to wait until TestCooperativeLevel indicates that the device can be restored, then call Reset(). You don't need to create the device.

Without seeing code, I can't speculate much on the GDI resource issue -- perhaps your original device isn't being completely released, perhaps you are not obeying the guidelines called out in the CreateDevice docs, et cetera.

Provide more information. And try doing things the more idiomatic way.
Yup - you definietly want to do as jpetrie suggests. While this may be a bug in D3D, you really shouldn't be trying to create the device over and over anyway - it's not very efficient, and if uyou destroy the device, you have to destroy all your resources too, and then reload them when the compuer is unlocked - which may well give a noticible stutter as your app reloads everything it needs.
We discarded using of TestCooperativeLevel+Reset, because we think that re call of CreateDevice and all textures more simple solution.

I remember some 3D games have some problems after Win-L - leaking of GDI resources.

Definitely there is a bug with CreateDevice in locked mode of PC.

We work with Delphi below you can find simple test application which reproduces this problem:

-------------------------------------------------
unit Unit1;

interface

uses
Direct3D9, Windows, Classes, Forms, StdCtrls, Controls, ExtCtrls;

type
TForm1 = class(TForm)
Timer1: TTimer;
procedure Timer1Timer(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
fd3d : IDirect3D9;
fd3ddevice : IDirect3DDevice9;
d3dpp : TD3DPresentParameters;
d3ddm : TD3DDisplayMode;
procedure d3d_init;
procedure CrDevice;
end;

var
Form1: TForm1;

implementation

procedure TForm1.Timer1Timer(Sender: TObject);
begin
CRDevice;
end;

var
cnt1 : Integer = 0;
cnt2 : Integer = 0;

procedure TForm1.CrDevice;
begin
fd3ddevice:=nil;

fd3d.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, Handle,
D3DCREATE_MULTITHREADED or D3DCREATE_FPU_PRESERVE or D3DCREATE_SOFTWARE_VERTEXPROCESSING,
@d3dpp, fd3ddevice);
end;


procedure TForm1.d3d_init;
begin
ZeroMemory(@d3dpp, SizeOf(d3dpp));
with d3dpp do begin
Windowed := true;
SwapEffect := D3DSWAPEFFECT_COPY;
BackBufferFormat := d3ddm.Format;
BackBufferWidth:=ClientWidth;
BackBufferHeight:=ClientHeight;
PresentationInterval:=D3DPRESENT_INTERVAL_IMMEDIATE;
BackBufferCount:=1;
end;

fd3ddevice:=nil;

ClientWidth:=512;
ClientHeight:=512;

fd3d.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, Handle,
D3DCREATE_MULTITHREADED or D3DCREATE_FPU_PRESERVE or D3DCREATE_SOFTWARE_VERTEXPROCESSING,
@d3dpp, fd3ddevice);
end;


procedure TForm1.FormCreate(Sender: TObject);
begin
fd3d:=nil;
fd3d := Direct3DCreate9(D3D_SDK_VERSION);
fd3d.GetAdapterDisplayMode(D3DADAPTER_DEFAULT, d3ddm);

d3d_init;

Timer1.Enabled:=True;
end;

{$R *.dfm}

end.
Quote:Original post by SirMalder
We discarded using of TestCooperativeLevel+Reset, because we think that re call of CreateDevice and all textures more simple solution.
Not really... Your code will be simpler if you use TestCooperativeLevel and Reset(), since you won't need to completely reinitialise all of your 3D resources.

Your main loop should look something like (psuedocode):
res = TestCooperativeLevel()if(failed(res)){   if(res == D3DERR_DEVICELOST)   {      // Device is lost, nothing to do. Probably want to yield for a while      // E.g. Sleep(100);   }   else if(res == D3DERR_DEVICENOTRESET)   {      // Reset device, reset render state   }   else   {      // Unknown failure, exit app probably   }}else{   BeginScene(), render, etc}
Thanks for your help and advices!

I understand that TestCooperativeLevel + Reset can help.

But imagine that you have multimedia application which has delayed initialization of D3D engine (by some event) and if user locks the PC before the program calls first CreateDevice we will have exactly same problem. The event will happen and the program will continiosly call CreateDevice in the timer thread. And we will get leaking of GDI resources. Note that we can't call TestCooperativeLevel because we didn't created at least one device.
Quote:
But imagine that you have multimedia application which has delayed initialization of D3D engine (by some event) and if user locks the PC before the program calls first CreateDevice we will have exactly same problem.

You get messages that tell you your application is losing focus, becoming minimized, et cetera. It's trivial to maintain that state and simply avoid creating the device until focused is restored -- you should do this anyway to avoid actually rendering while your app doesn't have focus, for example.

Quote:
call CreateDevice in the timer thread.

Note that creating the device with the multithreaded flag only means D3D will acquire its critical section more aggressively. You still need to be careful to make many calls to the device only from the thread that created it, so it's generally ill-advised to create the device in a temporary thread.

This topic is closed to new replies.

Advertisement