This fixes a misbehaving proton game that sets MF_XVP_PLAYBACK_MODE.
MF_XVP_PLAYBACK_MODE isn't mentioned in microsoft docs, but it's explained and used in the MIT-licensed [Windows-classic-samples](https://github.com/microsoft/Windows-classic-samples/blob/2b94df5730177ec27e...)
Not sure if mfplat is the right place to add the tests for this since the code is in winegstreamer, but it's very similar to the existing mfplat tests and the test does not interface with winegstreamer directly.
It should be noted that on windows, ProcessOutput errors out if we provide a pSample with MF_XVP_PLAYBACK_MODE unset, wine simply ignores it.
-- v4: winegstreamer: Allow caller to allocate samples in MF_XVP_PLAYBACK_MODE. mfplat/tests: Add test for MF_XVP_PLAYBACK_MODE.
From: Charlotte Pabst cpabst@codeweavers.com
--- dlls/mfplat/tests/mfplat.c | 195 +++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+)
diff --git a/dlls/mfplat/tests/mfplat.c b/dlls/mfplat/tests/mfplat.c index 1cb60d168e1..18f9c2b2154 100644 --- a/dlls/mfplat/tests/mfplat.c +++ b/dlls/mfplat/tests/mfplat.c @@ -122,6 +122,8 @@ DEFINE_MEDIATYPE_GUID(MEDIASUBTYPE_wvp2,MAKEFOURCC('w','v','p','2')); DEFINE_MEDIATYPE_GUID(MEDIASUBTYPE_X264,MAKEFOURCC('X','2','6','4')); DEFINE_MEDIATYPE_GUID(MEDIASUBTYPE_x264,MAKEFOURCC('x','2','6','4'));
+DEFINE_GUID(MF_XVP_PLAYBACK_MODE, 0x3c5d293f, 0xad67, 0x4e29, 0xaf, 0x12, 0xcf, 0x3e, 0x23, 0x8a, 0xcc, 0xe9); + static BOOL is_win8_plus;
#define EXPECT_REF(obj,ref) _expect_ref((IUnknown*)obj, ref, __LINE__) @@ -6762,6 +6764,198 @@ static void test_dxgi_device_manager(void) IMFDXGIDeviceManager_Release(manager2); }
+static IMFSample *create_dxgi_sample(ID3D11Device *device, UINT32 color, UINT bind_flags) +{ + D3D11_TEXTURE2D_DESC desc = { + .Width = 1, + .Height = 1, + .MipLevels = 1, + .ArraySize = 1, + .Format = DXGI_FORMAT_B8G8R8A8_UNORM, + .SampleDesc = { + .Count = 1, + .Quality = 0, + }, + .Usage = D3D11_USAGE_DEFAULT, + .BindFlags = bind_flags, + .CPUAccessFlags = 0, + .MiscFlags = 0, + }; + D3D11_SUBRESOURCE_DATA data = { + .pSysMem = &color, + .SysMemPitch = 4, + .SysMemSlicePitch = 4, + }; + IMFSample *sample; + ID3D11Texture2D *texture; + IMFMediaBuffer *buffer; + HRESULT hr; + + hr = ID3D11Device_CreateTexture2D(device, &desc, &data, &texture); + ok(hr == S_OK, "Failed to create d3d11 texture, hr %#lx.\n", hr); + + hr = pMFCreateDXGISurfaceBuffer(&IID_ID3D11Texture2D, (IUnknown *) texture, 0, FALSE, &buffer); + ok(hr == S_OK, "Failed to create dxgi surface buffer, hr %#lx.\n", hr); + + hr = IMFMediaBuffer_SetCurrentLength(buffer, 4); + ok(hr == S_OK, "Failed to set media buffer length, hr %#lx.\n", hr); + + hr = MFCreateSample(&sample); + ok(hr == S_OK, "Failed create sample, hr %#lx.\n", hr); + + hr = IMFSample_AddBuffer(sample, buffer); + ok(hr == S_OK, "Failed add buffer to sample, hr %#lx.\n", hr); + + hr = IMFSample_SetSampleTime(sample, 0); + ok(hr == S_OK, "Failed to set sample time, hr %#lx.\n", hr); + + hr = IMFSample_SetSampleDuration(sample, 10000000/60); + ok(hr == S_OK, "Failed to set sample duration, hr %#lx.\n", hr); + + IMFMediaBuffer_Release(buffer); + ID3D11Texture2D_Release(texture); + + return sample; +} + +static void test_xvp_playback_mode_helper(BOOL playback_mode) +{ + HRESULT hr; + UINT reset_token; + MFT_OUTPUT_STREAM_INFO info; + DWORD status; + MFT_OUTPUT_DATA_BUFFER out = { 0 }; + BYTE *out_data; + DWORD out_length; + IMFMediaBuffer *out_buffer; + IMFDXGIDeviceManager *manager; + ID3D11Device *device; + IMFTransform *xvp; + IMFAttributes *xvp_attrs; + IMFSample *in_sample, *out_sample; + IMFMediaType *type; + + if (!pMFCreateDXGIDeviceManager) + { + win_skip("MFCreateDXGIDeviceManager not found.\n"); + return; + } + + if (!pMFCreateDXGISurfaceBuffer) + { + win_skip("MFCreateDXGISurfaceBuffer() is not available.\n"); + return; + } + + hr = pD3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, D3D11_CREATE_DEVICE_VIDEO_SUPPORT, + NULL, 0, D3D11_SDK_VERSION, &device, NULL, NULL); + if (FAILED(hr)) + { + skip("Failed to create D3D11 device object.\n"); + return; + } + + hr = pMFCreateDXGIDeviceManager(&reset_token, &manager); + ok(hr == S_OK, "Failed to create device manager, hr %#lx.\n", hr); + + hr = IMFDXGIDeviceManager_ResetDevice(manager, (IUnknown *) device, reset_token); + ok(hr == S_OK, "Failed to reset device manager, hr %#lx.\n", hr); + + hr = CoCreateInstance(&CLSID_VideoProcessorMFT, NULL, 1, &IID_IMFTransform, (void **) &xvp); + ok(hr == S_OK, "Failed to create video processor MFT, hr %#lx.\n", hr); + + hr = IMFTransform_GetAttributes(xvp, &xvp_attrs); + ok(hr == S_OK, "Failed to get video processor attributes\n"); + + hr = IMFTransform_ProcessMessage(xvp, MFT_MESSAGE_SET_D3D_MANAGER, (ULONG_PTR) manager); + if (hr == E_NOINTERFACE) + { + win_skip("D3D11 manager does not support video processing.\n"); + return; + } + ok(hr == S_OK, "Failed to set D3D manager, hr %#lx.\n", hr); + + if (playback_mode) + { + hr = IMFAttributes_SetUINT32(xvp_attrs, &MF_XVP_PLAYBACK_MODE, TRUE); + ok(hr == S_OK, "Failed to set MF_XVP_PLAYBACK_MODE, hr %#lx.\n", hr); + } + + hr = MFCreateMediaType(&type); + ok(hr == S_OK, "Failed create media type, hr %#lx.\n", hr); + + hr = IMFMediaType_SetGUID(type, &MF_MT_MAJOR_TYPE, &MFMediaType_Video); + ok(hr == S_OK, "Failed to set major type, hr %#lx.\n", hr); + + hr = IMFMediaType_SetGUID(type, &MF_MT_SUBTYPE, &MFVideoFormat_ARGB32); + ok(hr == S_OK, "Failed to set subtype, hr %#lx.\n", hr); + + hr = IMFMediaType_SetUINT64(type, &MF_MT_FRAME_SIZE, ((UINT64) 1 << 32) | 1); + ok(hr == S_OK, "Failed to frame size, hr %#lx.\n", hr); + + hr = IMFTransform_SetInputType(xvp, 0, type, 0); + ok(hr == S_OK, "Failed to set input type, hr %#lx.\n", hr); + + hr = IMFTransform_SetOutputType(xvp, 0, type, 0); + ok(hr == S_OK, "Failed to set output type, hr %#lx.\n", hr); + + hr = IMFTransform_GetOutputStreamInfo(xvp, 0, &info); + ok(hr == S_OK, "Failed to get output stream info, hr %#lx.\n", hr); + + ok(!!(info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES), "MFT_OUTPUT_STREAM_PROVIDES_SAMPLES expected\n"); + ok(!(info.dwFlags & MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES), "MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES unexpected\n"); + ok(info.cbSize == 4, "output size should be 4\n"); + + in_sample = create_dxgi_sample(device, 0xdeadbeef, 0); + out_sample = create_dxgi_sample(device, 0, D3D11_BIND_RENDER_TARGET); + + hr = IMFTransform_ProcessInput(xvp, 0, in_sample, 0); + ok(hr == S_OK, "Failed to process input, hr %#lx.\n", hr); + + if (playback_mode) + { + out.pSample = out_sample; + } + + hr = IMFTransform_ProcessOutput(xvp, 0, 1, &out, &status); + ok(hr == S_OK, "Failed to process output, hr %#lx.\n", hr); + + if (playback_mode) + { + todo_wine + ok(out.pSample == out_sample, "Video processor should use caller-provided sample.\n"); + } + + hr = IMFSample_ConvertToContiguousBuffer(out.pSample, &out_buffer); + ok(hr == S_OK, "Failed to convert sample to contiguous buffer, hr %#lx.\n", hr); + + hr = IMFMediaBuffer_Lock(out_buffer, &out_data, &out_length, NULL); + ok(hr == S_OK, "Failed to lock buffer, hr %#lx.\n", hr); + + ok(out_length == 4, "Output sample length should be 4.\n"); + ok(*(UINT32 *) out_data == 0xdeadbeef, "Output sample should be populated with input data.\n"); + + hr = IMFMediaBuffer_Unlock(out_buffer); + ok(hr == S_OK, "Failed to unlock buffer, hr %#lx.\n", hr); + + if (out.pEvents) IMFCollection_Release(out.pEvents); + if (out.pSample != out_sample) IMFSample_Release(out_sample); + IMFSample_Release(out.pSample); + IMFSample_Release(in_sample); + IMFMediaBuffer_Release(out_buffer); + IMFMediaType_Release(type); + IMFTransform_Release(xvp); + IMFAttributes_Release(xvp_attrs); + IMFDXGIDeviceManager_Release(manager); + ID3D11Device_Release(device); +} + +static void test_xvp_playback_mode(void) +{ + test_xvp_playback_mode_helper(TRUE); + test_xvp_playback_mode_helper(FALSE); +} + static void test_MFCreateTransformActivate(void) { IMFActivate *activate; @@ -13709,6 +13903,7 @@ START_TEST(mfplat) test_MFInitMediaTypeFromAMMediaType(); test_MFCreatePathFromURL(); test_2dbuffer_copy(); + test_xvp_playback_mode();
CoUninitialize(); }
From: Charlotte Pabst cpabst@codeweavers.com
--- dlls/mfplat/tests/mfplat.c | 1 - dlls/winegstreamer/video_processor.c | 12 ++++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/dlls/mfplat/tests/mfplat.c b/dlls/mfplat/tests/mfplat.c index 18f9c2b2154..a67eb3513f3 100644 --- a/dlls/mfplat/tests/mfplat.c +++ b/dlls/mfplat/tests/mfplat.c @@ -6922,7 +6922,6 @@ static void test_xvp_playback_mode_helper(BOOL playback_mode)
if (playback_mode) { - todo_wine ok(out.pSample == out_sample, "Video processor should use caller-provided sample.\n"); }
diff --git a/dlls/winegstreamer/video_processor.c b/dlls/winegstreamer/video_processor.c index 3cb2c1a5bc6..347d2ca1d36 100644 --- a/dlls/winegstreamer/video_processor.c +++ b/dlls/winegstreamer/video_processor.c @@ -30,6 +30,8 @@ WINE_DECLARE_DEBUG_CHANNEL(winediag);
extern GUID MFVideoFormat_ABGR32;
+static const GUID MF_XVP_PLAYBACK_MODE = { 0x3c5d293f, 0xad67, 0x4e29, { 0xaf, 0x12, 0xcf, 0x3e, 0x23, 0x8a, 0xcc, 0xe9 } }; + static const GUID *const input_types[] = { &MFVideoFormat_IYUV, @@ -696,6 +698,7 @@ static HRESULT WINAPI video_processor_ProcessOutput(IMFTransform *iface, DWORD f MFT_OUTPUT_STREAM_INFO info; IMFSample *output_sample; HRESULT hr; + BOOL use_caller_samples;
TRACE("iface %p, flags %#lx, count %lu, samples %p, status %p.\n", iface, flags, count, samples, status);
@@ -709,7 +712,12 @@ static HRESULT WINAPI video_processor_ProcessOutput(IMFTransform *iface, DWORD f if (FAILED(hr = IMFTransform_GetOutputStreamInfo(iface, 0, &info))) return hr;
- if (impl->output_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) + if (FAILED(IMFAttributes_GetUINT32(impl->attributes, &MF_XVP_PLAYBACK_MODE, (UINT32 *) &use_caller_samples))) + use_caller_samples = FALSE; + + use_caller_samples = use_caller_samples || !(impl->output_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES); + + if (!use_caller_samples) { if (FAILED(hr = video_processor_init_allocator(impl))) return hr; @@ -727,7 +735,7 @@ static HRESULT WINAPI video_processor_ProcessOutput(IMFTransform *iface, DWORD f goto done; wg_sample_queue_flush(impl->wg_sample_queue, false);
- if (impl->output_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) + if (!use_caller_samples) { samples->pSample = output_sample; IMFSample_AddRef(output_sample);
On Mon Apr 14 15:01:02 2025 +0000, Nikolay Sivov wrote:
Why literal "1" ?
no reason, changed it to TRUE.
On Mon Apr 14 14:57:25 2025 +0000, Charlotte Pabst wrote:
yes, fallback to software processing is possible, but this test is specifically concerned with doing it via D3D11, since that is what sets MFT_OUTPUT_STREAM_PROVIDES_SAMPLES=TRUE in the first place, and it's also what's affected by MF_XVP_PLAYBACK_MODE. by "none of the DX11 software renderers support video processing" I meant that MS docs explicitly mention that MFT_MESSAGE_SET_D3D_MANAGER does not work with the WARP or REFERENCE driver types, so we can't do "pretend" hardware processing here either.
I mentioned fallback only because of how the need for such fallback is detected. Can we skip the whole test if video interfaces are not available for example? Or if MFT_MESSAGE_SET_D3D_MANAGER returns failure, maybe we can skip based on that?
if MFT_MESSAGE_SET_D3D_MANAGER returns failure, maybe we can skip based on that?
yes, I changed it to this. it seems simpler than querying ID3D11VideoDevice, tho I can change it to query if that's a better approach.