Signed-off-by: Zhiyi Zhang zzhang@codeweavers.com --- dlls/winex11.drv/Makefile.in | 2 +- dlls/winex11.drv/display.c | 172 +++++++++++++++++++++++++++++++++++ dlls/winex11.drv/x11drv.h | 22 +++++ dlls/winex11.drv/xinerama.c | 25 +++++ 4 files changed, 220 insertions(+), 1 deletion(-)
diff --git a/dlls/winex11.drv/Makefile.in b/dlls/winex11.drv/Makefile.in index 9ca2fc6efe..72ab1bb77b 100644 --- a/dlls/winex11.drv/Makefile.in +++ b/dlls/winex11.drv/Makefile.in @@ -1,5 +1,5 @@ MODULE = winex11.drv -IMPORTS = uuid user32 gdi32 advapi32 +IMPORTS = uuid user32 gdi32 advapi32 ntoskrnl setupapi DELAYIMPORTS = comctl32 ole32 shell32 imm32 EXTRAINCL = $(X_CFLAGS) EXTRALIBS = $(X_LIBS) $(X_EXTRA_LIBS) diff --git a/dlls/winex11.drv/display.c b/dlls/winex11.drv/display.c index 7d81de09ac..c45a0a4657 100644 --- a/dlls/winex11.drv/display.c +++ b/dlls/winex11.drv/display.c @@ -26,11 +26,43 @@ #include "winbase.h" #include "winuser.h" #include "winreg.h" +#include "initguid.h" +#include "devguid.h" +#include "devpkey.h" +#include "setupapi.h" +#define WIN32_NO_STATUS +#include "winternl.h" +#include "ddk/ntddk.h" #include "wine/debug.h" +#include "wine/unicode.h" #include "x11drv.h"
WINE_DEFAULT_DEBUG_CHANNEL(x11drv);
+static const WCHAR driver_descW[] = {'D','r','i','v','e','r','D','e','s','c',0}; +static const WCHAR graphics_driverW[] = {'G','r','a','p','h','i','c','s','D','r','i','v','e','r',0}; +static const WCHAR video_idW[] = {'V','i','d','e','o','I','D',0}; +static const WCHAR guid_fmtW[] = { + '{','%','0','8','x','-','%','0','4','x','-','%','0','4','x','-','%','0','2','x','%','0','2','x','-', + '%','0','2','x','%','0','2','x','%','0','2','x','%','0','2','x','%','0','2','x','%','0','2','x','}',0}; +static const WCHAR gpu_instance_fmtW[] = { + 'P','C','I','\', + 'V','E','N','_','%','0','4','X','&', + 'D','E','V','_','%','0','4','X','&', + 'S','U','B','S','Y','S','_','%','0','8','X','&', + 'R','E','V','_','%','0','2','X','\', + '%','0','8','X',0}; +static const WCHAR gpu_hardware_id_fmtW[] = { + 'P','C','I','\', + 'V','E','N','_','%','0','4','X','&', + 'D','E','V','_','%','0','4','X','&', + 'S','U','B','S','Y','S','_','0','0','0','0','0','0','0','0','&', + 'R','E','V','_','0','0',0}; +static const WCHAR winex11_drvW[] = { + 'C',':','\', + 'W','i','n','d','o','w','s','\', + 'S','y','s','t','e','m','3','2','\', + 'w','i','n','e','x','1','1','.','d','r','v',0}; static const WCHAR video_keyW[] = { 'H','A','R','D','W','A','R','E','\', 'D','E','V','I','C','E','M','A','P','\', @@ -47,8 +79,125 @@ void X11DRV_DisplayDevices_SetHandler(const struct x11drv_display_device_handler } }
+/* Initialize a GPU instance */ +static BOOL X11DRV_InitGpu(HDEVINFO devinfo, const struct x11drv_gpu *gpu, INT gpu_index) +{ + static const BOOL present = TRUE; + SP_DEVINFO_DATA device_data = {sizeof(device_data)}; + WCHAR instanceW[MAX_PATH]; + WCHAR bufferW[1024]; + HKEY hkey = NULL; + GUID guid; + INT written; + DWORD size; + BOOL ret = FALSE; + + sprintfW(instanceW, gpu_instance_fmtW, gpu->vendor_id, gpu->device_id, gpu->subsys_id, gpu->revision_id, gpu_index); + if (!SetupDiOpenDeviceInfoW(devinfo, instanceW, NULL, 0, &device_data)) + { + SetupDiCreateDeviceInfoW(devinfo, instanceW, &GUID_DEVCLASS_DISPLAY, gpu->name, NULL, 0, &device_data); + if (!SetupDiRegisterDeviceInfo(devinfo, &device_data, 0, NULL, NULL, NULL)) + goto fail; + } + + /* Write HaredwareID registry property */ + written = sprintfW(bufferW, gpu_hardware_id_fmtW, gpu->vendor_id, gpu->device_id); + bufferW[written + 1] = 0; + if (!SetupDiSetDeviceRegistryPropertyW(devinfo, &device_data, SPDRP_HARDWAREID, (const BYTE *)bufferW, + (written + 2) * sizeof(WCHAR))) + goto fail; + + /* Write DEVPKEY_Device_IsPresent property */ + if (!SetupDiSetDevicePropertyW(devinfo, &device_data, &DEVPKEY_Device_IsPresent, DEVPROP_TYPE_BOOLEAN, + (const BYTE *)&present, sizeof(present), 0)) + goto fail; + + /* Open driver key. + * This is where HKLM\System\CurrentControlSet\Control\Video{GPU GUID}{Adapter Index} links to */ + hkey = SetupDiOpenDevRegKey(devinfo, &device_data, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_ALL_ACCESS); + if (hkey == INVALID_HANDLE_VALUE) + hkey = SetupDiCreateDevRegKeyW(devinfo, &device_data, DICS_FLAG_GLOBAL, 0, DIREG_DRV, NULL, NULL); + + /* Write GraphicsDriver value */ + if (RegSetValueExW(hkey, graphics_driverW, 0, REG_SZ, (const BYTE *)winex11_drvW, sizeof(winex11_drvW))) + goto fail; + + /* Write DriverDesc value */ + if (RegSetValueExW(hkey, driver_descW, 0, REG_SZ, (const BYTE *)gpu->name, + (strlenW(gpu->name) + 1) * sizeof(WCHAR))) + goto fail; + RegCloseKey(hkey); + hkey = NULL; + + /* Write GUID in VideoID in .../instance/Device Parameters, reuse the GUID if it's existent */ + hkey = SetupDiOpenDevRegKey(devinfo, &device_data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_ALL_ACCESS); + if (hkey == INVALID_HANDLE_VALUE) + hkey = SetupDiCreateDevRegKeyW(devinfo, &device_data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, NULL, NULL); + + size = sizeof(bufferW); + bufferW[0] = 0; + if (RegQueryValueExW(hkey, video_idW, 0, NULL, (BYTE *)bufferW, &size)) + { + ExUuidCreate(&guid); + sprintfW(bufferW, guid_fmtW, guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], + guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); + if (RegSetValueExW(hkey, video_idW, 0, REG_SZ, (const BYTE *)bufferW, (strlenW(bufferW) + 1) * sizeof(WCHAR))) + goto fail; + } + + ret = TRUE; +fail: + RegCloseKey(hkey); + if (!ret) + ERR("Failed to initialize GPU\n"); + return ret; +} + +static void prepare_devices(void) +{ + static const BOOL not_present = FALSE; + SP_DEVINFO_DATA device_data = {sizeof(device_data)}; + HDEVINFO devinfo; + DWORD i = 0; + + /* Set all GPUs as not present. We can't simply delete them because we need to keep the GUID consistent with + * each initialization run. We clean up non present GPUs at the end of initialization */ + devinfo = SetupDiGetClassDevsW(&GUID_DEVCLASS_DISPLAY, NULL, NULL, 0); + while (SetupDiEnumDeviceInfo(devinfo, i++, &device_data)) + { + if (!SetupDiSetDevicePropertyW(devinfo, &device_data, &DEVPKEY_Device_IsPresent, DEVPROP_TYPE_BOOLEAN, + (const BYTE *)¬_present, sizeof(not_present), 0)) + ERR("Failed to set GPU present property\n"); + } + SetupDiDestroyDeviceInfoList(devinfo); +} + +static void cleanup_devices(void) +{ + SP_DEVINFO_DATA device_data = {sizeof(device_data)}; + HDEVINFO devinfo; + DWORD type; + DWORD i = 0; + BOOL present; + + devinfo = SetupDiGetClassDevsW(&GUID_DEVCLASS_DISPLAY, NULL, NULL, 0); + while (SetupDiEnumDeviceInfo(devinfo, i++, &device_data)) + { + present = FALSE; + SetupDiGetDevicePropertyW(devinfo, &device_data, &DEVPKEY_Device_IsPresent, &type, (BYTE *)&present, + sizeof(present), NULL, 0); + if (!present && !SetupDiRemoveDevice(devinfo, &device_data)) + ERR("Failed to remove GPU\n"); + } + SetupDiDestroyDeviceInfoList(devinfo); +} + void X11DRV_DisplayDevices_Init(void) { + struct x11drv_gpu *gpus = NULL; + INT gpu_count; + INT gpu; + HDEVINFO gpu_devinfo = NULL; HKEY video_hkey = NULL; DWORD disposition = 0; BOOL success = FALSE; @@ -66,9 +215,32 @@ void X11DRV_DisplayDevices_Init(void) goto fail; }
+ /* FIXME: + * Currently SetupDiGetClassDevsW with DIGCF_PRESENT is unsupported, So we need to clean up not present devices in + * case application use SetupDiGetClassDevsW to enumerate devices. Wrong devices could exist in registry as a result + * of prefix copying or having devices unplugged. But then we couldn't simply delete GPUs because we need to retain + * the same GUID for the same GPU. */ + prepare_devices(); + + gpu_devinfo = SetupDiCreateDeviceInfoList(&GUID_DEVCLASS_DISPLAY, NULL); + + /* Initialize GPUs */ + if (!handler.pGetGpus(&gpus, &gpu_count)) + goto fail; + + for (gpu = 0; gpu < gpu_count; gpu++) + { + if (!X11DRV_InitGpu(gpu_devinfo, &gpus[gpu], gpu)) + goto fail; + } + success = TRUE; fail: + cleanup_devices(); + SetupDiDestroyDeviceInfoList(gpu_devinfo); RegCloseKey(video_hkey); + if (gpus) + handler.pFreeGpus(gpus); if (!success) ERR("Failed to initialize display devices\n"); } diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h index fbc5a104f6..e50158d282 100644 --- a/dlls/winex11.drv/x11drv.h +++ b/dlls/winex11.drv/x11drv.h @@ -667,6 +667,20 @@ void X11DRV_XRandR_Init(void) DECLSPEC_HIDDEN;
/* X11 display device handler. Used to initialize display device registry data */
+/* Represent a physical GPU in the PCI slots */ +struct x11drv_gpu +{ + /* ID to uniquely identify a GPU in handler */ + ULONG_PTR id; + /* Name */ + WCHAR name[128]; + /* PCI ID */ + UINT vendor_id; + UINT device_id; + UINT subsys_id; + UINT revision_id; +}; + /* Required functions for display device registry initialization */ struct x11drv_display_device_handler { @@ -675,6 +689,14 @@ struct x11drv_display_device_handler
/* Higher priority can override handlers with lower proprity */ INT priority; + + /* pGetGpus will be called to get a list of GPUs. First GPU has to be where the primary adapter is. + * + * Return FALSE on failure with parameters unchanged */ + BOOL (*pGetGpus)(struct x11drv_gpu **gpus, int *count); + + /* pFreeGpus will be called to free a GPU list from pGetGpus */ + void (*pFreeGpus)(struct x11drv_gpu *gpus); };
extern void X11DRV_DisplayDevices_SetHandler(const struct x11drv_display_device_handler *handler) DECLSPEC_HIDDEN; diff --git a/dlls/winex11.drv/xinerama.c b/dlls/winex11.drv/xinerama.c index 35dd36049b..520f59f93d 100644 --- a/dlls/winex11.drv/xinerama.c +++ b/dlls/winex11.drv/xinerama.c @@ -201,6 +201,29 @@ RECT get_primary_monitor_rect(void) return get_primary()->rcMonitor; }
+static BOOL xinerama_get_gpus( struct x11drv_gpu **new_gpus, int *count ) +{ + static const WCHAR wine_gpuW[] = {'W','i','n','e',' ','G','P','U',0}; + struct x11drv_gpu *gpus; + + /* Xinerama has no support for GPU, faking one */ + gpus = heap_calloc( 1, sizeof(*gpus) ); + if (!gpus) + return FALSE; + + strcpyW( gpus[0].name, wine_gpuW ); + + *new_gpus = gpus; + *count = 1; + + return TRUE; +} + +static void xinerama_free_gpus( struct x11drv_gpu *gpus ) +{ + heap_free( gpus ); +} + void xinerama_init( unsigned int width, unsigned int height ) { struct x11drv_display_device_handler handler; @@ -239,6 +262,8 @@ void xinerama_init( unsigned int width, unsigned int height )
strcpy( handler.name, "Xinerama" ); handler.priority = 100; + handler.pGetGpus = xinerama_get_gpus; + handler.pFreeGpus = xinerama_free_gpus; X11DRV_DisplayDevices_SetHandler( &handler );
TRACE( "virtual size: %s primary: %s\n",
On Wed, May 22, 2019 at 05:16:43PM +0800, Zhiyi Zhang wrote:
@@ -66,9 +215,32 @@ void X11DRV_DisplayDevices_Init(void) goto fail; }
- /* FIXME:
* Currently SetupDiGetClassDevsW with DIGCF_PRESENT is unsupported, So we need to clean up not present devices in
* case application use SetupDiGetClassDevsW to enumerate devices. Wrong devices could exist in registry as a result
* of prefix copying or having devices unplugged. But then we couldn't simply delete GPUs because we need to retain
* the same GUID for the same GPU. */
Would things be easier if DIGCF_PRESENT was supported? If so, should we implement it first?
Huw.
On 5/22/19 7:49 PM, Huw Davies wrote:
On Wed, May 22, 2019 at 05:16:43PM +0800, Zhiyi Zhang wrote:
@@ -66,9 +215,32 @@ void X11DRV_DisplayDevices_Init(void) goto fail; }
- /* FIXME:
* Currently SetupDiGetClassDevsW with DIGCF_PRESENT is unsupported, So we need to clean up not present devices in
* case application use SetupDiGetClassDevsW to enumerate devices. Wrong devices could exist in registry as a result
* of prefix copying or having devices unplugged. But then we couldn't simply delete GPUs because we need to retain
* the same GUID for the same GPU. */
Would things be easier if DIGCF_PRESENT was supported? If so, should we implement it first?
Huw.
I don't think it will make it much easier .
The problem is that DIGCF_PRESENT is checking whether a device is present on the system. I assume it's querying through layers of device driver or maybe PnP to know that. Since wine currently doesn't support device driver nor should it support it, I think it best ignore DIGCF_PRESENT for now and treat all devices in SetupAPI as actually present.
On Wed, May 22, 2019 at 11:17 AM Zhiyi Zhang zzhang@codeweavers.com wrote:
+static BOOL xinerama_get_gpus( struct x11drv_gpu **new_gpus, int *count ) +{
- static const WCHAR wine_gpuW[] = {'W','i','n','e',' ','G','P','U',0};
- struct x11drv_gpu *gpus;
- /* Xinerama has no support for GPU, faking one */
- gpus = heap_calloc( 1, sizeof(*gpus) );
- if (!gpus)
return FALSE;
- strcpyW( gpus[0].name, wine_gpuW );
- *new_gpus = gpus;
- *count = 1;
- return TRUE;
+}
Is it really required to create fake GPU data? Do you see a path forward to improve this? In order to match the Windows behavior the GPU name should be consistent with the name returned by other APIs, i.e. OpenGL, Vulkan and Direct3D. Ideally, the GPU name should use the GPU database from wined3d. In the long term, it it might be needed to move the GPU database outside wined3d.
On 5/24/2019 12:39 AM, Józef Kucia wrote:
On Wed, May 22, 2019 at 11:17 AM Zhiyi Zhang zzhang@codeweavers.com wrote:
+static BOOL xinerama_get_gpus( struct x11drv_gpu **new_gpus, int *count ) +{
- static const WCHAR wine_gpuW[] = {'W','i','n','e',' ','G','P','U',0};
- struct x11drv_gpu *gpus;
- /* Xinerama has no support for GPU, faking one */
- gpus = heap_calloc( 1, sizeof(*gpus) );
- if (!gpus)
return FALSE;
- strcpyW( gpus[0].name, wine_gpuW );
- *new_gpus = gpus;
- *count = 1;
- return TRUE;
+}
Is it really required to create fake GPU data? Do you see a path forward to improve this? In order to match the Windows behavior the GPU name should be consistent with the name returned by other APIs, i.e. OpenGL, Vulkan and Direct3D. Ideally, the GPU name should use the GPU database from wined3d. In the long term, it it might be needed to move the GPU database outside wined3d.
Yes. This GPU data is needed to initialize PCI GPU registry keys. And an adapter registry key links to the GPU registry key.
In the future, we can use GPU database and OpenGL context to guess the primary GPU name for Xinerama. XRandR should report GPU name correctly, although I am not sure they will always be consistent with other APIs. And Xinerama will only be used when desktop mode is used or XRandR 1.4 is unavailable.
To reach the ideal state where all GPU names are consistent, we face the problem when trying to implement LUID support, which is that there is currently no way to identify each GPU across different APIs. Anyway, it can't be worse than current implementation though.