VOGONS


First post, by XJDHDR

User metadata
Rank Newbie
Rank
Newbie

I know these questions are only vaguely related to dgVoodoo, but I'm not sure where else I can ask them where someone would know the answers.

What API calls do DirectX/Draw 9 or below games make to start the game in fullscreen vs. windowed mode? What API calls allow a game to switch between the two while running? Finally, what calls allow a game to create a borderless window?

My creations and essays:
https://xjdhdr.gitlab.io/

Reply 1 of 7, by Azarien

User metadata
Rank Oldbie
Rank
Oldbie

The normal way of creating a fullscreen window in WinAPI is to do precisely that: use CreateWindow or CreateWindowEx to create a borderless* window located at 0,0 and size equal to the resolution of the main monitor. As simple and obvious as that.
The application may force a different resolution with ChangeDisplaySettings prior to creating its fullscreen window.

*) a window is borderless if it does not specify any of the window flags that cause border and title bar to appear. Use WS_OVERLAPPED (equals 0) as a window style in CreateWindow call.

Reply 2 of 7, by XJDHDR

User metadata
Rank Newbie
Rank
Newbie

Thanks for the info. That answers my questions about windowed. But how would a game run in fullscreen (meaning real fullscreen, not a borderless window that fills the screen)?

Also, I saw that MS' docs say that the WS_OVERLAPPED enum has a title bar and a border.

My creations and essays:
https://xjdhdr.gitlab.io/

Reply 3 of 7, by Dege

User metadata
Rank l33t
Rank
l33t
XJDHDR wrote on 2022-08-02, 20:40:

What API calls do DirectX/Draw 9 or below games make to start the game in fullscreen vs. windowed mode? What API calls allow a game to switch between the two while running?

It's not so simple regarding those old API's...

DirectDraw was so low-level to the hw that fullscreen and windowed mode required 2 different presenting paths in the application and you couldn't just switch between them with a single API call.

  • Full screen required "exclusive" mode, to own the display hw for the rendering. After you set it with IDirectDraw*::SetCooperativeLevel, you could create (IDirectDraw*::CreateSurface) a complex "primary" surface with one or more backbuffers that formed a "flipping chain" (the old term for swapchains). You could flip to the next backbuffer by calling IDirectDrawSurface*::Flip on the primary one in the chain and that flip was true hw flipping.
    Also worth to mention that exclusive mode was tied to a window. If that window lost the focus (e.g. Alt-Tab out from fullscreen) then all the hw resources not backed by system memory got lost and they had to be restored or recreated when returning (Alt-Tabbing back) to exclusive full screen.
  • Windowed mode could work in "normal" cooperating mode, even without a dedicated window, but you could only create a single primary surface representing the current dekstop. You could arbitrarily draw to it, even drawing into windows of another apps were possible. You rendered into an offscreen buffer and then copied the result into the client area of a window (IDirectDrawSurface*::Blt). To make sure that the copy only overwrites the visible parts of your window client area, you had to attach a clipper (IDirectDrawClipper) representing the visible region of the window, to the primary surface.

You could also change and restore the display mode (IDirectDraw*::SetDisplayMode and IDirectDraw*::RestoreDisplayMode) but it was not required and it was independent on the cooperative level. If your application rendered at the current desktop resolution (be it windowed or fullscreen) then it could be omitted. Since not all pixel formats were compatible with all of the display modes, when the display mode was changed then hw resources (textures, offscreens) could also got into lost state which weren't necessarily restorable but only re-creatable with another pixel format(s).

So, how to switch between windowed and fullscreen? Basically you have to release all your resources, and re-initialize your application (at least partially) to run on the other presenting path...

D3D8/9 made this all somewhat easier because a lot of this process, including setting the cooperative level, the display mode, the 2 different techniques to present a backbuffer in a window, was made implicitly inside a few API calls.
The term of swapchain (windowed or fullscreen) appeared here, a D3D8/9 device always had one implicit swapchain that were created when the device was created (IDirect3D8/9::CreateDevice) or recreated (IDirect3DDevice8/9::Reset). (But you could create additional ones in windowed mode (IDirect3DDevice8/9::CreateAdditionalSwapchain) if you wanted to render into multiple windows.)
Those API calls accepted a presentation descriptor that contained the window used for the presentation, the display mode, whether fullscreen or windowed mode was required, and so on, and the API made all the tedious work needed with DDraw in the background. Once initialized, you just had to call IDirect3DDevice8/9::Present to present the next backbuffer from the implicit swapchain.
One problem however remained: these API's (except D3D9Ex) still relied on non-virtual video memory unlike modern ones, so if the rendering window lost the focus in fullscreen then the device object itself (not particular resource objects like in DD) got into lost state and all resources backed only by pure hw memory had to be released before and re-created after resetting the device with IDirect3DDevice8/9::Reset with either the old or a new presentation descriptor.
To solve the problem of compatibility of certain resource pixel formats and display formats, the API proved general query methods for them (IDirect3D8/9::Check* methods).

How to switch between windowed and fullscreen? You can call IDirect3DDevice8/9::Reset with another presentation descriptor. But, as I wrote, it involves re-creating certain resources on your part, using the Check* methods to pick up usable pixel formats. It still makes the process elaborate. Not so easy like with a DXGI swapchain where you can just switch with literally one API call.

Reply 6 of 7, by XJDHDR

User metadata
Rank Newbie
Rank
Newbie

@Dege
Sorry for the late reply, but thank you so much for the very detailed reply. I'm trying to use your info to see how feasible it is to make some modifications to a game's EXE. The game in question is Emperor: Rise of the Middle Kingdom. I think it's a DirectDraw 7 game. While running, it only loads DDraw.dll, and none of the other DX DLLs.

There are two changes I want to make, or at least see if they can be done:

  1. Make the game run in a borderless window.
  2. The game allows switching between fullscreen and windowed mode. However, if you set the game to run in windowed mode, I've noticed that it initialises in fullscreen mode, then switches to windowed after reading it's config file. I want to see if this startup routine can be changed to start in Windowed, then switch to Fullscreen if needed.

From what I can gather, the starting point for making the game initialise in Windowed mode would be editing it's call to the SetCooperativeLevel function. However, I can't find any calls to this function anywhere in the game's EXE. That said, I also looked at the DDraw.dll and found that there doesn't seem to be a SetCooperativeLevel function defined.

As for Borderless Windowed mode, I gather from Azarien's post that I need to edit the DWStyle argument of the CreateWindowEx function calls. There appear to be 3 places where the game calls that function, but I only saw one of them get called while the game was starting up (at 0x12AC5C for the GOG version. The other 2 that don't get called are at 0x2C422F and 0x37D165).

Azarien wrote on 2022-08-05, 15:59:

No, what you saw was WS_OVERLAPPEDWINDOW.

What I wrote was pretty much quoted from MS's docs:

WS_OVERLAPPED 	0x00000000L 	The window is an overlapped window. An overlapped window has a title bar and a border. Same as the WS_TILED style.

The line below this says that WS_OVERLAPPEDWINDOW is this plus a few other enum values:

WS_OVERLAPPEDWINDOW 	(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX) 	The window is an overlapped window. Same as the WS_TILEDWINDOW style.

That said, I found where the game calls the CreateWindowEx function. However, when I changed the one function call's DWStyle argument from 0x90080000 -> 0, it didn't appear to change anything. The game still ran in a window with the same border and title bar as before.

My creations and essays:
https://xjdhdr.gitlab.io/

Reply 7 of 7, by Dege

User metadata
Rank l33t
Rank
l33t

DDraw as a lib has only a few functions like DirectDrawCreate and Enum*. Those are factoring functions to create/help creating DDraw objects. DDraw is not a function-based API like OGL, it's COM.
So, you won't encounter functions like SetCooperativeLevel exported , because those are methods on DD COM interfaces like IDirectDraw, IDirectDraw2, ..., IDirectDrawSurface, and so on.
Phyisically in the game code it means sg like this:

push methodParam1
push methodParam2
...
mov edx, [lpDD] ; this pointer of the interface
push edx
call dword ptr [edx+offset in the vtbl] ;the offset of the method in the interface virtual func table