narzoul / DDrawCompat

DirectDraw and Direct3D 1-7 compatibility, performance and visual enhancements for Windows Vista, 7, 8, 10 and 11
BSD Zero Clause License
893 stars 67 forks source link

D3D9 and DDraw7 overlays #170

Closed ghotik closed 1 year ago

ghotik commented 1 year ago

Well, honestly this is not a claim about DDrawCompact as much as a suggestion for DxWnd, but the matter could be interesting more in general, maybe. I was trying to make DxWnd able to support "The Godfather the Game" and I am stuck with a big problem. The game opens a D3D9 session, writes a few words on screen (just two lines for about 5 seconds), then opens a DDraw7 session with an overlay surface with YUY2 FourCC codec where one initial movie and other ingame movies are rendered. It was not too hard to pretend that the Win10+ desktop has the needed OVERLAY capabilities and then redirect the movie to a memory surface where a SW decoder decompress the YUY2 format to a plain 32bit surface. Here comes the problem: when I try to Flip (or Blit or whatever I try) this well-rendered surface I meet a screen region locked by the D3D9 session and the best that I can do is a highly flickering movie with frames that gets blackened by the D3D9 recovery. As a counterproof, I hacked the D3D9 methods (I bypassed IDirect3DDevice::Reset()) and this way the movie is rendered perfectly, though the 3D scenes remain fully black. The game running in native mode doesn't have this problem because it is coded to pretend OVERLAY capabilities, it terminates otherwise, and there's nothing that could prevent blitting to an overlay. The question is: do you know some way to override the D3D9 locked region? Of course, since I should do this from a hooker program, I can not inspect or modify the game source code (otherwise I suppose it wouldn't be so hard). Also in this case there's a wide discussion of what we did so far on the SourceForge DxWnd thread, but I think that this is a pretty complete summary of the problem. Thanks, gho

narzoul commented 1 year ago

I'm not that familiar with Direct3D 9, or any interworking between various DX versions to be honest. My best guess is that the game may be overwriting whatever you're blitting to the window with its own empty backbuffer by continuously calling IDirect3DDevice9::Present. Or the blitting method you are using is bypassing DWM composition (it goes to the composited screen surface rather than the off-screen window texture), and the composition itself is erasing whatever you were able to blit temporarily to the previous composited frame.

The most compatible method would probably be to either inject the overlay presentation via Direct3D 9 (maybe by hooking the game's existing swap chain), or by presenting the overlay content to a new layered window above the target window, where you can apply any needed transparency effects. Layered windows support both color keying and per-pixel (alpha channel) transparency as far as I remember. I'd probably choose this latter approach, it sounds less complicated, and in theory it should be compatible with any DX version up to 9.

PS: How are you doing the blitting? Did you set up a clipper? I think if you set up a clipper with a HWND, then blit to the primary surface with that clipper in DirectDraw, then the result should go to the window texture. If you don't use a clipper, then it might go to the final composited image, especially if you're trying to blit outside the window's client area, but I'm not exactly sure how DirectDraw decides what should happen in this case.

ghotik commented 1 year ago

Wow, you gave me the good idea. I was baffled by the ApiTrace logs that showed a D3D9 session blocked before the start of the fake-overlay operations, but this evidently didn't hold true. When I set the FPS indicator and the movie started it was clearly visible a double indicator, which means that both DDRAW7 and D3D9 are making operations at the same time. The quick & dirty solution (also considering that the game only has full-screen movies) was to set a semaphore to bypass the Direct3DDevice9::Present operations and the flickering is gone! Wonderful.

update It seems I said hurray a little too early. The good result was reached while ApiTrace wrappers were in place. The d3d9.dll wrapper in particular did the trick. Without this logging wrapper some flickering returned visible. In any case, I think the direction is correct, it is necessary maybe only to synchronize the operations better.

ghotik commented 1 year ago

This is another case that is going to be much harder than expected. These are the facts:

1) the flickering is caused by the contemporary screen updates from IDirectDrawSurface7::UpdateOverlay and IDirect3DDecice9::Present 2) for some unknown reason, if I don't put a d3d9.dll wrapper in between DxWnd and the system d3d9.dll just before the creation of the overlay surface the d3d9 device hook gets broken, I can still log some IDirect3D9 operations but no IDirect3DDevice9, so the Present calls get unfiltered.

To make things harder, the game throws thousands of method calls only to display a tiny text on screen and this is rather frustrating because to understand what's going on one should read thousands of log lines. I don't expect you to go through all this, bit it's just to give you a rough estimate. The two log files in attach show a working case (dxwnd.ok.log, with the d3d9.dll from ApiTrace copied in the game folder and the log showing bypassed Present calls) and the bad case (dxwnd.ko.log, no d3d9.dll proxy and no bypassed Present calls while the overlay is updated). The two files look quite identical up to a given point, then they start to look more and more different. Quite strange. dxwnd.logs.zip

ghotik commented 1 year ago

Trying to take advantage of the past experience thank to your support, I also tried another way: instead of hooking IDirect3DDevice9::Present I tried to go through the D3DDDI way and tried to hook the pfnPresent function pointer. Sadly also this way didn't work. The log messages that I put inside the wrapper don't appear in the log file and the game crashes as soon as the Direct3D7 session is started, so this solution didn't fix any problem and instead highlighted two of them: 1) the pfnPresent doesn't get called though the game shows a few frames on screen 2) the D3DDDI hooking makes this game crashing, as well as other games.

ghotik commented 1 year ago

So far I have a number of unresolved issues. 1) why the D3D9 session at a given point gets unhooked? My guess is that the initial session is closed and then reopened trough some OLE/COM interface that doesn't let me see the d3d9.dll link. 2) why the pfnPresent call is never called, even when in the initial phase the d3d9 hook is still active? 3) why the D3DDDI hooking crashes the game?

That said, I may reconsider your idea of remapping the UpdateOverlay not on the ddraw front buffer but by copying the frames to the game's existing d3d9 swap chain. Do you have any code example doing this?

AITUS95 commented 1 year ago

@ghotik I sent a post here , but I'm replying here too to give more information to the question, in fact I tried to start godfather with apitrace by activating the overlays on windows 10 via the registry with my gt 520m and the films as I already said are visible, maybe this can help, below i attach the trace files.

apitracer files_2.zip

ghotik commented 1 year ago

In the end I got tired of D3D9 oddities and built the other suggestion of yours, the emulated overlay surface copied (with a simple GDI StretchBlt operation) to a new window that is built exactly over the D3D9 rendering area and kept on top by a TOPMOST attribute renewed at each frame. I had to write a little more code, but it works like a charm! Thank you for the suggestion.