[PATCH 2/2] winex11.drv: Prioritize smaller depth formats when zero depth is requested in X11DRV_wglChoosePixelFormatARB().

Paul Gofman pgofman at codeweavers.com
Thu Feb 11 14:09:36 CST 2021


Fixes Ancient Cities' black screen on Nvidia.

Signed-off-by: Paul Gofman <pgofman at codeweavers.com>
---
    The order of formats returned from wglChoosePixelFormatARB() is implementation specific
    and actually differs between AMD, Nvidia and Intel. Still, there is a common place
    between all the three which is given the other attributes are equal and the application requests
    zero depth (or doesn't pass WGL_DEPTH_BITS_ARB at all), the smaller depth formats go first
    for accelerated formats.
    Our implementation in winex11.drv relies on the format order from glXChooseFBConfig().
    The description of this function [1] looks ambiguous with respect to depth buffer size ordering.
    The GLX_DEPTH_SIZE attribute description says if that value is zero, frame buffer configurations
    with no depth buffer are preferred. The precedence rules in the end have rule 6 which suggests
    that larger depth sizes go first. So AMD behaves like in the first suggestion and Nvidia uses the
    second.

    The game is using both ChoosePixelFormat and wglChoosePixelFormatARB (getting just one format from the latter)
    and effectively relies on the results being the same. The difference between formats that we get on Nvidia
    is in the depth bitness, which appears to be always 0 on Windows on all the GPUs. The formats chosen
    this way are not necessarily the same in the general case on Windows (even with equivalent formats restrictions),
    but are always the same in the game's case of formats selection.

    Currently we don't get zero depth from neither ChoosePixelFormat (this is addressed by patch 199716) nor from
    wglChoosePixelFormatARB (due to higher depth formats going earlier).

    1. https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glXChooseFBConfig.xml

 dlls/opengl32/tests/opengl.c |  57 ++++++++++++++
 dlls/winex11.drv/opengl.c    | 139 +++++++++++++++++++++++++++--------
 2 files changed, 166 insertions(+), 30 deletions(-)

diff --git a/dlls/opengl32/tests/opengl.c b/dlls/opengl32/tests/opengl.c
index e93636ed75e..314fa11225d 100644
--- a/dlls/opengl32/tests/opengl.c
+++ b/dlls/opengl32/tests/opengl.c
@@ -1764,6 +1764,62 @@ static void test_swap_control(HDC oldhdc)
     wglMakeCurrent(oldhdc, oldctx);
 }
 
+static void test_wglChoosePixelFormatARB(HDC hdc)
+{
+    static int attrib_list[] =
+    {
+        WGL_DRAW_TO_WINDOW_ARB, 1,
+        WGL_SUPPORT_OPENGL_ARB, 1,
+        0
+    };
+
+    PIXELFORMATDESCRIPTOR fmt, last_fmt;
+    BYTE depth, last_depth;
+    UINT format_count;
+    int formats[1024];
+    unsigned int i;
+    int res;
+
+    if (!pwglChoosePixelFormatARB)
+    {
+        skip("wglChoosePixelFormatARB is not available\n");
+        return;
+    }
+
+    format_count = 0;
+    res = pwglChoosePixelFormatARB(hdc, attrib_list, NULL, ARRAY_SIZE(formats), formats, &format_count);
+    ok(res, "Got unexpected result %d.\n", res);
+
+    memset(&last_fmt, 0, sizeof(last_fmt));
+    last_depth = 0;
+
+    for (i = 0; i < format_count; ++i)
+    {
+        memset(&fmt, 0, sizeof(fmt));
+        if (!DescribePixelFormat(hdc, formats[i], sizeof(fmt), &fmt)
+                || (fmt.dwFlags & PFD_GENERIC_FORMAT))
+        {
+            memset(&fmt, 0, sizeof(fmt));
+            continue;
+        }
+
+        depth = fmt.cDepthBits;
+        fmt.cDepthBits = 0;
+        fmt.cStencilBits = 0;
+
+        if (memcmp(&fmt, &last_fmt, sizeof(fmt)))
+        {
+            last_fmt = fmt;
+            last_depth = depth;
+        }
+        else
+        {
+            ok(last_depth <= depth, "Got unexpected depth %u, last_depth %u, i %u, format %u.\n",
+                    depth, last_depth, i, formats[i]);
+        }
+    }
+}
+
 START_TEST(opengl)
 {
     HWND hwnd;
@@ -1858,6 +1914,7 @@ START_TEST(opengl)
         }
 
         test_choosepixelformat();
+        test_wglChoosePixelFormatARB(hdc);
         test_debug_message_callback();
         test_setpixelformat(hdc);
         test_destroy(hdc);
diff --git a/dlls/winex11.drv/opengl.c b/dlls/winex11.drv/opengl.c
index e0e5849f010..eb78920a6a4 100644
--- a/dlls/winex11.drv/opengl.c
+++ b/dlls/winex11.drv/opengl.c
@@ -40,6 +40,7 @@
 #include "x11drv.h"
 #include "xcomposite.h"
 #include "winternl.h"
+#include "wine/heap.h"
 #include "wine/debug.h"
 
 #ifdef SONAME_LIBGL
@@ -2559,6 +2560,51 @@ static BOOL X11DRV_wglSetPbufferAttribARB( struct wgl_pbuffer *object, const int
     return ret;
 }
 
+struct choose_pixel_format_arb_formats
+{
+    int format;
+    int depth;
+};
+
+static int get_format_insert_position(int format, BYTE depth_bits, int format_count, PIXELFORMATDESCRIPTOR *last_pfd,
+        int *last_start, struct choose_pixel_format_arb_formats *formats, BYTE *depth)
+{
+    PIXELFORMATDESCRIPTOR pfd;
+    int i;
+
+    if (depth_bits)
+        return format_count;
+
+    memset(&pfd, 0, sizeof(pfd));
+    if (!describe_pixel_format(format, &pfd, TRUE))
+    {
+        ERR("describe_pixel_format failed.\n");
+        return format_count;
+    }
+    *depth = pfd.cDepthBits;
+    pfd.cDepthBits = 0;
+    pfd.cStencilBits = 0;
+
+    if (pfd.dwFlags & PFD_GENERIC_FORMAT)
+    {
+        *last_start = format_count;
+        memset(last_pfd, 0, sizeof(*last_pfd));
+        return format_count;
+    }
+    if (memcmp(last_pfd, &pfd, sizeof(pfd)))
+    {
+        *last_start = format_count;
+        *last_pfd = pfd;
+        return format_count;
+    }
+
+    for (i = *last_start; i < format_count; ++i)
+        if (*depth < formats[i].depth)
+            break;
+
+    return i;
+}
+
 /**
  * X11DRV_wglChoosePixelFormatARB
  *
@@ -2567,17 +2613,19 @@ static BOOL X11DRV_wglSetPbufferAttribARB( struct wgl_pbuffer *object, const int
 static BOOL X11DRV_wglChoosePixelFormatARB( HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList,
                                             UINT nMaxFormats, int *piFormats, UINT *nNumFormats )
 {
+    PIXELFORMATDESCRIPTOR last_onscreen_pfd, last_offscreen_pfd;
+    struct choose_pixel_format_arb_formats *formats;
+    int last_onscreen_start, last_offscreen_start;
+    int format, format_count, onscreen_count;
+    BYTE depth_bits = 0;
+    GLXFBConfig* cfgs;
+    DWORD dwFlags = 0;
     int attribs[256];
     int nAttribs = 0;
-    GLXFBConfig* cfgs;
     int nCfgs = 0;
-    int it;
+    BYTE depth;
     int fmt_id;
-    int start, end;
-    UINT pfmt_it = 0;
-    int run;
-    int i;
-    DWORD dwFlags = 0;
+    int it, i;
 
     TRACE("(%p, %p, %p, %d, %p, %p): hackish\n", hdc, piAttribIList, pfAttribFList, nMaxFormats, piFormats, nNumFormats);
     if (NULL != pfAttribFList) {
@@ -2621,6 +2669,10 @@ static BOOL X11DRV_wglChoosePixelFormatARB( HDC hdc, const int *piAttribIList, c
                 if(piAttribIList[i+1])
                     dwFlags |= PFD_SUPPORT_GDI;
                 break;
+            case WGL_DEPTH_BITS_ARB:
+                depth_bits = piAttribIList[i+1];
+                break;
+
         }
     }
 
@@ -2631,37 +2683,64 @@ static BOOL X11DRV_wglChoosePixelFormatARB( HDC hdc, const int *piAttribIList, c
         return GL_FALSE;
     }
 
-    /* Loop through all matching formats and check if they are suitable.
-     * Note that this function should at max return nMaxFormats different formats */
-    for(run=0; run < 2; run++)
+    if (!(formats = heap_alloc(nb_pixel_formats * sizeof(*formats))))
     {
-        for (it = 0; it < nCfgs && pfmt_it < nMaxFormats; ++it)
+        ERR("No memory.\n");
+        XFree(cfgs);
+        return GL_FALSE;
+    }
+    format_count = onscreen_count = 0;
+    last_onscreen_start = last_offscreen_start = 0;
+    memset(&last_onscreen_pfd, 0, sizeof(last_onscreen_pfd));
+    memset(&last_offscreen_pfd, 0, sizeof(last_offscreen_pfd));
+
+    /* Order formats so that onscreen formats go first. Then, if no depth bits requested,
+     * prioritize formats with smaller depth within the original sort order with respect to
+     * other attributes. */
+    for (it = 0; it < nCfgs; ++it)
+    {
+        if (pglXGetFBConfigAttrib(gdi_display, cfgs[it], GLX_FBCONFIG_ID, &fmt_id))
         {
-            if (pglXGetFBConfigAttrib(gdi_display, cfgs[it], GLX_FBCONFIG_ID, &fmt_id))
-            {
-                ERR("Failed to retrieve FBCONFIG_ID from GLXFBConfig, expect problems.\n");
-                continue;
-            }
+            ERR("Failed to retrieve FBCONFIG_ID from GLXFBConfig, expect problems.\n");
+            continue;
+        }
 
-            /* During the first run we only want onscreen formats and during the second only offscreen */
-            start = run == 1 ? nb_onscreen_formats : 0;
-            end = run == 1 ? nb_pixel_formats : nb_onscreen_formats;
+        for (i = 0; i < nb_pixel_formats; ++i)
+            if (pixel_formats[i].fmt_id == fmt_id)
+                break;
 
-            for (i = start; i < end; i++)
-            {
-                if (pixel_formats[i].fmt_id == fmt_id && (pixel_formats[i].dwFlags & dwFlags) == dwFlags)
-                {
-                    piFormats[pfmt_it++] = i + 1;
-                    TRACE("at %d/%d found FBCONFIG_ID 0x%x (%d)\n",
-                          it + 1, nCfgs, fmt_id, i + 1);
-                    break;
-                }
-            }
+        if (i == nb_pixel_formats)
+            continue;
+
+        format = i + 1;
+
+        if (format <= nb_onscreen_formats)
+        {
+            i = get_format_insert_position(format, depth_bits, onscreen_count, &last_onscreen_pfd,
+                    &last_onscreen_start, formats, &depth);
+            ++onscreen_count;
         }
+        else
+        {
+            i = get_format_insert_position(format, depth_bits, format_count, &last_offscreen_pfd,
+                    &last_offscreen_start, formats, &depth);
+        }
+
+        memmove(&formats[i + 1], &formats[i], sizeof(*formats) * (format_count - i));
+        formats[i].format = format;
+        formats[i].depth = depth;
+        ++format_count;
+
+        if (last_onscreen_start >= nMaxFormats)
+            break;
     }
 
-    *nNumFormats = pfmt_it;
+    *nNumFormats = min(nMaxFormats, format_count);
+    for (i = 0; i < *nNumFormats; ++i)
+        piFormats[i] = formats[i].format;
+
     /** free list */
+    heap_free(formats);
     XFree(cfgs);
     return GL_TRUE;
 }
-- 
2.29.2




More information about the wine-devel mailing list