https://bugs.winehq.org/show_bug.cgi?id=54659
Bug ID: 54659 Summary: d3d8:device & d3d9:device sometimes get floating point underflow in GenerateRampFromGamma() in Wine Product: Wine Version: unspecified Hardware: x86-64 OS: Linux Status: NEW Severity: normal Priority: P2 Component: d3d Assignee: wine-bugs@winehq.org Reporter: fgouget@codeweavers.com Distribution: ---
d3d8:device & d3d9:device sometimes get an floating point underflow exception in GenerateRampFromGamma() in Wine:
d3d8:device start dlls/d3d8/tests/device.c Unhandled exception: floating point underflow in 32-bit code (0x7e91438f). [...] Backtrace: =>0 0x7e91438f in libm.so.6 (+0x2538f) (0x0069ef68) 1 0x7e922d3b in libm.so.6 (+0x33d3b) (0x0069ef68) 2 0x7e55c181 GenerateRampFromGamma+0x51(ramp=00197C4C, gamma=0.000300) [Z:\home\winetest\tools\testbot\var\wine\dlls\winex11.drv\xvidmode.c:336] in winex11.so (0x0069ef68) 3 0x7e55c9b2 X11DRV_XF86VM_GetGammaRamp+0x60(ramp=<internal error>) [Z:\home\winetest\tools\testbot\var\wine\dlls\winex11.drv\xvidmode.c:519] in winex11.so (0x0069ef68) 4 0x7e55c9b2 X11DRV_GetDeviceGammaRamp+0x82(dev=<couldn't compute location>, ramp=<couldn't compute location>) [Z:\home\winetest\tools\testbot\var\wine\dlls\winex11.drv\xvidmode.c:569] in winex11.so (0x0069ef68) 5 0x7ea32408 NtGdiGetDeviceGammaRamp+0x88(hdc=<couldn't compute location>, ptr=<couldn't compute location>) [Z:\home\winetest\tools\testbot\var\wine\dlls\win32u\dc.c:1247] in win32u.so (0x0069efb8) 6 0x64a8d74c NtGdiGetDeviceGammaRamp+0x2c(hdc=<couldn't compute location>, ptr=<couldn't compute location>) [Z:\home\winetest\tools\testbot\var\wine\dlls\win32u\wrappers.c:280] in win32u (0x0069efe8) 7 0x6ccf9d3d wined3d_output_get_gamma_ramp+0x4d(output=00140E10, ramp=00197C4C) [Z:\home\winetest\tools\testbot\var\wine\dlls\wined3d\directx.c:1741] in wined3d (0x0069f028) 8 0x6cd5fcb0 wined3d_swapchain_get_gamma_ramp+0x30(swapchain=00197C30, ramp=00197C4C) [Z:\home\winetest\tools\testbot\var\wine\dlls\wined3d\swapchain.c:385] in wined3d (0x0069f058) 9 0x6cd61190 wined3d_swapchain_init+0x3c0(swapchain=<register EBX not accessible in this frame>, device=<register EDI not accessible in this frame>, desc=<internal error>, state_parent=00140D5C, parent=00140D50, parent_ops=67CDE7D4, swapchain_ops=6CE6933C) [Z:\home\winetest\tools\testbot\var\wine\dlls\wined3d\swapchain.c:1633] in wined3d (0x0069f168) 10 0x6cc8d9f3 adapter_gl_create_swapchain+0x73(device=0017B3E8, desc=0069F2BC, state_parent=00140D5C, parent=00140D50, parent_ops=67CDE7D4, swapchain=0069F1FC) [Z:\home\winetest\tools\testbot\var\wine\dlls\wined3d\adapter_gl.c:4701] in wined3d (0x0069f1b8) 11 0x6cd5fdd1 wined3d_swapchain_create+0x41(device=0017B3E8, desc=0069F2BC, state_parent=00140D5C, parent=00140D50, parent_ops=67CDE7D4, swapchain=00140D58) [Z:\home\winetest\tools\testbot\var\wine\dlls\wined3d\swapchain.c:1724] in wined3d (0x0069f208) 12 0x67ccfe84 swapchain_init+0x48(swap_interval=<internal error>, desc=<internal error>, device=<internal error>, swapchain=<internal error>) [Z:\home\winetest\tools\testbot\var\wine\dlls\d3d8\swapchain.c:180] in d3d8 (0x0069f248) 13 0x67ccfe84 d3d8_swapchain_create+0x84(device=001468F0, desc=0069F2BC, swap_interval=0xffffffff, swapchain=0069F290) [Z:\home\winetest\tools\testbot\var\wine\dlls\d3d8\swapchain.c:199] in d3d8 (0x0069f248) 14 0x67ccc467 device_init+0x217(device=001468F0, parent=00140A90, wined3d=00140AB0, adapter=0, device_type=D3DDEVTYPE_HAL, focus_window=0003004A, flags=0x42, parameters=0069F53C) [Z:\home\winetest\tools\testbot\var\wine\dlls\d3d8\device.c:3744] in d3d8 (0x0069f478) 15 0x67ccd649 d3d8_CreateDevice+0xa9(iface=<couldn't compute location>, adapter=<couldn't compute location>, device_type=<couldn't compute location>, focus_window=<couldn't compute location>, flags=<couldn't compute location>, parameters=<couldn't compute location>, device=<couldn't compute location>) [Z:\home\winetest\tools\testbot\var\wine\dlls\d3d8\directx.c:438] in d3d8 (0x0069f4e8) 16 0x004010ce in d3d8_test (+0x10ce) (0x0069f588) ...
This does not happen in the nightly Wine test runs but impacted at least two merge requests: * MR2072, repeatedly * MR2217, repeatedly
There are three immediate questions: * Where does the 0.000300 gamma come from? I don't think such a value makes sense so I suspect it's caused by a bug somewhere.
* Should xvidmode.c's GenerateRampFromGamma() crash in case of underflow (or overflow for that matter)? If not, ComputeGammaFromRamp() should probably be fixed too. Which other functions have the same issue? (not just in xvidmode.c)
* By default underflows don't cause exceptions. So which piece of code in d3d8:device does a _control87(0, _EM_UNDERFLOW) ?
Then, why does this failure not happen more often? The debian11 VM ran the tests multiple times in a row to test various locales: en -> success ar:MA -> success de -> success fr -> underflow he:IL -> underflow hi:IN -> underflow ja:JP -> underflow zh:CN -> underflow
While the wineprefix is recreated for each test, all the tests run on the same X server session. So my theory is that one of the tests in the first three runs progressively degraded the gamma at the X level, such that all the tests that followed got a bad gamma from the X server and crashed. Furthermore note that the first plain 32-bit run ran the full Wine test suite. That may have been a factor too.
MR2217 caused the following tests to run so the guilty party should be among them: d3d8:device d3d9:device ddraw:ddraw1 ddraw:ddraw2 ddraw:ddraw4 ddraw:ddraw7
That explains why this failure does not happen in the nightly Wine test runs: each is done after restoring the VM to a clean state. Similarly, other merge requests may run fewer tests so that the gamma does not get degraded that much.
That leaves a mystery though: I don't get this issue on my desktop (fg-deb64) despite running the tests every night and not ever restarting the X server (thankfully!). Maybe this gamma issue is caused by a bug that only happens with the VM environment (likely QXL GPU or dual screen configuration, such that it does not happen on single-screen Intel GPUs)?
https://bugs.winehq.org/show_bug.cgi?id=54659
François Gouget fgouget@codeweavers.com changed:
What |Removed |Added ---------------------------------------------------------------------------- Keywords| |source, testcase
https://bugs.winehq.org/show_bug.cgi?id=54659
--- Comment #1 from François Gouget fgouget@codeweavers.com --- This is nicely reproducible. The high-level recipe is as follows: * Pick a platform where xvidmode.c relies on XF86VidMode{Get|Set}Gamma() such as a QEmu VM. * Run d3d8:device or d3d9:device enough times. More precisely: - Run d3d8:device or d3d9:device once to get the gamma unmoored. - Create and destroy a thousand swapchains or so (e.g. by running Direct3D tests) so the gamma creeps down to the fatal 0.000300 value. - Run d3d8:device or d3d9:device once more to get the crash.
More details about what happens:
1. Gamma ramp vs. gamma -----------------------
When using the standard QXL emulated GPU, XF86VidModeGetGammaRampSize() returns a zero ramp size (no error) which causes xvidmode.c to use XF86VidMode{Get|Set}Gamma().
2. Swapchains vs gamma ramp ---------------------------
The gamma setting is saved whenever a swapchain is created, and restored when it is destroyed. But struct wined3d_swapchain only accounts for gamma ramps so the RGB gamma values returned by XF86VidModeGetGamma() are first converted to a gamma ramp, and then converted back to gamma values for XF86VidModeSetGamma() when the swapchain is destroyed.
When the gamma is 1.0 this round-trip works flawlessly. But as soon as the gamma is slightly different, rounding errors cause it to creep down with each round-trip:
0024:fixme:xvidmode:GenerateRampFromGamma gamma 0.09960000217 -> r_gamma 10.04016018 0024:fixme:xvidmode:ComputeGammaFromRamp gamma 0.09929743409 <- g_avg 10.07075405 0024:fixme:xvidmode:GenerateRampFromGamma gamma 0.09920000285 -> r_gamma 10.08064461 0024:fixme:xvidmode:ComputeGammaFromRamp gamma 0.09893912077 <- g_avg 10.10722542 ...
3. Direct3D vs. FPU rounding mode ---------------------------------
As said above, as long as the gamma is 1.0 all is fine.
So one way to get the ball rolling is to use SetDeviceGammaRamp() to set it to some other value. But that's not what d3d8:device and d3d9:device do.
Instead they play with the FPU rounding mode: * set_fpu_cw(0xf60); Where 0xc00 in the above mask changes the FPU rounding mode to 'truncate'.
* device_desc.flags = CREATE_DEVICE_FPU_PRESERVE; Which tells Direct3D to use our FPU settings instead of overriding them.
This causes the GenerateRampFromGamma() to generate a gamma ramp where values are off by one: 1111 -> 1110 ... eeee -> eeed. Then the conversion back to a gamma value gives 0.9998839036 instead of 1.0.
4. Unmasking underflow exceptions ---------------------------------
When the gamma is low enough, around 0.000300, generating the gamma ramp will generate underflows. This is usually fine.
However the same 0xf60 FPU test mentioned above also enables underflow exceptions (0x10 is cleared) and tells Direct3D to use that setting as before, thus causing the exception when the conditions are right.
https://bugs.winehq.org/show_bug.cgi?id=54659
--- Comment #2 from François Gouget fgouget@codeweavers.com --- Created attachment 74195 --> https://bugs.winehq.org/attachment.cgi?id=74195 Simplify reproducing the d3d8 gamma underflow crash
The attached patch simplifies reproducing the gamma underflow crash in d3d8:device by forcing the use of XF86VidMode{Get|Set}Gamma(), and adding options to test things out. It also adds some xvidmode traces.
To reproduce the crash run the following: ./wine dlls/d3d8/tests/i386-windows/d3d8_test.exe device break
The extended d3d8:device command line usage is: d3d8_test.exe device (truncate|roundup|high|low) [COUNT]
In all cases the test: * Resets the gamma to 1.0. * Creates and destroys COUNT swapchains. * Performs the truncate, roundup, high or low operation. * Creates and destroys COUNT swapchains again.
Where: * COUNT specifies how many swapchains to create and destroy. If omitted it defaults to 3. * truncate sets the FPU rounding mode to truncate and create+destroys a single swapchain with CREATE_DEVICE_FPU_PRESERVE. * roundup is the same but with round up rounding mode. * high uses SetDeviceGammaRamp() to set the gamma to a > 1.0 value. * low uses SetDeviceGammaRamp() to set the gamma to a < 1.0 value.
In the log look for the GenerateRampFromGamma and ComputeGammaFromRamp traces to observe gamma value changes. If possible add WINEDEBUG=xvidmode.
https://bugs.winehq.org/show_bug.cgi?id=54659
--- Comment #3 from François Gouget fgouget@codeweavers.com --- I can see a few options to fix this but it's probably best for the Direct3D experts to pick the right approach:
1. Maybe GenerateRampFromGamma() and/or ComputeGammaFromRamp() could do better to avoid rounding issues.
2. Directly store gamma values in struct wined3d_swapchain alongside the ramps with a flag to indicate which one to use. This would avoid the gamma -> ramp -> gamma round-trips. This may also speed things up a bit for this (presumably minor) use case.
3. Only restore the gamma on swapchain destruction if it was modified. Is it even possible to track gamma modifications? (I'm also assuming there is a good reason to restore the gamma)
4. Something else entirely.
https://bugs.winehq.org/show_bug.cgi?id=54659
--- Comment #4 from François Gouget fgouget@codeweavers.com --- Also this page is useful for understanding the FPU code word masks: http://www.cis.uoguelph.ca/~wgardner/intel_docs/compilers/compiler_f/main_fo...
And maybe the tests should use constants instead of undocumented obscure literals.