[PATCH v2 0/8] MR10645: mf/topology_loader: Insert optional nodes after each branch is connected.
New tests show each optional node is connected after the nodes on either side are connected. This is an issue in the game Psycho Sleuth (at least in the demo) because it uses a transform which must have its input types enumerated before it will work correctly. This occurs when a decoder is inserted before the transform, but an optional node lies immediately upstream of the transform, and if optional nodes are not handled correctly, the decoder is initially connected to the optional instead of being inserted later. A couple of other issues revealed while making new tests are fixed here too. -- v2: mf/topology_loader: Try to insert optional nodes after resolving the surrounding branch. mf/topology_loader: Introduce a helper to get the connection semantic flags. https://gitlab.winehq.org/wine/wine/-/merge_requests/10645
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/mf/tests/topology.c | 70 +++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/dlls/mf/tests/topology.c b/dlls/mf/tests/topology.c index 6d433b3e1c2..a967fbe600d 100644 --- a/dlls/mf/tests/topology.c +++ b/dlls/mf/tests/topology.c @@ -2446,6 +2446,19 @@ static void test_topology_loader(void) ATTR_UINT32(MF_MT_FIXED_SIZE_SAMPLES, 1), ATTR_UINT32(MF_MT_INTERLACE_MODE, 7), }; + static const media_type_desc video_yuy2_1280 = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_YUY2), + ATTR_RATIO(MF_MT_FRAME_RATE, 30000, 1001), + ATTR_RATIO(MF_MT_FRAME_SIZE, 1280, 720), + ATTR_RATIO(MF_MT_PIXEL_ASPECT_RATIO, 1, 1), + ATTR_UINT32(MF_MT_SAMPLE_SIZE, 1280 * 720 * 3 / 2), + ATTR_UINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1), + ATTR_UINT32(MF_MT_DEFAULT_STRIDE, 1280), + ATTR_UINT32(MF_MT_FIXED_SIZE_SAMPLES, 1), + ATTR_UINT32(MF_MT_INTERLACE_MODE, 7), + }; static const media_type_desc video_dummy = { ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), @@ -2455,7 +2468,7 @@ static void test_topology_loader(void) { const media_type_desc *input_types[2]; const media_type_desc *output_types[2]; - const media_type_desc *mft_input_type; + const media_type_desc *mft_input_types[3]; const media_type_desc *mft_output_types[5]; const media_type_desc *current_input; const media_type_desc *mft_current_output; @@ -2464,6 +2477,7 @@ static void test_topology_loader(void) MF_CONNECT_METHOD source_method; MF_CONNECT_METHOD sink_method; HRESULT expected_result; + unsigned int mft_current_input_1based_index; unsigned int expected_output_index; unsigned int flags; GUID decoder_class; @@ -2683,7 +2697,7 @@ static void test_topology_loader(void) { /* MP3 -> PCM, different enumerated bps, add test MFT, configure MFT */ .input_types = {&audio_mp3_44100}, .output_types = {&audio_pcm_48000}, .sink_method = MF_CONNECT_DIRECT, .source_method = MF_CONNECT_DIRECT, - .mft_input_type = &audio_pcm_44100, .mft_output_types = {&audio_pcm_48000}, + .mft_input_types = {&audio_pcm_44100}, .mft_output_types = {&audio_pcm_48000}, .decoded_type = &audio_pcm_44100, .expected_result = S_OK, .decoder_class = CLSID_CMP3DecMediaObject, .flags = LOADER_ADD_TEST_MFT | LOADER_EXPECT_MFT_INPUT_ENUMERATED, @@ -2691,7 +2705,7 @@ static void test_topology_loader(void) { /* MP3 -> PCM, different enumerated bps, add incompatible test MFT, configure MFT */ .input_types = {&audio_mp3_44100}, .output_types = {&audio_pcm_48000}, .sink_method = MF_CONNECT_DIRECT, .source_method = MF_CONNECT_DIRECT, - .mft_input_type = &audio_aac_44100, .mft_output_types = {&audio_pcm_48000}, + .mft_input_types = {&audio_aac_44100}, .mft_output_types = {&audio_pcm_48000}, .expected_result = MF_E_TOPO_CODEC_NOT_FOUND, .flags = LOADER_ADD_TEST_MFT | LOADER_EXPECT_MFT_INPUT_ENUMERATED, }, @@ -2822,24 +2836,33 @@ static void test_topology_loader(void) { /* H264 -> RGB32, Color Convert media type, add test MFT */ .input_types = {&video_h264_1280}, .output_types = {&video_color_convert_1280_rgb32}, .sink_method = -1, .source_method = -1, - .mft_input_type = &video_color_convert_1280_rgb32, .mft_output_types = {&video_color_convert_1280_rgb32}, + .mft_input_types = {&video_color_convert_1280_rgb32}, .mft_output_types = {&video_color_convert_1280_rgb32}, .expected_result = MF_E_TOPO_CODEC_NOT_FOUND, .decoder_class = CLSID_CMSH264DecoderMFT, .flags = LOADER_ADD_TEST_MFT | LOADER_EXPECT_MFT_INPUT_ENUMERATED, }, { /* H264 -> RGB32, Video Processor media type, add test MFT */ .input_types = {&video_h264_1280}, .output_types = {&video_video_processor_1280_rgb32}, .sink_method = -1, .source_method = -1, - .mft_input_type = &video_video_processor_1280_rgb32, .mft_output_types = {&video_video_processor_1280_rgb32}, + .mft_input_types = {&video_video_processor_1280_rgb32}, .mft_output_types = {&video_video_processor_1280_rgb32}, .expected_result = S_OK, .decoder_class = CLSID_CMSH264DecoderMFT, .converter_class = CLSID_CColorConvertDMO, .flags = LOADER_ADD_TEST_MFT | LOADER_TEST_MFT_EXPECT_CONVERTER | LOADER_EXPECT_MFT_INPUT_ENUMERATED, }, { /* H264 -> NV12, add test MFT */ .input_types = {&video_h264_1280}, .output_types = {&video_nv12_1280}, .sink_method = -1, .source_method = -1, - .mft_input_type = &video_nv12_1280, .mft_output_types = {&video_nv12_1280}, + .mft_input_types = {&video_nv12_1280}, .mft_output_types = {&video_nv12_1280}, .expected_result = S_OK, .decoder_class = CLSID_CMSH264DecoderMFT, .flags = LOADER_ADD_TEST_MFT | LOADER_EXPECT_MFT_INPUT_ENUMERATED, }, + + { + /* NV12 -> NV12, add test MFT, set unsupported MFT input */ + .input_types = {&video_nv12_1280}, .output_types = {&video_nv12_1280}, .sink_method = MF_CONNECT_DIRECT, .source_method = -1, + .mft_input_types = {&video_nv12_1280, &video_yuy2_1280}, .mft_output_types = {&video_nv12_1280}, + .mft_current_input_1based_index = 2, + .expected_result = S_OK, + .flags = LOADER_ADD_TEST_MFT, + }, }; IMFTopologyNode *src_node, *sink_node, *src_node2, *sink_node2, *mft_node; @@ -2987,7 +3010,7 @@ static void test_topology_loader(void) const struct loader_test *test = &loader_tests[i]; struct test_transform *test_transform = NULL; IMFMediaType *mft_output_types[4] = {0}; - IMFMediaType *mft_input_type = NULL; + IMFMediaType *mft_input_types[2] = {0}; winetest_push_context("%u", i); @@ -3042,21 +3065,25 @@ static void test_topology_loader(void) ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); if (test->flags & LOADER_ADD_TEST_MFT) { - mft_input_type = input_types[0]; + UINT input_count, output_count; - if (test->mft_input_type) + for (input_count = 0; test->mft_input_types[input_count]; ++input_count) { - hr = MFCreateMediaType(&mft_input_type); + hr = MFCreateMediaType(&mft_input_types[input_count]); ok(hr == S_OK, "Failed to create media type, hr %#lx.\n", hr); - init_media_type(mft_input_type, *test->mft_input_type, -1); + init_media_type(mft_input_types[input_count], *test->mft_input_types[input_count], -1); } - for (j = 0; test->mft_output_types[j]; ++j) + if (!input_count) + IMFMediaType_AddRef(mft_input_types[input_count++] = input_types[0]); + + for (output_count = 0; test->mft_output_types[output_count]; ++output_count) { - hr = MFCreateMediaType(&mft_output_types[j]); + hr = MFCreateMediaType(&mft_output_types[output_count]); ok(hr == S_OK, "Failed to create media type, hr %#lx.\n", hr); - init_media_type(mft_output_types[j], *test->mft_output_types[j], -1); + init_media_type(mft_output_types[output_count], *test->mft_output_types[output_count], -1); } - test_transform_create(1, &mft_input_type, j, mft_output_types, FALSE, &transform); + + test_transform_create(input_count, mft_input_types, output_count, mft_output_types, FALSE, &transform); test_transform = test_transform_from_IMFTransform(transform); IMFTransform_AddRef(transform); } @@ -3082,7 +3109,13 @@ static void test_topology_loader(void) } else { - IMFTransform_SetInputType(transform, 0, NULL, 0); + if (test->mft_current_input_1based_index) + { + hr = IMFTransform_SetInputType(transform, 0, mft_input_types[test->mft_current_input_1based_index - 1], 0); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + } + else + IMFTransform_SetInputType(transform, 0, NULL, 0); IMFTransform_SetOutputType(transform, 0, NULL, 0); } IMFTransform_Release(transform); @@ -3176,6 +3209,7 @@ todo_wine { hr = IMFTopology_GetNodeCount(full_topology, &node_count); ok(hr == S_OK, "Failed to get node count, hr %#lx.\n", hr); + todo_wine_if(test->mft_current_input_1based_index) ok(node_count == count, "Unexpected node count %u.\n", node_count); hr = IMFTopologyNode_GetTopoNodeID(src_node, &node_id); @@ -3396,8 +3430,8 @@ todo_wine { ok(ref == 0, "Release returned %ld\n", ref); ref = IMFStreamDescriptor_Release(sd); ok(ref == 0, "Release returned %ld\n", ref); - if (mft_input_type && test->mft_input_type) - IMFMediaType_Release(mft_input_type); + for (j = 0; j < ARRAY_SIZE(mft_input_types) && mft_input_types[j]; ++j) + IMFMediaType_Release(mft_input_types[j]); for (j = 0; j < ARRAY_SIZE(mft_output_types) && mft_output_types[j]; ++j) IMFMediaType_Release(mft_output_types[j]); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10645
From: Conor McCarthy <cmccarthy@codeweavers.com> This may be the rule for indirect connection too, but it's not clear if we need it. --- dlls/mf/tests/topology.c | 1 - dlls/mf/topology_loader.c | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dlls/mf/tests/topology.c b/dlls/mf/tests/topology.c index a967fbe600d..add77f3da79 100644 --- a/dlls/mf/tests/topology.c +++ b/dlls/mf/tests/topology.c @@ -3209,7 +3209,6 @@ todo_wine { hr = IMFTopology_GetNodeCount(full_topology, &node_count); ok(hr == S_OK, "Failed to get node count, hr %#lx.\n", hr); - todo_wine_if(test->mft_current_input_1based_index) ok(node_count == count, "Unexpected node count %u.\n", node_count); hr = IMFTopologyNode_GetTopoNodeID(src_node, &node_id); diff --git a/dlls/mf/topology_loader.c b/dlls/mf/topology_loader.c index 9cf4ee0b28c..c24f8bd72cb 100644 --- a/dlls/mf/topology_loader.c +++ b/dlls/mf/topology_loader.c @@ -599,7 +599,8 @@ static HRESULT topology_branch_connect_direct(IMFTopology *topology, struct topo if (SUCCEEDED(hr = IMFMediaTypeHandler_GetCurrentMediaType(branch->down.handler, &down_type))) { - if (IMFMediaType_IsEqual(up_type, down_type, &flags) == S_OK) + if (topology_node_get_type(branch->down.node) != MF_TOPOLOGY_OUTPUT_NODE + || IMFMediaType_IsEqual(up_type, down_type, &flags) == S_OK) hr = topology_branch_connect_with_type(topology, branch, up_type); else { -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10645
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/mf/tests/topology.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dlls/mf/tests/topology.c b/dlls/mf/tests/topology.c index add77f3da79..e6a026aec6e 100644 --- a/dlls/mf/tests/topology.c +++ b/dlls/mf/tests/topology.c @@ -1739,6 +1739,7 @@ struct test_handler ULONG media_types_count; IMFMediaType **media_types; BOOL enum_complete; + BOOL is_supported_called; }; static struct test_handler *impl_from_IMFMediaTypeHandler(IMFMediaTypeHandler *iface) @@ -1776,6 +1777,8 @@ static HRESULT WINAPI test_handler_IsMediaTypeSupported(IMFMediaTypeHandler *ifa BOOL result; ULONG i; + impl->is_supported_called = TRUE; + if (out_type) *out_type = NULL; @@ -3157,6 +3160,8 @@ static void test_topology_loader(void) ok(hr == S_OK, "Failed to get attribute count, hr %#lx.\n", hr); ok(!count, "Unexpected count %u.\n", count); + handler.is_supported_called = FALSE; + if (test->flags & LOADER_SET_ENUMERATE_SOURCE_TYPES) IMFTopology_SetUINT32(topology, &MF_TOPOLOGY_ENUMERATE_SOURCE_TYPES, 1); if (test->flags & LOADER_SET_XVP_FOR_PLAYBACK) @@ -3234,6 +3239,9 @@ todo_wine { if (hr == S_OK) IMFMediaType_Release(media_type); + todo_wine_if(!handler.is_supported_called) + ok(handler.is_supported_called, "Sink input support not checked.\n"); + if (!IsEqualGUID(&test->decoder_class, &GUID_NULL)) { GUID class_id; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10645
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/mf/tests/topology.c | 1 - dlls/mf/topology_loader.c | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dlls/mf/tests/topology.c b/dlls/mf/tests/topology.c index e6a026aec6e..e8327b7dc1c 100644 --- a/dlls/mf/tests/topology.c +++ b/dlls/mf/tests/topology.c @@ -3239,7 +3239,6 @@ todo_wine { if (hr == S_OK) IMFMediaType_Release(media_type); - todo_wine_if(!handler.is_supported_called) ok(handler.is_supported_called, "Sink input support not checked.\n"); if (!IsEqualGUID(&test->decoder_class, &GUID_NULL)) diff --git a/dlls/mf/topology_loader.c b/dlls/mf/topology_loader.c index c24f8bd72cb..a07942b6af3 100644 --- a/dlls/mf/topology_loader.c +++ b/dlls/mf/topology_loader.c @@ -599,8 +599,9 @@ static HRESULT topology_branch_connect_direct(IMFTopology *topology, struct topo if (SUCCEEDED(hr = IMFMediaTypeHandler_GetCurrentMediaType(branch->down.handler, &down_type))) { - if (topology_node_get_type(branch->down.node) != MF_TOPOLOGY_OUTPUT_NODE + if ((topology_node_get_type(branch->down.node) != MF_TOPOLOGY_OUTPUT_NODE || IMFMediaType_IsEqual(up_type, down_type, &flags) == S_OK) + && SUCCEEDED(hr = IMFMediaTypeHandler_IsMediaTypeSupported(branch->down.handler, down_type, NULL))) hr = topology_branch_connect_with_type(topology, branch, up_type); else { -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10645
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/mf/tests/topology.c | 247 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 234 insertions(+), 13 deletions(-) diff --git a/dlls/mf/tests/topology.c b/dlls/mf/tests/topology.c index e8327b7dc1c..fbb3a63785f 100644 --- a/dlls/mf/tests/topology.c +++ b/dlls/mf/tests/topology.c @@ -247,6 +247,8 @@ static void init_sink_node(IMFStreamSink *stream_sink, MF_CONNECT_METHOD method, } } +static LONG sequence_count; + struct test_transform { IMFTransform IMFTransform_iface; @@ -258,6 +260,8 @@ struct test_transform UINT input_count; IMFMediaType **input_types; IMFMediaType *input_type; + LONG set_input_sequence_id; + LONG set_output_sequence_id; UINT output_count; IMFMediaType **output_types; @@ -267,6 +271,7 @@ struct test_transform BOOL input_type_set; BOOL output_type_set; BOOL set_input_nulls_output; + BOOL validate_output_type; IDirect3DDeviceManager9 *expect_d3d9_device_manager; BOOL got_d3d9_device_manager; @@ -437,6 +442,9 @@ static HRESULT WINAPI test_transform_SetInputType(IMFTransform *iface, DWORD id, if (type) { + if (!transform->set_input_sequence_id) + transform->set_input_sequence_id = InterlockedIncrement(&sequence_count); + for (i = 0; i < transform->input_count; ++i) { if (IMFMediaType_Compare(transform->input_types[i], (IMFAttributes *)type, @@ -473,10 +481,25 @@ static HRESULT WINAPI test_transform_SetOutputType(IMFTransform *iface, DWORD id struct test_transform *transform = test_transform_from_IMFTransform(iface); if (flags & MFT_SET_TYPE_TEST_ONLY) return S_OK; + if (type && transform->validate_output_type) + { + BOOL result; + UINT i; + for (i = 0; i < transform->output_count; ++i) + { + if (IMFMediaType_Compare(transform->output_types[i], (IMFAttributes *)type, + MF_ATTRIBUTES_MATCH_OUR_ITEMS, &result) == S_OK && result) + break; + } + if (i == transform->output_count) + return MF_E_INVALIDMEDIATYPE; + } if (transform->output_type) IMFMediaType_Release(transform->output_type); if ((transform->output_type = type)) { + if (!transform->set_output_sequence_id) + transform->set_output_sequence_id = InterlockedIncrement(&sequence_count); transform->output_type_set = TRUE; IMFMediaType_AddRef(transform->output_type); } @@ -1739,7 +1762,7 @@ struct test_handler ULONG media_types_count; IMFMediaType **media_types; BOOL enum_complete; - BOOL is_supported_called; + LONG is_supported_sequence_id; }; static struct test_handler *impl_from_IMFMediaTypeHandler(IMFMediaTypeHandler *iface) @@ -1777,7 +1800,8 @@ static HRESULT WINAPI test_handler_IsMediaTypeSupported(IMFMediaTypeHandler *ifa BOOL result; ULONG i; - impl->is_supported_called = TRUE; + if (!impl->is_supported_sequence_id) + impl->is_supported_sequence_id = InterlockedIncrement(&sequence_count); if (out_type) *out_type = NULL; @@ -2265,6 +2289,10 @@ enum loader_test_flags LOADER_TEST_MFT_EXPECT_CONVERTER = 0x2000, LOADER_EXPECT_MFT_OUTPUT_ENUMERATED = 0x4000, LOADER_EXPECT_MFT_INPUT_ENUMERATED = 0x8000, + LOADER_ADD_OPTIONAL_TEST_MFT_UPSTREAM = 0x10000, + LOADER_ADD_OPTIONAL_TEST_MFT_DOWNSTREAM = 0x20000, + LOADER_EXPECT_OPTIONAL_REJECTED = 0x40000, + LOADER_EXPECT_MFT_INPUT_ENUMERATED_TODO = 0x80000, }; static void test_topology_loader(void) @@ -2473,6 +2501,8 @@ static void test_topology_loader(void) const media_type_desc *output_types[2]; const media_type_desc *mft_input_types[3]; const media_type_desc *mft_output_types[5]; + const media_type_desc *optional_mft_input_type; + const media_type_desc *optional_mft_output_type; const media_type_desc *current_input; const media_type_desc *mft_current_output; const media_type_desc *decoded_type; @@ -2866,11 +2896,89 @@ static void test_topology_loader(void) .expected_result = S_OK, .flags = LOADER_ADD_TEST_MFT, }, + + { + /* H264 -> NV12, add optional test MFT and test MFT */ + .input_types = {&video_h264_1280}, .output_types = {&video_nv12_1280}, .sink_method = -1, .source_method = -1, + .mft_input_types = {&video_nv12_1280}, .mft_output_types = {&video_nv12_1280}, + .expected_result = S_OK, .decoder_class = CLSID_CMSH264DecoderMFT, + .flags = LOADER_ADD_TEST_MFT | LOADER_ADD_OPTIONAL_TEST_MFT_UPSTREAM | LOADER_EXPECT_MFT_INPUT_ENUMERATED_TODO, + }, + { + /* H264 -> NV12, add unconnectable optional test MFT and test MFT */ + .input_types = {&video_h264_1280}, .output_types = {&video_nv12_1280}, .sink_method = -1, .source_method = -1, + .mft_input_types = {&video_nv12_1280}, .mft_output_types = {&video_nv12_1280}, + .optional_mft_input_type = &video_yuy2_1280, + .expected_result = S_OK, .decoder_class = CLSID_CMSH264DecoderMFT, + .flags = LOADER_ADD_TEST_MFT | LOADER_ADD_OPTIONAL_TEST_MFT_UPSTREAM | LOADER_EXPECT_OPTIONAL_REJECTED | LOADER_EXPECT_MFT_INPUT_ENUMERATED_TODO, + }, + { + /* H264 -> NV12, add optional test MFT and test MFT, require test MFT input change for optional */ + .input_types = {&video_h264_1280}, .output_types = {&video_nv12_1280}, .sink_method = -1, .source_method = -1, + .mft_input_types = {&video_nv12_1280, &video_yuy2_1280}, .mft_output_types = {&video_nv12_1280}, + .optional_mft_output_type = &video_yuy2_1280, + .expected_result = S_OK, .decoder_class = CLSID_CMSH264DecoderMFT, + .flags = LOADER_ADD_TEST_MFT | LOADER_ADD_OPTIONAL_TEST_MFT_UPSTREAM | LOADER_EXPECT_MFT_INPUT_ENUMERATED_TODO, + }, + { + /* H264 -> NV12, add test MFT and optional test MFT */ + .input_types = {&video_h264_1280}, .output_types = {&video_nv12_1280}, .sink_method = MF_CONNECT_DIRECT, .source_method = -1, + .mft_input_types = {&video_nv12_1280}, .mft_output_types = {&video_nv12_1280}, + .expected_result = S_OK, .decoder_class = CLSID_CMSH264DecoderMFT, + .flags = LOADER_ADD_TEST_MFT | LOADER_ADD_OPTIONAL_TEST_MFT_DOWNSTREAM | LOADER_EXPECT_MFT_INPUT_ENUMERATED, + }, + { + /* #60 H264 -> NV12, add test MFT and optional test MFT, require sink input change for optional */ + .input_types = {&video_h264_1280}, .output_types = {&video_nv12_1280, &video_yuy2_1280}, .sink_method = MF_CONNECT_DIRECT, .source_method = -1, + .mft_input_types = {&video_nv12_1280}, .mft_output_types = {&video_nv12_1280}, + .optional_mft_output_type = &video_yuy2_1280, + .expected_result = S_OK, .decoder_class = CLSID_CMSH264DecoderMFT, + .flags = LOADER_ADD_TEST_MFT | LOADER_NO_CURRENT_OUTPUT | LOADER_ADD_OPTIONAL_TEST_MFT_DOWNSTREAM | LOADER_EXPECT_MFT_INPUT_ENUMERATED, + }, + { + /* H264 -> NV12, add test MFT and unconnectable optional test MFT, converter is not added for optional connection */ + .input_types = {&video_h264_1280}, .output_types = {&video_nv12_1280}, .sink_method = -1, .source_method = -1, + .mft_input_types = {&video_nv12_1280}, .mft_output_types = {&video_nv12_1280}, + .optional_mft_output_type = &video_yuy2_1280, + .expected_result = S_OK, .decoder_class = CLSID_CMSH264DecoderMFT, + .flags = LOADER_ADD_TEST_MFT | LOADER_ADD_OPTIONAL_TEST_MFT_DOWNSTREAM | LOADER_EXPECT_OPTIONAL_REJECTED | LOADER_EXPECT_MFT_INPUT_ENUMERATED, + }, + + { + /* PCM -> PCM, optional sink */ + .input_types = {&audio_pcm_44100}, .output_types = {&audio_pcm_44100}, .sink_method = MF_CONNECT_AS_OPTIONAL, .source_method = -1, + .expected_result = MF_E_TOPO_UNSUPPORTED, + .flags = LOADER_TODO, + }, + { + /* PCM -> PCM, optional source is ignored */ + .input_types = {&audio_pcm_44100}, .output_types = {&audio_pcm_44100}, .sink_method = -1, .source_method = MF_CONNECT_AS_OPTIONAL, + .expected_result = S_OK, + }, + { + /* H264 -> NV12, optional sink */ + .input_types = {&video_h264_1280}, .output_types = {&video_nv12_1280}, .sink_method = MF_CONNECT_AS_OPTIONAL | MF_CONNECT_ALLOW_DECODER, .source_method = -1, + .expected_result = MF_E_TOPO_UNSUPPORTED, .decoder_class = CLSID_CMSH264DecoderMFT, + .flags = LOADER_TODO, + }, + { + /* H264 -> NV12, optional source is ignored */ + .input_types = {&video_h264_1280}, .output_types = {&video_nv12_1280}, .sink_method = -1, .source_method = MF_CONNECT_AS_OPTIONAL, + .expected_result = S_OK, .decoder_class = CLSID_CMSH264DecoderMFT, + }, + { + /* H264 -> NV12, add test MFT, optional source is ignored */ + .input_types = {&video_h264_1280}, .output_types = {&video_nv12_1280}, .sink_method = -1, .source_method = MF_CONNECT_AS_OPTIONAL, + .mft_input_types = {&video_nv12_1280}, .mft_output_types = {&video_nv12_1280}, + .expected_result = S_OK, .decoder_class = CLSID_CMSH264DecoderMFT, + .flags = LOADER_ADD_TEST_MFT | LOADER_EXPECT_MFT_INPUT_ENUMERATED, + }, }; IMFTopologyNode *src_node, *sink_node, *src_node2, *sink_node2, *mft_node; IMFSampleGrabberSinkCallback *grabber_callback = create_test_grabber_callback(); IMFMediaType *media_type, *input_types[2], *output_types[2]; + TOPOID node_id, oldtopoid, newtopoid, optional_mft_node_id; struct test_stream_sink stream_sink = test_stream_sink; IMFTopology *topology, *topology2, *full_topology; unsigned int i, j, count, expected_count, value; @@ -2884,7 +2992,6 @@ static void test_topology_loader(void) IMFTopoLoader *loader; IUnknown *node_object; WORD node_count; - TOPOID node_id, oldtopoid, newtopoid; DWORD index; HRESULT hr; BOOL ret; @@ -3010,10 +3117,12 @@ static void test_topology_loader(void) for (i = 0; i < ARRAY_SIZE(loader_tests); ++i) { + struct test_transform *test_transform = NULL, *optional_transform = NULL; const struct loader_test *test = &loader_tests[i]; - struct test_transform *test_transform = NULL; + IMFTopologyNode *optional_mft_node = NULL; IMFMediaType *mft_output_types[4] = {0}; IMFMediaType *mft_input_types[2] = {0}; + IMFMediaType *optional_mft_type = NULL; winetest_push_context("%u", i); @@ -3089,6 +3198,36 @@ static void test_topology_loader(void) test_transform_create(input_count, mft_input_types, output_count, mft_output_types, FALSE, &transform); test_transform = test_transform_from_IMFTransform(transform); IMFTransform_AddRef(transform); + + if (test->flags & (LOADER_ADD_OPTIONAL_TEST_MFT_UPSTREAM | LOADER_ADD_OPTIONAL_TEST_MFT_DOWNSTREAM)) + { + IMFMediaType **optional_input_types = &mft_input_types[0], **optional_output_types = &mft_input_types[0]; + const media_type_desc *optional_desc; + IMFTransform *optional_mft; + + hr = MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &optional_mft_node); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFTopologyNode_SetUINT32(optional_mft_node, &MF_TOPONODE_CONNECT_METHOD, MF_CONNECT_ALLOW_DECODER | MF_CONNECT_AS_OPTIONAL); + ok(hr == S_OK, "Failed to set connect method, hr %#lx.\n", hr); + + optional_desc = test->optional_mft_input_type ? test->optional_mft_input_type : test->optional_mft_output_type; + if (optional_desc) + { + hr = MFCreateMediaType(&optional_mft_type); + ok(hr == S_OK, "Failed to create media type, hr %#lx.\n", hr); + init_media_type(optional_mft_type, *optional_desc, -1); + if (test->optional_mft_input_type) + optional_input_types = &optional_mft_type; + else + optional_output_types = &optional_mft_type; + } + + test_transform_create(1, optional_input_types, 1, optional_output_types, FALSE, &optional_mft); + hr = IMFTopologyNode_SetObject(optional_mft_node, (IUnknown *)optional_mft); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + optional_transform = test_transform_from_IMFTransform(optional_mft); + optional_transform->validate_output_type = !!test->optional_mft_output_type; + } } else { @@ -3127,14 +3266,45 @@ static void test_topology_loader(void) { test_transform->input_type_set = FALSE; test_transform->output_type_set = FALSE; + test_transform->set_input_sequence_id = 0; + test_transform->set_output_sequence_id = 0; } hr = IMFTopology_AddNode(topology, mft_node); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - hr = IMFTopologyNode_ConnectOutput(src_node, 0, mft_node, 0); - ok(hr == S_OK, "Failed to connect nodes, hr %#lx.\n", hr); - hr = IMFTopologyNode_ConnectOutput(mft_node, 0, sink_node, 0); - ok(hr == S_OK, "Failed to connect nodes, hr %#lx.\n", hr); + if (optional_mft_node) + { + hr = IMFTopology_AddNode(topology, optional_mft_node); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (test->flags & LOADER_ADD_OPTIONAL_TEST_MFT_DOWNSTREAM) + { + hr = IMFTopologyNode_ConnectOutput(src_node, 0, mft_node, 0); + ok(hr == S_OK, "Failed to connect nodes, hr %#lx.\n", hr); + hr = IMFTopologyNode_ConnectOutput(mft_node, 0, optional_mft_node, 0); + ok(hr == S_OK, "Failed to connect nodes, hr %#lx.\n", hr); + hr = IMFTopologyNode_ConnectOutput(optional_mft_node, 0, sink_node, 0); + ok(hr == S_OK, "Failed to connect nodes, hr %#lx.\n", hr); + } + else + { + hr = IMFTopologyNode_ConnectOutput(src_node, 0, optional_mft_node, 0); + ok(hr == S_OK, "Failed to connect nodes, hr %#lx.\n", hr); + hr = IMFTopologyNode_ConnectOutput(optional_mft_node, 0, mft_node, 0); + ok(hr == S_OK, "Failed to connect nodes, hr %#lx.\n", hr); + hr = IMFTopologyNode_ConnectOutput(mft_node, 0, sink_node, 0); + ok(hr == S_OK, "Failed to connect nodes, hr %#lx.\n", hr); + } + hr = IMFTopologyNode_GetTopoNodeID(optional_mft_node, &optional_mft_node_id); + ok(hr == S_OK, "Failed to get source node id, hr %#lx.\n", hr); + IMFTopologyNode_Release(optional_mft_node); + } + else + { + hr = IMFTopologyNode_ConnectOutput(src_node, 0, mft_node, 0); + ok(hr == S_OK, "Failed to connect nodes, hr %#lx.\n", hr); + hr = IMFTopologyNode_ConnectOutput(mft_node, 0, sink_node, 0); + ok(hr == S_OK, "Failed to connect nodes, hr %#lx.\n", hr); + } IMFTopologyNode_Release(mft_node); } else @@ -3160,7 +3330,7 @@ static void test_topology_loader(void) ok(hr == S_OK, "Failed to get attribute count, hr %#lx.\n", hr); ok(!count, "Unexpected count %u.\n", count); - handler.is_supported_called = FALSE; + handler.is_supported_sequence_id = 0; if (test->flags & LOADER_SET_ENUMERATE_SOURCE_TYPES) IMFTopology_SetUINT32(topology, &MF_TOPOLOGY_ENUMERATE_SOURCE_TYPES, 1); @@ -3188,11 +3358,28 @@ static void test_topology_loader(void) } else if (test->expected_result == S_OK) { + BOOL expect_optional_rejected = !!(test->flags & LOADER_EXPECT_OPTIONAL_REJECTED); + IMFTopology_GetTopologyID(topology, &oldtopoid); IMFTopology_GetTopologyID(full_topology, &newtopoid); ok(oldtopoid == newtopoid, "Expected the same topology id. %I64u == %I64u\n", oldtopoid, newtopoid); ok(topology != full_topology, "Expected a different object for the resolved topology.\n"); + if (optional_mft_node) + { + hr = IMFTopology_GetNodeByID(full_topology, optional_mft_node_id, &mft_node); + todo_wine_if(expect_optional_rejected) + ok(hr == (expect_optional_rejected ? MF_E_NOT_FOUND : S_OK), "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + { + UINT32 method; + hr = IMFTopologyNode_GetUINT32(mft_node, &MF_TOPONODE_CONNECT_METHOD, &method); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(method == (MF_CONNECT_ALLOW_DECODER | MF_CONNECT_AS_OPTIONAL), "Unexpected method %#x\n", method); + IMFTopologyNode_Release(mft_node); + } + } + expected_count = 1 + !!(test->flags & LOADER_SET_ENUMERATE_SOURCE_TYPES) + !!(test->flags & LOADER_SET_XVP_FOR_PLAYBACK); hr = IMFTopology_GetCount(full_topology, &count); @@ -3204,9 +3391,10 @@ static void test_topology_loader(void) hr = IMFTopology_GetUINT32(full_topology, &MF_TOPOLOGY_RESOLUTION_STATUS, &value); todo_wine { ok(hr == S_OK, "Failed to get attribute, hr %#lx.\n", hr); - ok(value == MF_TOPOLOGY_RESOLUTION_SUCCEEDED, "Unexpected value %#x.\n", value); + ok(value == (expect_optional_rejected ? MF_OPTIONAL_NODE_REJECTED_MEDIA_TYPE : MF_TOPOLOGY_RESOLUTION_SUCCEEDED), + "Unexpected value %#x.\n", value); } - count = 2 + !!test_transform; + count = 2 + !!test_transform + (optional_transform && !expect_optional_rejected); if (!IsEqualGUID(&test->decoder_class, &GUID_NULL)) count++; if (!IsEqualGUID(&test->converter_class, &GUID_NULL)) @@ -3214,6 +3402,7 @@ todo_wine { hr = IMFTopology_GetNodeCount(full_topology, &node_count); ok(hr == S_OK, "Failed to get node count, hr %#lx.\n", hr); + todo_wine_if(expect_optional_rejected) ok(node_count == count, "Unexpected node count %u.\n", node_count); hr = IMFTopologyNode_GetTopoNodeID(src_node, &node_id); @@ -3239,7 +3428,36 @@ todo_wine { if (hr == S_OK) IMFMediaType_Release(media_type); - ok(handler.is_supported_called, "Sink input support not checked.\n"); + ok(!!handler.is_supported_sequence_id, "Sink input sequence id not set.\n"); + + if (optional_transform) + { + ok(!!optional_transform->set_input_sequence_id, "Optional transform input sequence id not set.\n"); + ok(!!test_transform->set_input_sequence_id, "Test transform input sequence id not set.\n"); + ok(!!test_transform->set_output_sequence_id, "Test transform output sequence id not set.\n"); + if (test->flags & LOADER_ADD_OPTIONAL_TEST_MFT_UPSTREAM) + { + todo_wine + ok(optional_transform->set_input_sequence_id > test_transform->set_input_sequence_id, + "Optional transform input was not configured after the non-optional one.\n"); + if (optional_transform->set_output_sequence_id) + { + /* Non-optional transform is connected to its downstream node after the optional + * node is inserted upstream, i.e. after each branch is connected, its optional + * nodes are inserted before the next branch is connected. */ + todo_wine + ok(optional_transform->set_output_sequence_id < test_transform->set_output_sequence_id, + "Optional transform output was not configured before the non-optional one.\n"); + } + } + else + { + todo_wine + ok(optional_transform->set_input_sequence_id > handler.is_supported_sequence_id, + "Optional transform input was not configured after the sink input.\n"); + } + IMFTransform_Release(&optional_transform->IMFTransform_iface); + } if (!IsEqualGUID(&test->decoder_class, &GUID_NULL)) { @@ -3408,7 +3626,8 @@ todo_wine { if (test_transform) { - ok(test_transform->input_enum_complete == !!(test->flags & LOADER_EXPECT_MFT_INPUT_ENUMERATED), + todo_wine_if(test->flags & LOADER_EXPECT_MFT_INPUT_ENUMERATED_TODO) + ok(test_transform->input_enum_complete == !!(test->flags & (LOADER_EXPECT_MFT_INPUT_ENUMERATED | LOADER_EXPECT_MFT_INPUT_ENUMERATED_TODO)), "got transform input_enum_complete %u\n", test_transform->input_enum_complete); todo_wine_if(test_transform->output_enum_complete && (test->flags & LOADER_TODO)) ok(test_transform->output_enum_complete == !!(test->flags & LOADER_EXPECT_MFT_OUTPUT_ENUMERATED), @@ -3438,6 +3657,8 @@ todo_wine { ok(ref == 0, "Release returned %ld\n", ref); for (j = 0; j < ARRAY_SIZE(mft_input_types) && mft_input_types[j]; ++j) IMFMediaType_Release(mft_input_types[j]); + if (optional_mft_type) + IMFMediaType_Release(optional_mft_type); for (j = 0; j < ARRAY_SIZE(mft_output_types) && mft_output_types[j]; ++j) IMFMediaType_Release(mft_output_types[j]); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10645
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/mf/tests/topology.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dlls/mf/tests/topology.c b/dlls/mf/tests/topology.c index fbb3a63785f..d56f3ed6325 100644 --- a/dlls/mf/tests/topology.c +++ b/dlls/mf/tests/topology.c @@ -2728,7 +2728,7 @@ static void test_topology_loader(void) .flags = LOADER_ADD_TEST_MFT | LOADER_NO_CURRENT_OUTPUT, }, { - /* MP3 -> PCM, different enumerated bps, add test MFT, configure MFT */ + /* MP3 -> PCM, different enumerated bps, add test MFT */ .input_types = {&audio_mp3_44100}, .output_types = {&audio_pcm_48000}, .sink_method = MF_CONNECT_DIRECT, .source_method = MF_CONNECT_DIRECT, .mft_input_types = {&audio_pcm_44100}, .mft_output_types = {&audio_pcm_48000}, .decoded_type = &audio_pcm_44100, @@ -2736,7 +2736,7 @@ static void test_topology_loader(void) .flags = LOADER_ADD_TEST_MFT | LOADER_EXPECT_MFT_INPUT_ENUMERATED, }, { - /* MP3 -> PCM, different enumerated bps, add incompatible test MFT, configure MFT */ + /* MP3 -> PCM, different enumerated bps, add incompatible test MFT */ .input_types = {&audio_mp3_44100}, .output_types = {&audio_pcm_48000}, .sink_method = MF_CONNECT_DIRECT, .source_method = MF_CONNECT_DIRECT, .mft_input_types = {&audio_aac_44100}, .mft_output_types = {&audio_pcm_48000}, .expected_result = MF_E_TOPO_CODEC_NOT_FOUND, -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10645
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/mf/topology_loader.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/dlls/mf/topology_loader.c b/dlls/mf/topology_loader.c index a07942b6af3..4ec8100241a 100644 --- a/dlls/mf/topology_loader.c +++ b/dlls/mf/topology_loader.c @@ -244,6 +244,21 @@ static HRESULT topology_branch_clone_nodes(struct topoloader_context *context, s return hr; } +static UINT32 topology_node_get_connect_method_semantics(IMFTopologyNode *node) +{ + const MF_CONNECT_METHOD src_methods = MF_CONNECT_RESOLVE_INDEPENDENT_OUTPUTTYPES | MF_CONNECT_AS_OPTIONAL_BRANCH; + UINT32 method; + + if (FAILED(IMFTopologyNode_GetUINT32(node, &MF_TOPONODE_CONNECT_METHOD, &method))) + return 0; + + /* invalid flags are ignored for sources */ + if (topology_node_get_type(node) == MF_TOPOLOGY_SOURCESTREAM_NODE) + method &= src_methods; + + return method; +} + static HRESULT topology_node_list_branches(IMFTopologyNode *node, struct list *branches) { struct topology_branch *branch; @@ -732,9 +747,7 @@ static HRESULT topology_branch_connect(IMFTopology *topology, enum connect_metho down_method = MF_CONNECT_ALLOW_DECODER; down_method = connect_method_from_mf(down_method) & method_mask; - if (topology_node_get_type(branch->up.node) != MF_TOPOLOGY_SOURCESTREAM_NODE - || FAILED(IMFTopologyNode_GetUINT32(branch->up.node, &MF_TOPONODE_CONNECT_METHOD, &up_method))) - up_method = MF_CONNECT_DIRECT; + up_method = topology_node_get_connect_method_semantics(branch->up.node); if (up_method & MF_CONNECT_RESOLVE_INDEPENDENT_OUTPUTTYPES) hr = topology_branch_foreach_up_types(topology, down_method, branch, upstream); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10645
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/mf/tests/topology.c | 17 +--- dlls/mf/topology_loader.c | 162 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 158 insertions(+), 21 deletions(-) diff --git a/dlls/mf/tests/topology.c b/dlls/mf/tests/topology.c index d56f3ed6325..057daa5dda8 100644 --- a/dlls/mf/tests/topology.c +++ b/dlls/mf/tests/topology.c @@ -2292,7 +2292,6 @@ enum loader_test_flags LOADER_ADD_OPTIONAL_TEST_MFT_UPSTREAM = 0x10000, LOADER_ADD_OPTIONAL_TEST_MFT_DOWNSTREAM = 0x20000, LOADER_EXPECT_OPTIONAL_REJECTED = 0x40000, - LOADER_EXPECT_MFT_INPUT_ENUMERATED_TODO = 0x80000, }; static void test_topology_loader(void) @@ -2902,7 +2901,7 @@ static void test_topology_loader(void) .input_types = {&video_h264_1280}, .output_types = {&video_nv12_1280}, .sink_method = -1, .source_method = -1, .mft_input_types = {&video_nv12_1280}, .mft_output_types = {&video_nv12_1280}, .expected_result = S_OK, .decoder_class = CLSID_CMSH264DecoderMFT, - .flags = LOADER_ADD_TEST_MFT | LOADER_ADD_OPTIONAL_TEST_MFT_UPSTREAM | LOADER_EXPECT_MFT_INPUT_ENUMERATED_TODO, + .flags = LOADER_ADD_TEST_MFT | LOADER_ADD_OPTIONAL_TEST_MFT_UPSTREAM | LOADER_EXPECT_MFT_INPUT_ENUMERATED, }, { /* H264 -> NV12, add unconnectable optional test MFT and test MFT */ @@ -2910,7 +2909,7 @@ static void test_topology_loader(void) .mft_input_types = {&video_nv12_1280}, .mft_output_types = {&video_nv12_1280}, .optional_mft_input_type = &video_yuy2_1280, .expected_result = S_OK, .decoder_class = CLSID_CMSH264DecoderMFT, - .flags = LOADER_ADD_TEST_MFT | LOADER_ADD_OPTIONAL_TEST_MFT_UPSTREAM | LOADER_EXPECT_OPTIONAL_REJECTED | LOADER_EXPECT_MFT_INPUT_ENUMERATED_TODO, + .flags = LOADER_ADD_TEST_MFT | LOADER_ADD_OPTIONAL_TEST_MFT_UPSTREAM | LOADER_EXPECT_OPTIONAL_REJECTED | LOADER_EXPECT_MFT_INPUT_ENUMERATED, }, { /* H264 -> NV12, add optional test MFT and test MFT, require test MFT input change for optional */ @@ -2918,7 +2917,7 @@ static void test_topology_loader(void) .mft_input_types = {&video_nv12_1280, &video_yuy2_1280}, .mft_output_types = {&video_nv12_1280}, .optional_mft_output_type = &video_yuy2_1280, .expected_result = S_OK, .decoder_class = CLSID_CMSH264DecoderMFT, - .flags = LOADER_ADD_TEST_MFT | LOADER_ADD_OPTIONAL_TEST_MFT_UPSTREAM | LOADER_EXPECT_MFT_INPUT_ENUMERATED_TODO, + .flags = LOADER_ADD_TEST_MFT | LOADER_ADD_OPTIONAL_TEST_MFT_UPSTREAM | LOADER_EXPECT_MFT_INPUT_ENUMERATED, }, { /* H264 -> NV12, add test MFT and optional test MFT */ @@ -2948,7 +2947,6 @@ static void test_topology_loader(void) /* PCM -> PCM, optional sink */ .input_types = {&audio_pcm_44100}, .output_types = {&audio_pcm_44100}, .sink_method = MF_CONNECT_AS_OPTIONAL, .source_method = -1, .expected_result = MF_E_TOPO_UNSUPPORTED, - .flags = LOADER_TODO, }, { /* PCM -> PCM, optional source is ignored */ @@ -2959,7 +2957,6 @@ static void test_topology_loader(void) /* H264 -> NV12, optional sink */ .input_types = {&video_h264_1280}, .output_types = {&video_nv12_1280}, .sink_method = MF_CONNECT_AS_OPTIONAL | MF_CONNECT_ALLOW_DECODER, .source_method = -1, .expected_result = MF_E_TOPO_UNSUPPORTED, .decoder_class = CLSID_CMSH264DecoderMFT, - .flags = LOADER_TODO, }, { /* H264 -> NV12, optional source is ignored */ @@ -3368,7 +3365,6 @@ static void test_topology_loader(void) if (optional_mft_node) { hr = IMFTopology_GetNodeByID(full_topology, optional_mft_node_id, &mft_node); - todo_wine_if(expect_optional_rejected) ok(hr == (expect_optional_rejected ? MF_E_NOT_FOUND : S_OK), "Unexpected hr %#lx.\n", hr); if (hr == S_OK) { @@ -3402,7 +3398,6 @@ todo_wine { hr = IMFTopology_GetNodeCount(full_topology, &node_count); ok(hr == S_OK, "Failed to get node count, hr %#lx.\n", hr); - todo_wine_if(expect_optional_rejected) ok(node_count == count, "Unexpected node count %u.\n", node_count); hr = IMFTopologyNode_GetTopoNodeID(src_node, &node_id); @@ -3437,7 +3432,6 @@ todo_wine { ok(!!test_transform->set_output_sequence_id, "Test transform output sequence id not set.\n"); if (test->flags & LOADER_ADD_OPTIONAL_TEST_MFT_UPSTREAM) { - todo_wine ok(optional_transform->set_input_sequence_id > test_transform->set_input_sequence_id, "Optional transform input was not configured after the non-optional one.\n"); if (optional_transform->set_output_sequence_id) @@ -3445,14 +3439,12 @@ todo_wine { /* Non-optional transform is connected to its downstream node after the optional * node is inserted upstream, i.e. after each branch is connected, its optional * nodes are inserted before the next branch is connected. */ - todo_wine ok(optional_transform->set_output_sequence_id < test_transform->set_output_sequence_id, "Optional transform output was not configured before the non-optional one.\n"); } } else { - todo_wine ok(optional_transform->set_input_sequence_id > handler.is_supported_sequence_id, "Optional transform input was not configured after the sink input.\n"); } @@ -3626,8 +3618,7 @@ todo_wine { if (test_transform) { - todo_wine_if(test->flags & LOADER_EXPECT_MFT_INPUT_ENUMERATED_TODO) - ok(test_transform->input_enum_complete == !!(test->flags & (LOADER_EXPECT_MFT_INPUT_ENUMERATED | LOADER_EXPECT_MFT_INPUT_ENUMERATED_TODO)), + ok(test_transform->input_enum_complete == !!(test->flags & LOADER_EXPECT_MFT_INPUT_ENUMERATED), "got transform input_enum_complete %u\n", test_transform->input_enum_complete); todo_wine_if(test_transform->output_enum_complete && (test->flags & LOADER_TODO)) ok(test_transform->output_enum_complete == !!(test->flags & LOADER_EXPECT_MFT_OUTPUT_ENUMERATED), diff --git a/dlls/mf/topology_loader.c b/dlls/mf/topology_loader.c index 4ec8100241a..89d52cd7c3b 100644 --- a/dlls/mf/topology_loader.c +++ b/dlls/mf/topology_loader.c @@ -145,7 +145,7 @@ struct topology_branch IMFTopologyNode *node; DWORD stream; IMFMediaTypeHandler *handler; - } up, down; + } up, down, optional; struct list entry; }; @@ -170,7 +170,8 @@ static const char *debugstr_topology_branch(struct topology_branch *branch) } static HRESULT topology_branch_create(IMFTopologyNode *source, DWORD source_stream, - IMFTopologyNode *sink, DWORD sink_stream, struct topology_branch **out) + IMFTopologyNode *optional, DWORD optional_stream, IMFTopologyNode *sink, DWORD sink_stream, + struct topology_branch **out) { IMFMediaTypeHandler *source_handler, *sink_handler; struct topology_branch *branch; @@ -197,6 +198,11 @@ static HRESULT topology_branch_create(IMFTopologyNode *source, DWORD source_stre IMFTopologyNode_AddRef((branch->down.node = sink)); branch->down.stream = sink_stream; branch->down.handler = sink_handler; + if (optional) + { + IMFTopologyNode_AddRef((branch->optional.node = optional)); + branch->optional.stream = optional_stream; + } *out = branch; return S_OK; @@ -208,6 +214,8 @@ static void topology_branch_destroy(struct topology_branch *branch) IMFMediaTypeHandler_Release(branch->up.handler); IMFTopologyNode_Release(branch->down.node); IMFMediaTypeHandler_Release(branch->down.handler); + if (branch->optional.node) + IMFTopologyNode_Release(branch->optional.node); free(branch); } @@ -216,9 +224,9 @@ static HRESULT topology_branch_create_indirect(struct topology_branch *branch, { HRESULT hr; - if (FAILED(hr = topology_branch_create(branch->up.node, branch->up.stream, node, 0, up))) + if (FAILED(hr = topology_branch_create(branch->up.node, branch->up.stream, NULL, 0, node, 0, up))) return hr; - if (FAILED(hr = topology_branch_create(node, 0, branch->down.node, branch->down.stream, down))) + if (FAILED(hr = topology_branch_create(node, 0, NULL, 0, branch->down.node, branch->down.stream, down))) topology_branch_destroy(*up); return hr; @@ -259,22 +267,86 @@ static UINT32 topology_node_get_connect_method_semantics(IMFTopologyNode *node) return method; } +static HRESULT topology_node_find_down_node(IMFTopologyNode *node, DWORD stream, IMFTopologyNode **optional_node, + DWORD *optional_stream, IMFTopologyNode **down_node, DWORD *down_stream) +{ + IMFTopologyNode *optional = NULL; + DWORD opt_stream = 0; + HRESULT hr; + + *optional_node = NULL; + *optional_stream = 0; + *down_node = NULL; + *down_stream = 0; + + IMFTopologyNode_AddRef(node); + + for (;;) + { + IMFTopologyNode *next_node; + + if (FAILED(hr = IMFTopologyNode_GetOutput(node, stream, &next_node, &stream))) + break; + + IMFTopologyNode_Release(node); + node = next_node; + + if (!(topology_node_get_connect_method_semantics(node) & MF_CONNECT_AS_OPTIONAL)) + { + *optional_node = optional; + *optional_stream = opt_stream; + *down_node = node; + *down_stream = stream; + return S_OK; + } + + if (topology_node_get_type(node) == MF_TOPOLOGY_OUTPUT_NODE) + break; + + if (!optional) + { + IMFTopologyNode_AddRef(optional = node); + opt_stream = stream; + } + else + { + FIXME("Multiple optional nodes per branch are not supported.\n"); + } + } + + if (optional) + IMFTopologyNode_Release(optional); + IMFTopologyNode_Release(node); + return hr; +} + static HRESULT topology_node_list_branches(IMFTopologyNode *node, struct list *branches) { + IMFTopologyNode *down_node = NULL, *optional_node = NULL; + DWORD i, count, down_stream, optional_stream = 0; struct topology_branch *branch; - DWORD i, count, down_stream; - IMFTopologyNode *down_node; + UINT32 method; HRESULT hr; + method = topology_node_get_connect_method_semantics(node); + + if (method & MF_CONNECT_AS_OPTIONAL_BRANCH) + FIXME("Optional branches are not supported.\n"); + + if (method & MF_CONNECT_AS_OPTIONAL) + return S_OK; + hr = IMFTopologyNode_GetOutputCount(node, &count); for (i = 0; SUCCEEDED(hr) && i < count; ++i) { - if (FAILED(IMFTopologyNode_GetOutput(node, i, &down_node, &down_stream))) + if (FAILED(topology_node_find_down_node(node, i, &optional_node, &optional_stream, &down_node, &down_stream)) || !down_node) continue; - if (SUCCEEDED(hr = topology_branch_create(node, i, down_node, down_stream, &branch))) + if (SUCCEEDED(hr = topology_branch_create(node, i, optional_node, optional_stream, down_node, down_stream, &branch))) list_add_tail(branches, &branch->entry); + if (optional_node) + IMFTopologyNode_Release(optional_node); IMFTopologyNode_Release(down_node); } @@ -734,6 +806,76 @@ static HRESULT topology_branch_foreach_up_types(IMFTopology *topology, enum conn return hr; } +static HRESULT topology_branch_connect_optional(struct topoloader_context *context, struct topology_branch *branch) +{ + DWORD up_stream, optional_stream = branch->optional.stream; + IMFTopologyNode *optional_node, *up_node = NULL; + struct topology_branch *optional = NULL; + IMFTransform *transform = NULL; + HRESULT hr, connect_hr = S_OK; + IMFMediaTypeHandler *handler; + IMFMediaType *type = NULL; + + if (!branch->optional.node) + return S_OK; + + TRACE("topology %p, branch %s.\n", context->output_topology, debugstr_topology_branch(branch)); + + if (topology_node_get_type(branch->optional.node) != MF_TOPOLOGY_TRANSFORM_NODE) + { + WARN("Optional node is not a transform.\n"); + return S_OK; + } + + if (FAILED(hr = topology_loader_clone_node(context, branch->optional.node, &optional_node))) + return hr; + + /* The loader may have inserted one or more transforms upstream. Only try inserting the optional node + * downstream of them. Support for upstream insertion is untested in native, but seems unlikely to work. */ + if (FAILED(hr = IMFTopologyNode_GetInput(branch->down.node, branch->down.stream, &up_node, &up_stream))) + { + ERR("Failed to get input.\n"); + goto done; + } + + if (FAILED(hr = topology_node_get_type_handler(up_node, up_stream, TRUE, &handler))) + goto done; + + if (FAILED(hr = media_type_handler_get_current(handler, &type))) + hr = media_type_handler_get_type(handler, 0, &type); + + IMFMediaTypeHandler_Release(handler); + + if (FAILED(hr) || FAILED(hr = topology_node_get_object(optional_node, &IID_IMFTransform, (void **)&transform))) + goto done; + + /* Optional node connections are restricted to the current or first upstream type. + * The up node is not reconfigured to allow connection even if possible. */ + if (FAILED(connect_hr = IMFTransform_SetInputType(transform, optional_stream, type, 0))) + goto done; + + if (FAILED(hr = topology_branch_create(branch->optional.node, branch->optional.stream, NULL, 0, branch->down.node, branch->down.stream, &optional))) + goto done; + connect_hr = topology_branch_foreach_up_types(context->output_topology, CONNECT_DIRECT, optional, type); + topology_branch_destroy(optional); + +done: + if (FAILED(connect_hr)) + WARN("Failed to insert optional node %p, hr %#lx.\n", branch->optional.node, connect_hr); + + if ((FAILED(hr) || FAILED(connect_hr)) && FAILED(IMFTopology_RemoveNode(context->output_topology, optional_node))) + ERR("Failed to remove node.\n"); + + if (transform) + IMFTransform_Release(transform); + if (type) + IMFMediaType_Release(type); + if (up_node) + IMFTopologyNode_Release(up_node); + IMFTopologyNode_Release(optional_node); + return hr; +} + static HRESULT topology_branch_connect(IMFTopology *topology, enum connect_method method_mask, struct topology_branch *branch, IMFMediaType *upstream) { @@ -778,7 +920,11 @@ static HRESULT topology_loader_resolve_branches(struct topoloader_context *conte if (FAILED(hr = topology_branch_clone_nodes(context, branch))) WARN("Failed to clone nodes for branch %s\n", debugstr_topology_branch(branch)); else + { hr = topology_branch_connect(context->output_topology, method_mask, branch, NULL); + if (SUCCEEDED(hr)) + hr = topology_branch_connect_optional(context, branch); + } topology_branch_destroy(branch); if (FAILED(hr)) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10645
Fixed an ARM64 compile error. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10645#note_136090
Rémi Bernon (@rbernon) commented about dlls/mf/topology_loader.c:
if (SUCCEEDED(hr = IMFMediaTypeHandler_GetCurrentMediaType(branch->down.handler, &down_type))) { - if (topology_node_get_type(branch->down.node) != MF_TOPOLOGY_OUTPUT_NODE + if ((topology_node_get_type(branch->down.node) != MF_TOPOLOGY_OUTPUT_NODE || IMFMediaType_IsEqual(up_type, down_type, &flags) == S_OK) + && SUCCEEDED(hr = IMFMediaTypeHandler_IsMediaTypeSupported(branch->down.handler, down_type, NULL))) hr = topology_branch_connect_with_type(topology, branch, up_type);
We're about to connect with `up_type` here, shouldn't we rather call `IMFMediaTypeHandler_IsMediaTypeSupported` with it? Also I think it could instead be called in `topology_branch_connect_with_type`, before `IMFMediaTypeHandler_SetCurrentMediaType`, keeping the if a bit simpler here. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10645#note_136469
Rémi Bernon (@rbernon) commented about dlls/mf/topology_loader.c:
IMFTopologyNode *node; DWORD stream; IMFMediaTypeHandler *handler; - } up, down; + } up, down, optional;
It seems to me that it would be better to support it in a more generic way rather than trying to implement only a subset of it with a single optional node. I would expect basically that it would just need a mechanism to ignore connection failures on optional branches, and some to skip already connected branches, for when an optional connection path has made a direct connection unnecessary. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10645#note_136470
On Thu Apr 16 09:29:40 2026 +0000, Rémi Bernon wrote:
It seems to me that it would be better to support it in a more generic way rather than trying to implement only a subset of it with a single optional node. I would expect basically that it would just need a mechanism to ignore connection failures on optional branches, and some to skip already connected branches, for when an optional connection path has made a direct connection unnecessary. This could also be something about dynamic branch discovery -which was probably something I had in mind at some point and related to 18e19a7903cf4c62f83aa1503aa14002a071efc4 removal- where you would only enumerate source branches first, then discover more of the graph either by continuing enumeration from each successfully connected downstream node to each of their outputs, or, in case of optional connection failures by keeping the upstream node and generating new branches from it to the downstream optional node outputs.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10645#note_136472
On Thu Apr 16 09:38:33 2026 +0000, Rémi Bernon wrote:
This could also be something about dynamic branch discovery -which was probably something I had in mind at some point and related to 18e19a7903cf4c62f83aa1503aa14002a071efc4 removal- where you would only enumerate source branches first, then discover more of the graph either by continuing enumeration from each successfully connected downstream node to each of their outputs, or, in case of optional connection failures by keeping the upstream node and generating new branches from it to the downstream optional node outputs. I've thought along the same lines. It should be possible to remove the branch list if we do this. Just pass each source node discovered to a function which resolves the chain all the way to the output. We would need to keep a careful count of nodes resolved/rejected to be able to determine at the end if all nodes in the input topology were accounted for.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10645#note_136514
On Thu Apr 16 14:23:09 2026 +0000, Conor McCarthy wrote:
I've thought along the same lines. It should be possible to remove the branch list if we do this. Just pass each source node discovered to a function which resolves the chain all the way to the output. We would need to keep a careful count of nodes resolved/rejected to be able to determine at the end if all nodes in the input topology were accounted for. Btw, should the fixme be removed from `topology_loader_Load()`?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10645#note_136515
On Thu Apr 16 14:28:49 2026 +0000, Conor McCarthy wrote:
Btw, should the fixme be removed from `topology_loader_Load()`? Yeah we can probably remove that now.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10645#note_136516
participants (3)
-
Conor McCarthy -
Conor McCarthy (@cmccarthy) -
Rémi Bernon (@rbernon)