The current YUV to RGB conversion provides values in studio-range [16-235], but full-range is required [0-255]. As a result, we currently write the value of 16 to represent black, and so we get gray instead.
This MR changes the coefficients used in the conversion so that the resultant output is in full-range. It actually uses two sets of coefficients as these differ between SD and HD.
Also included is a number of Direct Draw 7 tests.
The coefficients used are documented here: https://learn.microsoft.com/en-us/windows/win32/medfound/recommended-8-bit-y...
-- v2: wined3d: Use Microsoft provided coefficients for YUV to RGB conversion. ddraw/tests: Test yuv to rgb blt in ddraw7.
From: Brendan McGrath bmcgrath@codeweavers.com
--- dlls/ddraw/tests/ddraw7.c | 233 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+)
diff --git a/dlls/ddraw/tests/ddraw7.c b/dlls/ddraw/tests/ddraw7.c index e7d66278ad7..60d74bf77fc 100644 --- a/dlls/ddraw/tests/ddraw7.c +++ b/dlls/ddraw/tests/ddraw7.c @@ -13652,6 +13652,238 @@ static void test_blt(void) DestroyWindow(window); }
+static void test_yuv_to_rgb_blt(void) +{ + IDirectDrawSurface7 *rgb_surface, *yuv_surface; + DWORD flags, chroma_pitch, chroma_height; + DDSURFACEDESC2 surface_desc, lock_desc; + unsigned int i, j, x, y; + IDirectDraw7 *ddraw; + ULONG refcount; + UINT32 *pixel; + HWND window; + HRESULT hr; + BYTE *ptr; + + static const struct + { + const char *name; + DWORD fourCC; + BYTE h_subsampling; + BYTE v_subsampling; + BYTE u_plane; + BYTE v_plane; + BYTE y_offset; + BYTE u_offset; + BYTE v_offset; + BYTE y_increment; + BYTE u_increment; + BYTE v_increment; + } + test_textures[] = + { + {"UYVY", MAKEFOURCC('U', 'Y', 'V', 'Y'), 2, 1, 0, 0, 1, 0, 2, 2, 4, 4}, + {"YUY2", MAKEFOURCC('Y', 'U', 'Y', '2'), 2, 1, 0, 0, 0, 1, 3, 2, 4, 4}, + {"NV12", MAKEFOURCC('N', 'V', '1', '2'), 2, 2, 1, 1, 0, 0, 1, 1, 2, 2}, + {"YV12", MAKEFOURCC('Y', 'V', '1', '2'), 2, 2, 2, 1, 0, 0, 0, 1, 1, 1}, + }; + + static const struct + { + const char *name; + DWORD height; + BYTE y; + BYTE u; + BYTE v; + UINT32 expected; + BOOL todo; + /* some systems use SD values for HD. */ + BOOL test_for_broken; + UINT32 broken_value; + } + test_colors[] = + { + {"black SD", 576, 0x10, 0x80, 0x80, 0x000000, TRUE}, + {"white SD", 576, 0xeb, 0x80, 0x80, 0xffffff, TRUE}, + {"red SD", 576, 0x51, 0x5a, 0xf0, 0xfe0000, TRUE}, + {"green SD", 576, 0x91, 0x36, 0x22, 0x00ff01, TRUE}, + {"blue SD", 576, 0x29, 0xf0, 0x6e, 0x0000ff, TRUE}, + {"gray SD", 576, 0x7e, 0x80, 0x80, 0x808080, TRUE}, + {"past black SD", 576, 0x00, 0x80, 0x80, 0x000000}, + {"past white SD", 576, 0xff, 0x80, 0x80, 0xffffff}, + {"zeros SD", 576, 0x00, 0x00, 0x00, 0x008700}, + /* Windows will treat anything with a height greater than 576 as HD and, + * as per ITU-R Recommendation BT.709, change the color scheme */ + {"black HD", 580, 0x10, 0x80, 0x80, 0x000000, TRUE}, + {"white HD", 580, 0xeb, 0x80, 0x80, 0xffffff, TRUE}, + {"red HD", 580, 0x3f, 0x66, 0xf0, 0xff0000, TRUE, TRUE, 0xe90002}, + {"green HD", 580, 0xad, 0x2a, 0x1a, 0x00ff01, TRUE, TRUE, 0x14ff09}, + {"blue HD", 580, 0x20, 0xf0, 0x76, 0x0100ff, TRUE, TRUE, 0x0300f5}, + {"gray HD", 580, 0x7e, 0x80, 0x80, 0x808080, TRUE}, + {"past black HD", 580, 0x00, 0x80, 0x80, 0x000000}, + {"past white HD", 580, 0xff, 0x80, 0x80, 0xffffff}, + {"zeros HD", 580, 0x00, 0x00, 0x00, 0x004d00, TRUE, TRUE, 0x008700}, + }; + + window = create_window(); + if (!(ddraw = create_ddraw())) + { + skip("Failed to create a 3D device, skipping test.\n"); + DestroyWindow(window); + return; + } + + hr = IDirectDraw7_SetCooperativeLevel(ddraw, window, DDSCL_NORMAL); + ok(hr == S_OK, "Failed to set cooperative level, hr %#lx.\n", hr); + + memset(&surface_desc, 0, sizeof(surface_desc)); + surface_desc.dwSize = sizeof(surface_desc); + surface_desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS | DDSD_PIXELFORMAT; + surface_desc.dwWidth = 720; + surface_desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; + surface_desc.ddpfPixelFormat.dwSize = sizeof(surface_desc.ddpfPixelFormat); + surface_desc.ddpfPixelFormat.dwRGBBitCount = 32; + surface_desc.ddpfPixelFormat.dwRBitMask = 0xff0000; + surface_desc.ddpfPixelFormat.dwGBitMask = 0xff00; + surface_desc.ddpfPixelFormat.dwBBitMask = 0xff; + surface_desc.ddpfPixelFormat.dwRGBAlphaBitMask = 0; + + flags = DDSD_HEIGHT | DDSD_PITCH | DDSD_PIXELFORMAT | DDSD_WIDTH; + + memset(&lock_desc, 0, sizeof(lock_desc)); + lock_desc.dwSize = sizeof(lock_desc); + + yuv_surface = NULL; + + for (i = 0; i < ARRAY_SIZE(test_textures); ++i) + { + winetest_push_context("%s format", test_textures[i].name); + + surface_desc.ddpfPixelFormat.dwFourCC = test_textures[i].fourCC; + + for (j = 0; j < ARRAY_SIZE(test_colors); j++) + { + winetest_push_context("color %s", test_colors[j].name); + + if (!yuv_surface) + { + surface_desc.dwHeight = test_colors[j].height; + surface_desc.ddpfPixelFormat.dwFlags = DDPF_FOURCC; + hr = IDirectDraw7_CreateSurface(ddraw, &surface_desc, &yuv_surface, NULL); + if (hr != S_OK) + { + skip("Failed to create yuv surface, hr %#lx.\n", hr); + winetest_pop_context(); + break; + } + + surface_desc.ddpfPixelFormat.dwFlags = DDPF_RGB; + hr = IDirectDraw7_CreateSurface(ddraw, &surface_desc, &rgb_surface, NULL); + ok(hr == S_OK, "Failed to create rgb surface, hr %#lx.\n", hr); + } + + hr = IDirectDrawSurface7_Lock(yuv_surface, NULL, &lock_desc, DDLOCK_WRITEONLY | DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL); + ok(hr == S_OK, "Got unexpected hr %#lx, expected S_OK.\n", hr); + ok((lock_desc.dwFlags & flags) == flags, "Got unexpected flags %#lx, expected %#lx.\n", + lock_desc.dwFlags, flags); + ok(lock_desc.ddpfPixelFormat.dwFlags == DDPF_FOURCC, + "Got unexpected pixel format flags %#lx, expected DDPF_FOURCC.\n", + lock_desc.ddpfPixelFormat.dwFlags); + ok(lock_desc.ddpfPixelFormat.dwFourCC == test_textures[i].fourCC, + "Got unexpected pixel format %#lx, expected %#lx.\n", + lock_desc.ddpfPixelFormat.dwFourCC, test_textures[i].fourCC); + + ptr = (BYTE*)lock_desc.lpSurface + test_textures[i].y_offset; + + for (y = 0; y < lock_desc.dwHeight; y++) + { + for (x = 0; x < lock_desc.lPitch / test_textures[i].y_increment; x++) + { + *ptr = test_colors[j].y; + ptr += test_textures[i].y_increment; + } + } + + chroma_pitch = lock_desc.lPitch / (test_textures[i].h_subsampling * test_textures[i].y_increment); + chroma_height = lock_desc.dwHeight / test_textures[i].v_subsampling; + + ptr = (BYTE*)lock_desc.lpSurface + test_textures[i].u_offset; + + if (test_textures[i].u_plane) + { + ptr += lock_desc.lPitch * lock_desc.dwHeight; + ptr += chroma_pitch * chroma_height * (test_textures[i].u_plane - 1); + } + + for (y = 0; y < chroma_height; y++) + { + for (x = 0; x < chroma_pitch; x++) + { + *ptr = test_colors[j].u; + ptr += test_textures[i].u_increment; + } + } + + ptr = (BYTE*)lock_desc.lpSurface + test_textures[i].v_offset; + if (test_textures[i].v_plane) + { + ptr += lock_desc.lPitch * lock_desc.dwHeight; + ptr += chroma_pitch * chroma_height * (test_textures[i].v_plane - 1); + } + + for (y = 0; y < chroma_height; y++) + { + for (x = 0; x < chroma_pitch; x++) + { + *ptr = test_colors[j].v; + ptr += test_textures[i].v_increment; + } + } + + hr = IDirectDrawSurface7_Unlock(yuv_surface, NULL); + ok(hr == S_OK, "Got unexpected hr %#lx, expected S_OK.\n", hr); + + hr = IDirectDrawSurface7_Blt(rgb_surface, NULL, yuv_surface, NULL, DDBLT_WAIT, NULL); + ok(hr == S_OK, "Got unexpected hr %#lx, expected S_OK.\n", hr); + + hr = IDirectDrawSurface7_Lock(rgb_surface, NULL, &lock_desc, DDLOCK_READONLY | DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL); + ok(hr == S_OK, "Got unexpected hr %#lx, expected S_OK.\n", hr); + ok((lock_desc.dwFlags & flags) == flags, "Got unexpected flags %#lx, expected %#lx.\n", lock_desc.dwFlags, flags); + ok(lock_desc.ddpfPixelFormat.dwFlags == DDPF_RGB, "Got unexpected pixel format flags %#lx, expected DDPF_RGB.\n", + lock_desc.ddpfPixelFormat.dwFlags); + + /* check the top left 4x4 pixels */ + for (y = 0; y < 4; y++) + { + pixel = (UINT32*)((BYTE*)lock_desc.lpSurface + lock_desc.lPitch * y); + for (x = 0; x < 4; x++) + todo_wine_if(test_colors[j].todo) + ok(compare_color(pixel[x] & 0xffffff, test_colors[j].expected, 1) || + broken(test_colors[j].test_for_broken && compare_color(pixel[x] & 0xffffff, test_colors[j].broken_value, 1)), + "Got unexpected value at (%d,%d). Got %#lx, expected %#lx.\n", + x, y, (long unsigned)pixel[x] & 0xffffff, (long unsigned)test_colors[j].expected); + } + + hr = IDirectDrawSurface7_Unlock(rgb_surface, NULL); + ok(hr == S_OK, "Got unexpected hr %#lx, expected S_OK.\n", hr); + + if (j + 1 == ARRAY_SIZE(test_colors) || test_colors[j].height != test_colors[j+1].height) + { + IDirectDrawSurface7_Release(yuv_surface); + IDirectDrawSurface7_Release(rgb_surface); + yuv_surface = NULL; + } + winetest_pop_context(); + } + + winetest_pop_context(); + } + + refcount = IDirectDraw7_Release(ddraw); + ok(!refcount, "DirectDraw7 has %lu references left.\n", refcount); + DestroyWindow(window); +} + static void test_blt_z_alpha(void) { DWORD blt_flags[] = @@ -20609,6 +20841,7 @@ START_TEST(ddraw7) test_offscreen_overlay(); test_overlay_rect(); test_blt(); + test_yuv_to_rgb_blt(); test_blt_z_alpha(); test_cross_device_blt(); test_color_clamping();
From: Brendan McGrath bmcgrath@codeweavers.com
--- dlls/ddraw/tests/ddraw7.c | 28 +++++++++++++--------------- dlls/wined3d/glsl_shader.c | 23 +++++++++++++++++------ 2 files changed, 30 insertions(+), 21 deletions(-)
diff --git a/dlls/ddraw/tests/ddraw7.c b/dlls/ddraw/tests/ddraw7.c index 60d74bf77fc..b4b1498039e 100644 --- a/dlls/ddraw/tests/ddraw7.c +++ b/dlls/ddraw/tests/ddraw7.c @@ -13696,33 +13696,32 @@ static void test_yuv_to_rgb_blt(void) BYTE u; BYTE v; UINT32 expected; - BOOL todo; /* some systems use SD values for HD. */ BOOL test_for_broken; UINT32 broken_value; } test_colors[] = { - {"black SD", 576, 0x10, 0x80, 0x80, 0x000000, TRUE}, - {"white SD", 576, 0xeb, 0x80, 0x80, 0xffffff, TRUE}, - {"red SD", 576, 0x51, 0x5a, 0xf0, 0xfe0000, TRUE}, - {"green SD", 576, 0x91, 0x36, 0x22, 0x00ff01, TRUE}, - {"blue SD", 576, 0x29, 0xf0, 0x6e, 0x0000ff, TRUE}, - {"gray SD", 576, 0x7e, 0x80, 0x80, 0x808080, TRUE}, + {"black SD", 576, 0x10, 0x80, 0x80, 0x000000}, + {"white SD", 576, 0xeb, 0x80, 0x80, 0xffffff}, + {"red SD", 576, 0x51, 0x5a, 0xf0, 0xfe0000}, + {"green SD", 576, 0x91, 0x36, 0x22, 0x00ff01}, + {"blue SD", 576, 0x29, 0xf0, 0x6e, 0x0000ff}, + {"gray SD", 576, 0x7e, 0x80, 0x80, 0x808080}, {"past black SD", 576, 0x00, 0x80, 0x80, 0x000000}, {"past white SD", 576, 0xff, 0x80, 0x80, 0xffffff}, {"zeros SD", 576, 0x00, 0x00, 0x00, 0x008700}, /* Windows will treat anything with a height greater than 576 as HD and, * as per ITU-R Recommendation BT.709, change the color scheme */ - {"black HD", 580, 0x10, 0x80, 0x80, 0x000000, TRUE}, - {"white HD", 580, 0xeb, 0x80, 0x80, 0xffffff, TRUE}, - {"red HD", 580, 0x3f, 0x66, 0xf0, 0xff0000, TRUE, TRUE, 0xe90002}, - {"green HD", 580, 0xad, 0x2a, 0x1a, 0x00ff01, TRUE, TRUE, 0x14ff09}, - {"blue HD", 580, 0x20, 0xf0, 0x76, 0x0100ff, TRUE, TRUE, 0x0300f5}, - {"gray HD", 580, 0x7e, 0x80, 0x80, 0x808080, TRUE}, + {"black HD", 580, 0x10, 0x80, 0x80, 0x000000}, + {"white HD", 580, 0xeb, 0x80, 0x80, 0xffffff}, + {"red HD", 580, 0x3f, 0x66, 0xf0, 0xff0000, TRUE, 0xe90002}, + {"green HD", 580, 0xad, 0x2a, 0x1a, 0x00ff01, TRUE, 0x14ff09}, + {"blue HD", 580, 0x20, 0xf0, 0x76, 0x0100ff, TRUE, 0x0300f5}, + {"gray HD", 580, 0x7e, 0x80, 0x80, 0x808080}, {"past black HD", 580, 0x00, 0x80, 0x80, 0x000000}, {"past white HD", 580, 0xff, 0x80, 0x80, 0xffffff}, - {"zeros HD", 580, 0x00, 0x00, 0x00, 0x004d00, TRUE, TRUE, 0x008700}, + {"zeros HD", 580, 0x00, 0x00, 0x00, 0x004d00, TRUE, 0x008700}, };
window = create_window(); @@ -13857,7 +13856,6 @@ static void test_yuv_to_rgb_blt(void) { pixel = (UINT32*)((BYTE*)lock_desc.lpSurface + lock_desc.lPitch * y); for (x = 0; x < 4; x++) - todo_wine_if(test_colors[j].todo) ok(compare_color(pixel[x] & 0xffffff, test_colors[j].expected, 1) || broken(test_colors[j].test_for_broken && compare_color(pixel[x] & 0xffffff, test_colors[j].broken_value, 1)), "Got unexpected value at (%d,%d). Got %#lx, expected %#lx.\n", diff --git a/dlls/wined3d/glsl_shader.c b/dlls/wined3d/glsl_shader.c index df60415f062..7a1f351b2db 100644 --- a/dlls/wined3d/glsl_shader.c +++ b/dlls/wined3d/glsl_shader.c @@ -12677,10 +12677,12 @@ static void glsl_blitter_generate_yuv_shader(struct wined3d_string_buffer *buffe { enum complex_fixup complex_fixup = get_complex_fixup(args->fixup);
- shader_addline(buffer, "const vec4 yuv_coef = vec4(1.403, -0.344, -0.714, 1.770);\n"); + shader_addline(buffer, "const vec4 yuv_coef_sd = vec4(1.596, -0.392, -0.813, 2.017);\n"); + shader_addline(buffer, "const vec4 yuv_coef_hd = vec4(1.793, -0.213, -0.533, 2.112);\n"); shader_addline(buffer, "float luminance;\n"); shader_addline(buffer, "vec2 texcoord;\n"); shader_addline(buffer, "vec2 chroma;\n"); + shader_addline(buffer, "vec4 yuv_coef;\n"); shader_addline(buffer, "uniform vec2 size;\n");
shader_addline(buffer, "\nvoid main()\n{\n"); @@ -12715,11 +12717,20 @@ static void glsl_blitter_generate_yuv_shader(struct wined3d_string_buffer *buffe return; }
- /* Calculate the final result. Formula is taken from - * http://www.fourcc.org/fccyvrgb.php. Note that the chroma - * ranges from -0.5 to 0.5. */ - shader_addline(buffer, "\n chroma.xy -= 0.5;\n"); - + /* Calculate the final result. Formula is taken from: + * https://learn.microsoft.com/en-us/windows/win32/medfound/recommended-8-bit-y.... + * SD and HD textures use different coefficients. SD is anything with both width and height smaller or equal to 720x576. + * Note that input values are clamped for SD, but not HD. Luminance is clamped to [16,235] ([0.063, 0.922] when normalized). + * Chroma is clamped to [16,240] ([0.063, 0.941] when normalized). */ + shader_addline(buffer, "\n if (size.x <= 720 && size.y <= 576)\n {\n"); + shader_addline(buffer, " luminance = clamp(luminance, 0.063, 0.922);\n"); + shader_addline(buffer, " chroma.xy = clamp(chroma.xy, 0.063, 0.941);\n"); + shader_addline(buffer, " yuv_coef = yuv_coef_sd;\n"); + shader_addline(buffer, " }\n else\n {\n"); + shader_addline(buffer, " yuv_coef = yuv_coef_hd;\n"); + shader_addline(buffer, " }\n"); + shader_addline(buffer, " luminance = (luminance - 0.063) * 1.164;\n"); + shader_addline(buffer, " chroma.xy = chroma.xy - 0.5;\n"); shader_addline(buffer, " %s.x = luminance + chroma.x * yuv_coef.x;\n", output); shader_addline(buffer, " %s.y = luminance + chroma.y * yuv_coef.y + chroma.x * yuv_coef.z;\n", output); shader_addline(buffer, " %s.z = luminance + chroma.y * yuv_coef.w;\n", output);
static const
Done
We should instead use compare_color(..., 1),
Oh, I didn't know about this one. Thank-you. I had considered writing something like this myself, so very happy to find it already exists.
Which ones?
Good question. I'm not 100% where the implementation exists. It might be a hardware vs. software thing and/or possibly the graphics driver. But I discovered this when running the tests on testbot (on my Windows install it uses the HD values).
can we change these values to be, say, 576 and 572
I used 576 and 580 (otherwise they're both SD).
hr == S_OK
Done
we can move the surface creation to the outer loop, right?
Not quite, as we resize during the inner loop. But I did modify it to only recreate the surface if a resize is required. I could also separate the SD and HD tests, that way we'd only create the RGB texture twice instead of eight times. Let me know if you think that's worth it.
We have winetest_push_context() now; let's use that in new code
I knew about that one, I should have used it.
Also, specifying both an index and a name seems a bit redundant?
Agreed
A skip in that case seems odd, why not just use broken() when comparing colours?
Good point. I think I originally missed the difference, but if I understand correctly: a `skip` will skip the tests on Wine, where as a `broken` only applies to Windows. Is that right?
it seems this could be a regular todo_wine
Some tests actually pass with the current implementation, so I do need a `todo_wine_if`.
We're dealing with floats here, though, not integers. Also, as the tests show, being off by one isn't really a problem
Yeah, fair enough. I think I got caught up with making sure my results on Wine matched exactly with on the results on my Windows install. But after testing with testbot, it's evident that an off-by-one is pretty standard. I've changed that.
Which ones?
Good question. I'm not 100% where the implementation exists. It might be a hardware vs. software thing and/or possibly the graphics driver. But I discovered this when running the tests on testbot (on my Windows install it uses the HD values).
What Windows install is that, and with which vendor card?
None of the Windows 11 testbot results match. All three vendors we have (WARP, NVidia, and AMD) use the SD range for both tested sizes, and NVidia moreover yields results that almost match the current Wine algorithm.
I checked one of my contemporaneous graphics cards (ATI Rage 128) and it mostly agrees with the more modern AMD/WARP results, although I had to raise the tolerance a bit (4 seems to work). It also succeeds YV12 surface creation but fails the subsequent Blt(), so we'll probably need to handle that—assuming that this test is indeed useful in the first place, which it indeed may not be. Sorry I didn't check these sooner...
A skip in that case seems odd, why not just use broken() when comparing colours?
Good point. I think I originally missed the difference, but if I understand correctly: a `skip` will skip the tests on Wine, where as a `broken` only applies to Windows. Is that right?
skip() / win_skip() by itself doesn't do anything but print a message and increase the "skips" counter. The precise usefulness of that has never quite been clear to me, but if there are skips that stands out on the test.winehq.org pages.
Both win_skip() and broken() only apply to Windows. A win_skip() under Wine counts as a failure; broken() under Wine basically returns 0 (so in general it also counts as a failure). Generally they're used when, for some reason, we deem that Wine needs to be more strict than Windows actually is, or that some Windows behaviour is due to a bug and we don't want to be bug-compatible in that case.
it seems this could be a regular todo_wine
Some tests actually pass with the current implementation, so I do need a `todo_wine_if`.
Oops, I misread, sorry.
What Windows install is that, and with which vendor card?
Windows 10 Home Edition with an NVIDIA GeForce GTX 1050 Ti
NVidia moreover yields results that almost match the current Wine algorithm
Yeah, it looks like that NVidia system is returning values in studio range. Which is what the current Wine algorithm was doing (where black is R = G = B = 16 = 0x10 and white = R = G = B = 235 = 0xeb). I have no idea why though. Mine certainly doesn't.
On Sat Feb 15 02:30:01 2025 +0000, Brendan McGrath wrote:
What Windows install is that, and with which vendor card?
Windows 10 Home Edition with an NVIDIA GeForce GTX 1050 Ti
NVidia moreover yields results that almost match the current Wine algorithm
Yeah, it looks like that NVidia system is returning values in studio range. Which is what the current Wine algorithm was doing (where black is R = G = B = 16 = 0x10 and white = R = G = B = 235 = 0xeb). I have no idea why though. Mine certainly doesn't.
What is the impetus for this patch?
On Sat Feb 15 02:40:30 2025 +0000, Elizabeth Figura wrote:
What is the impetus for this patch?
It's to address reported regressive behaviour in the colors displayed with quartz video playback when using vmr7. I found in Proton 9, it went via d3d9 and dxvk, but it now goes via ddraw7 (and wined3d). The issue specifically reported a black background now being rendered grey (which stands out on a black border). I tracked it down to 16 being used to represent black (when 0 was expected). In otherwords, full-range was expected, but we were producing studio-range.
Yeah, it looks like that NVidia system is returning values in studio range. Which is what the current Wine algorithm was doing (where black is R = G = B = 16 = 0x10 and white = R = G = B = 235 = 0xeb). I have no idea why though. Mine certainly doesn't.
I've been able reproduce those NVidia results on my install. There's a setting in the NVidia control panel that allows you to set "video color settings" so that the 'Dynamic range' is 'Limited (16-235)'.
So it seems, on Windows, it is a driver implementation and can be influenced by settings. So I guess this MR is effectively changing "that setting" in Wine from 'Limited (16-235)' to 'Full (0-255)'.
But it also makes Wine consistent with `ffplay`, `gst-play-1.0` and dxvk; which all use "Full Range" (i.e. they all render black as 0).