Fixing conflicts between WineD3D and WGL
ken at codeweavers.com
Tue Jan 28 18:47:16 CST 2014
This email brings together several threads of conversation that have been spread across different bug reports. Sorry for the length.
The problem: WineD3D is based on WGL (a.k.a. opengl32). As a consequence, it conflicts with application use of WGL in a number of ways. It's also limited by WGL, which doesn't allow for all of the behaviors of DirectDraw and Direct3D.
WineD3D needs to make its own GL context current temporarily and then restore the previous GL context, but WGL requires a DC to restore it and the DC may no longer be valid.
WineD3D changes the window style of the device window when it should not. It needs to do that in order to render to the full screen where WGL only allows full-featured rendering to a window.
Again, WineD3D is rendering to a window when it wants to render full-screen. In this case, the affected apps create an owned window that sits in front of the device window, obscuring it.
WineD3D sets a pixel format on the application window. This conflicts with a later attempt by the app to set a different pixel format on the window via WGL.
I think the solution to these problems is for WineD3D to have an alternative path to set up its GL context. Henri and I have been discussing using Wine-specific extensions to WGL to do this.
There's already the extension WGL_WINE_pixel_format_passthrough which provides the wglSetPixelFormatWINE() function which is just like GDI's SetPixelFormat() and WGL's wglSetPixelFormat() except it allows for changing the pixel format after it's been set once. WineD3D needs to change it even though WGL doesn't allow that.
In some of the bug reports listed above, I've suggested adding new extensions or modifying WGL_WINE_pixel_format_passthrough. We probably need a more comprehensive approach.
WGL exposes various pieces of state to the application. Ideally, WineD3D would not alter any of that state since such alterations are visible to the app and Direct3D doesn't do that on Windows. I'm not sure that's achievable but we should at least minimize such visible state changes to the extent we can.
* The DC pixel format. For window DCs (which I believe are the only ones of real interest), this is actually a property of the window, not the DC. Changed by SetPixelFormat(), wglSetPixelFormat(); exposed by GetPixelFormat(), wglGetPixelFormat().
* The current GL context for the thread. Changed by wglMakeCurrent(), wglMakeContextCurrentARB(); exposed by wglGetCurrentContext().
* The DC for the current GL context. Changed by wglMakeCurrent(), wglMakeContextCurrentARB(); exposed by wglGetCurrentDC(), wglGetCurrentReadDCARB().
The most important things to achieve are a) to not "consume" the only opportunity the app has to set the window's pixel format, and b) to restore the app's current GL context (and implicitly its DC) when returning control to it. I don't think it's necessary to avoid WineD3D ever changing WGL's notion of the current context. Certainly, while a WineD3D GL context is current, GL functions like glClear() will operate on WineD3D's context. If the app thinks its context is current and calls GL functions, its context better really be current. So, WineD3D really needs to restore the app's context before returning control (and it generally does, I think). If it's going to do that, then wglGetCurrentContext() will take care of itself. Same for wglGetCurrentDC().
Here's my proposal:
HANDLE wglCreateSurfaceWINE(HDC hdc, int pixel_format);
// if hdc is GetDC(<some normal window>), a window surface is created
// if hdc is GetDC(0), GetDC(GetDesktopWindow()), or CreateDC("display", …), a full-screen surface is created.
// once the surface is created, it no longer refers to hdc
void wglDestroySurfaceWINE(HANDLE surface);
HDC wglGetSurfaceDCWINE(HANDLE surface);
// Returns a DC created for the surface. This is not the DC passed in to wglCreateSurfaceWINE().
void wglReleaseSurfaceDCWINE(HANDLE surface, HDC hdc);
This is somewhat modeled on WGL_ARB_pbuffer. <http://www.opengl.org/registry/specs/ARB/wgl_pbuffer.txt>
A surface represents a "place" where GL can render, a potential target for a context. It can correspond to a Win32 window or it can be a full-screen surface. A full-screen surface will likely be a platform window (X11 or Mac) managed by the graphics driver but won't be a Win32 window. It should not interfere with mouse events or focus.
A surface has a pixel format, similar to a window. Even if the surface was created from a window DC, its pixel format is independent of the window's. Since a surface is created with a pixel format, it shouldn't be necessary to set one with SetPixelFormat() on a surface DC. If it helps keep WineD3D's code simpler, we could allow it. I'm not sure if it will be necessary to support changing the pixel format after it's been set, using wglSetPixelFormatWINE(), or if the surface should just be destroyed and recreated as necessary. Doing the latter may eventually allow the removal of wglSetPixelFormatWINE().
The lifetime of a surface controls whatever resources the graphics driver has to manage for it. For a normal window, this may include a drawable. For a full-screen surface, this would include any full-screen platform window it might have created.
WineD3D would obtain a DC from a surface using wglGetSurfaceDCWINE() just as it now obtains one for a window using GetDC(). There can be multiple DCs for a given surface. In particular, a DC can only be used on a single thread, so multiple threads would each have to get their own DC. The different lifetimes of surfaces vs. surface DCs is why this proposal differs from my approach in bug 2082, where I proposed an extension which just added full-screen DCs without a corresponding full-screen surface object.
Destroying a surface while it still has extant surface DCs results in undefined behavior.
Once WineD3D has a surface DC, it should be able to work with it as it currently does with window DCs. wglMakeCurrent() with a surface DC will associate the context with the surface drawable. For a surface created from a window, this would work as it does now in the graphics drivers with the exception of how the drawable is looked up from the DC. For a full-screen surface, this would target whatever full-screen drawable the graphics driver provides (possibly a platform window).
There's a wrinkle having to do with the pixel format. User32 and the wineserver use the fact that a window has a pixel format to compute the "surface region" for the top-level window. This is a different "surface" than the concept central to this proposal. This "surface" is used for client-side GDI rendering using the DIB engine. Maintaining a surface region is necessary to prevent the contents of the window surface buffer from being blitted on top of any 3D rendering done to the window. To keep that functioning, the graphics drivers will still have to inform user32 that 3D rendering is being done to the window. It can do that using the existing mechanism, but that's not currently reversible because it's tied to the pixel format and that's a permanent property of a window. We'd like it to be reversible, since WineD3D can stop targeting a window. I'm thinking of using a count in user32.
BOOL wglMakeContextCurrentWINE(HGLRC hglrc);
The standard WGL function wglMakeCurrent() combines two operations. It sets the a GL context's drawable to the provided DC and it makes the GL context current for the thread. This new function, wglMakeContextCurrentWINE(), would only do the latter. It would make a GL context current for the thread without changing which DC it's associated with. Both the X11 and Mac drivers already maintain the necessary state to do that. They don't need to have the DC passed in again. (If a GL context is associated with separate draw and read DCs using wglMakeContextCurrentARB(), then this function will leave it associated with both DCs.)
This allows WineD3D to faithfully restore the app's previously-current WGL context. This addresses bug 28869.
I considered some other designs but I don't think they work well. I looked for a way to do without special DCs. We could directly specify a pixel format when creating a context rather than getting it from the window or surface via the DC. We could make a context current directly on a surface rather than a surface DC. Unfortunately, too much of WGL depends on having a DC. For example, ChoosePixelFormat() and wglSwapBuffers().
What do folks think? Any feedback would be appreciated.
More information about the wine-devel