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
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"
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
, ULONG_PTR
*id
)
87 WCHAR primary_adapter
[CCHDEVICENAME
];
89 if (!get_primary_adapter( primary_adapter
))
92 /* XVidMode only supports changing the primary adapter settings.
93 * For non-primary adapters, an id is still provided but getting
94 * and changing non-primary adapters' settings will be ignored. */
95 *id
= !lstrcmpiW( device_name
, primary_adapter
) ? 1 : 0;
99 static void add_xf86vm_mode(DEVMODEW
*mode
, DWORD depth
, const XF86VidModeModeInfo
*mode_info
)
101 mode
->dmSize
= sizeof(*mode
);
102 mode
->dmDriverExtra
= sizeof(mode_info
);
103 mode
->dmFields
= DM_DISPLAYORIENTATION
| DM_BITSPERPEL
| DM_PELSWIDTH
| DM_PELSHEIGHT
| DM_DISPLAYFLAGS
;
104 if (mode_info
->htotal
&& mode_info
->vtotal
)
106 mode
->dmFields
|= DM_DISPLAYFREQUENCY
;
107 mode
->dmDisplayFrequency
= mode_info
->dotclock
* 1000 / (mode_info
->htotal
* mode_info
->vtotal
);
109 mode
->u1
.s2
.dmDisplayOrientation
= DMDO_DEFAULT
;
110 mode
->dmBitsPerPel
= depth
;
111 mode
->dmPelsWidth
= mode_info
->hdisplay
;
112 mode
->dmPelsHeight
= mode_info
->vdisplay
;
113 mode
->u2
.dmDisplayFlags
= 0;
114 memcpy((BYTE
*)mode
+ sizeof(*mode
), &mode_info
, sizeof(mode_info
));
117 static BOOL
xf86vm_get_modes(ULONG_PTR id
, DWORD flags
, DEVMODEW
**new_modes
, UINT
*mode_count
)
119 INT xf86vm_mode_idx
, xf86vm_mode_count
;
120 XF86VidModeModeInfo
**xf86vm_modes
;
121 UINT depth_idx
, mode_idx
= 0;
122 DEVMODEW
*modes
, *mode
;
127 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
128 ret
= pXF86VidModeGetAllModeLines(gdi_display
, DefaultScreen(gdi_display
), &xf86vm_mode_count
, &xf86vm_modes
);
129 if (X11DRV_check_error() || !ret
|| !xf86vm_mode_count
)
132 /* Put a XF86VidModeModeInfo ** at the start to store the XF86VidMode modes pointer */
133 size
= sizeof(XF86VidModeModeInfo
**);
134 /* Display modes in different color depth, with a XF86VidModeModeInfo * at the end of each
135 * DEVMODEW as driver private data */
136 size
+= (xf86vm_mode_count
* DEPTH_COUNT
) * (sizeof(DEVMODEW
) + sizeof(XF86VidModeModeInfo
*));
137 ptr
= heap_alloc_zero(size
);
140 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
144 memcpy(ptr
, &xf86vm_modes
, sizeof(xf86vm_modes
));
145 modes
= (DEVMODEW
*)(ptr
+ sizeof(xf86vm_modes
));
147 for (depth_idx
= 0; depth_idx
< DEPTH_COUNT
; ++depth_idx
)
149 for (xf86vm_mode_idx
= 0; xf86vm_mode_idx
< xf86vm_mode_count
; ++xf86vm_mode_idx
)
151 mode
= (DEVMODEW
*)((BYTE
*)modes
+ (sizeof(DEVMODEW
) + sizeof(XF86VidModeModeInfo
*)) * mode_idx
++);
152 add_xf86vm_mode(mode
, depths
[depth_idx
], xf86vm_modes
[xf86vm_mode_idx
]);
157 *mode_count
= mode_idx
;
161 static void xf86vm_free_modes(DEVMODEW
*modes
)
163 XF86VidModeModeInfo
**xf86vm_modes
;
167 assert(modes
[0].dmDriverExtra
== sizeof(XF86VidModeModeInfo
*));
168 memcpy(&xf86vm_modes
, (BYTE
*)modes
- sizeof(xf86vm_modes
), sizeof(xf86vm_modes
));
174 static BOOL
xf86vm_get_current_mode(ULONG_PTR id
, DEVMODEW
*mode
)
176 XF86VidModeModeLine xf86vm_mode
;
180 mode
->dmFields
= DM_DISPLAYORIENTATION
| DM_BITSPERPEL
| DM_PELSWIDTH
| DM_PELSHEIGHT
|
181 DM_DISPLAYFLAGS
| DM_DISPLAYFREQUENCY
| DM_POSITION
;
182 mode
->u1
.s2
.dmDisplayOrientation
= DMDO_DEFAULT
;
183 mode
->u2
.dmDisplayFlags
= 0;
184 mode
->u1
.s2
.dmPosition
.x
= 0;
185 mode
->u1
.s2
.dmPosition
.y
= 0;
189 FIXME("Non-primary adapters are unsupported.\n");
190 mode
->dmBitsPerPel
= 0;
191 mode
->dmPelsWidth
= 0;
192 mode
->dmPelsHeight
= 0;
193 mode
->dmDisplayFrequency
= 0;
197 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
198 ret
= pXF86VidModeGetModeLine(gdi_display
, DefaultScreen(gdi_display
), &dotclock
, &xf86vm_mode
);
199 if (X11DRV_check_error() || !ret
)
202 mode
->dmBitsPerPel
= screen_bpp
;
203 mode
->dmPelsWidth
= xf86vm_mode
.hdisplay
;
204 mode
->dmPelsHeight
= xf86vm_mode
.vdisplay
;
205 if (xf86vm_mode
.htotal
&& xf86vm_mode
.vtotal
)
206 mode
->dmDisplayFrequency
= dotclock
* 1000 / (xf86vm_mode
.htotal
* xf86vm_mode
.vtotal
);
208 mode
->dmDisplayFrequency
= 0;
210 if (xf86vm_mode
.privsize
)
211 XFree(xf86vm_mode
.private);
215 static LONG
xf86vm_set_current_mode(ULONG_PTR id
, DEVMODEW
*mode
)
217 XF86VidModeModeInfo
*xf86vm_mode
;
222 FIXME("Non-primary adapters are unsupported.\n");
223 return DISP_CHANGE_SUCCESSFUL
;
226 if (is_detached_mode(mode
))
228 FIXME("Detaching adapters is unsupported.\n");
229 return DISP_CHANGE_SUCCESSFUL
;
232 if (mode
->dmFields
& DM_BITSPERPEL
&& mode
->dmBitsPerPel
!= screen_bpp
)
233 WARN("Cannot change screen bit depth from %dbits to %dbits!\n", screen_bpp
, mode
->dmBitsPerPel
);
235 assert(mode
->dmDriverExtra
== sizeof(XF86VidModeModeInfo
*));
236 memcpy(&xf86vm_mode
, (BYTE
*)mode
+ sizeof(*mode
), sizeof(xf86vm_mode
));
237 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
238 ret
= pXF86VidModeSwitchToMode(gdi_display
, DefaultScreen(gdi_display
), xf86vm_mode
);
239 if (X11DRV_check_error() || !ret
)
240 return DISP_CHANGE_FAILED
;
241 #if 0 /* it is said that SetViewPort causes problems with some X servers */
242 pXF86VidModeSetViewPort(gdi_display
, DefaultScreen(gdi_display
), 0, 0);
244 XWarpPointer(gdi_display
, None
, DefaultRootWindow(gdi_display
), 0, 0, 0, 0, 0, 0);
247 return DISP_CHANGE_SUCCESSFUL
;
250 void X11DRV_XF86VM_Init(void)
252 struct x11drv_settings_handler xf86vm_handler
;
253 void *xvidmode_handle
;
256 if (xf86vm_major
) return; /* already initialized? */
258 xvidmode_handle
= dlopen(SONAME_LIBXXF86VM
, RTLD_NOW
);
259 if (!xvidmode_handle
)
261 TRACE("Unable to open %s, XVidMode disabled\n", SONAME_LIBXXF86VM
);
266 #define LOAD_FUNCPTR(f) \
267 if((p##f = dlsym(xvidmode_handle, #f)) == NULL) goto sym_not_found
268 LOAD_FUNCPTR(XF86VidModeGetAllModeLines
);
269 LOAD_FUNCPTR(XF86VidModeGetModeLine
);
270 LOAD_FUNCPTR(XF86VidModeLockModeSwitch
);
271 LOAD_FUNCPTR(XF86VidModeQueryExtension
);
272 LOAD_FUNCPTR(XF86VidModeQueryVersion
);
273 LOAD_FUNCPTR(XF86VidModeSetViewPort
);
274 LOAD_FUNCPTR(XF86VidModeSwitchToMode
);
275 #ifdef X_XF86VidModeSetGamma
276 LOAD_FUNCPTR(XF86VidModeGetGamma
);
277 LOAD_FUNCPTR(XF86VidModeSetGamma
);
279 #ifdef X_XF86VidModeSetGammaRamp
280 LOAD_FUNCPTR(XF86VidModeGetGammaRamp
);
281 LOAD_FUNCPTR(XF86VidModeGetGammaRampSize
);
282 LOAD_FUNCPTR(XF86VidModeSetGammaRamp
);
286 /* see if XVidMode is available */
287 if (!pXF86VidModeQueryExtension(gdi_display
, &xf86vm_event
, &xf86vm_error
)) return;
289 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
290 ok
= pXF86VidModeQueryVersion(gdi_display
, &xf86vm_major
, &xf86vm_minor
);
291 if (X11DRV_check_error() || !ok
) return;
293 #ifdef X_XF86VidModeSetGammaRamp
294 if (xf86vm_major
> 2 || (xf86vm_major
== 2 && xf86vm_minor
>= 1))
296 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
297 pXF86VidModeGetGammaRampSize(gdi_display
, DefaultScreen(gdi_display
),
298 &xf86vm_gammaramp_size
);
299 if (X11DRV_check_error()) xf86vm_gammaramp_size
= 0;
300 TRACE("Gamma ramp size %d.\n", xf86vm_gammaramp_size
);
301 if (xf86vm_gammaramp_size
>= GAMMA_RAMP_SIZE
)
302 xf86vm_use_gammaramp
= TRUE
;
304 #endif /* X_XF86VidModeSetGammaRamp */
309 xf86vm_handler
.name
= "XF86VidMode";
310 xf86vm_handler
.priority
= 100;
311 xf86vm_handler
.get_id
= xf86vm_get_id
;
312 xf86vm_handler
.get_modes
= xf86vm_get_modes
;
313 xf86vm_handler
.free_modes
= xf86vm_free_modes
;
314 xf86vm_handler
.get_current_mode
= xf86vm_get_current_mode
;
315 xf86vm_handler
.set_current_mode
= xf86vm_set_current_mode
;
316 X11DRV_Settings_SetHandler(&xf86vm_handler
);
320 TRACE("Unable to load function pointers from %s, XVidMode disabled\n", SONAME_LIBXXF86VM
);
321 dlclose(xvidmode_handle
);
322 xvidmode_handle
= NULL
;
326 /***** GAMMA CONTROL *****/
327 /* (only available in XF86VidMode 2.x) */
329 #ifdef X_XF86VidModeSetGamma
331 static void GenerateRampFromGamma(WORD ramp
[GAMMA_RAMP_SIZE
], float gamma
)
333 float r_gamma
= 1/gamma
;
335 TRACE("gamma is %f\n", r_gamma
);
336 for (i
=0; i
<GAMMA_RAMP_SIZE
; i
++)
337 ramp
[i
] = pow(i
/255.0, r_gamma
) * 65535.0;
340 static BOOL
ComputeGammaFromRamp(WORD ramp
[GAMMA_RAMP_SIZE
], float *gamma
)
342 float r_x
, r_y
, r_lx
, r_ly
, r_d
, r_v
, r_e
, g_avg
, g_min
, g_max
;
343 unsigned i
, f
, l
, g_n
, c
;
347 ERR("inverted or flat gamma ramp (%d->%d), rejected\n", f
, l
);
351 g_min
= g_max
= g_avg
= 0.0;
352 /* check gamma ramp entries to estimate the gamma */
353 TRACE("analyzing gamma ramp (%d->%d)\n", f
, l
);
354 for (i
=1, g_n
=0; i
<255; i
++) {
355 if (ramp
[i
] < f
|| ramp
[i
] > l
) {
356 ERR("strange gamma ramp ([%d]=%d for %d->%d), rejected\n", i
, ramp
[i
], f
, l
);
360 if (!c
) continue; /* avoid log(0) */
362 /* normalize entry values into 0..1 range */
363 r_x
= i
/255.0; r_y
= c
/ r_d
;
364 /* compute logarithms of values */
365 r_lx
= log(r_x
); r_ly
= log(r_y
);
366 /* compute gamma for this entry */
368 /* compute differential (error estimate) for this entry */
369 /* some games use table-based logarithms that magnifies the error by 128 */
370 r_e
= -r_lx
* 128 / (c
* r_lx
* r_lx
);
372 /* compute min & max while compensating for estimated error */
373 if (!g_n
|| g_min
> (r_v
+ r_e
)) g_min
= r_v
+ r_e
;
374 if (!g_n
|| g_max
< (r_v
- r_e
)) g_max
= r_v
- r_e
;
379 /* TRACE("[%d]=%d, gamma=%f, error=%f\n", i, ramp[i], r_v, r_e); */
382 ERR("no gamma data, shouldn't happen\n");
386 TRACE("low bias is %d, high is %d, gamma is %5.3f\n", f
, 65535-l
, g_avg
);
387 /* the bias could be because the app wanted something like a "red shift"
388 * like when you're hit in Quake, but XVidMode doesn't support it,
389 * so we have to reject a significant bias */
390 if (f
&& f
> (pow(1/255.0, g_avg
) * 65536.0)) {
391 ERR("low-biased gamma ramp (%d), rejected\n", f
);
394 /* check that the gamma is reasonably uniform across the ramp */
395 if (g_max
- g_min
> 12.8) {
396 ERR("ramp not uniform (max=%f, min=%f, avg=%f), rejected\n", g_max
, g_min
, g_avg
);
399 /* check that the gamma is not too bright */
401 ERR("too bright gamma ( %5.3f), rejected\n", g_avg
);
404 /* ok, now we're pretty sure we can set the desired gamma ramp,
410 #endif /* X_XF86VidModeSetGamma */
412 /* Hmm... should gamma control be available in desktop mode or not?
413 * I'll assume that it should */
415 #ifdef X_XF86VidModeSetGammaRamp
416 static void interpolate_gamma_ramp(WORD
*dst_r
, WORD
*dst_g
, WORD
*dst_b
, unsigned int dst_size
,
417 const WORD
*src_r
, const WORD
*src_g
, const WORD
*src_b
, unsigned int src_size
)
419 double position
, distance
;
420 unsigned int dst_i
, src_i
;
422 for (dst_i
= 0; dst_i
< dst_size
; ++dst_i
)
424 position
= dst_i
* (src_size
- 1) / (double)(dst_size
- 1);
427 if (src_i
+ 1 < src_size
)
429 distance
= position
- src_i
;
431 dst_r
[dst_i
] = (1.0 - distance
) * src_r
[src_i
] + distance
* src_r
[src_i
+ 1] + 0.5;
432 dst_g
[dst_i
] = (1.0 - distance
) * src_g
[src_i
] + distance
* src_g
[src_i
+ 1] + 0.5;
433 dst_b
[dst_i
] = (1.0 - distance
) * src_b
[src_i
] + distance
* src_b
[src_i
+ 1] + 0.5;
437 dst_r
[dst_i
] = src_r
[src_i
];
438 dst_g
[dst_i
] = src_g
[src_i
];
439 dst_b
[dst_i
] = src_b
[src_i
];
444 static BOOL
xf86vm_get_gamma_ramp(struct x11drv_gamma_ramp
*ramp
)
446 WORD
*red
, *green
, *blue
;
449 if (xf86vm_gammaramp_size
== GAMMA_RAMP_SIZE
)
457 if (!(red
= heap_calloc(xf86vm_gammaramp_size
, 3 * sizeof(*red
))))
459 green
= red
+ xf86vm_gammaramp_size
;
460 blue
= green
+ xf86vm_gammaramp_size
;
463 ret
= pXF86VidModeGetGammaRamp(gdi_display
, DefaultScreen(gdi_display
),
464 xf86vm_gammaramp_size
, red
, green
, blue
);
465 if (ret
&& red
!= ramp
->red
)
466 interpolate_gamma_ramp(ramp
->red
, ramp
->green
, ramp
->blue
, GAMMA_RAMP_SIZE
,
467 red
, green
, blue
, xf86vm_gammaramp_size
);
468 if (red
!= ramp
->red
)
473 static BOOL
xf86vm_set_gamma_ramp(struct x11drv_gamma_ramp
*ramp
)
475 WORD
*red
, *green
, *blue
;
478 if (xf86vm_gammaramp_size
== GAMMA_RAMP_SIZE
)
486 if (!(red
= heap_calloc(xf86vm_gammaramp_size
, 3 * sizeof(*red
))))
488 green
= red
+ xf86vm_gammaramp_size
;
489 blue
= green
+ xf86vm_gammaramp_size
;
491 interpolate_gamma_ramp(red
, green
, blue
, xf86vm_gammaramp_size
,
492 ramp
->red
, ramp
->green
, ramp
->blue
, GAMMA_RAMP_SIZE
);
495 X11DRV_expect_error(gdi_display
, XVidModeErrorHandler
, NULL
);
496 ret
= pXF86VidModeSetGammaRamp(gdi_display
, DefaultScreen(gdi_display
),
497 xf86vm_gammaramp_size
, red
, green
, blue
);
498 if (ret
) XSync( gdi_display
, FALSE
);
499 if (X11DRV_check_error()) ret
= FALSE
;
501 if (red
!= ramp
->red
)
507 static BOOL
X11DRV_XF86VM_GetGammaRamp(struct x11drv_gamma_ramp
*ramp
)
509 #ifdef X_XF86VidModeSetGamma
510 XF86VidModeGamma gamma
;
512 if (xf86vm_major
< 2) return FALSE
; /* no gamma control */
513 #ifdef X_XF86VidModeSetGammaRamp
514 if (xf86vm_use_gammaramp
)
515 return xf86vm_get_gamma_ramp(ramp
);
517 if (pXF86VidModeGetGamma(gdi_display
, DefaultScreen(gdi_display
), &gamma
))
519 GenerateRampFromGamma(ramp
->red
, gamma
.red
);
520 GenerateRampFromGamma(ramp
->green
, gamma
.green
);
521 GenerateRampFromGamma(ramp
->blue
, gamma
.blue
);
524 #endif /* X_XF86VidModeSetGamma */
528 static BOOL
X11DRV_XF86VM_SetGammaRamp(struct x11drv_gamma_ramp
*ramp
)
530 #ifdef X_XF86VidModeSetGamma
531 XF86VidModeGamma gamma
;
533 if (xf86vm_major
< 2 || !usexvidmode
) return FALSE
; /* no gamma control */
534 if (!ComputeGammaFromRamp(ramp
->red
, &gamma
.red
) || /* ramp validation */
535 !ComputeGammaFromRamp(ramp
->green
, &gamma
.green
) ||
536 !ComputeGammaFromRamp(ramp
->blue
, &gamma
.blue
)) return FALSE
;
537 #ifdef X_XF86VidModeSetGammaRamp
538 if (xf86vm_use_gammaramp
)
539 return xf86vm_set_gamma_ramp(ramp
);
541 return pXF86VidModeSetGamma(gdi_display
, DefaultScreen(gdi_display
), &gamma
);
544 #endif /* X_XF86VidModeSetGamma */
547 #else /* SONAME_LIBXXF86VM */
549 void X11DRV_XF86VM_Init(void)
551 TRACE("XVidMode support not compiled in.\n");
554 #endif /* SONAME_LIBXXF86VM */
556 /***********************************************************************
557 * GetDeviceGammaRamp (X11DRV.@)
559 * FIXME: should move to somewhere appropriate, but probably not before
560 * the stuff in graphics/x11drv/ has been moved to dlls/x11drv, so that
561 * they can include xvidmode.h directly
563 BOOL CDECL
X11DRV_GetDeviceGammaRamp(PHYSDEV dev
, LPVOID ramp
)
565 #ifdef SONAME_LIBXXF86VM
566 return X11DRV_XF86VM_GetGammaRamp(ramp
);
572 /***********************************************************************
573 * SetDeviceGammaRamp (X11DRV.@)
575 * FIXME: should move to somewhere appropriate, but probably not before
576 * the stuff in graphics/x11drv/ has been moved to dlls/x11drv, so that
577 * they can include xvidmode.h directly
579 BOOL CDECL
X11DRV_SetDeviceGammaRamp(PHYSDEV dev
, LPVOID ramp
)
581 #ifdef SONAME_LIBXXF86VM
582 return X11DRV_XF86VM_SetGammaRamp(ramp
);