2 * DirectDraw XVidMode interface
4 * Copyright 2001 TransGaming Technologies, Inc.
5 * Copyright 2020 Zhiyi Zhang for CodeWeavers
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
36 #ifdef HAVE_X11_EXTENSIONS_XF86VMODE_H
37 #include <X11/extensions/xf86vmode.h>
39 #ifdef HAVE_X11_EXTENSIONS_XF86VMPROTO_H
40 #include <X11/extensions/xf86vmproto.h>
45 #include "wine/debug.h"
47 WINE_DEFAULT_DEBUG_CHANNEL(xvidmode
);
49 #ifdef SONAME_LIBXXF86VM
51 extern BOOL usexvidmode
;
53 static int xf86vm_event
, xf86vm_error
, xf86vm_major
, xf86vm_minor
;
55 #ifdef X_XF86VidModeSetGammaRamp
56 static int xf86vm_gammaramp_size
;
57 static BOOL xf86vm_use_gammaramp
;
58 #endif /* X_XF86VidModeSetGammaRamp */
60 #define MAKE_FUNCPTR(f) static typeof(f) * p##f;
61 MAKE_FUNCPTR(XF86VidModeGetAllModeLines
)
62 MAKE_FUNCPTR(XF86VidModeGetModeLine
)
63 MAKE_FUNCPTR(XF86VidModeLockModeSwitch
)
64 MAKE_FUNCPTR(XF86VidModeQueryExtension
)
65 MAKE_FUNCPTR(XF86VidModeQueryVersion
)
66 MAKE_FUNCPTR(XF86VidModeSetViewPort
)
67 MAKE_FUNCPTR(XF86VidModeSwitchToMode
)
68 #ifdef X_XF86VidModeSetGamma
69 MAKE_FUNCPTR(XF86VidModeGetGamma
)
70 MAKE_FUNCPTR(XF86VidModeSetGamma
)
72 #ifdef X_XF86VidModeSetGammaRamp
73 MAKE_FUNCPTR(XF86VidModeGetGammaRamp
)
74 MAKE_FUNCPTR(XF86VidModeGetGammaRampSize
)
75 MAKE_FUNCPTR(XF86VidModeSetGammaRamp
)
79 static int XVidModeErrorHandler(Display
*dpy
, XErrorEvent
*event
, void *arg
)
84 /* XF86VidMode display settings handler */
85 static BOOL
xf86vm_get_id(const WCHAR
*device_name
, BOOL is_primary
, x11drv_settings_id
*id
)
87 /* XVidMode only supports changing the primary adapter settings.
88 * For non-primary adapters, an id is still provided but getting
89 * and changing non-primary adapters' settings will be ignored. */
90 id
->id
= is_primary
? 1 : 0;
94 static void add_xf86vm_mode(DEVMODEW
*mode
, DWORD depth
, const XF86VidModeModeInfo
*mode_info
)
96 mode
->dmSize
= sizeof(*mode
);
97 mode
->dmDriverExtra
= sizeof(mode_info
);
98 mode
->dmFields
= DM_DISPLAYORIENTATION
| DM_BITSPERPEL
| DM_PELSWIDTH
| DM_PELSHEIGHT
| DM_DISPLAYFLAGS
;
99 if (mode_info
->htotal
&& mode_info
->vtotal
)
101 mode
->dmFields
|= DM_DISPLAYFREQUENCY
;
102 mode
->dmDisplayFrequency
= mode_info
->dotclock
* 1000 / (mode_info
->htotal
* mode_info
->vtotal
);
104 mode
->dmDisplayOrientation
= DMDO_DEFAULT
;
105 mode
->dmBitsPerPel
= depth
;
106 mode
->dmPelsWidth
= mode_info
->hdisplay
;
107 mode
->dmPelsHeight
= mode_info
->vdisplay
;
108 mode
->dmDisplayFlags
= 0;
109 memcpy((BYTE
*)mode
+ sizeof(*mode
), &mode_info
, sizeof(mode_info
));
112 static BOOL
xf86vm_get_modes(x11drv_settings_id id
, DWORD flags
, DEVMODEW
**new_modes
, UINT
*mode_count
)
114 INT xf86vm_mode_idx
, xf86vm_mode_count
;
115 XF86VidModeModeInfo
**xf86vm_modes
;
116 UINT depth_idx
, mode_idx
= 0;
117 DEVMODEW
*modes
, *mode
;
122 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
123 ret
= pXF86VidModeGetAllModeLines(gdi_display
, DefaultScreen(gdi_display
), &xf86vm_mode_count
, &xf86vm_modes
);
124 if (X11DRV_check_error() || !ret
|| !xf86vm_mode_count
)
127 /* Put a XF86VidModeModeInfo ** at the start to store the XF86VidMode modes pointer */
128 size
= sizeof(XF86VidModeModeInfo
**);
129 /* Display modes in different color depth, with a XF86VidModeModeInfo * at the end of each
130 * DEVMODEW as driver private data */
131 size
+= (xf86vm_mode_count
* DEPTH_COUNT
) * (sizeof(DEVMODEW
) + sizeof(XF86VidModeModeInfo
*));
132 ptr
= calloc(1, size
);
135 RtlSetLastWin32Error( ERROR_NOT_ENOUGH_MEMORY
);
139 memcpy(ptr
, &xf86vm_modes
, sizeof(xf86vm_modes
));
140 modes
= (DEVMODEW
*)(ptr
+ sizeof(xf86vm_modes
));
142 for (depth_idx
= 0; depth_idx
< DEPTH_COUNT
; ++depth_idx
)
144 for (xf86vm_mode_idx
= 0; xf86vm_mode_idx
< xf86vm_mode_count
; ++xf86vm_mode_idx
)
146 mode
= (DEVMODEW
*)((BYTE
*)modes
+ (sizeof(DEVMODEW
) + sizeof(XF86VidModeModeInfo
*)) * mode_idx
++);
147 add_xf86vm_mode(mode
, depths
[depth_idx
], xf86vm_modes
[xf86vm_mode_idx
]);
152 *mode_count
= mode_idx
;
156 static void xf86vm_free_modes(DEVMODEW
*modes
)
158 XF86VidModeModeInfo
**xf86vm_modes
;
162 assert(modes
[0].dmDriverExtra
== sizeof(XF86VidModeModeInfo
*));
163 memcpy(&xf86vm_modes
, (BYTE
*)modes
- sizeof(xf86vm_modes
), sizeof(xf86vm_modes
));
169 static BOOL
xf86vm_get_current_mode(x11drv_settings_id id
, DEVMODEW
*mode
)
171 XF86VidModeModeLine xf86vm_mode
;
175 mode
->dmFields
= DM_DISPLAYORIENTATION
| DM_BITSPERPEL
| DM_PELSWIDTH
| DM_PELSHEIGHT
|
176 DM_DISPLAYFLAGS
| DM_DISPLAYFREQUENCY
| DM_POSITION
;
177 mode
->dmDisplayOrientation
= DMDO_DEFAULT
;
178 mode
->dmDisplayFlags
= 0;
179 mode
->dmPosition
.x
= 0;
180 mode
->dmPosition
.y
= 0;
184 FIXME("Non-primary adapters are unsupported.\n");
185 mode
->dmBitsPerPel
= 0;
186 mode
->dmPelsWidth
= 0;
187 mode
->dmPelsHeight
= 0;
188 mode
->dmDisplayFrequency
= 0;
192 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
193 ret
= pXF86VidModeGetModeLine(gdi_display
, DefaultScreen(gdi_display
), &dotclock
, &xf86vm_mode
);
194 if (X11DRV_check_error() || !ret
)
197 mode
->dmBitsPerPel
= screen_bpp
;
198 mode
->dmPelsWidth
= xf86vm_mode
.hdisplay
;
199 mode
->dmPelsHeight
= xf86vm_mode
.vdisplay
;
200 if (xf86vm_mode
.htotal
&& xf86vm_mode
.vtotal
)
201 mode
->dmDisplayFrequency
= dotclock
* 1000 / (xf86vm_mode
.htotal
* xf86vm_mode
.vtotal
);
203 mode
->dmDisplayFrequency
= 0;
205 if (xf86vm_mode
.privsize
)
206 XFree(xf86vm_mode
.private);
210 static LONG
xf86vm_set_current_mode(x11drv_settings_id id
, const DEVMODEW
*mode
)
212 XF86VidModeModeInfo
*xf86vm_mode
;
217 FIXME("Non-primary adapters are unsupported.\n");
218 return DISP_CHANGE_SUCCESSFUL
;
221 if (is_detached_mode(mode
))
223 FIXME("Detaching adapters is unsupported.\n");
224 return DISP_CHANGE_SUCCESSFUL
;
227 if (mode
->dmFields
& DM_BITSPERPEL
&& mode
->dmBitsPerPel
!= screen_bpp
)
228 WARN("Cannot change screen bit depth from %dbits to %dbits!\n",
229 screen_bpp
, (int)mode
->dmBitsPerPel
);
231 assert(mode
->dmDriverExtra
== sizeof(XF86VidModeModeInfo
*));
232 memcpy(&xf86vm_mode
, (BYTE
*)mode
+ sizeof(*mode
), sizeof(xf86vm_mode
));
233 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
234 ret
= pXF86VidModeSwitchToMode(gdi_display
, DefaultScreen(gdi_display
), xf86vm_mode
);
235 if (X11DRV_check_error() || !ret
)
236 return DISP_CHANGE_FAILED
;
237 #if 0 /* it is said that SetViewPort causes problems with some X servers */
238 pXF86VidModeSetViewPort(gdi_display
, DefaultScreen(gdi_display
), 0, 0);
240 XWarpPointer(gdi_display
, None
, DefaultRootWindow(gdi_display
), 0, 0, 0, 0, 0, 0);
243 return DISP_CHANGE_SUCCESSFUL
;
246 void X11DRV_XF86VM_Init(void)
248 struct x11drv_settings_handler xf86vm_handler
;
249 void *xvidmode_handle
;
252 if (xf86vm_major
) return; /* already initialized? */
254 xvidmode_handle
= dlopen(SONAME_LIBXXF86VM
, RTLD_NOW
);
255 if (!xvidmode_handle
)
257 TRACE("Unable to open %s, XVidMode disabled\n", SONAME_LIBXXF86VM
);
262 #define LOAD_FUNCPTR(f) \
263 if((p##f = dlsym(xvidmode_handle, #f)) == NULL) goto sym_not_found
264 LOAD_FUNCPTR(XF86VidModeGetAllModeLines
);
265 LOAD_FUNCPTR(XF86VidModeGetModeLine
);
266 LOAD_FUNCPTR(XF86VidModeLockModeSwitch
);
267 LOAD_FUNCPTR(XF86VidModeQueryExtension
);
268 LOAD_FUNCPTR(XF86VidModeQueryVersion
);
269 LOAD_FUNCPTR(XF86VidModeSetViewPort
);
270 LOAD_FUNCPTR(XF86VidModeSwitchToMode
);
271 #ifdef X_XF86VidModeSetGamma
272 LOAD_FUNCPTR(XF86VidModeGetGamma
);
273 LOAD_FUNCPTR(XF86VidModeSetGamma
);
275 #ifdef X_XF86VidModeSetGammaRamp
276 LOAD_FUNCPTR(XF86VidModeGetGammaRamp
);
277 LOAD_FUNCPTR(XF86VidModeGetGammaRampSize
);
278 LOAD_FUNCPTR(XF86VidModeSetGammaRamp
);
282 /* see if XVidMode is available */
283 if (!pXF86VidModeQueryExtension(gdi_display
, &xf86vm_event
, &xf86vm_error
)) return;
285 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
286 ok
= pXF86VidModeQueryVersion(gdi_display
, &xf86vm_major
, &xf86vm_minor
);
287 if (X11DRV_check_error() || !ok
) return;
289 #ifdef X_XF86VidModeSetGammaRamp
290 if (xf86vm_major
> 2 || (xf86vm_major
== 2 && xf86vm_minor
>= 1))
292 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
293 pXF86VidModeGetGammaRampSize(gdi_display
, DefaultScreen(gdi_display
),
294 &xf86vm_gammaramp_size
);
295 if (X11DRV_check_error()) xf86vm_gammaramp_size
= 0;
296 TRACE("Gamma ramp size %d.\n", xf86vm_gammaramp_size
);
297 if (xf86vm_gammaramp_size
>= GAMMA_RAMP_SIZE
)
298 xf86vm_use_gammaramp
= TRUE
;
300 #endif /* X_XF86VidModeSetGammaRamp */
305 xf86vm_handler
.name
= "XF86VidMode";
306 xf86vm_handler
.priority
= 100;
307 xf86vm_handler
.get_id
= xf86vm_get_id
;
308 xf86vm_handler
.get_modes
= xf86vm_get_modes
;
309 xf86vm_handler
.free_modes
= xf86vm_free_modes
;
310 xf86vm_handler
.get_current_mode
= xf86vm_get_current_mode
;
311 xf86vm_handler
.set_current_mode
= xf86vm_set_current_mode
;
312 X11DRV_Settings_SetHandler(&xf86vm_handler
);
316 TRACE("Unable to load function pointers from %s, XVidMode disabled\n", SONAME_LIBXXF86VM
);
317 dlclose(xvidmode_handle
);
318 xvidmode_handle
= NULL
;
322 /***** GAMMA CONTROL *****/
323 /* (only available in XF86VidMode 2.x) */
325 #ifdef X_XF86VidModeSetGamma
327 static void GenerateRampFromGamma(WORD ramp
[GAMMA_RAMP_SIZE
], float gamma
)
329 float r_gamma
= 1/gamma
;
331 TRACE("gamma is %f\n", r_gamma
);
332 for (i
=0; i
<GAMMA_RAMP_SIZE
; i
++)
333 ramp
[i
] = pow(i
/255.0, r_gamma
) * 65535.0;
336 static BOOL
ComputeGammaFromRamp(WORD ramp
[GAMMA_RAMP_SIZE
], float *gamma
)
338 float r_x
, r_y
, r_lx
, r_ly
, r_d
, r_v
, r_e
, g_avg
, g_min
, g_max
;
339 unsigned i
, f
, l
, g_n
, c
;
343 ERR("inverted or flat gamma ramp (%d->%d), rejected\n", f
, l
);
347 g_min
= g_max
= g_avg
= 0.0;
348 /* check gamma ramp entries to estimate the gamma */
349 TRACE("analyzing gamma ramp (%d->%d)\n", f
, l
);
350 for (i
=1, g_n
=0; i
<255; i
++) {
351 if (ramp
[i
] < f
|| ramp
[i
] > l
) {
352 ERR("strange gamma ramp ([%d]=%d for %d->%d), rejected\n", i
, ramp
[i
], f
, l
);
356 if (!c
) continue; /* avoid log(0) */
358 /* normalize entry values into 0..1 range */
359 r_x
= i
/255.0; r_y
= c
/ r_d
;
360 /* compute logarithms of values */
361 r_lx
= log(r_x
); r_ly
= log(r_y
);
362 /* compute gamma for this entry */
364 /* compute differential (error estimate) for this entry */
365 /* some games use table-based logarithms that magnifies the error by 128 */
366 r_e
= -r_lx
* 128 / (c
* r_lx
* r_lx
);
368 /* compute min & max while compensating for estimated error */
369 if (!g_n
|| g_min
> (r_v
+ r_e
)) g_min
= r_v
+ r_e
;
370 if (!g_n
|| g_max
< (r_v
- r_e
)) g_max
= r_v
- r_e
;
375 /* TRACE("[%d]=%d, gamma=%f, error=%f\n", i, ramp[i], r_v, r_e); */
378 ERR("no gamma data, shouldn't happen\n");
382 TRACE("low bias is %d, high is %d, gamma is %5.3f\n", f
, 65535-l
, g_avg
);
383 /* the bias could be because the app wanted something like a "red shift"
384 * like when you're hit in Quake, but XVidMode doesn't support it,
385 * so we have to reject a significant bias */
386 if (f
&& f
> (pow(1/255.0, g_avg
) * 65536.0)) {
387 ERR("low-biased gamma ramp (%d), rejected\n", f
);
390 /* check that the gamma is reasonably uniform across the ramp */
391 if (g_max
- g_min
> 12.8) {
392 ERR("ramp not uniform (max=%f, min=%f, avg=%f), rejected\n", g_max
, g_min
, g_avg
);
395 /* check that the gamma is not too bright */
397 ERR("too bright gamma ( %5.3f), rejected\n", g_avg
);
400 /* ok, now we're pretty sure we can set the desired gamma ramp,
406 #endif /* X_XF86VidModeSetGamma */
408 /* Hmm... should gamma control be available in desktop mode or not?
409 * I'll assume that it should */
411 #ifdef X_XF86VidModeSetGammaRamp
412 static void interpolate_gamma_ramp(WORD
*dst_r
, WORD
*dst_g
, WORD
*dst_b
, unsigned int dst_size
,
413 const WORD
*src_r
, const WORD
*src_g
, const WORD
*src_b
, unsigned int src_size
)
415 double position
, distance
;
416 unsigned int dst_i
, src_i
;
418 for (dst_i
= 0; dst_i
< dst_size
; ++dst_i
)
420 position
= dst_i
* (src_size
- 1) / (double)(dst_size
- 1);
423 if (src_i
+ 1 < src_size
)
425 distance
= position
- src_i
;
427 dst_r
[dst_i
] = (1.0 - distance
) * src_r
[src_i
] + distance
* src_r
[src_i
+ 1] + 0.5;
428 dst_g
[dst_i
] = (1.0 - distance
) * src_g
[src_i
] + distance
* src_g
[src_i
+ 1] + 0.5;
429 dst_b
[dst_i
] = (1.0 - distance
) * src_b
[src_i
] + distance
* src_b
[src_i
+ 1] + 0.5;
433 dst_r
[dst_i
] = src_r
[src_i
];
434 dst_g
[dst_i
] = src_g
[src_i
];
435 dst_b
[dst_i
] = src_b
[src_i
];
440 static BOOL
xf86vm_get_gamma_ramp(struct x11drv_gamma_ramp
*ramp
)
442 WORD
*red
, *green
, *blue
;
445 if (xf86vm_gammaramp_size
== GAMMA_RAMP_SIZE
)
453 if (!(red
= calloc(xf86vm_gammaramp_size
, 3 * sizeof(*red
))))
455 green
= red
+ xf86vm_gammaramp_size
;
456 blue
= green
+ xf86vm_gammaramp_size
;
459 ret
= pXF86VidModeGetGammaRamp(gdi_display
, DefaultScreen(gdi_display
),
460 xf86vm_gammaramp_size
, red
, green
, blue
);
461 if (ret
&& red
!= ramp
->red
)
462 interpolate_gamma_ramp(ramp
->red
, ramp
->green
, ramp
->blue
, GAMMA_RAMP_SIZE
,
463 red
, green
, blue
, xf86vm_gammaramp_size
);
464 if (red
!= ramp
->red
)
469 static BOOL
xf86vm_set_gamma_ramp(struct x11drv_gamma_ramp
*ramp
)
471 WORD
*red
, *green
, *blue
;
474 if (xf86vm_gammaramp_size
== GAMMA_RAMP_SIZE
)
482 if (!(red
= calloc(xf86vm_gammaramp_size
, 3 * sizeof(*red
))))
484 green
= red
+ xf86vm_gammaramp_size
;
485 blue
= green
+ xf86vm_gammaramp_size
;
487 interpolate_gamma_ramp(red
, green
, blue
, xf86vm_gammaramp_size
,
488 ramp
->red
, ramp
->green
, ramp
->blue
, GAMMA_RAMP_SIZE
);
491 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
492 ret
= pXF86VidModeSetGammaRamp(gdi_display
, DefaultScreen(gdi_display
),
493 xf86vm_gammaramp_size
, red
, green
, blue
);
494 if (ret
) XSync( gdi_display
, FALSE
);
495 if (X11DRV_check_error()) ret
= FALSE
;
497 if (red
!= ramp
->red
)
503 static BOOL
X11DRV_XF86VM_GetGammaRamp(struct x11drv_gamma_ramp
*ramp
)
505 #ifdef X_XF86VidModeSetGamma
506 XF86VidModeGamma gamma
;
508 if (xf86vm_major
< 2) return FALSE
; /* no gamma control */
509 #ifdef X_XF86VidModeSetGammaRamp
510 if (xf86vm_use_gammaramp
)
511 return xf86vm_get_gamma_ramp(ramp
);
513 if (pXF86VidModeGetGamma(gdi_display
, DefaultScreen(gdi_display
), &gamma
))
515 GenerateRampFromGamma(ramp
->red
, gamma
.red
);
516 GenerateRampFromGamma(ramp
->green
, gamma
.green
);
517 GenerateRampFromGamma(ramp
->blue
, gamma
.blue
);
520 #endif /* X_XF86VidModeSetGamma */
524 static BOOL
X11DRV_XF86VM_SetGammaRamp(struct x11drv_gamma_ramp
*ramp
)
526 #ifdef X_XF86VidModeSetGamma
527 XF86VidModeGamma gamma
;
529 if (xf86vm_major
< 2 || !usexvidmode
) return FALSE
; /* no gamma control */
530 if (!ComputeGammaFromRamp(ramp
->red
, &gamma
.red
) || /* ramp validation */
531 !ComputeGammaFromRamp(ramp
->green
, &gamma
.green
) ||
532 !ComputeGammaFromRamp(ramp
->blue
, &gamma
.blue
)) return FALSE
;
533 #ifdef X_XF86VidModeSetGammaRamp
534 if (xf86vm_use_gammaramp
)
535 return xf86vm_set_gamma_ramp(ramp
);
537 return pXF86VidModeSetGamma(gdi_display
, DefaultScreen(gdi_display
), &gamma
);
540 #endif /* X_XF86VidModeSetGamma */
543 #else /* SONAME_LIBXXF86VM */
545 void X11DRV_XF86VM_Init(void)
547 TRACE("XVidMode support not compiled in.\n");
550 #endif /* SONAME_LIBXXF86VM */
552 /***********************************************************************
555 BOOL
X11DRV_GetDeviceGammaRamp(PHYSDEV dev
, LPVOID ramp
)
557 #ifdef SONAME_LIBXXF86VM
558 return X11DRV_XF86VM_GetGammaRamp(ramp
);
564 /***********************************************************************
567 BOOL
X11DRV_SetDeviceGammaRamp(PHYSDEV dev
, LPVOID ramp
)
569 #ifdef SONAME_LIBXXF86VM
570 return X11DRV_XF86VM_SetGammaRamp(ramp
);