narzoul / DDrawCompat

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

How to mix GDI and Direct3D #269

Closed elishacloud closed 6 months ago

elishacloud commented 7 months ago

There is a long standing issue that was caused in an update to Windows 10 a few years ago (specifically in Windows 10 Fall Creators Edition) and still manifests itself in Windows 11. The issue is that once you call Present() onto a window from Direct3D9 when running in exclusive fullscreen mode it will overwrite all GDI on the screen. Even if later you try and draw things in GDI then it will not be seen at all.

This used to work, and according to the Microsoft documentation this should work. It also works fine on Direct3D8 (and older) and on DirectDraw (as you know). But the issue only manifests itself if using Direct3D9 and exclusive fullscreen mode. The reason this is an issue is because I have been working to convert older Direct3D into Direct3D9 and some games play their video or show text on the screen using GDI, which completely disappears when upgrading it to Direct3D9.

I have already tried setting DisableMaxWindowedMode, which I thought should solve it but it does not. The only solution I have been able to come up with is to build a fullscreen windowed mode option. But this causes other issues.

There are some additional details about this here and in this thread starting here.

I know you are not an expert at Direct3D, but this question is not directly related to DirectD3, rather the interaction between things created in GDI vs Direct3D.

Any ideas or thoughts you have here would be appreciated.

narzoul commented 7 months ago

I thought that GDI hasn't worked in exclusive fullscreen mode since DWM was introduced, or at least not since Windows 8 when DWM became mandatory and borderless fullscreen mode was added for ddraw/D3D8. I don't have a way to verify this anymore, but I guess in Vista and 7 it worked because DWM could turn itself off, and on Windows 8+ it still works because ddraw and D3D8 run in borderless fullscreen mode by default. However, this is not the case for D3D9 according to this post: https://www.vogons.org/viewtopic.php?p=624073#p624073

Unfortunately, I found no way to enable the same kind of borderless windowed presentation mode for D3D9 either. The shim and the registry settings don't seem to do anything.

I can reproduce the problem with Indiana Jones and the Emperor's Tomb. According to PresentMon, with native D3D8 it uses "Composed: Copy with GPU GDI", but with d3d8to9 it uses "Hardware Composed: Independent Flip", no matter what I do.

For comparison, DirectDraw natively also uses "Composed: Copy with GPU GDI" and can display GDI content and other top-level windows in fullscreen mode. With the DXPrimaryEmulation -DisableMaxWindowedMode shim, the process doesn't show up in PresentMon, so I'm not quite sure what it uses, but I think it should be "Hardware: Legacy Flip", based on my memory from very old testing. In this mode, neither GDI rendering on the fullscreen window itself, nor any other top level windows above it are displayed (e.g. dialog windows or popup menus).

"Hardware Composed: Independent Flip" seems a bit different in that it allows other top level windows to show up, but I'm guessing it still prevents GDI from drawing on any window that uses a Direct3D 9 swapchain. Most likely, Independent Flip mode simply wasn't activated in older Windows versions, and now it seems rather difficult to get rid of.

There is some more info about the various presentation modes here: https://wiki.special-k.info/en/SwapChain

narzoul commented 7 months ago

I followed the registry changes (except for the GameDVR_FSEBehavior key, which I don't have) here: https://www.reddit.com/r/pcgaming/comments/ck13z9/psa_windows_10_1903_will_now_ignore_the_disable/

After restarting my PC (not sure if needed), Indiana Jones now uses "Hardware: Legacy Flip" mode with d3d8to9, and indeed a top-level window is no longer visible above it (but the cursor still changes, so I know it's there). Unless the top-level window was already above the game window around the time when the game entered fullscreen mode, in which case it uses "Hardware Composed: Independent Flip" instead, and the other top-level window becomes visible again. What a mess...

However, I still couldn't figure out how to enable maximized windowed mode for D3D9.

elishacloud commented 7 months ago

I still couldn't figure out how to enable maximized windowed mode for D3D9

Here is some code I made to set maximized windowed mode for D3D9.

Edit: I tested this code and it did work in disabling maximized windowed mode.

elishacloud commented 7 months ago

BTW: I also noticed that D3D9 creates a new window and puts that window at the top of all other windows. I caught this by hooking the CreateWindowEx() functions.

narzoul commented 7 months ago

Here is some code I made to set maximized windowed mode for D3D9.

Edit: I tested this code and it did work in disabling maximized windowed mode.

I mean we're looking for a way to enable it, not disable it. It should be disabled by default. With disabled, GDI won't work I think. It doesn't work with DirectDraw either, except in maximized windowed mode.

elishacloud commented 7 months ago

I thought you could just call Direct3D9EnableMaximizedWindowedModeShim to enable it? When I looked at the code in a debugger it was set to '1' by default. When I set it to '0' it disables it. When I tested it last I could see a difference between the two modes.

elishacloud commented 7 months ago

Maybe it has to do with the Direct3D9SetMaximizedWindowedModeShim?

elishacloud commented 7 months ago

Deleted.

elishacloud commented 7 months ago

Sorry for the mistake in the last message. If I call Direct3D9SetSwapEffectUpgradeShim with a parameter of 0. Then the game runs and GDI can be seen on fullscreen exclusive mode.

narzoul commented 7 months ago

Well, I have no idea anymore. I've never heard of these shims before. What shim is supposed to call this function? I can't find any references to it. Does it change the presentation mode according to PresentMon?

elishacloud commented 6 months ago

First of all, thank you for the response and looking into this. Your comments helped me search for this solution. I have never heard of these extra shims either.

PresentMon is showing as "Hardware: Legacy Flip" when I call Direct3D9SetSwapEffectUpgradeShim(0).

elishacloud commented 6 months ago

Closing this for now. Thanks for your help here!

elishacloud commented 6 months ago

Closing

mirh commented 6 months ago

First time I hear about this shim.. Though I guess it makes sense it exists, considering microsoft wanted to uplift everything to flip. Though, you should always interpret any windows 10 behaviour detail to be just a data point, only applying to the specific version/build that was tested (and even then with not that much of a strong confidence, considering just how contrived some conditional checks can be). See for instance the lost big epic of d3d8 exclusive fullscreen.

elishacloud commented 6 months ago

only applying to the specific version/build that was tested (and even then with not that much of a strong confidence, considering just how contrived some conditional checks can be

Yeah. For this I check the following:

  1. Make sure it is a System32 dll.
  2. Load the address with the specific ordinal.
  3. Make sure the address is going to an unnamed ordinal.
  4. Add a dxwrapper option to disable this if needed in the future.

Hopefully, Microsoft will not remove this function and add a different hidden function with the same ordinal as this one was. I don't see that as very likely. I suspect if Microsoft creates other unnamed functions they will give them different ordinal numbers. There are plenty of numbers out there to use...

mirh commented 6 months ago

I meant the conditions that triggers such or such behaviors to begin with (i.e. when conversely the fixes are required) Not something that would concern you implementation-wise. (I just mentioned the d3d8 thing because the linked comment in the second post wasn't exactly fresh)

elishacloud commented 6 months ago

I meant the conditions that triggers such or such behaviors to begin with (i.e. when conversely the fixes are required)

When a game tries to display GDI (like a bik movie) to the screen when it is in Direct3D9 fullscreen exclusive mode then it just shows a black screen and nothing can be seen. This could also happen when a game writes text to the screen using GDI rather than DirectX and then game is in Direct3D9 fullscreen exclusive mode.

However, the issue here is that I need to call this Direct3D9SetSwapEffectUpgradeShim(0) before creating the d3d9 device, which also happens to be before the issue happens. Meaning I need to call this API before knowing whether I need to call it.