Why are functions duplicated between opengl32.dll and gdi32.dll?
Asked Answered
T

1

11

The following functions are duplicated between opengl32.dll and gdi32.dll:

WGL GDI
wglChoosePixelFormat ChoosePixelFormat
wglDescribePixelFormat DescribePixelFormat
wglGetPixelFormat GetPixelFormat
wglSetPixelFormat SetPixelFormat
wglSwapBuffers SwapBuffers

I have been searching for an answer for a long time now, but noone appears to have any concrete information why that is and what their exact difference is.

The OpenGL FAQ, section 5.190, suggests that these functions are not functionally identical:

To ensure correct operation of OpenGL use ChoosePixelformat, DescribePixelformat, GetPixelformat, SetPixelformat, and SwapBuffers, instead of the wgl equivalents, wglChoosePixelformat, wglDescribePixelformat, wglGetPixelformat, wglSetPixelformat, and wglSwapBuffers. In all other cases use the wgl function where available. Using the five wgl functions is only of interest to developers run-time linking to an OpenGL driver.

Does "run-time linking to an OpenGL driver" imply bypassing opengl32.dll and loading an ICD directly?

A StackOverflow thread named "Mesa3D does not like my context creation code", appears to reinforce this.

Another StackOverflow thread, named "wglCreateContext in C# failing but not in managed C++" suggests that opengl32.dll must be loaded before gdi32.dll when using the GDI functions, or risk runtime failure ("error: 2000").

My own testing indicates that "error: 2000" occurs on some systems (Nvidia, but not Intel or a Parallels VM) if the WGL version of these functions is called. Changing to the GDI version clears this issue, but using LoadLibrary("opengl32.dll") does not appear to change anything.

Has anyone ever investigated the difference between these WGL and GDI functions? It is clear that there is some form of difference, and I am trying to understand which version should be used under which circumstances and what are the potential pitfalls if the wrong version is used.

Edit: Wayback Machine brings up a webpage that describes how direct loading of an ICD works. This was apparently required back in the Voodoo 1/2 days when the 2D and 3D accelerators were two different pieces of hardware with separate ICDs (which the normal, single-ICD mechanism in opengl32.dll couldn't handle). Quake 1 and 2 would apparently load ICDs directly because of this.

However, a post below shows that the AMD ICD does not export the wgl* variants, which contradicts this idea.

There has to be someone or some place out there that holds the keys to this knowledge.

Edit 2: From the webpage above comes the clearest suggestion yet:

Therefore if you are using a OpenGL driver named opengl32.dll you must call the GDI functions, and if you are not using a driver named opengl32.dll you must NOT call the GDI functions.

But how does this fit in with the fact that the AMD ICD does not export WGL functions?

Edit 3: Apparently Mesa 3D exports WGL symbols, as can be seen here: http://cgit.freedesktop.org/mesa/mesa/tree/src/mesa/drivers/windows/gdi

This makes sense, since Mesa3D is not supposed to be used as an ICD. This fits with the pattern in the Mesa3D thread linked above: their calls are not being routed through Microsoft's opengl32.dll, so GDI functions fail, but Mesa3D is exporting wgl* functions so these still work. However, that's specific to Mesa3D - that method would fail if you tried to use AMD's ICD directly.

Teriteria answered 17/12, 2013 at 22:6 Comment(6)
There is a huge difference, namely that the prototypes for the WGL functions are not defined in any system headers. OpenGL32.DLL exports the symbols, but unless you manually import the functions you would never know this. I suspect this is what was meant by "run-time linking," you have to do some nasty LoadLibrary (...) stuff. What language are you using, by the way? Most often when this question comes up it is in the context of C#, where LoadLibrary (...) is a much more common practice. In C and C++, most people link against opengl32.lib / gdi32.lib and use the GDI32 functions.Ariannearianrhod
I first came across this issue with C#, while working on OpenTK, but I am currently using C++. This question is not-language specific.The reason for using LoadLibrary("opengl32.dll") is to maintain a single code-path for pre-1.1 and post-1.1 OpenGL functions (GetProcAddress + wglGetProcAddress).Teriteria
This is certainly more than an early/late binding difference. The functions are defined in the wgl.spec and documented in MSDN, so it doesn't matter whether they are exported in a specific gl.h implementation.Teriteria
Those functions you mentioned are not documented on MSDN, you might want to search again using the proper prefixes. They are not publicly defined, you have to manually load the DLL imports. For maximum compatibility you should avoid undocumented/private functions, they are often the first thing to break whenever a new version of Windows comes out.Ariannearianrhod
opengl32.dll is not an ICD. Maybe this will help to clear a few things up. It communicates with ICDs using a different interface: Drv..., which is why ICDs do not export wgl... functions. And as far as Quake 1 and 2 go, they did not use ICDs. They had MiniGL drivers that were awful things; they only implemented exactly the functions that Quake 1 and 2 needed, and usually would not work with any other games.Ariannearianrhod
opengl32.dll is the router dll, this much is clear. This is similar to openal32.dll and the newer opencl router. The question is under what circumstances must one use the wgl* functions instead of the gdi versions? These circumstances exist but they are completely undocumented.Teriteria
A
9

There is a huge difference, namely that the prototypes for the WGL functions are not defined in any system headers. opengl32.dll exports the symbols, but unless you manually import the functions you would never know this.

However, the functions that WGL Installable Client Drivers (ICD) implement are actually prefixed like this: DrvSwapBuffers (...), DrvSetPixelFormat (...), DrvGetProcAddress (...), etc... So you are definitely not linking directly to an ICD if you call wglChoosePixelFormat (...) instead of ChoosePixelFormat (...).

opengl32.dll is basically Microsoft's GDI implementation of OpenGL and a wrapper for ICDs. You can even see what an implementation of an ICD looks like if you look at Mesa; notice how none of the functions are prefixed with wgl? ICDs do not export any wgl-prefixed symbols, the WGL functions they do implement are all extensions (e.g. wglSwapIntervalEXT (...), wglChoosePixelFormatARB (...), etc.) and can only be loaded using wglGetProcAddress (...) or DrvGetProcAddress (...).


Take a look at AMD's OpenGL ICD:

        ICD Exports

You will notice that AMD actually fully implements the EGL API in their ICD (and you can get the necessary headers to use EGL on AMD hardware here), but WGL symbols are not exported.


Update:

As explained in comments, gdi32.dll actually invokes wglChoosePixelFormat (...) when you call ChoosePixelFormat (...). The very first thing the function does is try and load opengl32.dll and call wglChoosePixelFormat (...):

.text:4D579CAC ; int __stdcall ChoosePixelFormat(HDC,const PIXELFORMATDESCRIPTOR *)
.text:4D579CAC                 public _ChoosePixelFormat@8
.text:4D579CAC _ChoosePixelFormat@8 proc near
.text:4D579CAC
.text:4D579CAC hLibModule      = dword ptr -4
.text:4D579CAC arg_0           = dword ptr  8
.text:4D579CAC arg_4           = dword ptr  0Ch
.text:4D579CAC
.text:4D579CAC                 mov     edi, edi
.text:4D579CAE                 push    ebp
.text:4D579CAF                 mov     ebp, esp
.text:4D579CB1                 push    ecx
.text:4D579CB2                 push    esi
.text:4D579CB3                 lea     eax, [ebp+hLibModule]
.text:4D579CB6                 push    eax             ; int
.text:4D579CB7                 push    offset aWglchoosepixel ; "wglChoosePixelFormat"
.text:4D579CBC                 call    _GetAPI@12      ; GetAPI(x,x,x)
.text:4D579CC1                 xor     esi, esi
.text:4D579CC3                 test    eax, eax
.text:4D579CC5                 jz      short loc_4D579CD1
.text:4D579CC7                 push    [ebp+arg_4]
.text:4D579CCA                 push    [ebp+arg_0]
.text:4D579CCD                 call    eax
.text:4D579CCF                 mov     esi, eax

Here is GetAPI (all it does is load opengl32.dll and import a named function from it):

                               GetAPI - gdi32.dll

Now, ICDs do not actually implement ChoosePixelFormat (...), as it is functionally identical across all implementations. It is a simple pattern matching function. If you want to see how opengl32.dll dispatches one of its wgl... functions to an ICD at run-time, take a look at the control flow for wglSwapBuffers:

  wglSwapBuffers - opengl32.dll

The red left-hand branch is what occurs when an ICD is installed and the green right-hand branch is the default GDI implementation of wglSwapBuffers. Interestingly, you can see that the GDI implementation requires a full glFinish (...). Most hardware drivers will tend to flush the command queue instead of finishing when you swap buffers, this allows better CPU/GPU parallelism.

Ariannearianrhod answered 17/12, 2013 at 23:18 Comment(9)
This contradicts the comment from the OpenGL FAQ that suggests using the wgl variants when loading an ICD directly. Digging a little bit deeper brings up an interesting webpage that explains how to load an ICD directly. This was apparently required back in the Voodoo 1/2 days where the 2d and 3d accelerators were two separate pieces of hardware (which could not be handled by the normal opengl32.dll+ICD mechanism).Teriteria
The stackoverflow thread I linked "Mesa3d doesn't like my context creation code" also supports that you must use the wgl variants when loading an ICD directly. If the ICD does not export these functions, then how does this work? (And it does work, as evidenced by that thread.)Teriteria
@TheFiddler: Like I was trying to tell you, opengl32.dll wraps ICDs. The wglSwapBuffers (...) function it exports, for instance, will invoke an ICD's DrvSwapBuffers (...) function if any ICDs are installed. But the ICDs themselves do not include those functions. The article you listed even explains this, though not in as great detail. But you will never find wglSwapBuffers (...) as an exported function in an ICD DLL, that is simply not the interface that Microsoft designed for ICDs; all ICD functions begin with Drv....Ariannearianrhod
in the "Mesa3d" thread, they are linking the Mesa ICD directly, without going through Microsoft's opengl32.dll. How do the wgl* functions work in that case, if the ICD is not exporting them? Edit: if you get the time to show how opengl32.dll, please do so. Not only because this information is still relevant, but also for posterity. It's part of the history of computing and it would be sad if it disappeared with the passage of time.Teriteria
Almost nobody directly links to an ICD (not even in the example you posted), I think this is where you are getting confused. ICDs are loaded by the runtime: opengl32.dll. If you wanted to explicitly use an ICD, you would have to load the system registry and query the value of HKLM/System/CurrentControlSet/Control/Class/{Adapter GUID}/0000/OpenGLDriverName. You also would not have access to ChoosePixelFormat (...) if you bypassed the OpenGL runtime and linked directly to an ICD, since this is one of many functions ICDs do not implement.Ariannearianrhod
Thanks for the update, much appreciated. I must say I disagree with your last comment: the Mesa3d thread I linked bypasses opengl32.dll because you can't install Mesa3d as an ICD. The webpage from Wayback Machine explicitly bypasses opengl32.dll and loads 3dfxvgl.dll instead. In both cases, they are still using wgl* calls - this is what doesn't make sense to me.Teriteria
I am accepting your answer because it's by far the best source of information on this topic I have ever seen. Thanks a lot for taking the time on this.Teriteria
@TheFiddler: The thing is, 3dfxvgl.dll is not an ICD. It is a MiniGL driver. You certainly can bypass opengl32.dll, but you will have to use something more sophisticated than an ICD to replace it. ICDs are basically a way to extend opengl32.dll, they do not implement all of WGL. MiniGL drivers on the other hand are not ICDs; they are stand-alone drivers.Ariannearianrhod
Aha! "Enter the Voodoo1/2/Rush/Banshee cards, they do have a full OpenGL driver, named 3dfxvgl.dll, but it is not an ICD and as such will not be supported by linking your application to opengl32.lib." So 3dfxvgl.dll is a full OpenGL driver but not an ICD - exactly like Mesa3d. This clears everything up!Teriteria

© 2022 - 2024 — McMap. All rights reserved.