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
23 #include "wine/port.h"
30 #define NONAMELESSSTRUCT
31 #define NONAMELESSUNION
35 #ifdef HAVE_X11_EXTENSIONS_XF86VMODE_H
36 #include <X11/extensions/xf86vmode.h>
38 #ifdef HAVE_X11_EXTENSIONS_XF86VMPROTO_H
39 #include <X11/extensions/xf86vmproto.h>
44 #include "wine/debug.h"
45 #include "wine/heap.h"
46 #include "wine/unicode.h"
48 WINE_DEFAULT_DEBUG_CHANNEL(xvidmode
);
50 #ifdef SONAME_LIBXXF86VM
52 extern BOOL usexvidmode
;
54 static int xf86vm_event
, xf86vm_error
, xf86vm_major
, xf86vm_minor
;
56 #ifdef X_XF86VidModeSetGammaRamp
57 static int xf86vm_gammaramp_size
;
58 static BOOL xf86vm_use_gammaramp
;
59 #endif /* X_XF86VidModeSetGammaRamp */
61 #define MAKE_FUNCPTR(f) static typeof(f) * p##f;
62 MAKE_FUNCPTR(XF86VidModeGetAllModeLines
)
63 MAKE_FUNCPTR(XF86VidModeGetModeLine
)
64 MAKE_FUNCPTR(XF86VidModeLockModeSwitch
)
65 MAKE_FUNCPTR(XF86VidModeQueryExtension
)
66 MAKE_FUNCPTR(XF86VidModeQueryVersion
)
67 MAKE_FUNCPTR(XF86VidModeSetViewPort
)
68 MAKE_FUNCPTR(XF86VidModeSwitchToMode
)
69 #ifdef X_XF86VidModeSetGamma
70 MAKE_FUNCPTR(XF86VidModeGetGamma
)
71 MAKE_FUNCPTR(XF86VidModeSetGamma
)
73 #ifdef X_XF86VidModeSetGammaRamp
74 MAKE_FUNCPTR(XF86VidModeGetGammaRamp
)
75 MAKE_FUNCPTR(XF86VidModeGetGammaRampSize
)
76 MAKE_FUNCPTR(XF86VidModeSetGammaRamp
)
80 static int XVidModeErrorHandler(Display
*dpy
, XErrorEvent
*event
, void *arg
)
85 /* XF86VidMode display settings handler */
86 static BOOL
xf86vm_get_id(const WCHAR
*device_name
, ULONG_PTR
*id
)
88 WCHAR primary_adapter
[CCHDEVICENAME
];
90 if (!get_primary_adapter( primary_adapter
))
93 /* XVidMode only supports changing the primary adapter settings.
94 * For non-primary adapters, an id is still provided but getting
95 * and changing non-primary adapters' settings will be ignored. */
96 *id
= !lstrcmpiW( device_name
, primary_adapter
) ? 1 : 0;
100 static void add_xf86vm_mode(DEVMODEW
*mode
, DWORD depth
, const XF86VidModeModeInfo
*mode_info
)
102 mode
->dmSize
= sizeof(*mode
);
103 mode
->dmDriverExtra
= sizeof(mode_info
);
104 mode
->dmFields
= DM_DISPLAYORIENTATION
| DM_BITSPERPEL
| DM_PELSWIDTH
| DM_PELSHEIGHT
| DM_DISPLAYFLAGS
;
105 if (mode_info
->htotal
&& mode_info
->vtotal
)
107 mode
->dmFields
|= DM_DISPLAYFREQUENCY
;
108 mode
->dmDisplayFrequency
= mode_info
->dotclock
* 1000 / (mode_info
->htotal
* mode_info
->vtotal
);
110 mode
->u1
.s2
.dmDisplayOrientation
= DMDO_DEFAULT
;
111 mode
->dmBitsPerPel
= depth
;
112 mode
->dmPelsWidth
= mode_info
->hdisplay
;
113 mode
->dmPelsHeight
= mode_info
->vdisplay
;
114 mode
->u2
.dmDisplayFlags
= 0;
115 memcpy((BYTE
*)mode
+ sizeof(*mode
), &mode_info
, sizeof(mode_info
));
118 static BOOL
xf86vm_get_modes(ULONG_PTR id
, DWORD flags
, DEVMODEW
**new_modes
, UINT
*mode_count
)
120 INT xf86vm_mode_idx
, xf86vm_mode_count
;
121 XF86VidModeModeInfo
**xf86vm_modes
;
122 UINT depth_idx
, mode_idx
= 0;
123 DEVMODEW
*modes
, *mode
;
128 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
129 ret
= pXF86VidModeGetAllModeLines(gdi_display
, DefaultScreen(gdi_display
), &xf86vm_mode_count
, &xf86vm_modes
);
130 if (X11DRV_check_error() || !ret
|| !xf86vm_mode_count
)
133 /* Put a XF86VidModeModeInfo ** at the start to store the XF86VidMode modes pointer */
134 size
= sizeof(XF86VidModeModeInfo
**);
135 /* Display modes in different color depth, with a XF86VidModeModeInfo * at the end of each
136 * DEVMODEW as driver private data */
137 size
+= (xf86vm_mode_count
* DEPTH_COUNT
) * (sizeof(DEVMODEW
) + sizeof(XF86VidModeModeInfo
*));
138 ptr
= heap_alloc_zero(size
);
141 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
145 memcpy(ptr
, &xf86vm_modes
, sizeof(xf86vm_modes
));
146 modes
= (DEVMODEW
*)(ptr
+ sizeof(xf86vm_modes
));
148 for (depth_idx
= 0; depth_idx
< DEPTH_COUNT
; ++depth_idx
)
150 for (xf86vm_mode_idx
= 0; xf86vm_mode_idx
< xf86vm_mode_count
; ++xf86vm_mode_idx
)
152 mode
= (DEVMODEW
*)((BYTE
*)modes
+ (sizeof(DEVMODEW
) + sizeof(XF86VidModeModeInfo
*)) * mode_idx
++);
153 add_xf86vm_mode(mode
, depths
[depth_idx
], xf86vm_modes
[xf86vm_mode_idx
]);
158 *mode_count
= mode_idx
;
162 static void xf86vm_free_modes(DEVMODEW
*modes
)
164 XF86VidModeModeInfo
**xf86vm_modes
;
168 assert(modes
[0].dmDriverExtra
== sizeof(XF86VidModeModeInfo
*));
169 memcpy(&xf86vm_modes
, (BYTE
*)modes
- sizeof(xf86vm_modes
), sizeof(xf86vm_modes
));
175 static BOOL
xf86vm_get_current_mode(ULONG_PTR id
, DEVMODEW
*mode
)
177 XF86VidModeModeLine xf86vm_mode
;
181 mode
->dmFields
= DM_DISPLAYORIENTATION
| DM_BITSPERPEL
| DM_PELSWIDTH
| DM_PELSHEIGHT
|
182 DM_DISPLAYFLAGS
| DM_DISPLAYFREQUENCY
| DM_POSITION
;
183 mode
->u1
.s2
.dmDisplayOrientation
= DMDO_DEFAULT
;
184 mode
->u2
.dmDisplayFlags
= 0;
185 mode
->u1
.s2
.dmPosition
.x
= 0;
186 mode
->u1
.s2
.dmPosition
.y
= 0;
190 FIXME("Non-primary adapters are unsupported.\n");
191 mode
->dmBitsPerPel
= 0;
192 mode
->dmPelsWidth
= 0;
193 mode
->dmPelsHeight
= 0;
194 mode
->dmDisplayFrequency
= 0;
198 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
199 ret
= pXF86VidModeGetModeLine(gdi_display
, DefaultScreen(gdi_display
), &dotclock
, &xf86vm_mode
);
200 if (X11DRV_check_error() || !ret
)
203 mode
->dmBitsPerPel
= screen_bpp
;
204 mode
->dmPelsWidth
= xf86vm_mode
.hdisplay
;
205 mode
->dmPelsHeight
= xf86vm_mode
.vdisplay
;
206 if (xf86vm_mode
.htotal
&& xf86vm_mode
.vtotal
)
207 mode
->dmDisplayFrequency
= dotclock
* 1000 / (xf86vm_mode
.htotal
* xf86vm_mode
.vtotal
);
209 mode
->dmDisplayFrequency
= 0;
211 if (xf86vm_mode
.privsize
)
212 XFree(xf86vm_mode
.private);
216 static LONG
xf86vm_set_current_mode(ULONG_PTR id
, DEVMODEW
*mode
)
218 XF86VidModeModeInfo
*xf86vm_mode
;
223 FIXME("Non-primary adapters are unsupported.\n");
224 return DISP_CHANGE_SUCCESSFUL
;
227 if (is_detached_mode(mode
))
229 FIXME("Detaching adapters is unsupported.\n");
230 return DISP_CHANGE_SUCCESSFUL
;
233 if (mode
->dmFields
& DM_BITSPERPEL
&& mode
->dmBitsPerPel
!= screen_bpp
)
234 WARN("Cannot change screen bit depth from %dbits to %dbits!\n", screen_bpp
, mode
->dmBitsPerPel
);
236 assert(mode
->dmDriverExtra
== sizeof(XF86VidModeModeInfo
*));
237 memcpy(&xf86vm_mode
, (BYTE
*)mode
+ sizeof(*mode
), sizeof(xf86vm_mode
));
238 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
239 ret
= pXF86VidModeSwitchToMode(gdi_display
, DefaultScreen(gdi_display
), xf86vm_mode
);
240 if (X11DRV_check_error() || !ret
)
241 return DISP_CHANGE_FAILED
;
242 #if 0 /* it is said that SetViewPort causes problems with some X servers */
243 pXF86VidModeSetViewPort(gdi_display
, DefaultScreen(gdi_display
), 0, 0);
245 XWarpPointer(gdi_display
, None
, DefaultRootWindow(gdi_display
), 0, 0, 0, 0, 0, 0);
248 return DISP_CHANGE_SUCCESSFUL
;
251 void X11DRV_XF86VM_Init(void)
253 struct x11drv_settings_handler xf86vm_handler
;
254 void *xvidmode_handle
;
257 if (xf86vm_major
) return; /* already initialized? */
259 xvidmode_handle
= dlopen(SONAME_LIBXXF86VM
, RTLD_NOW
);
260 if (!xvidmode_handle
)
262 TRACE("Unable to open %s, XVidMode disabled\n", SONAME_LIBXXF86VM
);
267 #define LOAD_FUNCPTR(f) \
268 if((p##f = dlsym(xvidmode_handle, #f)) == NULL) goto sym_not_found
269 LOAD_FUNCPTR(XF86VidModeGetAllModeLines
);
270 LOAD_FUNCPTR(XF86VidModeGetModeLine
);
271 LOAD_FUNCPTR(XF86VidModeLockModeSwitch
);
272 LOAD_FUNCPTR(XF86VidModeQueryExtension
);
273 LOAD_FUNCPTR(XF86VidModeQueryVersion
);
274 LOAD_FUNCPTR(XF86VidModeSetViewPort
);
275 LOAD_FUNCPTR(XF86VidModeSwitchToMode
);
276 #ifdef X_XF86VidModeSetGamma
277 LOAD_FUNCPTR(XF86VidModeGetGamma
);
278 LOAD_FUNCPTR(XF86VidModeSetGamma
);
280 #ifdef X_XF86VidModeSetGammaRamp
281 LOAD_FUNCPTR(XF86VidModeGetGammaRamp
);
282 LOAD_FUNCPTR(XF86VidModeGetGammaRampSize
);
283 LOAD_FUNCPTR(XF86VidModeSetGammaRamp
);
287 /* see if XVidMode is available */
288 if (!pXF86VidModeQueryExtension(gdi_display
, &xf86vm_event
, &xf86vm_error
)) return;
290 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
291 ok
= pXF86VidModeQueryVersion(gdi_display
, &xf86vm_major
, &xf86vm_minor
);
292 if (X11DRV_check_error() || !ok
) return;
294 #ifdef X_XF86VidModeSetGammaRamp
295 if (xf86vm_major
> 2 || (xf86vm_major
== 2 && xf86vm_minor
>= 1))
297 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
298 pXF86VidModeGetGammaRampSize(gdi_display
, DefaultScreen(gdi_display
),
299 &xf86vm_gammaramp_size
);
300 if (X11DRV_check_error()) xf86vm_gammaramp_size
= 0;
301 TRACE("Gamma ramp size %d.\n", xf86vm_gammaramp_size
);
302 if (xf86vm_gammaramp_size
>= GAMMA_RAMP_SIZE
)
303 xf86vm_use_gammaramp
= TRUE
;
305 #endif /* X_XF86VidModeSetGammaRamp */
310 xf86vm_handler
.name
= "XF86VidMode";
311 xf86vm_handler
.priority
= 100;
312 xf86vm_handler
.get_id
= xf86vm_get_id
;
313 xf86vm_handler
.get_modes
= xf86vm_get_modes
;
314 xf86vm_handler
.free_modes
= xf86vm_free_modes
;
315 xf86vm_handler
.get_current_mode
= xf86vm_get_current_mode
;
316 xf86vm_handler
.set_current_mode
= xf86vm_set_current_mode
;
317 X11DRV_Settings_SetHandler(&xf86vm_handler
);
321 TRACE("Unable to load function pointers from %s, XVidMode disabled\n", SONAME_LIBXXF86VM
);
322 dlclose(xvidmode_handle
);
323 xvidmode_handle
= NULL
;
327 /***** GAMMA CONTROL *****/
328 /* (only available in XF86VidMode 2.x) */
330 #ifdef X_XF86VidModeSetGamma
332 static void GenerateRampFromGamma(WORD ramp
[GAMMA_RAMP_SIZE
], float gamma
)
334 float r_gamma
= 1/gamma
;
336 TRACE("gamma is %f\n", r_gamma
);
337 for (i
=0; i
<GAMMA_RAMP_SIZE
; i
++)
338 ramp
[i
] = pow(i
/255.0, r_gamma
) * 65535.0;
341 static BOOL
ComputeGammaFromRamp(WORD ramp
[GAMMA_RAMP_SIZE
], float *gamma
)
343 float r_x
, r_y
, r_lx
, r_ly
, r_d
, r_v
, r_e
, g_avg
, g_min
, g_max
;
344 unsigned i
, f
, l
, g_n
, c
;
348 ERR("inverted or flat gamma ramp (%d->%d), rejected\n", f
, l
);
352 g_min
= g_max
= g_avg
= 0.0;
353 /* check gamma ramp entries to estimate the gamma */
354 TRACE("analyzing gamma ramp (%d->%d)\n", f
, l
);
355 for (i
=1, g_n
=0; i
<255; i
++) {
356 if (ramp
[i
] < f
|| ramp
[i
] > l
) {
357 ERR("strange gamma ramp ([%d]=%d for %d->%d), rejected\n", i
, ramp
[i
], f
, l
);
361 if (!c
) continue; /* avoid log(0) */
363 /* normalize entry values into 0..1 range */
364 r_x
= i
/255.0; r_y
= c
/ r_d
;
365 /* compute logarithms of values */
366 r_lx
= log(r_x
); r_ly
= log(r_y
);
367 /* compute gamma for this entry */
369 /* compute differential (error estimate) for this entry */
370 /* some games use table-based logarithms that magnifies the error by 128 */
371 r_e
= -r_lx
* 128 / (c
* r_lx
* r_lx
);
373 /* compute min & max while compensating for estimated error */
374 if (!g_n
|| g_min
> (r_v
+ r_e
)) g_min
= r_v
+ r_e
;
375 if (!g_n
|| g_max
< (r_v
- r_e
)) g_max
= r_v
- r_e
;
380 /* TRACE("[%d]=%d, gamma=%f, error=%f\n", i, ramp[i], r_v, r_e); */
383 ERR("no gamma data, shouldn't happen\n");
387 TRACE("low bias is %d, high is %d, gamma is %5.3f\n", f
, 65535-l
, g_avg
);
388 /* the bias could be because the app wanted something like a "red shift"
389 * like when you're hit in Quake, but XVidMode doesn't support it,
390 * so we have to reject a significant bias */
391 if (f
&& f
> (pow(1/255.0, g_avg
) * 65536.0)) {
392 ERR("low-biased gamma ramp (%d), rejected\n", f
);
395 /* check that the gamma is reasonably uniform across the ramp */
396 if (g_max
- g_min
> 12.8) {
397 ERR("ramp not uniform (max=%f, min=%f, avg=%f), rejected\n", g_max
, g_min
, g_avg
);
400 /* check that the gamma is not too bright */
402 ERR("too bright gamma ( %5.3f), rejected\n", g_avg
);
405 /* ok, now we're pretty sure we can set the desired gamma ramp,
411 #endif /* X_XF86VidModeSetGamma */
413 /* Hmm... should gamma control be available in desktop mode or not?
414 * I'll assume that it should */
416 #ifdef X_XF86VidModeSetGammaRamp
417 static void interpolate_gamma_ramp(WORD
*dst_r
, WORD
*dst_g
, WORD
*dst_b
, unsigned int dst_size
,
418 const WORD
*src_r
, const WORD
*src_g
, const WORD
*src_b
, unsigned int src_size
)
420 double position
, distance
;
421 unsigned int dst_i
, src_i
;
423 for (dst_i
= 0; dst_i
< dst_size
; ++dst_i
)
425 position
= dst_i
* (src_size
- 1) / (double)(dst_size
- 1);
428 if (src_i
+ 1 < src_size
)
430 distance
= position
- src_i
;
432 dst_r
[dst_i
] = (1.0 - distance
) * src_r
[src_i
] + distance
* src_r
[src_i
+ 1] + 0.5;
433 dst_g
[dst_i
] = (1.0 - distance
) * src_g
[src_i
] + distance
* src_g
[src_i
+ 1] + 0.5;
434 dst_b
[dst_i
] = (1.0 - distance
) * src_b
[src_i
] + distance
* src_b
[src_i
+ 1] + 0.5;
438 dst_r
[dst_i
] = src_r
[src_i
];
439 dst_g
[dst_i
] = src_g
[src_i
];
440 dst_b
[dst_i
] = src_b
[src_i
];
445 static BOOL
xf86vm_get_gamma_ramp(struct x11drv_gamma_ramp
*ramp
)
447 WORD
*red
, *green
, *blue
;
450 if (xf86vm_gammaramp_size
== GAMMA_RAMP_SIZE
)
458 if (!(red
= heap_calloc(xf86vm_gammaramp_size
, 3 * sizeof(*red
))))
460 green
= red
+ xf86vm_gammaramp_size
;
461 blue
= green
+ xf86vm_gammaramp_size
;
464 ret
= pXF86VidModeGetGammaRamp(gdi_display
, DefaultScreen(gdi_display
),
465 xf86vm_gammaramp_size
, red
, green
, blue
);
466 if (ret
&& red
!= ramp
->red
)
467 interpolate_gamma_ramp(ramp
->red
, ramp
->green
, ramp
->blue
, GAMMA_RAMP_SIZE
,
468 red
, green
, blue
, xf86vm_gammaramp_size
);
469 if (red
!= ramp
->red
)
474 static BOOL
xf86vm_set_gamma_ramp(struct x11drv_gamma_ramp
*ramp
)
476 WORD
*red
, *green
, *blue
;
479 if (xf86vm_gammaramp_size
== GAMMA_RAMP_SIZE
)
487 if (!(red
= heap_calloc(xf86vm_gammaramp_size
, 3 * sizeof(*red
))))
489 green
= red
+ xf86vm_gammaramp_size
;
490 blue
= green
+ xf86vm_gammaramp_size
;
492 interpolate_gamma_ramp(red
, green
, blue
, xf86vm_gammaramp_size
,
493 ramp
->red
, ramp
->green
, ramp
->blue
, GAMMA_RAMP_SIZE
);
496 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
497 ret
= pXF86VidModeSetGammaRamp(gdi_display
, DefaultScreen(gdi_display
),
498 xf86vm_gammaramp_size
, red
, green
, blue
);
499 if (ret
) XSync( gdi_display
, FALSE
);
500 if (X11DRV_check_error()) ret
= FALSE
;
502 if (red
!= ramp
->red
)
508 static BOOL
X11DRV_XF86VM_GetGammaRamp(struct x11drv_gamma_ramp
*ramp
)
510 #ifdef X_XF86VidModeSetGamma
511 XF86VidModeGamma gamma
;
513 if (xf86vm_major
< 2) return FALSE
; /* no gamma control */
514 #ifdef X_XF86VidModeSetGammaRamp
515 if (xf86vm_use_gammaramp
)
516 return xf86vm_get_gamma_ramp(ramp
);
518 if (pXF86VidModeGetGamma(gdi_display
, DefaultScreen(gdi_display
), &gamma
))
520 GenerateRampFromGamma(ramp
->red
, gamma
.red
);
521 GenerateRampFromGamma(ramp
->green
, gamma
.green
);
522 GenerateRampFromGamma(ramp
->blue
, gamma
.blue
);
525 #endif /* X_XF86VidModeSetGamma */
529 static BOOL
X11DRV_XF86VM_SetGammaRamp(struct x11drv_gamma_ramp
*ramp
)
531 #ifdef X_XF86VidModeSetGamma
532 XF86VidModeGamma gamma
;
534 if (xf86vm_major
< 2 || !usexvidmode
) return FALSE
; /* no gamma control */
535 if (!ComputeGammaFromRamp(ramp
->red
, &gamma
.red
) || /* ramp validation */
536 !ComputeGammaFromRamp(ramp
->green
, &gamma
.green
) ||
537 !ComputeGammaFromRamp(ramp
->blue
, &gamma
.blue
)) return FALSE
;
538 #ifdef X_XF86VidModeSetGammaRamp
539 if (xf86vm_use_gammaramp
)
540 return xf86vm_set_gamma_ramp(ramp
);
542 return pXF86VidModeSetGamma(gdi_display
, DefaultScreen(gdi_display
), &gamma
);
545 #endif /* X_XF86VidModeSetGamma */
548 #else /* SONAME_LIBXXF86VM */
550 void X11DRV_XF86VM_Init(void)
552 TRACE("XVidMode support not compiled in.\n");
555 #endif /* SONAME_LIBXXF86VM */
557 /***********************************************************************
558 * GetDeviceGammaRamp (X11DRV.@)
560 * FIXME: should move to somewhere appropriate, but probably not before
561 * the stuff in graphics/x11drv/ has been moved to dlls/x11drv, so that
562 * they can include xvidmode.h directly
564 BOOL CDECL
X11DRV_GetDeviceGammaRamp(PHYSDEV dev
, LPVOID ramp
)
566 #ifdef SONAME_LIBXXF86VM
567 return X11DRV_XF86VM_GetGammaRamp(ramp
);
573 /***********************************************************************
574 * SetDeviceGammaRamp (X11DRV.@)
576 * FIXME: should move to somewhere appropriate, but probably not before
577 * the stuff in graphics/x11drv/ has been moved to dlls/x11drv, so that
578 * they can include xvidmode.h directly
580 BOOL CDECL
X11DRV_SetDeviceGammaRamp(PHYSDEV dev
, LPVOID ramp
)
582 #ifdef SONAME_LIBXXF86VM
583 return X11DRV_XF86VM_SetGammaRamp(ramp
);