diff --git a/MAINTAINERS b/MAINTAINERS index 4bab36bfdf9..59873f6804c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -223,6 +223,7 @@ F: dlls/winegstreamer/mfplat.c F: dlls/winegstreamer/resampler.c F: dlls/winegstreamer/video_decoder.c F: dlls/winegstreamer/video_processor.c +F: dlls/winegstreamer/wg_source.c F: dlls/winegstreamer/wg_sample.c F: dlls/winegstreamer/wg_transform.c F: dlls/winegstreamer/wma_decoder.c diff --git a/configure.ac b/configure.ac index 3decacab398..708c8269fc2 100644 --- a/configure.ac +++ b/configure.ac @@ -60,6 +60,7 @@ AC_ARG_WITH(udev, AS_HELP_STRING([--without-udev],[do not use udev (plug an AC_ARG_WITH(unwind, AS_HELP_STRING([--without-unwind],[do not use the libunwind library (exception handling)])) AC_ARG_WITH(usb, AS_HELP_STRING([--without-usb],[do not use the libusb library])) AC_ARG_WITH(v4l2, AS_HELP_STRING([--without-v4l2],[do not use v4l2 (video capture)])) +AC_ARG_WITH(vosk, AS_HELP_STRING([--without-vosk],[do not use Vosk])) AC_ARG_WITH(vulkan, AS_HELP_STRING([--without-vulkan],[do not use Vulkan])) AC_ARG_WITH(wayland, AS_HELP_STRING([--without-wayland],[do not build the Wayland driver])) AC_ARG_WITH(xcomposite,AS_HELP_STRING([--without-xcomposite],[do not use the Xcomposite extension]), @@ -492,7 +493,8 @@ AC_CHECK_HEADERS(\ syscall.h \ utime.h \ valgrind/memcheck.h \ - valgrind/valgrind.h + valgrind/valgrind.h \ + vosk_api.h ) WINE_HEADER_MAJOR() AC_HEADER_STAT() @@ -1236,6 +1238,7 @@ then dnl *** All of the following tests require X11/Xlib.h AC_CHECK_HEADERS([X11/extensions/shape.h \ + X11/extensions/XInput.h \ X11/extensions/XInput2.h \ X11/extensions/XShm.h \ X11/extensions/Xfixes.h \ @@ -1874,6 +1877,14 @@ then WINE_WARNING([No sound system was found. Windows applications will be silent.]) fi +dnl **** Check for Vosk **** +if test x$with_vosk != xno +then + WINE_CHECK_SONAME(vosk,vosk_recognizer_new) +fi +WINE_NOTICE_WITH(vosk,[test x$ac_cv_lib_soname_vosk = x], + [libvosk ${notice_platform}development files not found, speech recognition won't be supported.]) + dnl *** Check for Vulkan *** if test "x$with_vulkan" != "xno" then diff --git a/dlls/advapi32/registry.c b/dlls/advapi32/registry.c index 85e883bdcc9..e3226c38d0a 100644 --- a/dlls/advapi32/registry.c +++ b/dlls/advapi32/registry.c @@ -70,6 +70,9 @@ LSTATUS WINAPI RegOverridePredefKey( HKEY hkey, HKEY override ) */ LSTATUS WINAPI RegCreateKeyW( HKEY hkey, LPCWSTR lpSubKey, PHKEY phkResult ) { + if (!phkResult) + return ERROR_INVALID_PARAMETER; + return RegCreateKeyExW( hkey, lpSubKey, 0, NULL, REG_OPTION_NON_VOLATILE, MAXIMUM_ALLOWED, NULL, phkResult, NULL ); } @@ -82,6 +85,9 @@ LSTATUS WINAPI RegCreateKeyW( HKEY hkey, LPCWSTR lpSubKey, PHKEY phkResult ) */ LSTATUS WINAPI RegCreateKeyA( HKEY hkey, LPCSTR lpSubKey, PHKEY phkResult ) { + if (!phkResult) + return ERROR_INVALID_PARAMETER; + return RegCreateKeyExA( hkey, lpSubKey, 0, NULL, REG_OPTION_NON_VOLATILE, MAXIMUM_ALLOWED, NULL, phkResult, NULL ); } diff --git a/dlls/advapi32/tests/registry.c b/dlls/advapi32/tests/registry.c index 30a79368c94..ecfdbbe547e 100644 --- a/dlls/advapi32/tests/registry.c +++ b/dlls/advapi32/tests/registry.c @@ -1268,6 +1268,19 @@ static void test_reg_create_key(void) PACL key_acl; SECURITY_DESCRIPTOR *sd; + /* NULL return key check */ + ret = RegCreateKeyA(hkey_main, "Subkey1", NULL); + ok(ret == ERROR_INVALID_PARAMETER, "Got unexpected ret %ld.\n", ret); + + ret = RegCreateKeyW(hkey_main, L"Subkey1", NULL); + ok(ret == ERROR_INVALID_PARAMETER, "Got unexpected ret %ld.\n", ret); + + ret = RegCreateKeyExA(hkey_main, "Subkey1", 0, NULL, 0, KEY_NOTIFY, NULL, NULL, NULL); + ok(ret == ERROR_BADKEY, "Got unexpected ret %ld.\n", ret); + + ret = RegCreateKeyExW(hkey_main, L"Subkey1", 0, NULL, 0, KEY_NOTIFY, NULL, NULL, NULL); + ok(ret == ERROR_BADKEY, "Got unexpected ret %ld.\n", ret); + ret = RegCreateKeyExA(hkey_main, "Subkey1", 0, NULL, 0, KEY_NOTIFY, NULL, &hkey1, NULL); ok(!ret, "RegCreateKeyExA failed with error %ld\n", ret); /* should succeed: all versions of Windows ignore the access rights @@ -2763,6 +2776,28 @@ static void test_redirection(void) RegCloseKey( root32 ); RegCloseKey( root64 ); + err = RegCreateKeyExW( HKEY_LOCAL_MACHINE, L"Software\\WOW6432Node\\test1\\test2", 0, NULL, 0, + KEY_WRITE | KEY_WOW64_32KEY, NULL, &key, NULL ); + ok(!err, "got %#lx.\n", err); + RegCloseKey(key); + + err = RegCreateKeyExW( HKEY_LOCAL_MACHINE, L"Software\\test1\\test2", 0, NULL, 0, KEY_WRITE | KEY_WOW64_32KEY, + NULL, &key, NULL ); + ok(!err, "got %#lx.\n", err); + RegCloseKey(key); + + err = RegOpenKeyExW( HKEY_LOCAL_MACHINE, L"Software\\test1\\test2", 0, KEY_WRITE | KEY_WOW64_32KEY, &key ); + ok(!err, "got %#lx.\n", err); + RegCloseKey(key); + + if (pRegDeleteTreeA) + { + err = pRegDeleteTreeA(HKEY_LOCAL_MACHINE, "Software\\WOW6432Node\\test1"); + ok(!err, "got %#lx.\n", err); + err = pRegDeleteTreeA(HKEY_LOCAL_MACHINE, "Software\\test1"); + ok(err == ERROR_FILE_NOT_FOUND, "got %#lx.\n", err); + } + /* Software\Classes is shared/reflected so behavior is different */ err = RegCreateKeyExA( HKEY_LOCAL_MACHINE, "Software\\Classes\\Wine", diff --git a/dlls/amd_ags_x64/amd_ags.h b/dlls/amd_ags_x64/amd_ags.h index 8eab0732d5b..f6e50cad8f3 100644 --- a/dlls/amd_ags_x64/amd_ags.h +++ b/dlls/amd_ags_x64/amd_ags.h @@ -733,7 +733,39 @@ typedef union AGSConfiguration AGSConfiguration_520 agsConfiguration520; } AGSConfiguration; +struct AGSGPUInfo_311 +{ + ArchitectureVersion version; // Set to Unknown if not AMD hardware + const char* adapterString; // The adapter name string. NULL if not AMD hardware + int deviceId; // The device id + int revisionId; // The revision id + const char* driverVersion; // The driver package version + + int iNumCUs; // Number of GCN compute units. Zero if not GCN + int iCoreClock; // core clock speed at 100% power in MHz + int iMemoryClock; // memory clock speed at 100% power in MHz + float fTFlops; // Teraflops of GPU. Zero if not GCN. Calculated from iCoreClock * iNumCUs * 64 Pixels/clk * 2 instructions/MAD +}; + +struct AGSGPUInfo_320 +{ + int agsVersionMajor; // Major field of Major.Minor.Patch AGS version number + int agsVersionMinor; // Minor field of Major.Minor.Patch AGS version number + int agsVersionPatch; // Patch field of Major.Minor.Patch AGS version number + + ArchitectureVersion architectureVersion; // Set to Unknown if not AMD hardware + const char* adapterString; // The adapter name string. NULL if not AMD hardware + int deviceId; // The device id + int revisionId; // The revision id + + const char* driverVersion; // The driver package version + + int iNumCUs; // Number of GCN compute units. Zero if not GCN + int iCoreClock; // core clock speed at 100% power in MHz + int iMemoryClock; // memory clock speed at 100% power in MHz + float fTFlops; // Teraflops of GPU. Zero if not GCN. Calculated from iCoreClock * iNumCUs * 64 Pixels/clk * 2 instructions/MAD +}; /// The top level GPU information returned from \ref agsInitialize struct AGSGPUInfo_403 diff --git a/dlls/amd_ags_x64/amd_ags_x64.spec b/dlls/amd_ags_x64/amd_ags_x64.spec index 4dab7f1e892..744035a9a69 100644 --- a/dlls/amd_ags_x64/amd_ags_x64.spec +++ b/dlls/amd_ags_x64/amd_ags_x64.spec @@ -2,30 +2,40 @@ @ stdcall agsDeInitialize(ptr) @ stdcall agsCheckDriverVersion(ptr long) @ stdcall -norelay -arch=win64 agsDriverExtensionsDX11_BeginUAVOverlap() DX11_BeginUAVOverlap_impl +@ stub agsDriverExtensions_IASetPrimitiveTopology @ stub agsDriverExtensionsDX11_CreateBuffer +@ stub agsDriverExtensions_CreateBuffer @ stdcall agsDriverExtensionsDX11_CreateDevice(ptr ptr ptr ptr) @ stub agsDriverExtensionsDX11_CreateFromDevice @ stub agsDriverExtensionsDX11_CreateTexture1D @ stub agsDriverExtensionsDX11_CreateTexture2D @ stub agsDriverExtensionsDX11_CreateTexture3D +@ stub agsDriverExtensions_CreateTexture1D +@ stub agsDriverExtensions_CreateTexture2D +@ stub agsDriverExtensions_CreateTexture3D +@ stdcall agsDriverExtensions_DeInit(ptr) @ stdcall agsDriverExtensionsDX11_DeInit(ptr) @ stub agsDriverExtensionsDX11_Destroy @ stdcall -norelay -arch=win64 agsDriverExtensionsDX11_DestroyDevice() @ stdcall -norelay -arch=win64 agsDriverExtensionsDX11_EndUAVOverlap() DX11_EndUAVOverlap_impl @ stub agsDriverExtensionsDX11_GetMaxClipRects @ stub agsDriverExtensionsDX11_IASetPrimitiveTopology +@ stdcall agsDriverExtensions_Init(ptr ptr ptr) @ stdcall agsDriverExtensionsDX11_Init(ptr ptr long ptr) -@ stub agsDriverExtensionsDX11_MultiDrawIndexedInstancedIndirect -@ stub agsDriverExtensionsDX11_MultiDrawIndexedInstancedIndirectCountIndirect -@ stub agsDriverExtensionsDX11_MultiDrawInstancedIndirect -@ stub agsDriverExtensionsDX11_MultiDrawInstancedIndirectCountIndirect +@ stdcall -norelay -arch=win64 agsDriverExtensionsDX11_MultiDrawIndexedInstancedIndirect() DX11_MultiDrawIndexedInstancedIndirect_impl +@ stdcall -norelay -arch=win64 agsDriverExtensionsDX11_MultiDrawIndexedInstancedIndirectCountIndirect() DX11_MultiDrawIndexedInstancedIndirectCountIndirect_impl +@ stdcall -norelay -arch=win64 agsDriverExtensionsDX11_MultiDrawInstancedIndirect() DX11_MultiDrawInstancedIndirect_impl +@ stdcall -norelay -arch=win64 agsDriverExtensionsDX11_MultiDrawInstancedIndirectCountIndirect() DX11_MultiDrawInstancedIndirectCountIndirect_impl @ stub agsDriverExtensionsDX11_NotifyResourceBeginAllAccess @ stub agsDriverExtensionsDX11_NotifyResourceEndAllAccess @ stub agsDriverExtensionsDX11_NotifyResourceEndWrites +@ stub agsDriverExtensions_NotifyResourceBeginAllAccess +@ stub agsDriverExtensions_NotifyResourceEndAllAccess +@ stub agsDriverExtensions_NotifyResourceEndWrites @ stub agsDriverExtensionsDX11_NumPendingAsyncCompileJobs @ stub agsDriverExtensionsDX11_SetClipRects @ stdcall -norelay -arch=win64 agsDriverExtensionsDX11_SetDepthBounds() DX11_SetDepthBounds_impl -@ stub agsDriverExtensionsDX11_SetDiskShaderCacheEnabled +@ stdcall agsDriverExtensionsDX11_SetDiskShaderCacheEnabled(ptr long) @ stub agsDriverExtensionsDX11_SetMaxAsyncCompileThreadCount @ stub agsDriverExtensionsDX11_SetViewBroadcastMasks @ stub agsDriverExtensionsDX11_WriteBreadcrumb @@ -35,9 +45,9 @@ @ stub agsDriverExtensionsDX12_Destroy @ stdcall agsDriverExtensionsDX12_DestroyDevice(ptr ptr ptr) @ stdcall agsDriverExtensionsDX12_Init(ptr ptr ptr) -@ stub agsDriverExtensionsDX12_PopMarker -@ stub agsDriverExtensionsDX12_PushMarker -@ stub agsDriverExtensionsDX12_SetMarker +@ stdcall agsDriverExtensionsDX12_PopMarker(ptr ptr) +@ stdcall agsDriverExtensionsDX12_PushMarker(ptr ptr ptr) +@ stdcall agsDriverExtensionsDX12_SetMarker(ptr ptr ptr) @ stdcall agsGetCrossfireGPUCount(ptr ptr) @ stdcall agsGetVersionNumber() @ stdcall agsInit(ptr ptr ptr) @@ -46,3 +56,4 @@ @ stdcall agsGetTotalGPUCount(ptr ptr) @ stdcall agsGetGPUMemorySize(ptr long ptr) @ stdcall agsGetEyefinityConfigInfo(ptr long ptr ptr ptr) +@ stdcall agsDriverExtensions_SetCrossfireMode(ptr long) diff --git a/dlls/amd_ags_x64/amd_ags_x64_main.c b/dlls/amd_ags_x64/amd_ags_x64_main.c index c402da755b0..75f74e86b20 100644 --- a/dlls/amd_ags_x64/amd_ags_x64_main.c +++ b/dlls/amd_ags_x64/amd_ags_x64_main.c @@ -74,7 +74,7 @@ static const struct } amd_ags_info[AMD_AGS_VERSION_COUNT] = { - {AGS_MAKE_VERSION(4, 0, 0), AGS_MAKE_VERSION(4, 0, 3), sizeof(AGSDeviceInfo_511), sizeof(AGSDX11ReturnedParams_511), 0}, + {AGS_MAKE_VERSION(3, 1, 0), AGS_MAKE_VERSION(4, 0, 3), sizeof(AGSDeviceInfo_511), sizeof(AGSDX11ReturnedParams_511), 0}, {AGS_MAKE_VERSION(5, 0, 0), AGS_MAKE_VERSION(5, 0, 6), sizeof(AGSDeviceInfo_511), sizeof(AGSDX11ReturnedParams_511), 0}, {AGS_MAKE_VERSION(5, 1, 1), AGS_MAKE_VERSION(5, 1, 1), sizeof(AGSDeviceInfo_511), sizeof(AGSDX11ReturnedParams_511), 0}, {AGS_MAKE_VERSION(5, 2, 0), AGS_MAKE_VERSION(5, 2, 1), sizeof(AGSDeviceInfo_520), sizeof(AGSDX11ReturnedParams_520), 0}, @@ -364,13 +364,27 @@ static BOOL get_ags_version_from_resource(const WCHAR *filename, enum amd_ags_ve return TRUE; } -static enum amd_ags_version guess_version_from_exports(HMODULE hnative) +static enum amd_ags_version guess_version_from_exports(HMODULE hnative, int *ags_version) { /* Known DLL versions without version info: * - An update to AGS 5.4.1 included an amd_ags_x64.dll with no file version info; * - CoD: Modern Warfare Remastered (2017) ships dll without version info which is version 5.0.1 * (not tagged in AGSSDK history), compatible with 5.0.5. */ + if (GetProcAddress(hnative, "agsDriverExtensions_SetCrossfireMode")) + { + /* agsDriverExtensions_SetCrossfireMode was deprecated in 3.2.0 */ + TRACE("agsDriverExtensions_SetCrossfireMode found.\n"); + *ags_version = AGS_MAKE_VERSION(3, 1, 1); + return AMD_AGS_VERSION_4_0_3; + } + if (GetProcAddress(hnative, "agsDriverExtensions_Init")) + { + /* agsGetEyefinityConfigInfo was deprecated in 4.0.0 */ + TRACE("agsDriverExtensions_Init found.\n"); + *ags_version = AGS_MAKE_VERSION(3, 2, 2); + return AMD_AGS_VERSION_4_0_3; + } if (GetProcAddress(hnative, "agsGetEyefinityConfigInfo")) { /* agsGetEyefinityConfigInfo was deprecated in 5.0.0 */ @@ -440,7 +454,7 @@ static enum amd_ags_version determine_ags_version(int *ags_version) goto done; } - ret = guess_version_from_exports(hnative); + ret = guess_version_from_exports(hnative, ags_version); done: if (!*ags_version) @@ -692,6 +706,20 @@ static void init_device_displays_511(const char *adapter_name, AGSDisplayInfo_51 heap_free(displays); } +static int hide_apu(void) +{ + static int cached = -1; + + if (cached == -1) + { + const char *s; + + cached = ((s = getenv("WINE_HIDE_APU"))) && *s != '0'; + if (cached) + FIXME("hack: hiding APU.\n"); + } + return cached; +} static AGSReturnCode init_ags_context(AGSContext *context, int ags_version) { @@ -766,7 +794,7 @@ static AGSReturnCode init_ags_context(AGSContext *context, int ags_version) { SET_DEVICE_FIELD(device, asicFamily, AsicFamily, context->version, AsicFamily_GCN4); } - if (vk_properties->deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) + if (vk_properties->deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU && !hide_apu()) { if (context->version >= AMD_AGS_VERSION_6_0_0) device_600->isAPU = 1; @@ -814,7 +842,7 @@ AGSReturnCode WINAPI agsInit(AGSContext **context, const AGSConfiguration *confi TRACE("context %p, config %p, gpu_info %p.\n", context, config, gpu_info); - if (!context || !gpu_info) + if (!context) return AGS_INVALID_ARGS; if (config) @@ -829,12 +857,97 @@ AGSReturnCode WINAPI agsInit(AGSContext **context, const AGSConfiguration *confi return ret; } - if (object->version <= AMD_AGS_VERSION_4_0_3) + if (object->public_version <= AGS_MAKE_VERSION(3, 1, 1)) + { + /* Unfortunately it doesn't look sanely possible to distinguish 3.1.1 and 3.1.0 versions, while in + * 3.1.0 radeonSoftwareVersion was present, removed in 3.1.1 and brought back in 3.2.2. */ + struct AGSDeviceInfo_511 *devices = (struct AGSDeviceInfo_511 *)object->devices, *device; + /* config parameter was added in 3.2.0, so gpu_info is actually the second parameter. */ + struct AGSGPUInfo_311 *info = (struct AGSGPUInfo_311 *)config; + unsigned int i; + + if (!info) + goto done; + + TRACE("filling AGSGPUInfo_311.\n"); + if (!object->device_count) + { + ERR("No devices.\n"); + agsDeInit(object); + return AGS_FAILURE; + } + + for (i = 0; i < object->device_count; ++i) + if (devices[i].isPrimaryDevice) + break; + if (i == object->device_count) + { + WARN("No primary device, using first.\n"); + i = 0; + } + device = &devices[i]; + memset(info, 0, sizeof(*info)); + info->adapterString = device->adapterString; + info->deviceId = device->deviceId; + info->revisionId = device->revisionId; + info->driverVersion = driver_version; + info->iNumCUs = device->numCUs; + info->iCoreClock = device->coreClock; + info->iMemoryClock = device->memoryClock; + info->fTFlops = device->teraFlops; + } + else if (object->public_version <= AGS_MAKE_VERSION(3, 2, 2)) + { + /* Unfortunately it doesn't look sanely possible to distinguish 3.2.2 and 3.2.0 versions, while in + * 3.2.2 radeonSoftwareVersion was added in the middle of the structure. So fill the shorter one + * to avoid out of bound write. */ + struct AGSDeviceInfo_511 *devices = (struct AGSDeviceInfo_511 *)object->devices, *device; + struct AGSGPUInfo_320 *info = (struct AGSGPUInfo_320 *)gpu_info; + unsigned int i; + + if (!gpu_info) + goto done; + + TRACE("filling AGSGPUInfo_320.\n"); + if (!object->device_count) + { + ERR("No devices.\n"); + agsDeInit(object); + return AGS_FAILURE; + } + + for (i = 0; i < object->device_count; ++i) + if (devices[i].isPrimaryDevice) + break; + if (i == object->device_count) + { + WARN("No primary device, using first.\n"); + i = 0; + } + device = &devices[i]; + memset(info, 0, sizeof(*info)); + info->agsVersionMajor = AGS_VER_MAJOR(object->public_version); + info->agsVersionMinor = AGS_VER_MINOR(object->public_version); + info->agsVersionPatch = AGS_VER_PATCH(object->public_version); + info->architectureVersion = device->architectureVersion; + info->adapterString = device->adapterString; + info->deviceId = device->deviceId; + info->revisionId = device->revisionId; + info->driverVersion = driver_version; + info->iNumCUs = device->numCUs; + info->iCoreClock = device->coreClock; + info->iMemoryClock = device->memoryClock; + info->fTFlops = device->teraFlops; + } + else if (object->version <= AMD_AGS_VERSION_4_0_3) { struct AGSDeviceInfo_511 *devices = (struct AGSDeviceInfo_511 *)object->devices, *device; struct AGSGPUInfo_403 *info = (struct AGSGPUInfo_403 *)gpu_info; unsigned int i; + if (!gpu_info) + goto done; + if (!object->device_count) { ERR("No devices.\n"); @@ -868,6 +981,9 @@ AGSReturnCode WINAPI agsInit(AGSContext **context, const AGSConfiguration *confi } else { + if (!gpu_info) + goto done; + memset(gpu_info, 0, sizeof(*gpu_info)); gpu_info->agsVersionMajor = AGS_VER_MAJOR(object->public_version); gpu_info->agsVersionMinor = AGS_VER_MINOR(object->public_version); @@ -878,6 +994,7 @@ AGSReturnCode WINAPI agsInit(AGSContext **context, const AGSConfiguration *confi gpu_info->devices = object->devices; } +done: TRACE("Created context %p.\n", object); *context = object; @@ -892,7 +1009,7 @@ AGSReturnCode WINAPI agsInitialize(int ags_version, const AGSConfiguration *conf TRACE("ags_verison %d, context %p, config %p, gpu_info %p.\n", ags_version, context, config, gpu_info); - if (!context || !gpu_info) + if (!context) return AGS_INVALID_ARGS; if (config) @@ -907,11 +1024,14 @@ AGSReturnCode WINAPI agsInitialize(int ags_version, const AGSConfiguration *conf return ret; } - memset(gpu_info, 0, sizeof(*gpu_info)); - gpu_info->driverVersion = driver_version; - gpu_info->radeonSoftwareVersion = radeon_version; - gpu_info->numDevices = object->device_count; - gpu_info->devices = object->devices; + if (gpu_info) + { + memset(gpu_info, 0, sizeof(*gpu_info)); + gpu_info->driverVersion = driver_version; + gpu_info->radeonSoftwareVersion = radeon_version; + gpu_info->numDevices = object->device_count; + gpu_info->devices = object->devices; + } TRACE("Created context %p.\n", object); @@ -1126,6 +1246,8 @@ static void get_dx11_extensions_supported(ID3D11Device *device, AGSDX11Extension extensions->depthBoundsTest = !!ID3D11VkExtDevice_GetExtensionSupport(ext_device, D3D11_VK_EXT_DEPTH_BOUNDS); extensions->uavOverlap = !!ID3D11VkExtDevice_GetExtensionSupport(ext_device, D3D11_VK_EXT_BARRIER_CONTROL); + extensions->multiDrawIndirect = !!ID3D11VkExtDevice_GetExtensionSupport(ext_device, D3D11_VK_EXT_MULTI_DRAW_INDIRECT); + extensions->multiDrawIndirectCountIndirect = !!ID3D11VkExtDevice_GetExtensionSupport(ext_device, D3D11_VK_EXT_MULTI_DRAW_INDIRECT_COUNT); extensions->UAVOverlapDeferredContexts = extensions->uavOverlap; ID3D11VkExtDevice_Release(ext_device); @@ -1301,6 +1423,20 @@ AGSReturnCode WINAPI agsDriverExtensionsDX11_Init( AGSContext *context, ID3D11De return AGS_SUCCESS; } +AGSReturnCode WINAPI agsDriverExtensions_Init( AGSContext* context, ID3D11Device* device, unsigned int* extensionsSupported ) +{ + TRACE("context %p, device %p, extensionsSupported %p.\n", context, device, extensionsSupported); + + return agsDriverExtensionsDX11_Init(context, device, ~0u, extensionsSupported); +} + +AGSReturnCode WINAPI agsDriverExtensions_SetCrossfireMode(AGSContext *context, AGSCrossfireMode mode) +{ + FIXME("context %p, mode %d stub.\n", context, mode); + + return AGS_SUCCESS; +} + AGSReturnCode WINAPI agsDriverExtensionsDX11_DeInit( AGSContext* context ) { TRACE("context %p.\n", context); @@ -1314,6 +1450,11 @@ AGSReturnCode WINAPI agsDriverExtensionsDX11_DeInit( AGSContext* context ) return AGS_SUCCESS; } +AGSReturnCode WINAPI agsDriverExtensions_DeInit(AGSContext *context) +{ + return agsDriverExtensionsDX11_DeInit(context); +} + AGSReturnCode WINAPI agsDriverExtensionsDX12_Init( AGSContext* context, ID3D12Device* device, unsigned int* extensionsSupported ) { FIXME("context %p, device %p, extensionsSupported %p stub.\n", context, device, extensionsSupported); @@ -1329,6 +1470,27 @@ AGSReturnCode WINAPI agsDriverExtensionsDX12_DeInit( AGSContext* context ) return AGS_SUCCESS; } +AGSReturnCode WINAPI agsDriverExtensionsDX12_SetMarker( AGSContext *context, ID3D12GraphicsCommandList *command_list, const char *data) +{ + WARN("context %p, command_list %p, data %p stub.\n", context, command_list, data); + + return AGS_SUCCESS; +} + +AGSReturnCode WINAPI agsDriverExtensionsDX12_PushMarker( AGSContext *context, ID3D12GraphicsCommandList *command_list, const char* data) +{ + WARN("context %p, command_list %p, data %p stub.\n", context, command_list, data); + + return AGS_SUCCESS; +} + +AGSReturnCode WINAPI agsDriverExtensionsDX12_PopMarker(AGSContext *context, ID3D12GraphicsCommandList *command_list) +{ + WARN("context %p, command_list %p stub.\n", context, command_list); + + return AGS_SUCCESS; +} + BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, void *reserved) { TRACE("%p, %u, %p.\n", instance, reason, reserved); @@ -1486,6 +1648,238 @@ __ASM_GLOBAL_FUNC( DX11_EndUAVOverlap_impl, "jmp " __ASM_NAME("agsDriverExtensionsDX11_EndUAVOverlap_520") "\n\t" "1:\tjmp " __ASM_NAME("agsDriverExtensionsDX11_EndUAVOverlap") ) +AGSReturnCode WINAPI agsDriverExtensionsDX11_MultiDrawIndexedInstancedIndirect(AGSContext *context, ID3D11DeviceContext *dx_context, + unsigned int draw_count, ID3D11Buffer *buffer_for_args, unsigned int aligned_byte_offset_for_args, + unsigned int byte_stride_for_args) +{ + ID3D11VkExtContext *ext_context; + + TRACE("context %p, dx_context %p, draw_count %u, buffer_for_args %p, aligned_byte_offset_for_args %u, byte_stride_for_args %u.\n", + context, dx_context, draw_count, buffer_for_args, aligned_byte_offset_for_args, byte_stride_for_args); + + if (!context || !dx_context) + { + WARN("Invalid arguments.\n"); + return AGS_INVALID_ARGS; + } + + if (!context->extensions.multiDrawIndirect) + return AGS_EXTENSION_NOT_SUPPORTED; + + if (FAILED(ID3D11DeviceContext_QueryInterface(dx_context, &IID_ID3D11VkExtContext, (void **)&ext_context))) + { + TRACE("No ID3D11VkExtContext.\n"); + return AGS_EXTENSION_NOT_SUPPORTED; + } + + ID3D11VkExtContext_MultiDrawIndexedIndirect(ext_context, draw_count, buffer_for_args, aligned_byte_offset_for_args, + byte_stride_for_args); + ID3D11VkExtContext_Release(ext_context); + return AGS_SUCCESS; +} + +AGSReturnCode WINAPI agsDriverExtensionsDX11_MultiDrawIndexedInstancedIndirect_520(AGSContext *context, + unsigned int draw_count, ID3D11Buffer *buffer_for_args, unsigned int aligned_byte_offset_for_args, + unsigned int byte_stride_for_args) +{ + if (!context || !context->d3d11_context) + { + WARN("Invalid arguments.\n"); + return AGS_INVALID_ARGS; + } + return agsDriverExtensionsDX11_MultiDrawIndexedInstancedIndirect(context, context->d3d11_context, draw_count, + buffer_for_args, aligned_byte_offset_for_args, byte_stride_for_args); +} + +C_ASSERT(AMD_AGS_VERSION_5_3_0 == 4); +__ASM_GLOBAL_FUNC( DX11_MultiDrawIndexedInstancedIndirect_impl, + "mov (%rcx),%eax\n\t" /* version */ + "cmp $4,%eax\n\t" + "jge 1f\n\t" + "jmp " __ASM_NAME("agsDriverExtensionsDX11_MultiDrawIndexedInstancedIndirect_520") "\n\t" + "1:\tjmp " __ASM_NAME("agsDriverExtensionsDX11_MultiDrawIndexedInstancedIndirect") ) + + +AGSReturnCode WINAPI agsDriverExtensionsDX11_MultiDrawInstancedIndirect(AGSContext *context, ID3D11DeviceContext *dx_context, + unsigned int draw_count, ID3D11Buffer *buffer_for_args, unsigned int aligned_byte_offset_for_args, + unsigned int byte_stride_for_args) +{ + ID3D11VkExtContext *ext_context; + + TRACE("context %p, dx_context %p, draw_count %u, buffer_for_args %p, aligned_byte_offset_for_args %u, byte_stride_for_args %u.\n", + context, dx_context, draw_count, buffer_for_args, aligned_byte_offset_for_args, byte_stride_for_args); + + if (!context || !dx_context) + { + WARN("Invalid arguments.\n"); + return AGS_INVALID_ARGS; + } + + if (!context->extensions.multiDrawIndirect) + return AGS_EXTENSION_NOT_SUPPORTED; + + if (FAILED(ID3D11DeviceContext_QueryInterface(dx_context, &IID_ID3D11VkExtContext, (void **)&ext_context))) + { + TRACE("No ID3D11VkExtContext.\n"); + return AGS_EXTENSION_NOT_SUPPORTED; + } + + ID3D11VkExtContext_MultiDrawIndirect(ext_context, draw_count, buffer_for_args, aligned_byte_offset_for_args, + byte_stride_for_args); + ID3D11VkExtContext_Release(ext_context); + return AGS_SUCCESS; +} + +AGSReturnCode WINAPI agsDriverExtensionsDX11_MultiDrawInstancedIndirect_520( AGSContext* context, unsigned int draw_count, + ID3D11Buffer *buffer_for_args, unsigned int aligned_byte_offset_for_args, unsigned int byte_stride_for_args) +{ + if (!context || !context->d3d11_context) + { + WARN("Invalid arguments.\n"); + return AGS_INVALID_ARGS; + } + return agsDriverExtensionsDX11_MultiDrawInstancedIndirect(context, context->d3d11_context, draw_count, + buffer_for_args, aligned_byte_offset_for_args, byte_stride_for_args); +} + +C_ASSERT(AMD_AGS_VERSION_5_3_0 == 4); +__ASM_GLOBAL_FUNC( DX11_MultiDrawInstancedIndirect_impl, + "mov (%rcx),%eax\n\t" /* version */ + "cmp $4,%eax\n\t" + "jge 1f\n\t" + "jmp " __ASM_NAME("agsDriverExtensionsDX11_MultiDrawInstancedIndirect_520") "\n\t" + "1:\tjmp " __ASM_NAME("agsDriverExtensionsDX11_MultiDrawInstancedIndirect") ) + +static unsigned int get_max_draw_count(ID3D11Buffer *args, unsigned int offset, unsigned int stride, unsigned int size) +{ + D3D11_BUFFER_DESC desc; + unsigned int count; + + ID3D11Buffer_GetDesc(args, &desc); + if (offset >= desc.ByteWidth) + { + WARN("offset %u, buffer size %u.\n", offset, desc.ByteWidth); + return 0; + } + count = (desc.ByteWidth - offset) / stride; + if (desc.ByteWidth - offset - count * stride >= size) + ++count; + if (!count) + WARN("zero count, buffer size %u, offset %u, stride %u, size %u.\n", desc.ByteWidth, offset, stride, size); + return count; +} + +AGSReturnCode WINAPI agsDriverExtensionsDX11_MultiDrawIndexedInstancedIndirectCountIndirect(AGSContext *context, ID3D11DeviceContext *dx_context, + ID3D11Buffer *buffer_for_draw_count, unsigned int aligned_byte_offset_for_draw_count, ID3D11Buffer *buffer_for_args, + unsigned int aligned_byte_offset_for_args, unsigned int byte_stride_for_args) +{ + ID3D11VkExtContext *ext_context; + unsigned int max_draw_count; + + TRACE("context %p, dx_context %p, count buffer %p, offset %u, args buffer %p, offset %u, stride %u.\n", + context, dx_context, buffer_for_draw_count, aligned_byte_offset_for_draw_count, buffer_for_args, + aligned_byte_offset_for_args, byte_stride_for_args); + + if (!context || !dx_context) + { + WARN("Invalid arguments.\n"); + return AGS_INVALID_ARGS; + } + + if (!context->extensions.multiDrawIndirectCountIndirect) + return AGS_EXTENSION_NOT_SUPPORTED; + + if (FAILED(ID3D11DeviceContext_QueryInterface(dx_context, &IID_ID3D11VkExtContext, (void **)&ext_context))) + { + TRACE("No ID3D11VkExtContext.\n"); + return AGS_EXTENSION_NOT_SUPPORTED; + } + + max_draw_count = get_max_draw_count(buffer_for_args, aligned_byte_offset_for_args, byte_stride_for_args, sizeof(D3D11_DRAW_INDEXED_INSTANCED_INDIRECT_ARGS)); + ID3D11VkExtContext_MultiDrawIndexedIndirectCount(ext_context, max_draw_count, buffer_for_draw_count, aligned_byte_offset_for_draw_count, + buffer_for_args, aligned_byte_offset_for_args, byte_stride_for_args); + ID3D11VkExtContext_Release(ext_context); + return AGS_SUCCESS; +} + +AGSReturnCode WINAPI agsDriverExtensionsDX11_MultiDrawIndexedInstancedIndirectCountIndirect_520(AGSContext *context, + ID3D11Buffer *buffer_for_draw_count, unsigned int aligned_byte_offset_for_draw_count, ID3D11Buffer *buffer_for_args, + unsigned int aligned_byte_offset_for_args, unsigned int byte_stride_for_args) +{ + if (!context || !context->d3d11_context) + { + WARN("Invalid arguments.\n"); + return AGS_INVALID_ARGS; + } + return agsDriverExtensionsDX11_MultiDrawIndexedInstancedIndirectCountIndirect(context, context->d3d11_context, + buffer_for_draw_count, aligned_byte_offset_for_draw_count, + buffer_for_args, aligned_byte_offset_for_args, byte_stride_for_args); +} + +C_ASSERT(AMD_AGS_VERSION_5_3_0 == 4); +__ASM_GLOBAL_FUNC( DX11_MultiDrawIndexedInstancedIndirectCountIndirect_impl, + "mov (%rcx),%eax\n\t" /* version */ + "cmp $4,%eax\n\t" + "jge 1f\n\t" + "jmp " __ASM_NAME("agsDriverExtensionsDX11_MultiDrawIndexedInstancedIndirectCountIndirect_520") "\n\t" + "1:\tjmp " __ASM_NAME("agsDriverExtensionsDX11_MultiDrawIndexedInstancedIndirectCountIndirect") ) + + +AGSReturnCode WINAPI agsDriverExtensionsDX11_MultiDrawInstancedIndirectCountIndirect(AGSContext *context, ID3D11DeviceContext *dx_context, + ID3D11Buffer *buffer_for_draw_count, unsigned int aligned_byte_offset_for_draw_count, ID3D11Buffer *buffer_for_args, + unsigned int aligned_byte_offset_for_args, unsigned int byte_stride_for_args) +{ + ID3D11VkExtContext *ext_context; + unsigned int max_draw_count; + + TRACE("context %p, dx_context %p, count buffer %p, offset %u, args buffer %p, offset %u, stride %u.\n", + context, dx_context, buffer_for_draw_count, aligned_byte_offset_for_draw_count, buffer_for_args, + aligned_byte_offset_for_args, byte_stride_for_args); + + if (!context || !dx_context) + { + WARN("Invalid arguments.\n"); + return AGS_INVALID_ARGS; + } + + if (!context->extensions.multiDrawIndirectCountIndirect) + return AGS_EXTENSION_NOT_SUPPORTED; + + if (FAILED(ID3D11DeviceContext_QueryInterface(dx_context, &IID_ID3D11VkExtContext, (void **)&ext_context))) + { + TRACE("No ID3D11VkExtContext.\n"); + return AGS_EXTENSION_NOT_SUPPORTED; + } + + max_draw_count = get_max_draw_count(buffer_for_args, aligned_byte_offset_for_args, byte_stride_for_args, sizeof(D3D11_DRAW_INSTANCED_INDIRECT_ARGS)); + ID3D11VkExtContext_MultiDrawIndirectCount(ext_context, max_draw_count, buffer_for_draw_count, aligned_byte_offset_for_draw_count, + buffer_for_args, aligned_byte_offset_for_args, byte_stride_for_args); + ID3D11VkExtContext_Release(ext_context); + return AGS_SUCCESS; +} + +AGSReturnCode WINAPI agsDriverExtensionsDX11_MultiDrawInstancedIndirectCountIndirect_520(AGSContext *context, + ID3D11Buffer *buffer_for_draw_count, unsigned int aligned_byte_offset_for_draw_count, ID3D11Buffer *buffer_for_args, + unsigned int aligned_byte_offset_for_args, unsigned int byte_stride_for_args) +{ + if (!context || !context->d3d11_context) + { + WARN("Invalid arguments.\n"); + return AGS_INVALID_ARGS; + } + return agsDriverExtensionsDX11_MultiDrawInstancedIndirectCountIndirect(context, context->d3d11_context, + buffer_for_draw_count, aligned_byte_offset_for_draw_count, + buffer_for_args, aligned_byte_offset_for_args, byte_stride_for_args); +} + +C_ASSERT(AMD_AGS_VERSION_5_3_0 == 4); +__ASM_GLOBAL_FUNC( DX11_MultiDrawInstancedIndirectCountIndirect_impl, + "mov (%rcx),%eax\n\t" /* version */ + "cmp $4,%eax\n\t" + "jge 1f\n\t" + "jmp " __ASM_NAME("agsDriverExtensionsDX11_MultiDrawInstancedIndirectCountIndirect_520") "\n\t" + "1:\tjmp " __ASM_NAME("agsDriverExtensionsDX11_MultiDrawInstancedIndirectCountIndirect") ) + AGSReturnCode WINAPI agsDriverExtensionsDX11_DestroyDevice_520(AGSContext *context, ID3D11Device* device, unsigned int *device_ref, ID3D11DeviceContext *device_context, unsigned int *context_ref) @@ -1533,3 +1927,9 @@ __ASM_GLOBAL_FUNC( agsDriverExtensionsDX11_DestroyDevice, "jmp " __ASM_NAME("agsDriverExtensionsDX11_DestroyDevice_511") "\n\t" "1:\tjmp " __ASM_NAME("agsDriverExtensionsDX11_DestroyDevice_520") ) #endif + +AGSReturnCode WINAPI agsDriverExtensionsDX11_SetDiskShaderCacheEnabled(AGSContext *context, int enable) +{ + FIXME("context %p, enable %d stub.\n", context, enable); + return AGS_SUCCESS; +} diff --git a/dlls/appwiz.cpl/addons.c b/dlls/appwiz.cpl/addons.c index a2957c3040a..12d32a46216 100644 --- a/dlls/appwiz.cpl/addons.c +++ b/dlls/appwiz.cpl/addons.c @@ -56,10 +56,10 @@ WINE_DEFAULT_DEBUG_CHANNEL(appwizcpl); #define GECKO_SHA "???" #endif -#define MONO_VERSION "8.1.0" +#define MONO_VERSION "9.0.0" #if defined(__i386__) || defined(__x86_64__) #define MONO_ARCH "x86" -#define MONO_SHA "0ed3ec533aef79b2f312155931cf7b1080009ac0c5b4c2bcfeb678ac948e0810" +#define MONO_SHA "79f6c43100675566c112f4199b9ea7b944d338164b34bd91fa11b0b0a414e1c4" #else #define MONO_ARCH "" #define MONO_SHA "???" diff --git a/dlls/avifil32/getframe.c b/dlls/avifil32/getframe.c index c6df7339256..0b58c3f064f 100644 --- a/dlls/avifil32/getframe.c +++ b/dlls/avifil32/getframe.c @@ -254,6 +254,8 @@ static LPVOID WINAPI IGetFrame_fnGetFrame(IGetFrame *iface, LONG lPos) } /* for (lNext < lPos) */ } /* if (This->lCurrentFrame != lPos) */ + This->lCurrentFrame = lPos; + return (This->hic == NULL ? This->lpInFormat : This->lpOutFormat); } diff --git a/dlls/bcrypt/gnutls.c b/dlls/bcrypt/gnutls.c index 4abe0c354ed..115219a64f6 100644 --- a/dlls/bcrypt/gnutls.c +++ b/dlls/bcrypt/gnutls.c @@ -2905,7 +2905,7 @@ static NTSTATUS key_asymmetric_encrypt_gcrypt( void *args ) result_len = EXPORT_SIZE(result, key->u.a.bitlen / 8, 1); *ret_len = result_len; - if (params->output_len >= result_len) export_gnutls_datum(output, params->output_len, &result, 1); + if (params->output_len >= result_len) export_gnutls_datum(output, result_len, &result, 1); else if (params->output_len == 0) status = STATUS_SUCCESS; else status = STATUS_BUFFER_TOO_SMALL; @@ -3028,7 +3028,7 @@ static NTSTATUS key_asymmetric_decrypt_gcrypt( void *args ) { status = STATUS_SUCCESS; if (flags == BCRYPT_PAD_NONE) - export_gnutls_datum(output, params->output_len, &result, 1); + export_gnutls_datum(output, result_len, &result, 1); else memcpy(output, result.data, result_len); } diff --git a/dlls/bcrypt/tests/bcrypt.c b/dlls/bcrypt/tests/bcrypt.c index 04b85be7b5e..511ee7634b6 100644 --- a/dlls/bcrypt/tests/bcrypt.c +++ b/dlls/bcrypt/tests/bcrypt.c @@ -2485,12 +2485,12 @@ static void test_rsa_encrypt(void) ok(ret == STATUS_SUCCESS, "got %lx\n", ret); ok(encrypted_size == 64, "got size of %ld\n", encrypted_size); - encrypted_a = malloc(encrypted_size); + encrypted_a = malloc(encrypted_size * 2); memset(encrypted_a, 0, encrypted_size); - encrypted_b = malloc(encrypted_size); + encrypted_b = malloc(encrypted_size * 2); memset(encrypted_b, 0xff, encrypted_size); - ret = BCryptEncrypt(key, input, sizeof(input), NULL, NULL, 0, encrypted_a, encrypted_size, &encrypted_size, BCRYPT_PAD_NONE); + ret = BCryptEncrypt(key, input, sizeof(input), NULL, NULL, 0, encrypted_a, encrypted_size * 2, &encrypted_size, BCRYPT_PAD_NONE); ok(ret == STATUS_INVALID_PARAMETER, "got %lx\n", ret); ret = BCryptEncrypt(key, input_no_padding, sizeof(input_no_padding), NULL, NULL, 0, encrypted_a, 12, &encrypted_size, BCRYPT_PAD_NONE); @@ -2506,7 +2506,7 @@ static void test_rsa_encrypt(void) decrypted_size = 0xdeadbeef; BCryptDecrypt(key, encrypted_a, encrypted_size, NULL, NULL, 0, NULL, 0, &decrypted_size, BCRYPT_PAD_NONE); ok(decrypted_size == sizeof(input_no_padding), "got %lu\n", decrypted_size); - BCryptDecrypt(key, encrypted_a, encrypted_size, NULL, NULL, 0, decrypted, decrypted_size, &decrypted_size, BCRYPT_PAD_NONE); + BCryptDecrypt(key, encrypted_a, encrypted_size, NULL, NULL, 0, decrypted, decrypted_size * 2, &decrypted_size, BCRYPT_PAD_NONE); ok(!memcmp(decrypted, input_no_padding, sizeof(input_no_padding)), "Decrypted output it's not what expected\n"); encrypted_size = 60; @@ -2537,12 +2537,12 @@ static void test_rsa_encrypt(void) ok(ret == STATUS_SUCCESS, "got %lx\n", ret); ok(encrypted_size == 64, "got size of %ld\n", encrypted_size); - encrypted_a = realloc(encrypted_a, encrypted_size); - memset(encrypted_a, 0, encrypted_size); - encrypted_b = realloc(encrypted_b, encrypted_size); + encrypted_a = realloc(encrypted_a, encrypted_size * 2); + memset(encrypted_a, 0, encrypted_size * 2); + encrypted_b = realloc(encrypted_b, encrypted_size * 2); memset(encrypted_b, 0, encrypted_size); - ret = BCryptEncrypt(key, input, sizeof(input), &oaep_pad, NULL, 0, encrypted_a, encrypted_size, &encrypted_size, BCRYPT_PAD_OAEP); + ret = BCryptEncrypt(key, input, sizeof(input), &oaep_pad, NULL, 0, encrypted_a, encrypted_size * 2, &encrypted_size, BCRYPT_PAD_OAEP); ok(ret == STATUS_SUCCESS, "got %lx\n", ret); ret = BCryptEncrypt(key, input, sizeof(input), &oaep_pad, NULL, 0, encrypted_b, encrypted_size, &encrypted_size, BCRYPT_PAD_OAEP); ok(ret == STATUS_SUCCESS, "got %lx\n", ret); @@ -2553,7 +2553,7 @@ static void test_rsa_encrypt(void) ret = BCryptDecrypt(key, encrypted_a, encrypted_size, &oaep_pad, NULL, 0, NULL, 0, &decrypted_size, BCRYPT_PAD_OAEP); ok(ret == STATUS_SUCCESS, "got %lx\n", ret); ok(decrypted_size == sizeof(input), "got %lu\n", decrypted_size); - ret = BCryptDecrypt(key, encrypted_a, encrypted_size, &oaep_pad, NULL, 0, decrypted, decrypted_size, &decrypted_size, BCRYPT_PAD_OAEP); + ret = BCryptDecrypt(key, encrypted_a, encrypted_size, &oaep_pad, NULL, 0, decrypted, decrypted_size * 2, &decrypted_size, BCRYPT_PAD_OAEP); ok(ret == STATUS_SUCCESS, "got %lx\n", ret); ok(!memcmp(decrypted, input, sizeof(input)), "Decrypted output it's not what expected\n"); diff --git a/dlls/cfgmgr32/cfgmgr32.spec b/dlls/cfgmgr32/cfgmgr32.spec index e4cd845e2a4..29de7bbdc55 100644 --- a/dlls/cfgmgr32/cfgmgr32.spec +++ b/dlls/cfgmgr32/cfgmgr32.spec @@ -91,7 +91,7 @@ @ stdcall CM_Get_Device_Interface_List_SizeW(ptr ptr wstr long) setupapi.CM_Get_Device_Interface_List_SizeW @ stdcall CM_Get_Device_Interface_List_Size_ExA(ptr ptr str long ptr) setupapi.CM_Get_Device_Interface_List_Size_ExA @ stdcall CM_Get_Device_Interface_List_Size_ExW(ptr ptr wstr long ptr) setupapi.CM_Get_Device_Interface_List_Size_ExW -@ stub CM_Get_Device_Interface_PropertyW +@ stdcall CM_Get_Device_Interface_PropertyW(wstr ptr ptr ptr ptr long) @ stub CM_Get_First_Log_Conf @ stub CM_Get_First_Log_Conf_Ex @ stub CM_Get_Global_State diff --git a/dlls/cfgmgr32/main.c b/dlls/cfgmgr32/main.c index f40068af0df..7d48a96be85 100644 --- a/dlls/cfgmgr32/main.c +++ b/dlls/cfgmgr32/main.c @@ -67,3 +67,16 @@ CONFIGRET WINAPI CM_Register_Notification( CM_NOTIFY_FILTER *filter, void *conte return CR_CALL_NOT_IMPLEMENTED; } + +/*********************************************************************** + * CM_Get_Device_Interface_PropertyW (cfgmgr32.@) + */ +CONFIGRET WINAPI CM_Get_Device_Interface_PropertyW( LPCWSTR device_interface, const DEVPROPKEY *property_key, + DEVPROPTYPE *property_type, BYTE *property_buffer, + ULONG *property_buffer_size, ULONG flags ) +{ + FIXME("%s %p %p %p %p %ld stub!\n", debugstr_w(device_interface), property_key, property_type, + property_buffer, property_buffer_size, flags); + + return CR_CALL_NOT_IMPLEMENTED; +} diff --git a/dlls/crypt32/base64.c b/dlls/crypt32/base64.c index b61ed7ff8cc..298756ca541 100644 --- a/dlls/crypt32/base64.c +++ b/dlls/crypt32/base64.c @@ -111,8 +111,6 @@ static DWORD encodeBase64A(const BYTE *in_buf, int in_len, LPCSTR sep, i = 0; while (div > 0 && ptr < end) { - if (i && i % 64 == 0) - ptr += stradd(ptr, end, sep, strlen(sep)); /* first char is the first 6 bits of the first byte*/ chunk[0] = b64[ ( d[0] >> 2) & 0x3f ]; /* second char is the last 2 bits of the first byte and the first 4 @@ -127,6 +125,9 @@ static DWORD encodeBase64A(const BYTE *in_buf, int in_len, LPCSTR sep, i += 4; d += 3; div--; + + if (i && i % 64 == 0) + ptr += stradd(ptr, end, sep, strlen(sep)); } switch(pad_bytes) @@ -393,11 +394,6 @@ static LONG encodeBase64W(const BYTE *in_buf, int in_len, LPCWSTR sep, i = 0; while (div > 0) { - if (i && i % 64 == 0) - { - lstrcpyW(ptr, sep); - ptr += lstrlenW(sep); - } /* first char is the first 6 bits of the first byte*/ *ptr++ = b64[ ( d[0] >> 2) & 0x3f ]; /* second char is the last 2 bits of the first byte and the first 4 @@ -411,6 +407,12 @@ static LONG encodeBase64W(const BYTE *in_buf, int in_len, LPCWSTR sep, i += 4; d += 3; div--; + + if (i && i % 64 == 0) + { + lstrcpyW(ptr, sep); + ptr += lstrlenW(sep); + } } switch(pad_bytes) diff --git a/dlls/crypt32/tests/base64.c b/dlls/crypt32/tests/base64.c index e81a57c576d..2263b236fa8 100644 --- a/dlls/crypt32/tests/base64.c +++ b/dlls/crypt32/tests/base64.c @@ -57,6 +57,8 @@ static const BYTE toEncode4[] = static const BYTE toEncode5[] = "abcdefghijlkmnopqrstuvwxyz01234567890ABCDEFGHI"; +static const BYTE toEncode6[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + static const struct BinTests tests[] = { { toEncode1, sizeof(toEncode1), "AA==\r\n", }, { toEncode2, sizeof(toEncode2), "AQI=\r\n", }, @@ -69,6 +71,9 @@ static const struct BinTests tests[] = { "SElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTY3ODkwAA==\r\n" }, { toEncode5, sizeof(toEncode5), "YWJjZGVmZ2hpamxrbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5MEFCQ0RFRkdISQA=\r\n" }, + { toEncode6, sizeof(toEncode6), + "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh\r\n" + "YQA=\r\n" }, }; static const struct BinTests testsNoCR[] = { @@ -83,6 +88,9 @@ static const struct BinTests testsNoCR[] = { "SElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTY3ODkwAA==\n" }, { toEncode5, sizeof(toEncode5), "YWJjZGVmZ2hpamxrbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5MEFCQ0RFRkdISQA=\n" }, + { toEncode6, sizeof(toEncode6), + "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh\n" + "YQA=\n" }, }; static WCHAR *strdupAtoW(const char *str) diff --git a/dlls/dbghelp/cpu_x86_64.c b/dlls/dbghelp/cpu_x86_64.c index eeae1f042b2..23ed5271128 100644 --- a/dlls/dbghelp/cpu_x86_64.c +++ b/dlls/dbghelp/cpu_x86_64.c @@ -951,44 +951,7 @@ static BOOL x86_64_fetch_minidump_thread(struct dump_context* dc, unsigned index static BOOL x86_64_fetch_minidump_module(struct dump_context* dc, unsigned index, unsigned flags) { - /* FIXME: not sure about the flags... */ - if (1) - { - /* FIXME: crop values across module boundaries, */ -#ifdef __x86_64__ - struct process* pcs; - struct module* module; - const RUNTIME_FUNCTION* rtf; - ULONG size; - - if (!(pcs = process_find_by_handle(dc->process->handle)) || - !(module = module_find_by_addr(pcs, dc->modules[index].base))) - return FALSE; - rtf = (const RUNTIME_FUNCTION*)pe_map_directory(module, IMAGE_DIRECTORY_ENTRY_EXCEPTION, &size); - if (rtf) - { - const RUNTIME_FUNCTION* end = (const RUNTIME_FUNCTION*)((const char*)rtf + size); - UNWIND_INFO ui; - - while (rtf + 1 < end) - { - while (rtf->UnwindData & 1) /* follow chained entry */ - { - FIXME("RunTime_Function outside IMAGE_DIRECTORY_ENTRY_EXCEPTION unimplemented yet!\n"); - return FALSE; - /* we need to read into the other process */ - /* rtf = (RUNTIME_FUNCTION*)(module->module.BaseOfImage + (rtf->UnwindData & ~1)); */ - } - if (read_process_memory(dc->process, dc->modules[index].base + rtf->UnwindData, &ui, sizeof(ui))) - minidump_add_memory_block(dc, dc->modules[index].base + rtf->UnwindData, - FIELD_OFFSET(UNWIND_INFO, UnwindCode) + ui.CountOfCodes * sizeof(UNWIND_CODE), 0); - rtf++; - } - } -#endif - } - - return TRUE; + return FALSE; } struct cpu cpu_x86_64 = { diff --git a/dlls/ddraw/ddraw.c b/dlls/ddraw/ddraw.c index 8603f4a1113..bfb7721c34d 100644 --- a/dlls/ddraw/ddraw.c +++ b/dlls/ddraw/ddraw.c @@ -420,6 +420,9 @@ static void ddraw_destroy_swapchain(struct ddraw *ddraw) *****************************************************************************/ static void ddraw_destroy(struct ddraw *This) { + struct d3d_device *device; + unsigned int i; + IDirectDraw7_SetCooperativeLevel(&This->IDirectDraw7_iface, NULL, DDSCL_NORMAL); IDirectDraw7_RestoreDisplayMode(&This->IDirectDraw7_iface); @@ -441,8 +444,43 @@ static void ddraw_destroy(struct ddraw *This) wined3d_device_decref(This->wined3d_device); wined3d_decref(This->wined3d); - if (This->d3ddevice) - This->d3ddevice->ddraw = NULL; + for (i = 0; i < This->handle_table.entry_count; ++i) + { + struct ddraw_handle_entry *entry = &This->handle_table.entries[i]; + + switch (entry->type) + { + case DDRAW_HANDLE_FREE: + break; + + case DDRAW_HANDLE_MATERIAL: + { + struct d3d_material *m = entry->object; + FIXME("Material handle %#x (%p) not unset properly.\n", i + 1, m); + m->Handle = 0; + break; + } + + case DDRAW_HANDLE_SURFACE: + { + struct ddraw_surface *surf = entry->object; + FIXME("Texture handle %#x (%p) not unset properly.\n", i + 1, surf); + surf->Handle = 0; + break; + } + + default: + FIXME("Handle %#x (%p) has unknown type %#x.\n", i + 1, entry->object, entry->type); + break; + } + } + + ddraw_handle_table_destroy(&This->handle_table); + + LIST_FOR_EACH_ENTRY(device, &This->d3ddevice_list, struct d3d_device, ddraw_entry) + { + device->ddraw = NULL; + } /* Now free the object */ free(This); @@ -5124,6 +5162,16 @@ HRESULT ddraw_init(struct ddraw *ddraw, DWORD flags, enum wined3d_device_type de ddraw->immediate_context = wined3d_device_get_immediate_context(ddraw->wined3d_device); list_init(&ddraw->surface_list); + list_init(&ddraw->d3ddevice_list); + + if (!ddraw_handle_table_init(&ddraw->handle_table, 64)) + { + ERR("Failed to initialize handle table.\n"); + ddraw_handle_table_destroy(&ddraw->handle_table); + wined3d_device_decref(ddraw->wined3d_device); + wined3d_decref(ddraw->wined3d); + return DDERR_OUTOFMEMORY; + } if (FAILED(hr = wined3d_stateblock_create(ddraw->wined3d_device, NULL, WINED3D_SBT_PRIMARY, &ddraw->state))) { diff --git a/dlls/ddraw/ddraw_private.h b/dlls/ddraw/ddraw_private.h index b1fd015aa14..eed8c737037 100644 --- a/dlls/ddraw/ddraw_private.h +++ b/dlls/ddraw/ddraw_private.h @@ -81,6 +81,31 @@ enum ddraw_device_state DDRAW_DEVICE_STATE_NOT_RESTORED, }; +#define DDRAW_INVALID_HANDLE ~0U + +enum ddraw_handle_type +{ + DDRAW_HANDLE_FREE, + DDRAW_HANDLE_MATERIAL, + DDRAW_HANDLE_MATRIX, + DDRAW_HANDLE_STATEBLOCK, + DDRAW_HANDLE_SURFACE, +}; + +struct ddraw_handle_entry +{ + void *object; + enum ddraw_handle_type type; +}; + +struct ddraw_handle_table +{ + struct ddraw_handle_entry *entries; + struct ddraw_handle_entry *free_entries; + UINT table_size; + UINT entry_count; +}; + struct ddraw { /* Interfaces */ @@ -117,7 +142,7 @@ struct ddraw /* D3D things */ HWND d3d_window; - struct d3d_device *d3ddevice; + struct list d3ddevice_list; int d3dversion; /* Various HWNDs */ @@ -132,6 +157,8 @@ struct ddraw */ struct list surface_list; + struct ddraw_handle_table handle_table; + /* FVF management */ struct FvfToDecl *decls; UINT numConvertedDecls, declArraySize; @@ -285,31 +312,6 @@ struct ddraw_surface *unsafe_impl_from_IDirectDrawSurface7(IDirectDrawSurface7 * struct ddraw_surface *unsafe_impl_from_IDirect3DTexture(IDirect3DTexture *iface); struct ddraw_surface *unsafe_impl_from_IDirect3DTexture2(IDirect3DTexture2 *iface); -#define DDRAW_INVALID_HANDLE ~0U - -enum ddraw_handle_type -{ - DDRAW_HANDLE_FREE, - DDRAW_HANDLE_MATERIAL, - DDRAW_HANDLE_MATRIX, - DDRAW_HANDLE_STATEBLOCK, - DDRAW_HANDLE_SURFACE, -}; - -struct ddraw_handle_entry -{ - void *object; - enum ddraw_handle_type type; -}; - -struct ddraw_handle_table -{ - struct ddraw_handle_entry *entries; - struct ddraw_handle_entry *free_entries; - UINT table_size; - UINT entry_count; -}; - BOOL ddraw_handle_table_init(struct ddraw_handle_table *t, UINT initial_size); void ddraw_handle_table_destroy(struct ddraw_handle_table *t); DWORD ddraw_allocate_handle(struct ddraw_handle_table *t, void *object, enum ddraw_handle_type type); @@ -333,6 +335,7 @@ struct d3d_device struct wined3d_device *wined3d_device; struct wined3d_device_context *immediate_context; struct ddraw *ddraw; + struct list ddraw_entry; IUnknown *rt_iface; struct wined3d_streaming_buffer vertex_buffer, index_buffer; diff --git a/dlls/ddraw/device.c b/dlls/ddraw/device.c index bc1d91ee00b..b0f0bd910dc 100644 --- a/dlls/ddraw/device.c +++ b/dlls/ddraw/device.c @@ -303,14 +303,6 @@ static ULONG WINAPI d3d_device_inner_Release(IUnknown *iface) case DDRAW_HANDLE_FREE: break; - case DDRAW_HANDLE_MATERIAL: - { - struct d3d_material *m = entry->object; - FIXME("Material handle %#lx (%p) not unset properly.\n", i + 1, m); - m->Handle = 0; - break; - } - case DDRAW_HANDLE_MATRIX: { /* No FIXME here because this might happen because of sloppy applications. */ @@ -327,14 +319,6 @@ static ULONG WINAPI d3d_device_inner_Release(IUnknown *iface) break; } - case DDRAW_HANDLE_SURFACE: - { - struct ddraw_surface *surf = entry->object; - FIXME("Texture handle %#lx (%p) not unset properly.\n", i + 1, surf); - surf->Handle = 0; - break; - } - default: FIXME("Handle %#lx (%p) has unknown type %#x.\n", i + 1, entry->object, entry->type); break; @@ -359,7 +343,10 @@ static ULONG WINAPI d3d_device_inner_Release(IUnknown *iface) /* Releasing the render target above may have released the last * reference to the ddraw object. */ if (This->ddraw) - This->ddraw->d3ddevice = NULL; + { + list_remove(&This->ddraw_entry); + This->ddraw = NULL; + } /* Now free the structure */ free(This); @@ -2768,7 +2755,7 @@ static HRESULT WINAPI d3d_device3_SetRenderState(IDirect3DDevice3 *iface, break; } - surf = ddraw_get_object(&device->handle_table, value - 1, DDRAW_HANDLE_SURFACE); + surf = ddraw_get_object(&device->ddraw->handle_table, value - 1, DDRAW_HANDLE_SURFACE); if (!surf) { WARN("Invalid texture handle.\n"); @@ -2936,7 +2923,7 @@ static HRESULT WINAPI d3d_device3_SetLightState(IDirect3DDevice3 *iface, { struct d3d_material *m; - if (!(m = ddraw_get_object(&device->handle_table, value - 1, DDRAW_HANDLE_MATERIAL))) + if (!(m = ddraw_get_object(&device->ddraw->handle_table, value - 1, DDRAW_HANDLE_MATERIAL))) { WARN("Invalid material handle.\n"); wined3d_mutex_unlock(); @@ -6890,7 +6877,7 @@ static HRESULT d3d_device_init(struct d3d_device *device, struct ddraw *ddraw, c if (version != 1) IUnknown_AddRef(device->rt_iface); - ddraw->d3ddevice = device; + list_add_head(&ddraw->d3ddevice_list, &device->ddraw_entry); wined3d_stateblock_set_render_state(ddraw->state, WINED3D_RS_ZENABLE, d3d_device_update_depth_stencil(device)); @@ -6938,12 +6925,6 @@ HRESULT d3d_device_create(struct ddraw *ddraw, const GUID *guid, struct ddraw_su return DDERR_OUTOFMEMORY; } - if (ddraw->d3ddevice) - { - FIXME("Only one Direct3D device per DirectDraw object supported.\n"); - return DDERR_INVALIDPARAMS; - } - if (!(object = calloc(1, sizeof(*object)))) { ERR("Failed to allocate device memory.\n"); diff --git a/dlls/ddraw/executebuffer.c b/dlls/ddraw/executebuffer.c index 320ce6649d4..f505eea37c6 100644 --- a/dlls/ddraw/executebuffer.c +++ b/dlls/ddraw/executebuffer.c @@ -347,13 +347,13 @@ HRESULT d3d_execute_buffer_execute(struct d3d_execute_buffer *buffer, struct d3d instr += size; - if (!(dst = ddraw_get_object(&device->handle_table, + if (!(dst = ddraw_get_object(&device->ddraw->handle_table, ci->hDestTexture - 1, DDRAW_HANDLE_SURFACE))) { WARN("Invalid destination texture handle %#lx.\n", ci->hDestTexture); continue; } - if (!(src = ddraw_get_object(&device->handle_table, + if (!(src = ddraw_get_object(&device->ddraw->handle_table, ci->hSrcTexture - 1, DDRAW_HANDLE_SURFACE))) { WARN("Invalid source texture handle %#lx.\n", ci->hSrcTexture); diff --git a/dlls/ddraw/main.c b/dlls/ddraw/main.c index 1ba2900704f..52f82e3c256 100644 --- a/dlls/ddraw/main.c +++ b/dlls/ddraw/main.c @@ -874,8 +874,8 @@ BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, void *reserved) WARN("DirectDraw object %p has reference counts {%lu, %lu, %lu, %lu, %lu}.\n", ddraw, ddraw->ref7, ddraw->ref4, ddraw->ref3, ddraw->ref2, ddraw->ref1); - if (ddraw->d3ddevice) - WARN("DirectDraw object %p has Direct3D device %p attached.\n", ddraw, ddraw->d3ddevice); + if (!list_empty(&ddraw->d3ddevice_list)) + WARN("DirectDraw object %p has Direct3D device(s) attached.\n", ddraw); LIST_FOR_EACH_ENTRY(surface, &ddraw->surface_list, struct ddraw_surface, surface_list_entry) { diff --git a/dlls/ddraw/material.c b/dlls/ddraw/material.c index d8cb41f1733..4f8266f00d2 100644 --- a/dlls/ddraw/material.c +++ b/dlls/ddraw/material.c @@ -143,7 +143,7 @@ static ULONG WINAPI d3d_material3_Release(IDirect3DMaterial3 *iface) if (material->Handle) { wined3d_mutex_lock(); - ddraw_free_handle(&material->ddraw->d3ddevice->handle_table, material->Handle - 1, DDRAW_HANDLE_MATERIAL); + ddraw_free_handle(&material->ddraw->handle_table, material->Handle - 1, DDRAW_HANDLE_MATERIAL); wined3d_mutex_unlock(); } @@ -300,7 +300,7 @@ static HRESULT WINAPI d3d_material3_GetHandle(IDirect3DMaterial3 *iface, material->active_device = device_impl; if (!material->Handle) { - DWORD h = ddraw_allocate_handle(&device_impl->handle_table, material, DDRAW_HANDLE_MATERIAL); + DWORD h = ddraw_allocate_handle(&device_impl->ddraw->handle_table, material, DDRAW_HANDLE_MATERIAL); if (h == DDRAW_INVALID_HANDLE) { ERR("Failed to allocate a material handle.\n"); diff --git a/dlls/ddraw/surface.c b/dlls/ddraw/surface.c index 5836a49f4fd..20f79c9453d 100644 --- a/dlls/ddraw/surface.c +++ b/dlls/ddraw/surface.c @@ -1986,6 +1986,8 @@ static HRESULT WINAPI DECLSPEC_HOTPATCH ddraw_surface2_Blt(IDirectDrawSurface2 * *****************************************************************************/ static HRESULT ddraw_surface_attach_surface(struct ddraw_surface *This, struct ddraw_surface *Surf) { + struct d3d_device *device; + TRACE("surface %p, attachment %p.\n", This, Surf); if(Surf == This) @@ -2012,8 +2014,10 @@ static HRESULT ddraw_surface_attach_surface(struct ddraw_surface *This, struct d This->next_attached = Surf; /* Check if the WineD3D depth stencil needs updating */ - if (This->ddraw->d3ddevice) - d3d_device_update_depth_stencil(This->ddraw->d3ddevice); + LIST_FOR_EACH_ENTRY(device, &This->ddraw->d3ddevice_list, struct d3d_device, ddraw_entry) + { + d3d_device_update_depth_stencil(device); + } wined3d_mutex_unlock(); @@ -5419,7 +5423,7 @@ static HRESULT WINAPI d3d_texture2_GetHandle(IDirect3DTexture2 *iface, if (!surface->Handle) { - DWORD h = ddraw_allocate_handle(&device_impl->handle_table, surface, DDRAW_HANDLE_SURFACE); + DWORD h = ddraw_allocate_handle(&device_impl->ddraw->handle_table, surface, DDRAW_HANDLE_SURFACE); if (h == DDRAW_INVALID_HANDLE) { ERR("Failed to allocate a texture handle.\n"); @@ -6030,7 +6034,7 @@ static void STDMETHODCALLTYPE ddraw_surface_wined3d_object_destroyed(void *paren /* Having a texture handle set implies that the device still exists. */ if (surface->Handle) - ddraw_free_handle(&surface->ddraw->d3ddevice->handle_table, surface->Handle - 1, DDRAW_HANDLE_SURFACE); + ddraw_free_handle(&surface->ddraw->handle_table, surface->Handle - 1, DDRAW_HANDLE_SURFACE); /* Reduce the ddraw surface count. */ list_remove(&surface->surface_list_entry); @@ -6733,18 +6737,19 @@ HRESULT ddraw_surface_create(struct ddraw *ddraw, const DDSURFACEDESC2 *surface_ if (ddraw->cooperative_level & DDSCL_EXCLUSIVE) { struct wined3d_swapchain_desc swapchain_desc; + struct d3d_device *device; wined3d_swapchain_get_desc(ddraw->wined3d_swapchain, &swapchain_desc); swapchain_desc.backbuffer_width = mode.width; swapchain_desc.backbuffer_height = mode.height; swapchain_desc.backbuffer_format = mode.format_id; - if (ddraw->d3ddevice) + LIST_FOR_EACH_ENTRY(device, &ddraw->d3ddevice_list, struct d3d_device, ddraw_entry) { - if (ddraw->d3ddevice->recording) - wined3d_stateblock_decref(ddraw->d3ddevice->recording); - ddraw->d3ddevice->recording = NULL; - ddraw->d3ddevice->update_state = ddraw->d3ddevice->state; + if (device->recording) + wined3d_stateblock_decref(device->recording); + device->recording = NULL; + device->update_state = device->state; } wined3d_stateblock_reset(ddraw->state); @@ -6808,11 +6813,19 @@ HRESULT ddraw_surface_create(struct ddraw *ddraw, const DDSURFACEDESC2 *surface_ { if (!(desc->ddsCaps.dwCaps2 & (DDSCAPS2_TEXTUREMANAGE | DDSCAPS2_D3DTEXTUREMANAGE))) { - unsigned int bind_flags = WINED3D_BIND_SHADER_RESOURCE; + unsigned int bind_flags = 0; DWORD usage = 0; if (desc->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP) + { usage |= WINED3DUSAGE_LEGACY_CUBEMAP; + bind_flags |= WINED3D_BIND_SHADER_RESOURCE; + } + else if (desc->ddsCaps.dwCaps & DDSCAPS_TEXTURE) + { + bind_flags |= WINED3D_BIND_SHADER_RESOURCE; + } + if (desc->ddsCaps.dwCaps & DDSCAPS_ZBUFFER) bind_flags |= WINED3D_BIND_DEPTH_STENCIL; else if (desc->ddsCaps.dwCaps & DDSCAPS_3DDEVICE) diff --git a/dlls/ddraw/tests/ddraw1.c b/dlls/ddraw/tests/ddraw1.c index 02d6adbb433..88569d01bf5 100644 --- a/dlls/ddraw/tests/ddraw1.c +++ b/dlls/ddraw/tests/ddraw1.c @@ -15457,6 +15457,85 @@ static void test_enum_devices(void) ok(!refcount, "Device has %lu references left.\n", refcount); } +static void test_multiple_devices(void) +{ + D3DTEXTUREHANDLE texture_handle, texture_handle2; + D3DMATERIALHANDLE mat_handle, mat_handle2; + IDirect3DViewport *viewport, *viewport2; + IDirect3DDevice *device, *device2; + IDirectDrawSurface *texture_surf; + IDirect3DMaterial *material; + DDSURFACEDESC surface_desc; + IDirect3DTexture *texture; + IDirectDraw *ddraw; + ULONG refcount; + HWND window; + HRESULT hr; + + window = create_window(); + ddraw = create_ddraw(); + ok(!!ddraw, "Failed to create a ddraw object.\n"); + + if (!(device = create_device_ex(ddraw, window, DDSCL_NORMAL, &IID_IDirect3DHALDevice))) + { + skip("Failed to create a 3D device, skipping test.\n"); + DestroyWindow(window); + return; + } + + device2 = create_device_ex(ddraw, window, DDSCL_NORMAL, &IID_IDirect3DHALDevice); + ok(!!device2, "got NULL.\n"); + + viewport = create_viewport(device, 0, 0, 640, 480); + viewport2 = create_viewport(device2, 0, 0, 640, 480); + + material = create_diffuse_material(device, 1.0f, 0.0f, 0.0f, 1.0f); + hr = IDirect3DMaterial2_GetHandle(material, device, &mat_handle); + ok(hr == D3D_OK, "got %#lx.\n", hr); + hr = IDirect3DMaterial2_GetHandle(material, device, &mat_handle2); + ok(hr == D3D_OK, "got %#lx.\n", hr); + ok(mat_handle == mat_handle2, "got different handles.\n"); + + hr = IDirect3DMaterial_GetHandle(material, device2, &mat_handle2); + ok(hr == D3D_OK, "got %#lx.\n", hr); + todo_wine ok(mat_handle != mat_handle2, "got same handles.\n"); + + hr = IDirect3DViewport_SetBackground(viewport, mat_handle); + ok(hr == D3D_OK, "got %#lx.\n", hr); + hr = IDirect3DViewport_SetBackground(viewport2, mat_handle); + ok(hr == D3D_OK, "got %#lx.\n", hr); + + memset(&surface_desc, 0, sizeof(surface_desc)); + surface_desc.dwSize = sizeof(surface_desc); + surface_desc.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT; + surface_desc.ddsCaps.dwCaps = DDSCAPS_TEXTURE; + surface_desc.dwWidth = 256; + surface_desc.dwHeight = 256; + hr = IDirectDraw_CreateSurface(ddraw, &surface_desc, &texture_surf, NULL); + ok(hr == D3D_OK, "got %#lx.\n", hr); + hr = IDirectDrawSurface_QueryInterface(texture_surf, &IID_IDirect3DTexture2, (void **)&texture); + ok(hr == D3D_OK, "got %#lx.\n", hr); + hr = IDirect3DTexture_GetHandle(texture, device, &texture_handle); + ok(hr == D3D_OK, "got %#lx.\n", hr); + hr = IDirect3DTexture_GetHandle(texture, device2, &texture_handle2); + ok(hr == D3D_OK, "got %#lx.\n", hr); + ok(texture_handle != texture_handle2, "got same handles.\n"); + + IDirect3DTexture_Release(texture); + IDirectDrawSurface_Release(texture_surf); + IDirect3DMaterial_Release(material); + IDirect3DViewport_Release(viewport); + IDirect3DViewport_Release(viewport2); + + refcount = IDirect3DDevice_Release(device); + ok(!refcount, "Device has %lu references left.\n", refcount); + refcount = IDirect3DDevice_Release(device2); + ok(!refcount, "Device has %lu references left.\n", refcount); + + IDirectDraw_Release(ddraw); + DestroyWindow(window); +} + START_TEST(ddraw1) { DDDEVICEIDENTIFIER identifier; @@ -15576,4 +15655,5 @@ START_TEST(ddraw1) run_for_each_device_type(test_texture_wrong_caps); test_filling_convention(); test_enum_devices(); + test_multiple_devices(); } diff --git a/dlls/ddraw/tests/ddraw2.c b/dlls/ddraw/tests/ddraw2.c index f4cc5df0558..1ddb36b3b4f 100644 --- a/dlls/ddraw/tests/ddraw2.c +++ b/dlls/ddraw/tests/ddraw2.c @@ -462,7 +462,8 @@ static IDirectDraw2 *create_ddraw(void) return ddraw2; } -static IDirect3DDevice2 *create_device_ex(IDirectDraw2 *ddraw, HWND window, DWORD coop_level, const GUID *device_guid) +static IDirect3DDevice2 *create_device_ex(IDirectDraw2 *ddraw, HWND window, DWORD coop_level, const GUID *device_guid, + IDirectDrawSurface **ret_surface) { /* Prefer 16 bit depth buffers because Nvidia gives us an unpadded D24 buffer if we ask * for 24 bit and handles such buffers incorrectly in DDBLT_DEPTHFILL. AMD only supports @@ -541,13 +542,17 @@ static IDirect3DDevice2 *create_device_ex(IDirectDraw2 *ddraw, HWND window, DWOR } IDirect3D2_Release(d3d); - IDirectDrawSurface_Release(surface); + if (ret_surface) + *ret_surface = surface; + else + IDirectDrawSurface_Release(surface); + return device; } static IDirect3DDevice2 *create_device(IDirectDraw2 *ddraw, HWND window, DWORD coop_level) { - return create_device_ex(ddraw, window, coop_level, &IID_IDirect3DHALDevice); + return create_device_ex(ddraw, window, coop_level, &IID_IDirect3DHALDevice, NULL); } static IDirect3DViewport2 *create_viewport(IDirect3DDevice2 *device, UINT x, UINT y, UINT w, UINT h) @@ -1330,7 +1335,7 @@ static void test_depth_blit(const GUID *device_guid) window = create_window(); ddraw = create_ddraw(); ok(!!ddraw, "Failed to create a ddraw object.\n"); - if (!(device = create_device_ex(ddraw, window, DDSCL_NORMAL, device_guid))) + if (!(device = create_device_ex(ddraw, window, DDSCL_NORMAL, device_guid, NULL))) { skip("Failed to create a 3D device, skipping test.\n"); IDirectDraw2_Release(ddraw); @@ -1856,7 +1861,7 @@ static void test_zenable(const GUID *device_guid) window = create_window(); ddraw = create_ddraw(); ok(!!ddraw, "Failed to create a ddraw object.\n"); - if (!(device = create_device_ex(ddraw, window, DDSCL_NORMAL, device_guid))) + if (!(device = create_device_ex(ddraw, window, DDSCL_NORMAL, device_guid, NULL))) { skip("Failed to create a 3D device, skipping test.\n"); IDirectDraw2_Release(ddraw); @@ -1971,7 +1976,7 @@ static void test_ck_rgba(const GUID *device_guid) window = create_window(); ddraw = create_ddraw(); ok(!!ddraw, "Failed to create a ddraw object.\n"); - if (!(device = create_device_ex(ddraw, window, DDSCL_NORMAL, device_guid))) + if (!(device = create_device_ex(ddraw, window, DDSCL_NORMAL, device_guid, NULL))) { skip("Failed to create a 3D device, skipping test.\n"); IDirectDraw2_Release(ddraw); @@ -5204,7 +5209,7 @@ static void test_rt_caps(const GUID *device_guid) window = create_window(); ddraw = create_ddraw(); ok(!!ddraw, "Failed to create a ddraw object.\n"); - if (!(device = create_device_ex(ddraw, window, DDSCL_NORMAL, device_guid))) + if (!(device = create_device_ex(ddraw, window, DDSCL_NORMAL, device_guid, NULL))) { skip("Failed to create a 3D device, skipping test.\n"); IDirectDraw2_Release(ddraw); @@ -15692,7 +15697,7 @@ static void test_texture_wrong_caps(const GUID *device_guid) window = create_window(); ddraw = create_ddraw(); ok(!!ddraw, "Failed to create a ddraw object.\n"); - if (!(device = create_device_ex(ddraw, window, DDSCL_NORMAL, device_guid))) + if (!(device = create_device_ex(ddraw, window, DDSCL_NORMAL, device_guid, NULL))) { skip("Failed to create a 3D device, skipping test.\n"); DestroyWindow(window); @@ -16442,6 +16447,105 @@ static void run_for_each_device_type(void (*test_func)(const GUID *)) test_func(&IID_IDirect3DRGBDevice); } +static void test_multiple_devices(void) +{ + D3DTEXTUREHANDLE texture_handle, texture_handle2; + IDirectDrawSurface *surface, *texture_surf; + D3DMATERIALHANDLE mat_handle, mat_handle2; + IDirect3DViewport2 *viewport, *viewport2; + IDirect3DDevice2 *device, *device2; + IDirect3DMaterial2 *material; + DDSURFACEDESC surface_desc; + IDirect3DTexture2 *texture; + IDirectDraw2 *ddraw; + IDirect3D2 *d3d; + ULONG refcount; + HWND window; + HRESULT hr; + + window = create_window(); + ddraw = create_ddraw(); + ok(!!ddraw, "Failed to create a ddraw object.\n"); + + if (!(device = create_device_ex(ddraw, window, DDSCL_NORMAL, &IID_IDirect3DHALDevice, &surface))) + { + skip("Failed to create a 3D device, skipping test.\n"); + DestroyWindow(window); + return; + } + + hr = IDirect3DDevice2_GetDirect3D(device, &d3d); + ok(hr == D3D_OK, "got %#lx.\n", hr); + + hr = IDirect3D2_CreateDevice(d3d, &IID_IDirect3DHALDevice, surface, &device2); + ok(hr == D3D_OK, "got %#lx.\n", hr); + + viewport = create_viewport(device, 0, 0, 640, 480); + viewport2 = create_viewport(device2, 0, 0, 640, 480); + hr = IDirect3DDevice2_SetCurrentViewport(device, viewport); + ok(hr == D3D_OK, "got %#lx.\n", hr); + hr = IDirect3DDevice2_SetCurrentViewport(device2, viewport); + ok(hr == DDERR_INVALIDPARAMS, "got %#lx.\n", hr); + hr = IDirect3DDevice2_SetCurrentViewport(device2, viewport2); + ok(hr == D3D_OK, "got %#lx.\n", hr); + + material = create_diffuse_material(device, 1.0f, 0.0f, 0.0f, 1.0f); + hr = IDirect3DMaterial2_GetHandle(material, device, &mat_handle); + ok(hr == D3D_OK, "got %#lx.\n", hr); + hr = IDirect3DMaterial2_GetHandle(material, device, &mat_handle2); + ok(hr == D3D_OK, "got %#lx.\n", hr); + ok(mat_handle == mat_handle2, "got different handles.\n"); + + hr = IDirect3DMaterial2_GetHandle(material, device2, &mat_handle2); + ok(hr == D3D_OK, "got %#lx.\n", hr); + todo_wine ok(mat_handle != mat_handle2, "got same handles.\n"); + + hr = IDirect3DDevice2_SetLightState(device, D3DLIGHTSTATE_MATERIAL, mat_handle); + ok(hr == D3D_OK, "Got unexpected hr %#lx.\n", hr); + hr = IDirect3DDevice2_SetLightState(device2, D3DLIGHTSTATE_MATERIAL, mat_handle); + ok(hr == D3D_OK, "Got unexpected hr %#lx.\n", hr); + hr = IDirect3DDevice2_SetLightState(device, D3DLIGHTSTATE_MATERIAL, mat_handle2); + ok(hr == D3D_OK, "Got unexpected hr %#lx.\n", hr); + + hr = IDirect3DViewport2_SetBackground(viewport, mat_handle); + ok(hr == D3D_OK, "got %#lx.\n", hr); + hr = IDirect3DViewport2_SetBackground(viewport2, mat_handle); + ok(hr == D3D_OK, "got %#lx.\n", hr); + + memset(&surface_desc, 0, sizeof(surface_desc)); + surface_desc.dwSize = sizeof(surface_desc); + surface_desc.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT; + surface_desc.ddsCaps.dwCaps = DDSCAPS_TEXTURE; + surface_desc.dwWidth = 256; + surface_desc.dwHeight = 256; + hr = IDirectDraw2_CreateSurface(ddraw, &surface_desc, &texture_surf, NULL); + ok(hr == D3D_OK, "got %#lx.\n", hr); + hr = IDirectDrawSurface_QueryInterface(texture_surf, &IID_IDirect3DTexture2, (void **)&texture); + ok(hr == D3D_OK, "got %#lx.\n", hr); + hr = IDirect3DTexture2_GetHandle(texture, device, &texture_handle); + ok(hr == D3D_OK, "got %#lx.\n", hr); + hr = IDirect3DTexture2_GetHandle(texture, device2, &texture_handle2); + ok(hr == D3D_OK, "got %#lx.\n", hr); + ok(texture_handle == texture_handle2, "got different handles.\n"); + + IDirect3DTexture2_Release(texture); + IDirectDrawSurface_Release(texture_surf); + IDirect3DMaterial2_Release(material); + IDirect3DViewport2_Release(viewport); + IDirect3DViewport2_Release(viewport2); + + refcount = IDirect3DDevice2_Release(device); + ok(!refcount, "Device has %lu references left.\n", refcount); + refcount = IDirect3DDevice2_Release(device2); + ok(!refcount, "Device has %lu references left.\n", refcount); + refcount = IDirectDrawSurface_Release(surface); + ok(!refcount, "Surface has %lu references left.\n", refcount); + + IDirectDraw2_Release(ddraw); + IDirect3D2_Release(d3d); + DestroyWindow(window); +} + START_TEST(ddraw2) { DDDEVICEIDENTIFIER identifier; @@ -16567,4 +16671,5 @@ START_TEST(ddraw2) run_for_each_device_type(test_texture_wrong_caps); test_filling_convention(); test_enum_devices(); + test_multiple_devices(); } diff --git a/dlls/ddraw/tests/ddraw4.c b/dlls/ddraw/tests/ddraw4.c index 4811053a231..8c850ca2c93 100644 --- a/dlls/ddraw/tests/ddraw4.c +++ b/dlls/ddraw/tests/ddraw4.c @@ -450,7 +450,8 @@ static IDirectDraw4 *create_ddraw(void) return ddraw4; } -static IDirect3DDevice3 *create_device_ex(HWND window, DWORD coop_level, const GUID *device_guid) +static IDirect3DDevice3 *create_device_ex(HWND window, DWORD coop_level, const GUID *device_guid, + IDirectDrawSurface4 **ret_surface) { IDirectDrawSurface4 *surface, *ds; IDirect3DDevice3 *device = NULL; @@ -539,16 +540,22 @@ static IDirect3DDevice3 *create_device_ex(HWND window, DWORD coop_level, const G hr = IDirect3D3_CreateDevice(d3d3, device_guid, surface, &device, NULL); IDirect3D3_Release(d3d3); - IDirectDrawSurface4_Release(surface); if (FAILED(hr)) + { + IDirectDrawSurface4_Release(surface); return NULL; + } + if (ret_surface) + *ret_surface = surface; + else + IDirectDrawSurface4_Release(surface); return device; } static IDirect3DDevice3 *create_device(HWND window, DWORD coop_level) { - return create_device_ex(window, coop_level, &IID_IDirect3DHALDevice); + return create_device_ex(window, coop_level, &IID_IDirect3DHALDevice, NULL); } static IDirect3DViewport3 *create_viewport(IDirect3DDevice3 *device, UINT x, UINT y, UINT w, UINT h) @@ -1506,7 +1513,7 @@ static void test_depth_blit(const GUID *device_guid) D3DRECT d3drect; window = create_window(); - if (!(device = create_device_ex(window, DDSCL_NORMAL, device_guid))) + if (!(device = create_device_ex(window, DDSCL_NORMAL, device_guid, NULL))) { skip("Failed to create a 3D device, skipping test.\n"); DestroyWindow(window); @@ -2101,7 +2108,7 @@ static void test_zenable(const GUID *device_guid) HRESULT hr; window = create_window(); - if (!(device = create_device_ex(window, DDSCL_NORMAL, device_guid))) + if (!(device = create_device_ex(window, DDSCL_NORMAL, device_guid, NULL))) { skip("Failed to create a 3D device, skipping test.\n"); DestroyWindow(window); @@ -2213,7 +2220,7 @@ static void test_ck_rgba(const GUID *device_guid) HRESULT hr; window = create_window(); - if (!(device = create_device_ex(window, DDSCL_NORMAL, device_guid))) + if (!(device = create_device_ex(window, DDSCL_NORMAL, device_guid, NULL))) { skip("Failed to create a 3D device, skipping test.\n"); DestroyWindow(window); @@ -18750,7 +18757,7 @@ static void test_texture_wrong_caps(const GUID *device_guid) HRESULT hr; window = create_window(); - if (!(device = create_device_ex(window, DDSCL_NORMAL, device_guid))) + if (!(device = create_device_ex(window, DDSCL_NORMAL, device_guid, NULL))) { skip("Failed to create a 3D device, skipping test.\n"); DestroyWindow(window); @@ -19506,6 +19513,82 @@ static void test_enum_devices(void) ok(!refcount, "Device has %lu references left.\n", refcount); } +static void test_multiple_devices(void) +{ + D3DMATERIALHANDLE mat_handle, mat_handle2; + IDirect3DViewport3 *viewport, *viewport2; + IDirect3DDevice3 *device, *device2; + IDirect3DMaterial3 *material; + IDirectDrawSurface4 *surface; + IDirectDraw4 *ddraw; + IDirect3D3 *d3d; + ULONG refcount; + HWND window; + HRESULT hr; + + window = create_window(); + if (!(device = create_device_ex(window, DDSCL_NORMAL, &IID_IDirect3DHALDevice, &surface))) + { + skip("Failed to create a 3D device, skipping test.\n"); + DestroyWindow(window); + return; + } + + hr = IDirect3DDevice3_GetDirect3D(device, &d3d); + ok(hr == D3D_OK, "got %#lx.\n", hr); + hr = IDirect3DDevice3_QueryInterface(d3d, &IID_IDirectDraw4, (void **)&ddraw); + ok(hr == D3D_OK, "got %#lx.\n", hr); + + hr = IDirect3D3_CreateDevice(d3d, &IID_IDirect3DHALDevice, surface, &device2, NULL); + ok(hr == D3D_OK, "got %#lx.\n", hr); + + viewport = create_viewport(device, 0, 0, 640, 480); + viewport2 = create_viewport(device2, 0, 0, 640, 480); + hr = IDirect3DDevice3_SetCurrentViewport(device, viewport); + ok(hr == D3D_OK, "got %#lx.\n", hr); + hr = IDirect3DDevice3_SetCurrentViewport(device2, viewport); + ok(hr == DDERR_INVALIDPARAMS, "got %#lx.\n", hr); + hr = IDirect3DDevice3_SetCurrentViewport(device2, viewport2); + ok(hr == D3D_OK, "got %#lx.\n", hr); + + material = create_diffuse_material(device, 1.0f, 0.0f, 0.0f, 1.0f); + hr = IDirect3DMaterial3_GetHandle(material, device, &mat_handle); + ok(hr == D3D_OK, "got %#lx.\n", hr); + hr = IDirect3DMaterial3_GetHandle(material, device, &mat_handle2); + ok(hr == D3D_OK, "got %#lx.\n", hr); + ok(mat_handle == mat_handle2, "got different handles.\n"); + + hr = IDirect3DMaterial3_GetHandle(material, device2, &mat_handle2); + ok(hr == D3D_OK, "got %#lx.\n", hr); + todo_wine ok(mat_handle != mat_handle2, "got same handles.\n"); + + hr = IDirect3DDevice3_SetLightState(device, D3DLIGHTSTATE_MATERIAL, mat_handle); + ok(hr == D3D_OK, "Got unexpected hr %#lx.\n", hr); + hr = IDirect3DDevice3_SetLightState(device2, D3DLIGHTSTATE_MATERIAL, mat_handle); + ok(hr == D3D_OK, "Got unexpected hr %#lx.\n", hr); + hr = IDirect3DDevice3_SetLightState(device, D3DLIGHTSTATE_MATERIAL, mat_handle2); + ok(hr == D3D_OK, "Got unexpected hr %#lx.\n", hr); + + hr = IDirect3DViewport3_SetBackground(viewport, mat_handle); + ok(hr == D3D_OK, "got %#lx.\n", hr); + hr = IDirect3DViewport3_SetBackground(viewport2, mat_handle); + ok(hr == D3D_OK, "got %#lx.\n", hr); + + IDirect3DMaterial3_Release(material); + IDirect3DViewport3_Release(viewport); + IDirect3DViewport3_Release(viewport2); + + refcount = IDirect3DDevice3_Release(device); + ok(!refcount, "Device has %lu references left.\n", refcount); + refcount = IDirect3DDevice3_Release(device2); + ok(!refcount, "Device has %lu references left.\n", refcount); + refcount = IDirectDrawSurface4_Release(surface); + ok(!refcount, "Surface has %lu references left.\n", refcount); + IDirectDraw4_Release(ddraw); + IDirect3D3_Release(d3d); + DestroyWindow(window); +} + START_TEST(ddraw4) { DDDEVICEIDENTIFIER identifier; @@ -19647,4 +19730,5 @@ START_TEST(ddraw4) run_for_each_device_type(test_texture_wrong_caps); test_filling_convention(); test_enum_devices(); + test_multiple_devices(); } diff --git a/dlls/ddraw/tests/ddraw7.c b/dlls/ddraw/tests/ddraw7.c index 2771a4ed368..2ec1ab611e4 100644 --- a/dlls/ddraw/tests/ddraw7.c +++ b/dlls/ddraw/tests/ddraw7.c @@ -498,7 +498,8 @@ static HRESULT WINAPI enum_devtype_cb(char *desc_str, char *name, D3DDEVICEDESC7 return DDENUMRET_OK; } -static IDirect3DDevice7 *create_device_ex(HWND window, DWORD coop_level, const GUID *device_guid) +static IDirect3DDevice7 *create_device_ex(HWND window, DWORD coop_level, const GUID *device_guid, + IDirectDrawSurface7 **ret_surface) { IDirectDrawSurface7 *surface, *ds; IDirect3DDevice7 *device = NULL; @@ -586,9 +587,16 @@ static IDirect3DDevice7 *create_device_ex(HWND window, DWORD coop_level, const G hr = IDirect3D7_CreateDevice(d3d7, device_guid, surface, &device); IDirect3D7_Release(d3d7); - IDirectDrawSurface7_Release(surface); if (FAILED(hr)) + { + IDirectDrawSurface7_Release(surface); return NULL; + } + + if (ret_surface) + *ret_surface = surface; + else + IDirectDrawSurface7_Release(surface); return device; } @@ -615,7 +623,7 @@ static IDirect3DDevice7 *create_device(HWND window, DWORD coop_level) IDirect3D7_Release(d3d7); - return create_device_ex(window, coop_level, device_guid); + return create_device_ex(window, coop_level, device_guid, NULL); } static bool init_3d_test_context_guid(struct ddraw_test_context *context, const GUID *device_guid) @@ -625,7 +633,7 @@ static bool init_3d_test_context_guid(struct ddraw_test_context *context, const memset(context, 0, sizeof(*context)); context->window = create_window(); - if (!(context->device = create_device_ex(context->window, DDSCL_NORMAL, device_guid))) + if (!(context->device = create_device_ex(context->window, DDSCL_NORMAL, device_guid, NULL))) { skip("Failed to create a D3D device.\n"); DestroyWindow(context->window); @@ -1586,7 +1594,7 @@ static void test_depth_blit(const GUID *device_guid) HWND window; window = create_window(); - if (!(device = create_device_ex(window, DDSCL_NORMAL, device_guid))) + if (!(device = create_device_ex(window, DDSCL_NORMAL, device_guid, NULL))) { skip("Failed to create a 3D device, skipping test.\n"); DestroyWindow(window); @@ -1844,7 +1852,7 @@ static void test_zenable(const GUID *device_guid) HRESULT hr; window = create_window(); - if (!(device = create_device_ex(window, DDSCL_NORMAL, device_guid))) + if (!(device = create_device_ex(window, DDSCL_NORMAL, device_guid, NULL))) { skip("Failed to create a 3D device, skipping test.\n"); DestroyWindow(window); @@ -1948,7 +1956,7 @@ static void test_ck_rgba(const GUID *device_guid) HRESULT hr; window = create_window(); - if (!(device = create_device_ex(window, DDSCL_NORMAL, device_guid))) + if (!(device = create_device_ex(window, DDSCL_NORMAL, device_guid, NULL))) { skip("Failed to create a 3D device, skipping test.\n"); DestroyWindow(window); @@ -19134,7 +19142,7 @@ static void test_texture_wrong_caps(const GUID *device_guid) HRESULT hr; window = create_window(); - if (!(device = create_device_ex(window, DDSCL_NORMAL, device_guid))) + if (!(device = create_device_ex(window, DDSCL_NORMAL, device_guid, NULL))) { skip("Failed to create a 3D device, skipping test.\n"); DestroyWindow(window); @@ -20001,6 +20009,43 @@ static void run_for_each_device_type(void (*test_func)(const GUID *)) winetest_pop_context(); } +static void test_multiple_devices(void) +{ + IDirect3DDevice7 *device, *device2; + IDirectDrawSurface7 *surface; + IDirectDraw7 *ddraw; + IDirect3D7 *d3d; + ULONG refcount; + HWND window; + HRESULT hr; + + window = create_window(); + if (!(device = create_device_ex(window, DDSCL_NORMAL, &IID_IDirect3DTnLHalDevice, &surface))) + { + skip("Failed to create a 3D device, skipping test.\n"); + DestroyWindow(window); + return; + } + + hr = IDirect3DDevice7_GetDirect3D(device, &d3d); + ok(hr == D3D_OK, "got %#lx.\n", hr); + hr = IDirect3DDevice7_QueryInterface(d3d, &IID_IDirectDraw7, (void **)&ddraw); + ok(hr == D3D_OK, "got %#lx.\n", hr); + + hr = IDirect3D7_CreateDevice(d3d, &IID_IDirect3DHALDevice, surface, &device2); + ok(hr == D3D_OK, "got %#lx.\n", hr); + + refcount = IDirect3DDevice3_Release(device); + ok(!refcount, "Device has %lu references left.\n", refcount); + refcount = IDirect3DDevice3_Release(device2); + ok(!refcount, "Device has %lu references left.\n", refcount); + refcount = IDirectDrawSurface4_Release(surface); + ok(!refcount, "Surface has %lu references left.\n", refcount); + IDirectDraw4_Release(ddraw); + IDirect3D3_Release(d3d); + DestroyWindow(window); +} + START_TEST(ddraw7) { DDDEVICEIDENTIFIER2 identifier; @@ -20176,4 +20221,5 @@ START_TEST(ddraw7) test_enum_devices(); run_for_each_device_type(test_user_memory); test_flip_3d(); + test_multiple_devices(); } diff --git a/dlls/ddraw/viewport.c b/dlls/ddraw/viewport.c index 4eda2fe4763..db235721f75 100644 --- a/dlls/ddraw/viewport.c +++ b/dlls/ddraw/viewport.c @@ -627,7 +627,7 @@ static HRESULT WINAPI d3d_viewport_SetBackground(IDirect3DViewport3 *iface, D3DM wined3d_mutex_lock(); - if (!(m = ddraw_get_object(&viewport->ddraw->d3ddevice->handle_table, material - 1, DDRAW_HANDLE_MATERIAL))) + if (!(m = ddraw_get_object(&viewport->ddraw->handle_table, material - 1, DDRAW_HANDLE_MATERIAL))) { WARN("Invalid material handle %#lx.\n", material); wined3d_mutex_unlock(); diff --git a/dlls/dinput/joystick_hid.c b/dlls/dinput/joystick_hid.c index b5fc76b9222..a28c3ba39e7 100644 --- a/dlls/dinput/joystick_hid.c +++ b/dlls/dinput/joystick_hid.c @@ -864,6 +864,16 @@ static HRESULT hid_joystick_get_property( IDirectInputDevice8W *iface, DWORD pro { DIPROPGUIDANDPATH *value = (DIPROPGUIDANDPATH *)header; value->guidClass = GUID_DEVCLASS_HIDCLASS; + + /* CW-Bug-Id: #23185 Emulate Steam Input native hooks for native SDL */ + if (impl->attrs.VendorID == 0x28de && impl->attrs.ProductID == 0x11ff) + { + const WCHAR *tmp; + if ((tmp = wcschr( impl->device_path, '#' ))) tmp = wcschr( tmp + 1, '#' ); + lstrcpynW( value->wszPath, impl->device_path, tmp - impl->device_path + 1 ); + return DI_OK; + } + lstrcpynW( value->wszPath, impl->device_path, MAX_PATH ); return DI_OK; } diff --git a/dlls/dsound/dsound.c b/dlls/dsound/dsound.c index b1c0c76c367..247d1edcf6b 100644 --- a/dlls/dsound/dsound.c +++ b/dlls/dsound/dsound.c @@ -489,6 +489,9 @@ static HRESULT DirectSoundDevice_CreateSoundBuffer( return DSERR_INVALIDPARAM; } + if (dsbd->lpwfxFormat->nChannels > 2 && dsbd->lpwfxFormat->wFormatTag != WAVE_FORMAT_EXTENSIBLE) + return DSERR_INVALIDPARAM; + if (dsbd->lpwfxFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { WAVEFORMATEXTENSIBLE *pwfxe = (WAVEFORMATEXTENSIBLE*)dsbd->lpwfxFormat; diff --git a/dlls/dsound/primary.c b/dlls/dsound/primary.c index 8cfcd1d8a28..22cf475a674 100644 --- a/dlls/dsound/primary.c +++ b/dlls/dsound/primary.c @@ -538,6 +538,9 @@ HRESULT primarybuffer_SetFormat(DirectSoundDevice *device, LPCWAVEFORMATEX passe return DSERR_INVALIDPARAM; } + if (passed_fmt->nChannels > 2 && passed_fmt->wFormatTag != WAVE_FORMAT_EXTENSIBLE) + return DSERR_ALLOCATED; + /* **** */ AcquireSRWLockExclusive(&device->buffer_list_lock); EnterCriticalSection(&(device->mixlock)); diff --git a/dlls/dsound/tests/dsound.c b/dlls/dsound/tests/dsound.c index 9656a458415..500bc19f655 100644 --- a/dlls/dsound/tests/dsound.c +++ b/dlls/dsound/tests/dsound.c @@ -1481,6 +1481,49 @@ static void perform_invalid_fmt_tests(const char *testname, IDirectSound *dso, I fmtex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; rc = do_invalid_fmt_test(dso, buf, (WAVEFORMATEX*)&fmtex, &got_buf); ok(rc == E_INVALIDARG, "%s: SetFormat: %08lx\n", testname, rc); + + /* The following 4 tests show that formats with more than two channels require WAVEFORMATEXTENSIBLE */ + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = 2; + wfx.nSamplesPerSec = 44100; + wfx.wBitsPerSample = 16; + wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + rc = do_invalid_fmt_test(dso, buf, &wfx, &got_buf); + ok(rc == S_OK, "%s: SetFormat: %08lx\n", testname, rc); + IDirectSoundBuffer_Release(got_buf); + + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = 4; + wfx.nSamplesPerSec = 44100; + wfx.wBitsPerSample = 16; + wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + rc = do_invalid_fmt_test(dso, buf, &wfx, &got_buf); + ok(rc == (buf ? DSERR_ALLOCATED : DSERR_INVALIDPARAM), "%s: SetFormat: %08lx\n", testname, rc); + + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = 6; + wfx.nSamplesPerSec = 44100; + wfx.wBitsPerSample = 16; + wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + rc = do_invalid_fmt_test(dso, buf, &wfx, &got_buf); + ok(rc == (buf ? DSERR_ALLOCATED : DSERR_INVALIDPARAM), "%s: SetFormat: %08lx\n", testname, rc); + + fmtex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + fmtex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + fmtex.Format.nChannels = 6; + fmtex.Format.nSamplesPerSec = 44100; + fmtex.Format.wBitsPerSample = 16; + fmtex.Format.nBlockAlign = fmtex.Format.nChannels * fmtex.Format.wBitsPerSample / 8; + fmtex.Format.nAvgBytesPerSec = fmtex.Format.nSamplesPerSec * fmtex.Format.nBlockAlign; + fmtex.Samples.wValidBitsPerSample = fmtex.Format.wBitsPerSample; + fmtex.dwChannelMask = KSAUDIO_SPEAKER_5POINT1; + fmtex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + rc = do_invalid_fmt_test(dso, buf, (WAVEFORMATEX *)&fmtex, &got_buf); + ok(rc == S_OK, "%s: SetFormat: %08lx\n", testname, rc); + IDirectSoundBuffer_Release(got_buf); } static HRESULT test_invalid_fmts(LPGUID lpGuid) diff --git a/dlls/gdiplus/Makefile.in b/dlls/gdiplus/Makefile.in index 382033be901..76474f2e086 100644 --- a/dlls/gdiplus/Makefile.in +++ b/dlls/gdiplus/Makefile.in @@ -1,6 +1,6 @@ MODULE = gdiplus.dll IMPORTLIB = gdiplus -IMPORTS = uuid shlwapi ole32 oleaut32 user32 gdi32 +IMPORTS = uuid shlwapi ole32 oleaut32 user32 gdi32 mlang DELAYIMPORTS = windowscodecs SOURCES = \ diff --git a/dlls/gdiplus/font.c b/dlls/gdiplus/font.c index da37279fbef..67d75ed97f6 100644 --- a/dlls/gdiplus/font.c +++ b/dlls/gdiplus/font.c @@ -1385,12 +1385,13 @@ static WCHAR *copy_name_table_string( const tt_name_record *name, const BYTE *da static WCHAR *load_ttf_name_id( const BYTE *mem, DWORD_PTR size, DWORD id ) { + static const WORD platform_id_table[] = {TT_PLATFORM_MICROSOFT, TT_PLATFORM_MACINTOSH, TT_PLATFORM_APPLE_UNICODE}; LANGID lang = GetSystemDefaultLangID(); const tt_header *header; const tt_name_table *name_table; - const tt_name_record *name_record; + const tt_name_record *name_record_table, *name_record; DWORD pos, ofs = 0, count; - int i, res, best_lang = 0, best_index = -1; + int i, j, res, best_lang = 0, best_index = -1; if (sizeof(tt_header) > size) return NULL; @@ -1421,26 +1422,33 @@ static WCHAR *load_ttf_name_id( const BYTE *mem, DWORD_PTR size, DWORD id ) if (pos > size) return NULL; name_table = (const tt_name_table*)&mem[ofs]; + name_record_table = (const tt_name_record *)&mem[pos]; count = GET_BE_WORD(name_table->count); if (GET_BE_WORD(name_table->string_offset) >= size - ofs) return NULL; ofs += GET_BE_WORD(name_table->string_offset); - for (i=0; i size) - return NULL; + for (j = 0; j < count; j++) + { + name_record = name_record_table + j; + if ((const BYTE *)name_record - mem > size) + return NULL; - if (GET_BE_WORD(name_record->name_id) != id) continue; - if (GET_BE_WORD(name_record->offset) >= size - ofs) return NULL; - if (GET_BE_WORD(name_record->length) > size - ofs - GET_BE_WORD(name_record->offset)) return NULL; + if (GET_BE_WORD(name_record->platform_id) != platform_id_table[i]) continue; + if (GET_BE_WORD(name_record->name_id) != id) continue; + if (GET_BE_WORD(name_record->offset) >= size - ofs) return NULL; + if (GET_BE_WORD(name_record->length) > size - ofs - GET_BE_WORD(name_record->offset)) return NULL; - res = match_name_table_language( name_record, lang ); - if (res > best_lang) - { - best_lang = res; - best_index = i; + res = match_name_table_language(name_record, lang); + if (res > best_lang) + { + best_lang = res; + best_index = j; + } } + + if (best_index != -1) + break; } if (best_lang) diff --git a/dlls/gdiplus/gdiplus_private.h b/dlls/gdiplus/gdiplus_private.h index 6f7e72124c2..0b302a405ea 100644 --- a/dlls/gdiplus/gdiplus_private.h +++ b/dlls/gdiplus/gdiplus_private.h @@ -127,6 +127,10 @@ extern void calc_curve_bezier(const GpPointF *pts, REAL tension, REAL *x1, extern void calc_curve_bezier_endp(REAL xend, REAL yend, REAL xadj, REAL yadj, REAL tension, REAL *x, REAL *y); +extern void get_font_hfont(GpGraphics *graphics, GDIPCONST GpFont *font, + GDIPCONST GpStringFormat *format, HFONT *hfont, + LOGFONTW *lfw_return, GDIPCONST GpMatrix *matrix); + extern void free_installed_fonts(void); extern BOOL lengthen_path(GpPath *path, INT len); @@ -606,13 +610,38 @@ static inline const void *buffer_read(struct memory_buffer *mbuf, INT size) return NULL; } -typedef GpStatus (*gdip_format_string_callback)(HDC hdc, - GDIPCONST WCHAR *string, INT index, INT length, GDIPCONST GpFont *font, - GDIPCONST RectF *rect, GDIPCONST GpStringFormat *format, - INT lineno, const RectF *bounds, INT *underlined_indexes, - INT underlined_index_count, void *user_data); +/* Represents a string section and the font it should use. */ +struct gdip_font_link_section { + struct list entry; + DWORD start; /* The starting index of the string where the font applies. */ + DWORD end; /* The end index of the string. */ + GpFont *font; +}; + +struct gdip_font_link_info { + GDIPCONST GpFont *base_font; + struct list sections; +}; + +struct gdip_format_string_info { + GpGraphics *graphics; + HDC hdc; + GDIPCONST WCHAR *string; + INT index; + INT length; + struct gdip_font_link_info font_link_info; + GDIPCONST RectF *rect; + GDIPCONST GpStringFormat *format; + INT lineno; + const RectF *bounds; + INT *underlined_indexes; + INT underlined_index_count; + void *user_data; +}; + +typedef GpStatus (*gdip_format_string_callback)(struct gdip_format_string_info *info); -GpStatus gdip_format_string(HDC hdc, +GpStatus gdip_format_string(GpGraphics *graphics, HDC hdc, GDIPCONST WCHAR *string, INT length, GDIPCONST GpFont *font, GDIPCONST RectF *rect, GDIPCONST GpStringFormat *format, int ignore_empty_clip, gdip_format_string_callback callback, void *user_data); diff --git a/dlls/gdiplus/graphics.c b/dlls/gdiplus/graphics.c index 36ff473a6af..ce852e7c940 100644 --- a/dlls/gdiplus/graphics.c +++ b/dlls/gdiplus/graphics.c @@ -34,6 +34,7 @@ #include "winreg.h" #include "shlwapi.h" +#include "mlang.h" #include "gdiplus.h" #include "gdiplus_private.h" #include "wine/debug.h" @@ -2295,7 +2296,7 @@ void get_log_fontW(const GpFont *font, GpGraphics *graphics, LOGFONTW *lf) lstrcpyW(lf->lfFaceName, font->family->FamilyName); } -static void get_font_hfont(GpGraphics *graphics, GDIPCONST GpFont *font, +void get_font_hfont(GpGraphics *graphics, GDIPCONST GpFont *font, GDIPCONST GpStringFormat *format, HFONT *hfont, LOGFONTW *lfw_return, GDIPCONST GpMatrix *matrix) { @@ -5161,7 +5162,110 @@ GpStatus WINGDIPAPI GdipIsVisibleRectI(GpGraphics *graphics, INT x, INT y, INT w return GdipIsVisibleRect(graphics, (REAL)x, (REAL)y, (REAL)width, (REAL)height, result); } -GpStatus gdip_format_string(HDC hdc, +/* Populates gdip_font_link_info struct based on the base_font and input string */ +static void generate_font_link_info(struct gdip_format_string_info *info, DWORD length, GDIPCONST GpFont *base_font) +{ + IUnknown *unk; + IMLangFontLink *iMLFL; + GpFont *gpfont; + HFONT map_hfont, hfont, old_font; + LONG processed, progress = 0; + struct gdip_font_link_section *section; + DWORD font_codepages, string_codepages; + + list_init(&info->font_link_info.sections); + info->font_link_info.base_font = base_font; + + GetGlobalFontLinkObject((void**)&unk); + IUnknown_QueryInterface(unk, &IID_IMLangFontLink, (void**)&iMLFL); + IUnknown_Release(unk); + + get_font_hfont(info->graphics, base_font, NULL, &hfont, NULL, NULL); + IMLangFontLink_GetFontCodePages(iMLFL, info->hdc, hfont, &font_codepages); + + while (progress < length) + { + section = calloc(1, sizeof(*section)); + section->start = progress; + IMLangFontLink_GetStrCodePages(iMLFL, &info->string[progress], length - progress, + font_codepages, &string_codepages, &processed); + + if (font_codepages & string_codepages) + { + section->font = (GpFont *)base_font; + } + else + { + IMLangFontLink_MapFont(iMLFL, info->hdc, string_codepages, hfont, &map_hfont); + old_font = SelectObject(info->hdc, map_hfont); + GdipCreateFontFromDC(info->hdc, &gpfont); + SelectObject(info->hdc, old_font); + IMLangFontLink_ReleaseFont(iMLFL, map_hfont); + section->font = gpfont; + } + + section->end = section->start + processed; + list_add_tail(&info->font_link_info.sections, §ion->entry); + progress += processed; + } + + DeleteObject(hfont); + IMLangFontLink_Release(iMLFL); +} + +static void font_link_get_text_extent_point(struct gdip_format_string_info *info, + INT index, int length, int max_ext, LPINT fit, SIZE *size) +{ + DWORD to_measure_length; + HFONT hfont, oldhfont; + SIZE sizeaux = { 0 }; + int i = index, fitaux = 0; + struct gdip_font_link_section *section; + + size->cx = 0; + size->cy = 0; + + if (fit) + *fit = 0; + + LIST_FOR_EACH_ENTRY(section, &info->font_link_info.sections, struct gdip_font_link_section, entry) + { + if (i >= section->end) continue; + + to_measure_length = min(length - (i - index), section->end - i); + + get_font_hfont(info->graphics, section->font, NULL, &hfont, NULL, NULL); + oldhfont = SelectObject(info->hdc, hfont); + GetTextExtentExPointW(info->hdc, &info->string[i], to_measure_length, max_ext, &fitaux, NULL, &sizeaux); + SelectObject(info->hdc, oldhfont); + DeleteObject(hfont); + + max_ext -= sizeaux.cx; + if (fit) + *fit += fitaux; + size->cx += sizeaux.cx; + size->cy = max(size->cy, sizeaux.cy); + + i += to_measure_length; + if ((i - index) >= length || fitaux < to_measure_length) break; + } +} + +static void release_font_link_info(struct gdip_font_link_info *font_link_info) +{ + struct list *entry; + + while ((entry = list_head(&font_link_info->sections))) + { + struct gdip_font_link_section *section = LIST_ENTRY(entry, struct gdip_font_link_section, entry); + list_remove(entry); + if (section->font != font_link_info->base_font) + GdipDeleteFont(section->font); + free(section); + } +} + +GpStatus gdip_format_string(GpGraphics *graphics, HDC hdc, GDIPCONST WCHAR *string, INT length, GDIPCONST GpFont *font, GDIPCONST RectF *rect, GDIPCONST GpStringFormat *format, int ignore_empty_clip, gdip_format_string_callback callback, void *user_data) @@ -5178,15 +5282,26 @@ GpStatus gdip_format_string(HDC hdc, INT hotkeyprefix_count=0; INT hotkeyprefix_pos=0, hotkeyprefix_end_pos=0; BOOL seen_prefix = FALSE, unixstyle_newline = TRUE; + struct gdip_format_string_info info; + + info.graphics = graphics; + info.hdc = hdc; + info.rect = rect; + info.bounds = &bounds; + info.user_data = user_data; if(length == -1) length = lstrlenW(string); stringdup = calloc(length + 1, sizeof(WCHAR)); if(!stringdup) return OutOfMemory; + info.string = stringdup; + if (!format) format = &default_drawstring_format; + info.format = format; + nwidth = (int)(rect->Width + 0.005f); nheight = (int)(rect->Height + 0.005f); if (ignore_empty_clip) @@ -5242,9 +5357,10 @@ GpStatus gdip_format_string(HDC hdc, halign = format->align; + generate_font_link_info(&info, length, font); + while(sum < length){ - GetTextExtentExPointW(hdc, stringdup + sum, length - sum, - nwidth, &fit, NULL, &size); + font_link_get_text_extent_point(&info, sum, length - sum, nwidth, &fit, &size); fitcpy = fit; if(fit == 0) @@ -5292,8 +5408,7 @@ GpStatus gdip_format_string(HDC hdc, else lineend = fit; - GetTextExtentExPointW(hdc, stringdup + sum, lineend, - nwidth, &j, NULL, &size); + font_link_get_text_extent_point(&info, sum, lineend, nwidth, &j, &size); bounds.Width = size.cx; @@ -5326,10 +5441,13 @@ GpStatus gdip_format_string(HDC hdc, if (hotkeyprefix_offsets[hotkeyprefix_end_pos] >= sum + lineend) break; - stat = callback(hdc, stringdup, sum, lineend, - font, rect, format, lineno, &bounds, - &hotkeyprefix_offsets[hotkeyprefix_pos], - hotkeyprefix_end_pos-hotkeyprefix_pos, user_data); + info.index = sum; + info.length = lineend; + info.lineno = lineno; + info.underlined_indexes = &hotkeyprefix_offsets[hotkeyprefix_pos]; + info.underlined_index_count = hotkeyprefix_end_pos-hotkeyprefix_pos; + + stat = callback(&info); if (stat != Ok) break; @@ -5358,6 +5476,7 @@ GpStatus gdip_format_string(HDC hdc, break; } + release_font_link_info(&info.font_link_info); free(stringdup); free(hotkeyprefix_offsets); @@ -5392,35 +5511,30 @@ struct measure_ranges_args { REAL rel_width, rel_height; }; -static GpStatus measure_ranges_callback(HDC hdc, - GDIPCONST WCHAR *string, INT index, INT length, GDIPCONST GpFont *font, - GDIPCONST RectF *rect, GDIPCONST GpStringFormat *format, - INT lineno, const RectF *bounds, INT *underlined_indexes, - INT underlined_index_count, void *user_data) +static GpStatus measure_ranges_callback(struct gdip_format_string_info *info) { int i; GpStatus stat = Ok; - struct measure_ranges_args *args = user_data; + struct measure_ranges_args *args = info->user_data; + CharacterRange *ranges = info->format->character_ranges; - for (i=0; irange_count; i++) + for (i=0; i < info->format->range_count; i++) { - INT range_start = max(index, format->character_ranges[i].First); - INT range_end = min(index+length, format->character_ranges[i].First+format->character_ranges[i].Length); + INT range_start = max(info->index, ranges[i].First); + INT range_end = min(info->index + info->length, ranges[i].First + ranges[i].Length); if (range_start < range_end) { GpRectF range_rect; SIZE range_size; - range_rect.Y = bounds->Y / args->rel_height; - range_rect.Height = bounds->Height / args->rel_height; + range_rect.Y = info->bounds->Y / args->rel_height; + range_rect.Height = info->bounds->Height / args->rel_height; - GetTextExtentExPointW(hdc, string + index, range_start - index, - INT_MAX, NULL, NULL, &range_size); - range_rect.X = (bounds->X + range_size.cx) / args->rel_width; + font_link_get_text_extent_point(info, info->index, range_start - info->index, INT_MAX, NULL, &range_size); + range_rect.X = (info->bounds->X + range_size.cx) / args->rel_width; - GetTextExtentExPointW(hdc, string + index, range_end - index, - INT_MAX, NULL, NULL, &range_size); - range_rect.Width = (bounds->X + range_size.cx) / args->rel_width - range_rect.X; + font_link_get_text_extent_point(info, info->index, range_end - info->index, INT_MAX, NULL, &range_size); + range_rect.Width = (info->bounds->X + range_size.cx) / args->rel_width - range_rect.X; stat = GdipCombineRegionRect(args->regions[i], &range_rect, CombineModeUnion); if (stat != Ok) @@ -5496,7 +5610,7 @@ GpStatus WINGDIPAPI GdipMeasureCharacterRanges(GpGraphics* graphics, gdi_transform_acquire(graphics); - stat = gdip_format_string(hdc, string, length, font, &scaled_rect, stringFormat, + stat = gdip_format_string(graphics, hdc, string, length, font, &scaled_rect, stringFormat, (stringFormat->attr & StringFormatFlagsNoClip) != 0, measure_ranges_callback, &args); gdi_transform_release(graphics); @@ -5517,17 +5631,13 @@ struct measure_string_args { REAL rel_width, rel_height; }; -static GpStatus measure_string_callback(HDC hdc, - GDIPCONST WCHAR *string, INT index, INT length, GDIPCONST GpFont *font, - GDIPCONST RectF *rect, GDIPCONST GpStringFormat *format, - INT lineno, const RectF *bounds, INT *underlined_indexes, - INT underlined_index_count, void *user_data) +static GpStatus measure_string_callback(struct gdip_format_string_info *info) { - struct measure_string_args *args = user_data; + struct measure_string_args *args = info->user_data; REAL new_width, new_height; - new_width = bounds->Width / args->rel_width; - new_height = (bounds->Height + bounds->Y) / args->rel_height - args->bounds->Y; + new_width = info->bounds->Width / args->rel_width; + new_height = (info->bounds->Height + info->bounds->Y) / args->rel_height - args->bounds->Y; if (new_width > args->bounds->Width) args->bounds->Width = new_width; @@ -5536,7 +5646,7 @@ static GpStatus measure_string_callback(HDC hdc, args->bounds->Height = new_height; if (args->codepointsfitted) - *args->codepointsfitted = index + length; + *args->codepointsfitted = info->index + info->length; if (args->linesfilled) (*args->linesfilled)++; @@ -5619,7 +5729,7 @@ GpStatus WINGDIPAPI GdipMeasureString(GpGraphics *graphics, gdi_transform_acquire(graphics); - gdip_format_string(hdc, string, length, font, &scaled_rect, format, TRUE, + gdip_format_string(graphics, hdc, string, length, font, &scaled_rect, format, TRUE, measure_string_callback, &args); gdi_transform_release(graphics); @@ -5640,52 +5750,62 @@ GpStatus WINGDIPAPI GdipMeasureString(GpGraphics *graphics, } struct draw_string_args { - GpGraphics *graphics; GDIPCONST GpBrush *brush; REAL x, y, rel_width, rel_height, ascent; }; -static GpStatus draw_string_callback(HDC hdc, - GDIPCONST WCHAR *string, INT index, INT length, GDIPCONST GpFont *font, - GDIPCONST RectF *rect, GDIPCONST GpStringFormat *format, - INT lineno, const RectF *bounds, INT *underlined_indexes, - INT underlined_index_count, void *user_data) +static GpStatus draw_string_callback(struct gdip_format_string_info *info) { - struct draw_string_args *args = user_data; + struct draw_string_args *args = info->user_data; + int i = info->index; PointF position; - GpStatus stat; + SIZE size; + DWORD to_draw_length; + struct gdip_font_link_section *section; + GpStatus stat = Ok; - position.X = args->x + bounds->X / args->rel_width; - position.Y = args->y + bounds->Y / args->rel_height + args->ascent; + position.X = args->x + info->bounds->X / args->rel_width; + position.Y = args->y + info->bounds->Y / args->rel_height + args->ascent; - stat = draw_driver_string(args->graphics, &string[index], length, font, format, - args->brush, &position, - DriverStringOptionsCmapLookup|DriverStringOptionsRealizedAdvance, NULL); + LIST_FOR_EACH_ENTRY(section, &info->font_link_info.sections, struct gdip_font_link_section, entry) + { + if (i >= section->end) continue; + + to_draw_length = min(info->length - (i - info->index), section->end - i); + TRACE("index %d, todraw %ld, used %s\n", i, to_draw_length, section->font == info->font_link_info.base_font ? "base font" : "map"); + font_link_get_text_extent_point(info, i, to_draw_length, 0, NULL, &size); + stat = draw_driver_string(info->graphics, &info->string[i], to_draw_length, + section->font, info->format, args->brush, &position, + DriverStringOptionsCmapLookup|DriverStringOptionsRealizedAdvance, NULL); + position.X += size.cx / args->rel_width; + i += to_draw_length; + if (stat != Ok || (i - info->index) >= info->length) break; + } - if (stat == Ok && underlined_index_count) + if (stat == Ok && info->underlined_index_count) { OUTLINETEXTMETRICW otm; REAL underline_y, underline_height; int i; - GetOutlineTextMetricsW(hdc, sizeof(otm), &otm); + GetOutlineTextMetricsW(info->hdc, sizeof(otm), &otm); underline_height = otm.otmsUnderscoreSize / args->rel_height; underline_y = position.Y - otm.otmsUnderscorePosition / args->rel_height - underline_height / 2; - for (i=0; iunderlined_index_count; i++) { REAL start_x, end_x; SIZE text_size; - INT ofs = underlined_indexes[i] - index; + INT ofs = info->underlined_indexes[i] - info->index; - GetTextExtentExPointW(hdc, string + index, ofs, INT_MAX, NULL, NULL, &text_size); + font_link_get_text_extent_point(info, info->index, ofs, INT_MAX, NULL, &text_size); start_x = text_size.cx / args->rel_width; - GetTextExtentExPointW(hdc, string + index, ofs+1, INT_MAX, NULL, NULL, &text_size); + font_link_get_text_extent_point(info, info->index, ofs+1, INT_MAX, NULL, &text_size); end_x = text_size.cx / args->rel_width; - GdipFillRectangle(args->graphics, (GpBrush*)args->brush, position.X+start_x, underline_y, end_x-start_x, underline_height); + GdipFillRectangle(info->graphics, (GpBrush*)args->brush, position.X+start_x, underline_y, end_x-start_x, underline_height); } } @@ -5783,7 +5903,6 @@ GpStatus WINGDIPAPI GdipDrawString(GpGraphics *graphics, GDIPCONST WCHAR *string get_font_hfont(graphics, font, format, &gdifont, NULL, NULL); SelectObject(hdc, gdifont); - args.graphics = graphics; args.brush = brush; args.x = rect->X; @@ -5797,7 +5916,7 @@ GpStatus WINGDIPAPI GdipDrawString(GpGraphics *graphics, GDIPCONST WCHAR *string GetTextMetricsW(hdc, &textmetric); args.ascent = textmetric.tmAscent / rel_height; - gdip_format_string(hdc, string, length, font, &scaled_rect, format, TRUE, + gdip_format_string(graphics, hdc, string, length, font, &scaled_rect, format, TRUE, draw_string_callback, &args); gdi_transform_release(graphics); diff --git a/dlls/gdiplus/graphicspath.c b/dlls/gdiplus/graphicspath.c index 24c2888cfe8..38a195a1371 100644 --- a/dlls/gdiplus/graphicspath.c +++ b/dlls/gdiplus/graphicspath.c @@ -949,33 +949,48 @@ struct format_string_args float ascent; }; -static GpStatus format_string_callback(HDC dc, - GDIPCONST WCHAR *string, INT index, INT length, GDIPCONST GpFont *font, - GDIPCONST RectF *rect, GDIPCONST GpStringFormat *format, - INT lineno, const RectF *bounds, INT *underlined_indexes, - INT underlined_index_count, void *priv) +static GpStatus format_string_callback(struct gdip_format_string_info* info) { static const MAT2 identity = { {0,1}, {0,0}, {0,0}, {0,1} }; - struct format_string_args *args = priv; + struct format_string_args *args = info->user_data; + struct gdip_font_link_section *section = LIST_ENTRY(list_head(&info->font_link_info.sections), struct gdip_font_link_section, entry); + HFONT hfont = NULL, oldhfont = NULL; + int section_start = -1; GpPath *path = args->path; GpStatus status = Ok; - float x = rect->X + (bounds->X - rect->X) * args->scale; - float y = rect->Y + (bounds->Y - rect->Y) * args->scale; + float x = info->rect->X + (info->bounds->X - info->rect->X) * args->scale; + float y = info->rect->Y + (info->bounds->Y - info->rect->Y) * args->scale; int i; - if (underlined_index_count) + if (info->underlined_index_count) FIXME("hotkey underlines not drawn yet\n"); - if (y + bounds->Height * args->scale > args->maxY) - args->maxY = y + bounds->Height * args->scale; + if (y + info->bounds->Height * args->scale > args->maxY) + args->maxY = y + info->bounds->Height * args->scale; - for (i = index; i < length + index; ++i) + for (i = info->index; i < info->length + info->index; ++i) { GLYPHMETRICS gm; TTPOLYGONHEADER *ph = NULL, *origph; char *start; DWORD len, ofs = 0; - len = GetGlyphOutlineW(dc, string[i], GGO_BEZIER, &gm, 0, NULL, &identity); + + while (i >= section->end) + section = LIST_ENTRY(list_next(&info->font_link_info.sections, §ion->entry), struct gdip_font_link_section, entry); + + if (section_start != section->start) + { + if (hfont) + { + SelectObject(info->hdc, oldhfont); + DeleteObject(hfont); + } + get_font_hfont(info->graphics, section->font, NULL, &hfont, NULL, NULL); + oldhfont = SelectObject(info->hdc, hfont); + section_start = section->start; + } + + len = GetGlyphOutlineW(info->hdc, info->string[i], GGO_BEZIER, &gm, 0, NULL, &identity); if (len == GDI_ERROR) { status = GenericError; @@ -989,7 +1004,7 @@ static GpStatus format_string_callback(HDC dc, status = OutOfMemory; break; } - GetGlyphOutlineW(dc, string[i], GGO_BEZIER, &gm, len, start, &identity); + GetGlyphOutlineW(info->hdc, info->string[i], GGO_BEZIER, &gm, len, start, &identity); ofs = 0; while (ofs < len) @@ -1041,6 +1056,12 @@ static GpStatus format_string_callback(HDC dc, break; } + if (hfont) + { + SelectObject(info->hdc, oldhfont); + DeleteObject(hfont); + } + return status; } @@ -1097,8 +1118,6 @@ GpStatus WINGDIPAPI GdipAddPathString(GpPath* path, GDIPCONST WCHAR* string, INT } get_log_fontW(font, graphics, &lfw); - GdipDeleteFont(font); - GdipDeleteGraphics(graphics); hfont = CreateFontIndirectW(&lfw); if (!hfont) @@ -1106,6 +1125,7 @@ GpStatus WINGDIPAPI GdipAddPathString(GpPath* path, GDIPCONST WCHAR* string, INT WARN("Failed to create font\n"); DeleteDC(dc); GdipDeletePath(backup); + GdipDeleteFont(font); return GenericError; } @@ -1117,11 +1137,13 @@ GpStatus WINGDIPAPI GdipAddPathString(GpPath* path, GDIPCONST WCHAR* string, INT args.maxY = 0; args.scale = emSize / native_height; args.ascent = textmetric.tmAscent * args.scale; - status = gdip_format_string(dc, string, length, NULL, &scaled_layout_rect, + status = gdip_format_string(graphics, dc, string, length, font, &scaled_layout_rect, format, TRUE, format_string_callback, &args); DeleteDC(dc); DeleteObject(hfont); + GdipDeleteFont(font); + GdipDeleteGraphics(graphics); if (status != Ok) /* free backup */ { diff --git a/dlls/gdiplus/tests/font.c b/dlls/gdiplus/tests/font.c index 8f0bbce0eb4..1bbb18f4c6d 100644 --- a/dlls/gdiplus/tests/font.c +++ b/dlls/gdiplus/tests/font.c @@ -43,11 +43,21 @@ static void set_rect_empty(RectF *rc) rc->Height = 0.0; } +#define load_resource(a, b, c) _load_resource(__LINE__, a, b, c) +static void _load_resource(int line, const WCHAR *filename, BYTE **data, DWORD *size) +{ + HRSRC resource = FindResourceW(NULL, filename, (const WCHAR *)RT_RCDATA); + ok_(__FILE__, line)(!!resource, "FindResourceW failed, error %lu\n", GetLastError()); + *data = LockResource(LoadResource(GetModuleHandleW(NULL), resource)); + ok_(__FILE__, line)(!!*data, "LockResource failed, error %lu\n", GetLastError()); + *size = SizeofResource(GetModuleHandleW(NULL), resource); + ok_(__FILE__, line)(*size > 0, "SizeofResource failed, error %lu\n", GetLastError()); +} + static void create_testfontfile(const WCHAR *filename, int resource, WCHAR pathW[MAX_PATH]) { - DWORD written; + DWORD written, length; HANDLE file; - HRSRC res; void *ptr; GetTempPathW(MAX_PATH, pathW); @@ -56,11 +66,9 @@ static void create_testfontfile(const WCHAR *filename, int resource, WCHAR pathW file = CreateFileW(pathW, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0); ok(file != INVALID_HANDLE_VALUE, "file creation failed, at %s, error %ld\n", wine_dbgstr_w(pathW), GetLastError()); - res = FindResourceA(GetModuleHandleA(NULL), MAKEINTRESOURCEA(resource), (LPCSTR)RT_RCDATA); - ok(res != 0, "couldn't find resource\n"); - ptr = LockResource(LoadResource(GetModuleHandleA(NULL), res)); - WriteFile(file, ptr, SizeofResource(GetModuleHandleA(NULL), res), &written, NULL); - ok(written == SizeofResource(GetModuleHandleA(NULL), res), "couldn't write resource\n"); + load_resource(MAKEINTRESOURCEW(resource), (BYTE **)&ptr, &length); + WriteFile(file, ptr, length, &written, NULL); + ok(written == length, "couldn't write resource\n"); CloseHandle(file); } @@ -1524,6 +1532,46 @@ static void test_CloneFont(void) GdipDeleteFontFamily(family); } +static void test_GdipPrivateAddMemoryFont(void) +{ + static const WORD resource_ids[] = + { + 3, /* A font that has an invalid full name on Mac platform and a valid full name on Microsoft platform */ + 4, /* A font that has an invalid full name on Unicode platform and a valid full name on Mac platform */ + }; + GpFontCollection *fonts; + GpStatus stat; + int count, i; + void *buffer; + DWORD size; + + for (i = 0; i < ARRAY_SIZE(resource_ids); i++) + { + winetest_push_context("test %d", i); + + stat = GdipNewPrivateFontCollection(&fonts); + ok(stat == Ok, "GdipNewPrivateFontCollection failed, error %d\n", stat); + + load_resource(MAKEINTRESOURCEW(resource_ids[i]), (BYTE **)&buffer, &size); + stat = GdipPrivateAddMemoryFont(fonts, buffer, size); + if (stat == Ok) + { + stat = GdipGetFontCollectionFamilyCount(fonts, &count); + ok(stat == Ok, "GdipGetFontCollectionFamilyCount failed, error %d\n", stat); + ok(count == 1, "Expected count 1, got %d\n", count); + } + else if (i == 1 && stat == FileNotFound) + win_skip("Fonts without Microsoft platform names are unsupported on win7.\n"); + else + ok(0, "GdipPrivateAddMemoryFont failed, error %d\n", stat); + + stat = GdipDeletePrivateFontCollection(&fonts); + ok(stat == Ok, "GdipDeletePrivateFontCollection failed, error %d\n", stat); + + winetest_pop_context(); + } +} + START_TEST(font) { struct GdiplusStartupInput gdiplusStartupInput; @@ -1558,6 +1606,7 @@ START_TEST(font) test_heightgivendpi(); test_GdipGetFontCollectionFamilyList(); test_GdipGetFontCollectionFamilyCount(); + test_GdipPrivateAddMemoryFont(); GdiplusShutdown(gdiplusToken); } diff --git a/dlls/gdiplus/tests/graphics.c b/dlls/gdiplus/tests/graphics.c index d71bc78c2ae..18fe1078e76 100644 --- a/dlls/gdiplus/tests/graphics.c +++ b/dlls/gdiplus/tests/graphics.c @@ -2305,10 +2305,29 @@ static void test_GdipDrawString(void) } expect(Ok, status); - status = GdipCreateSolidFill((ARGB)0xdeadbeef, (GpSolidFill**)&brush); + status = GdipCreateStringFormat(0,0,&format); expect(Ok, status); - status = GdipCreateStringFormat(0,0,&format); + if (winetest_interactive) + { + status = GdipCreateSolidFill(0xFF000000, (GpSolidFill**)&brush); + expect(Ok, status); + rect.X = 0; + rect.Y = 0; + rect.Width = 0; + rect.Height = 14; + GdipRotateWorldTransform(graphics, 45, MatrixOrderPrepend); + GdipScaleWorldTransform(graphics, 2, 2, MatrixOrderPrepend); + GdipGraphicsClear(graphics, 0xFFFFFFFF); + status = GdipDrawString(graphics, L"\u8336Hola\u8336", 6, fnt, &rect, format, brush); + expect(Ok, status); + RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW); /* FIXME: In Windows this test works without this line. */ + Sleep(4000); + GdipDeleteBrush(brush); + GdipResetWorldTransform(graphics); + } + + status = GdipCreateSolidFill((ARGB)0xdeadbeef, (GpSolidFill**)&brush); expect(Ok, status); rect.X = 0; diff --git a/dlls/gdiplus/tests/resource.rc b/dlls/gdiplus/tests/resource.rc index 2db8d6359ea..4af61fef65d 100644 --- a/dlls/gdiplus/tests/resource.rc +++ b/dlls/gdiplus/tests/resource.rc @@ -23,3 +23,11 @@ /* @makedep: wine_testfont0.ttf */ 2 RCDATA wine_testfont0.ttf + +/* Generated with: fonttools ttx wine_mac_win.ttx */ +/* @makedep: wine_mac_win.ttf */ +3 RCDATA wine_mac_win.ttf + +/* Generated with: fonttools ttx wine_unicode_mac.ttx */ +/* @makedep: wine_unicode_mac.ttf */ +4 RCDATA wine_unicode_mac.ttf diff --git a/dlls/gdiplus/tests/wine_mac_win.ttf b/dlls/gdiplus/tests/wine_mac_win.ttf new file mode 100644 index 00000000000..0a0493134de Binary files /dev/null and b/dlls/gdiplus/tests/wine_mac_win.ttf differ diff --git a/dlls/gdiplus/tests/wine_mac_win.ttx b/dlls/gdiplus/tests/wine_mac_win.ttx new file mode 100644 index 00000000000..c85a8abd50b --- /dev/null +++ b/dlls/gdiplus/tests/wine_mac_win.ttx @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Copyright (c) 2024 Zhiyi Zhang for CodeWeavers + + + wine_mac_win + + + Regular + + + wine_mac_win + + + invalid_full_name + + + Version 1.0 + + + wine_mac_win + + + Copyright (c) 2024 Zhiyi Zhang for CodeWeavers + + + wine_mac_win + + + Regular + + + wine_mac_win + + + wine_mac_win + + + Version 1.0 + + + wine_mac_win + + + + diff --git a/dlls/gdiplus/tests/wine_unicode_mac.ttf b/dlls/gdiplus/tests/wine_unicode_mac.ttf new file mode 100644 index 00000000000..4c0af94ade4 Binary files /dev/null and b/dlls/gdiplus/tests/wine_unicode_mac.ttf differ diff --git a/dlls/gdiplus/tests/wine_unicode_mac.ttx b/dlls/gdiplus/tests/wine_unicode_mac.ttx new file mode 100644 index 00000000000..377b4b3d4ed --- /dev/null +++ b/dlls/gdiplus/tests/wine_unicode_mac.ttx @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Copyright (c) 2024 Zhiyi Zhang for CodeWeavers + + + wine_unicode_mac + + + Regular + + + wine_unicode_mac + + + invalid_full_name + + + Version 1.0 + + + wine_unicode_mac + + + Copyright (c) 2024 Zhiyi Zhang for CodeWeavers + + + wine_unicode_mac + + + Regular + + + wine_unicode_mac + + + wine_unicode_mac + + + Version 1.0 + + + wine_unicode_mac + + + + diff --git a/dlls/hidclass.sys/device.c b/dlls/hidclass.sys/device.c index 7d1b9d2c356..135e58bf587 100644 --- a/dlls/hidclass.sys/device.c +++ b/dlls/hidclass.sys/device.c @@ -390,6 +390,8 @@ struct device_strings static const struct device_strings device_strings[] = { + /* CW-Bug-Id: #23185 Emulate Steam Input native hooks for native SDL */ + { .id = L"VID_28DE&PID_11FF", .product = L"Controller (XBOX 360 For Windows)" }, /* Microsoft controllers */ { .id = L"VID_045E&PID_028E", .product = L"Controller (XBOX 360 For Windows)" }, { .id = L"VID_045E&PID_028F", .product = L"Controller (XBOX 360 For Windows)" }, diff --git a/dlls/imm32/imm.c b/dlls/imm32/imm.c index cc843ce4a8e..264276d53d6 100644 --- a/dlls/imm32/imm.c +++ b/dlls/imm32/imm.c @@ -2539,6 +2539,9 @@ BOOL WINAPI ImmSetCompositionStringA( return FALSE; if (!(ime = imc_select_ime( data ))) return FALSE; + if (!lpComp) dwCompLen = 0; + if (!lpRead) dwReadLen = 0; + if (!ime_is_unicode( ime )) return ime->pImeSetCompositionString( hIMC, dwIndex, lpComp, dwCompLen, lpRead, dwReadLen ); comp_len = MultiByteToWideChar(CP_ACP, 0, lpComp, dwCompLen, NULL, 0); @@ -2596,6 +2599,9 @@ BOOL WINAPI ImmSetCompositionStringW( return FALSE; if (!(ime = imc_select_ime( data ))) return FALSE; + if (!lpComp) dwCompLen = 0; + if (!lpRead) dwReadLen = 0; + if (ime_is_unicode( ime )) return ime->pImeSetCompositionString( hIMC, dwIndex, lpComp, dwCompLen, lpRead, dwReadLen ); comp_len = WideCharToMultiByte(CP_ACP, 0, lpComp, dwCompLen, NULL, 0, NULL, diff --git a/dlls/imm32/tests/imm32.c b/dlls/imm32/tests/imm32.c index 66ebbfd161d..c5d2b85558e 100644 --- a/dlls/imm32/tests/imm32.c +++ b/dlls/imm32/tests/imm32.c @@ -1077,12 +1077,24 @@ static void test_SCS_SETSTR(void) DWORD prop; imc = ImmGetContext(hwnd); - ret = ImmSetCompositionStringW(imc, SCS_SETSTR, string, sizeof(string), NULL,0); + ret = ImmSetCompositionStringW(imc, SCS_SETSTR, string, sizeof(string), NULL, 0); if (!ret) { win_skip("Composition isn't supported\n"); ImmReleaseContext(hwnd, imc); return; } + + ret = ImmSetCompositionStringW(imc, SCS_SETSTR, NULL, 128, NULL, 128); + ok(ret, "got error %lu.\n", GetLastError()); + + alen = ImmGetCompositionStringA(imc, GCS_COMPSTR, cstring, 20); + ok(!alen, "got %ld.\n", alen); + wlen = ImmGetCompositionStringW(imc, GCS_COMPSTR, wstring, 20); + ok(!wlen, "got %ld.\n", alen); + + ret = ImmSetCompositionStringW(imc, SCS_SETSTR, string, sizeof(string), NULL, 2); + ok(ret, "got error %lu.\n", GetLastError()); + msg_spy_flush_msgs(); alen = ImmGetCompositionStringA(imc, GCS_COMPSTR, cstring, 20); diff --git a/dlls/iphlpapi/iphlpapi_main.c b/dlls/iphlpapi/iphlpapi_main.c index 9c7582b71fb..207c2ae20d0 100644 --- a/dlls/iphlpapi/iphlpapi_main.c +++ b/dlls/iphlpapi/iphlpapi_main.c @@ -993,6 +993,7 @@ static DWORD gateway_and_prefix_addresses_alloc( IP_ADAPTER_ADDRESSES *aa, ULONG { struct nsi_ipv4_forward_key *key4; struct nsi_ipv6_forward_key *key6; + struct nsi_ip_forward_rw *rw; IP_ADAPTER_GATEWAY_ADDRESS *gw, **gw_next; IP_ADAPTER_PREFIX *prefix, **prefix_next; DWORD err, count, i, prefix_len, key_size = (family == AF_INET) ? sizeof(*key4) : sizeof(*key6); @@ -1002,11 +1003,14 @@ static DWORD gateway_and_prefix_addresses_alloc( IP_ADAPTER_ADDRESSES *aa, ULONG void *key; err = NsiAllocateAndGetTable( 1, ip_module_id( family ), NSI_IP_FORWARD_TABLE, &key, key_size, - NULL, 0, NULL, 0, NULL, 0, &count, 0 ); + (void **)&rw, sizeof(*rw), NULL, 0, NULL, 0, &count, 0 ); if (err) return err; while (aa) { + if (family == AF_INET) aa->Ipv4Metric = ~0u; + else aa->Ipv6Metric = ~0u; + for (gw_next = &aa->FirstGatewayAddress; *gw_next; gw_next = &(*gw_next)->Next) ; for (prefix_next = &aa->FirstPrefix; *prefix_next; prefix_next = &(*prefix_next)->Next) @@ -1019,6 +1023,12 @@ static DWORD gateway_and_prefix_addresses_alloc( IP_ADAPTER_ADDRESSES *aa, ULONG luid = (family == AF_INET) ? &key4->luid : &key6->luid; if (luid->Value != aa->Luid.Value) continue; + if (rw[i].metric) + { + if (family == AF_INET) aa->Ipv4Metric = min( aa->Ipv4Metric, rw[i].metric ); + else aa->Ipv6Metric = min( aa->Ipv6Metric, rw[i].metric ); + } + if (flags & GAA_FLAG_INCLUDE_GATEWAYS) { memset( &sockaddr, 0, sizeof(sockaddr) ); @@ -1103,7 +1113,7 @@ static DWORD gateway_and_prefix_addresses_alloc( IP_ADAPTER_ADDRESSES *aa, ULONG } err: - NsiFreeTable( key, NULL, NULL, NULL ); + NsiFreeTable( key, rw, NULL, NULL ); return err; } @@ -1269,11 +1279,8 @@ static DWORD adapters_addresses_alloc( ULONG family, ULONG flags, IP_ADAPTER_ADD if (err) goto err; } - if (flags & (GAA_FLAG_INCLUDE_GATEWAYS | GAA_FLAG_INCLUDE_PREFIX)) - { - err = call_families( gateway_and_prefix_addresses_alloc, aa, family, flags ); - if (err) goto err; - } + err = call_families( gateway_and_prefix_addresses_alloc, aa, family, flags ); + if (err) goto err; err = dns_info_alloc( aa, family, flags ); if (err) goto err; diff --git a/dlls/ir50_32/ir50.c b/dlls/ir50_32/ir50.c index 65c93f7fe5e..69700359e99 100644 --- a/dlls/ir50_32/ir50.c +++ b/dlls/ir50_32/ir50.c @@ -252,7 +252,7 @@ static LRESULT IV50_Decompress( IMFTransform *decoder, ICDECOMPRESS *icd, DWORD mft_buf.pSample = out_sample; hr = IMFTransform_ProcessOutput( decoder, 0, 1, &mft_buf, &mft_status ); - if ( SUCCEEDED(hr) && (mft_status & MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE) ) + if ( hr == MF_E_TRANSFORM_STREAM_CHANGE ) hr = IMFTransform_ProcessOutput( decoder, 0, 1, &mft_buf, &mft_status ); if ( SUCCEEDED(hr) ) diff --git a/dlls/kernel32/tests/virtual.c b/dlls/kernel32/tests/virtual.c index 445f519f409..bc573644d97 100644 --- a/dlls/kernel32/tests/virtual.c +++ b/dlls/kernel32/tests/virtual.c @@ -1631,7 +1631,7 @@ static void test_write_watch(void) MEMORY_BASIC_INFORMATION info; HANDLE readpipe, writepipe, file; OVERLAPPED overlapped, *overlapped2; - void *results[64]; + void *results[2048]; ULONG_PTR count; ULONG i, pagesize; BOOL success; @@ -1671,6 +1671,7 @@ static void test_write_watch(void) SetLastError( 0xdeadbeef ); ret = pGetWriteWatch( 0, GetModuleHandleW(NULL), size, results, &count, &pagesize ); + ok(ret, "failed.\n"); if (ret) { ok( ret == ~0u, "GetWriteWatch succeeded %lu\n", ret ); @@ -1694,6 +1695,7 @@ static void test_write_watch(void) ok( results[0] == base + pagesize, "wrong result %p\n", results[0] ); count = 64; + results[0] = (void *)0xdeadbeef; ret = pGetWriteWatch( WRITE_WATCH_FLAG_RESET, base, size, results, &count, &pagesize ); ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); ok( count == 1, "wrong count %Iu\n", count ); @@ -2103,6 +2105,16 @@ static void test_write_watch(void) base = VirtualAlloc( 0, size, MEM_RESERVE | MEM_WRITE_WATCH, PAGE_NOACCESS ); ok( base != NULL, "VirtualAlloc failed %lu\n", GetLastError() ); + + count = 64; + ret = pGetWriteWatch( 0, base, size, results, &count, &pagesize ); + ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); + ok( count == 0, "wrong count %Iu\n", count ); + + ret = pResetWriteWatch( base, size ); + ok( !ret, "pResetWriteWatch failed %lu\n", GetLastError() ); + + base = VirtualAlloc( base, size, MEM_COMMIT, PAGE_NOACCESS ); ok( base != NULL, "VirtualAlloc failed %lu\n", GetLastError() ); @@ -2122,17 +2134,50 @@ static void test_write_watch(void) ok( old_prot == PAGE_READWRITE, "wrong old prot %lx\n", old_prot ); count = 64; - ret = pGetWriteWatch( 0, base, size, results, &count, &pagesize ); + ret = pGetWriteWatch( /*0*/WRITE_WATCH_FLAG_RESET, base, size, results, &count, &pagesize ); ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); ok( count == 1, "wrong count %Iu\n", count ); ok( results[0] == base + 5*pagesize, "wrong result %p\n", results[0] ); ret = pResetWriteWatch( base, size ); - ok( !ret, "pResetWriteWatch failed %u\n", GetLastError() ); + ret = pResetWriteWatch( base + 6*pagesize, size - 6 * pagesize ); + ok( !ret, "pResetWriteWatch failed %lu\n", GetLastError() ); + + count = 64; + results[0] = (void *)0xdeadbeef; + ret = pGetWriteWatch( /*0*/WRITE_WATCH_FLAG_RESET, base, size, results, &count, &pagesize ); + ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); + ok( count == 0, "wrong count %Iu\n", count ); + ok( results[0] == (void *)0xdeadbeef, "wrong result %p\n", results[0] ); + + ret = VirtualFree( base + pagesize, pagesize, MEM_DECOMMIT ); + ok( ret, "VirtualFree failed %lu\n", GetLastError() ); + + ret = VirtualProtect( base + 2*pagesize, pagesize, PAGE_READWRITE, &old_prot ); + ok( ret, "VirtualProtect failed error %lu\n", GetLastError() ); + ok( old_prot == PAGE_NOACCESS, "wrong old prot %lx\n", old_prot ); + + count = 64; + results[0] = (void *)0xdeadbeef; + ret = pGetWriteWatch( WRITE_WATCH_FLAG_RESET, base, size, results, &count, &pagesize ); + ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); + ok( count == 0, "wrong count %Iu\n", count ); + ok( results[0] == (void *)0xdeadbeef, "wrong result %p\n", results[0] ); + + base[2*pagesize + 200] = 3; + count = 64; + results[0] = (void *)0xdeadbeef; + ret = pGetWriteWatch( WRITE_WATCH_FLAG_RESET, base, size, results, &count, &pagesize ); + ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); + ok( count == 1, "wrong count %Iu\n", count ); + ok( results[0] == base + 2*pagesize, "wrong result %p\n", results[0] ); + + base = VirtualAlloc( base, size, MEM_COMMIT, PAGE_NOACCESS ); + ok( !!base, "VirtualFree failed %lu\n", GetLastError() ); ret = VirtualProtect( base, 6*pagesize, PAGE_READWRITE, &old_prot ); - ok( ret, "VirtualProtect failed error %u\n", GetLastError() ); - ok( old_prot == PAGE_NOACCESS, "wrong old prot %x\n", old_prot ); + ok( ret, "VirtualProtect failed error %lu\n", GetLastError() ); + ok( old_prot == PAGE_NOACCESS, "wrong old prot %lx\n", old_prot ); base[3*pagesize + 200] = 3; base[5*pagesize + 200] = 3; @@ -2142,36 +2187,39 @@ static void test_write_watch(void) count = 64; ret = pGetWriteWatch( 0, base, size, results, &count, &pagesize ); - ok( !ret, "GetWriteWatch failed %u\n", GetLastError() ); - ok( !count, "wrong count %lu\n", count ); + ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); + ok( !count, "wrong count %Iu\n", count ); base = VirtualAlloc( base, size, MEM_COMMIT, PAGE_READWRITE ); ok(!!base, "VirtualAlloc failed.\n"); count = 64; ret = pGetWriteWatch( 0, base, size, results, &count, &pagesize ); - ok( !ret, "GetWriteWatch failed %u\n", GetLastError() ); - ok( !count, "wrong count %lu\n", count ); + ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); + ok( !count, "wrong count %Iu\n", count ); + /* Looks like VirtualProtect latches write watch state somewhere, so if pages are decommitted after, + * (which normally clears write watch state), a page from range which previously had protection change + * is still reported as dirty. */ base[3*pagesize + 200] = 3; ret = VirtualProtect( base, 6*pagesize, PAGE_READWRITE, &old_prot ); - ok( ret, "VirtualProtect failed error %u\n", GetLastError() ); - ok( old_prot == PAGE_READWRITE, "wrong old prot %x\n", old_prot ); + ok( ret, "VirtualProtect failed error %lu\n", GetLastError() ); + ok( old_prot == PAGE_READWRITE, "wrong old prot %lx\n", old_prot ); base[5*pagesize + 200] = 3; count = 64; ret = pGetWriteWatch( 0, base, size, results, &count, &pagesize ); - ok( !ret, "GetWriteWatch failed %u\n", GetLastError() ); - ok( count == 2, "wrong count %lu\n", count ); + ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); + ok( count == 2, "wrong c ount %Iu\n", count ); ok( results[0] == base + 3*pagesize && results[1] == base + 5*pagesize, "wrong result %p\n", results[0] ); ret = VirtualFree( base, size, MEM_DECOMMIT ); - ok( ret, "VirtualFree failed %u\n", GetLastError() ); + ok( ret, "VirtualFree failed %lu\n", GetLastError() ); count = 64; ret = pGetWriteWatch( 0, base, size, results, &count, &pagesize ); - ok( !ret, "GetWriteWatch failed %u\n", GetLastError() ); - todo_wine ok( count == 1, "wrong count %lu\n", count ); + ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); + todo_wine ok( count == 1, "wrong count %Iu\n", count ); ok( results[0] == base + 3*pagesize, "wrong result %p\n", results[0] ); base = VirtualAlloc( base, size, MEM_COMMIT, PAGE_READWRITE ); @@ -2179,10 +2227,68 @@ static void test_write_watch(void) count = 64; ret = pGetWriteWatch( 0, base, size, results, &count, &pagesize ); - ok( !ret, "GetWriteWatch failed %u\n", GetLastError() ); - todo_wine ok( count == 1, "wrong count %lu\n", count ); + ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); + todo_wine ok( count == 1, "wrong count %Iu\n", count ); ok( results[0] == base + 3*pagesize, "wrong result %p\n", results[0] ); + base[4*pagesize + 200] = 4; + base[2*pagesize + 200] = 4; + base[6*pagesize + 200] = 4; + + count = 64; + ret = pGetWriteWatch( 0, base, size, results, &count, &pagesize ); + ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); + todo_wine ok( count == 4, "wrong count %Iu\n", count ); + ok( results[0] == base + 2*pagesize, "wrong result %p\n", results[0] ); + todo_wine ok( results[1] == base + 3*pagesize, "wrong result %p\n", results[0] ); + todo_wine ok( results[2] == base + 4*pagesize, "wrong result %p\n", results[0] ); + todo_wine ok( results[3] == base + 6*pagesize, "wrong result %p\n", results[0] ); + + VirtualFree( base, 0, MEM_RELEASE ); + + /* Test longer range */ + size = 2048 * pagesize; + base = VirtualAlloc( 0, size, MEM_RESERVE | MEM_COMMIT | MEM_WRITE_WATCH, PAGE_READWRITE ); + ok( base != NULL, "VirtualAlloc failed %lu\n", GetLastError() ); + + count = 2048; + ret = pGetWriteWatch( 0, base, size, results, &count, &pagesize ); + ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); + ok( count == 0, "wrong count %Iu\n", count ); + + count = 2048; + for (i = 0; i < count; i += 2) + ++base[i * pagesize]; + + ret = VirtualProtect( base, size / 2, PAGE_READONLY, &old_prot ); + ok( ret, "VirtualProtect failed error %lu\n", GetLastError() ); + + ret = pGetWriteWatch( 0, base, size, results, &count, &pagesize ); + ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); + ok( count == 1024, "wrong count %Iu\n", count ); + + for (i = 0; i < count; ++i) + { + ok( results[i] == base + i * 2 * pagesize, "wrong result %p\n", results[i] ); + if (results[i] != base + i * 2 * pagesize) + break; + } + + ret = pGetWriteWatch( WRITE_WATCH_FLAG_RESET, base, size, results, &count, &pagesize ); + ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); + ok( count == 1024, "wrong count %Iu\n", count ); + + for (i = 0; i < count; ++i) + { + ok( results[i] == base + i * 2 * pagesize, "wrong result %p\n", results[i] ); + if (results[i] != base + i * 2 * pagesize) + break; + } + + ret = pGetWriteWatch( 0, base, size, results, &count, &pagesize ); + ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); + ok( count == 0, "wrong count %Iu\n", count ); + VirtualFree( base, 0, MEM_RELEASE ); } diff --git a/dlls/kernelbase/process.c b/dlls/kernelbase/process.c index 7ea75b58a9b..4267a8c57e6 100644 --- a/dlls/kernelbase/process.c +++ b/dlls/kernelbase/process.c @@ -594,9 +594,15 @@ static const WCHAR *hack_append_command_line( const WCHAR *cmd ) } options[] = { + {L"Bloody Walls\\game.exe", L" --disable_direct_composition=1"}, + {L"Insanitys Blade\\nw.exe", L" --use-gl=swiftshader"}, + {L"Warhammer2.exe", L" --in-process-gpu"}, + {L"SummerIslands.exe", L" --in-process-gpu"}, {L"UplayWebCore.exe", L" --use-angle=vulkan"}, {L"Paradox Launcher.exe", L" --use-angle=gl"}, {L"Montaro\\nw.exe", L" --use-gl=swiftshader"}, + {L"Aisling and the Tavern of Elves\\nw.exe", L" --use-gl=swiftshader"}, + {L"Snares of Ruin 2\\SoR2.exe", L" --use-gl=swiftshader"}, {L"\\EOSOverlayRenderer-Win64-Shipping.exe", L" --use-gl=swiftshader --in-process-gpu"}, {L"\\EpicOnlineServicesUIHelper", L" --use-angle=vulkan"}, {L"OlympiaRising.exe", L" --use-gl=swiftshader"}, diff --git a/dlls/kernelbase/registry.c b/dlls/kernelbase/registry.c index a767d993254..ee57a4acdd1 100644 --- a/dlls/kernelbase/registry.c +++ b/dlls/kernelbase/registry.c @@ -165,7 +165,7 @@ static HANDLE open_wow6432node( HANDLE key ) attr.Attributes = 0; attr.SecurityDescriptor = NULL; attr.SecurityQualityOfService = NULL; - if (NtOpenKeyEx( &ret, MAXIMUM_ALLOWED, &attr, 0 )) return key; + if (NtOpenKeyEx( &ret, MAXIMUM_ALLOWED | KEY_WOW64_64KEY, &attr, 0 )) return key; return ret; } @@ -211,7 +211,7 @@ static NTSTATUS open_key( HKEY *retkey, HKEY root, UNICODE_STRING *name, DWORD o static NTSTATUS open_subkey( HKEY *subkey, HKEY root, UNICODE_STRING *name, DWORD options, ACCESS_MASK access ) { BOOL is_wow64_key = (is_win64 && (access & KEY_WOW64_32KEY)) || (is_wow64 && !(access & KEY_WOW64_64KEY)); - ACCESS_MASK access_64 = access & ~KEY_WOW64_32KEY; + ACCESS_MASK access_64 = (access & ~KEY_WOW64_32KEY) | KEY_WOW64_64KEY; DWORD i = 0, len = name->Length / sizeof(WCHAR); WCHAR *buffer = name->Buffer; UNICODE_STRING str; @@ -592,6 +592,7 @@ LSTATUS WINAPI DECLSPEC_HOTPATCH RegCreateKeyExW( HKEY hkey, LPCWSTR name, DWORD { UNICODE_STRING nameW, classW; + if (!retkey) return ERROR_BADKEY; if (reserved) return ERROR_INVALID_PARAMETER; if (!(hkey = get_special_root_hkey( hkey ))) return ERROR_INVALID_HANDLE; @@ -633,6 +634,7 @@ LSTATUS WINAPI DECLSPEC_HOTPATCH RegCreateKeyExA( HKEY hkey, LPCSTR name, DWORD ANSI_STRING nameA, classA; NTSTATUS status; + if (!retkey) return ERROR_BADKEY; if (reserved) return ERROR_INVALID_PARAMETER; if (!is_version_nt()) { diff --git a/dlls/mf/session.c b/dlls/mf/session.c index a3cf57ab047..ef707dea4de 100644 --- a/dlls/mf/session.c +++ b/dlls/mf/session.c @@ -2864,10 +2864,10 @@ static struct topo_node *session_get_node_object(struct media_session *session, LIST_FOR_EACH_ENTRY(node, &session->presentation.nodes, struct topo_node, entry) { if (node->type == node_type && object == node->object.object) - break; + return node; } - return node; + return NULL; } static BOOL session_set_node_object_state(struct media_session *session, IUnknown *object, diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index 3dd0d9b076d..0a34329bd75 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -2983,8 +2983,6 @@ static IMFSampleGrabberSinkCallback *create_test_grabber_callback(void) enum loader_test_flags { - LOADER_EXPECTED_DECODER = 0x1, - LOADER_EXPECTED_CONVERTER = 0x2, LOADER_TODO = 0x4, LOADER_NEEDS_VIDEO_PROCESSOR = 0x8, LOADER_SET_ENUMERATE_SOURCE_TYPES = 0x10, @@ -3066,6 +3064,18 @@ static void test_topology_loader(void) ATTR_UINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, 1), ATTR_UINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 8), }; + static const media_type_desc audio_float_44100_stereo = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio), + ATTR_GUID(MF_MT_SUBTYPE, MFAudioFormat_Float), + ATTR_UINT32(MF_MT_AUDIO_NUM_CHANNELS, 2), + ATTR_UINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, 2 * 4), + ATTR_UINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, 44100), + ATTR_UINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, 2 * 4 * 44100), + ATTR_UINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 4 * 8), + ATTR_UINT32(MF_MT_AUDIO_CHANNEL_MASK, 3), + ATTR_UINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1), + }; static const media_type_desc video_i420_1280 = { ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), @@ -3089,6 +3099,25 @@ static void test_topology_loader(void) ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32), }; + static const media_type_desc video_h264_1280 = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_H264), + ATTR_RATIO(MF_MT_FRAME_SIZE, 1280, 720), + }; + static const media_type_desc video_nv12_1280 = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_NV12), + 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), @@ -3099,10 +3128,13 @@ static void test_topology_loader(void) const media_type_desc *input_type; const media_type_desc *output_type; const media_type_desc *current_input; + const media_type_desc *decoded_type; MF_CONNECT_METHOD source_method; MF_CONNECT_METHOD sink_method; HRESULT expected_result; unsigned int flags; + GUID decoder_class; + GUID converter_class; } loader_tests[] = { @@ -3167,27 +3199,24 @@ static void test_topology_loader(void) { /* PCM -> PCM, different enumerated bps, no current type, sink allow converter */ .input_type = &audio_pcm_44100, .output_type = &audio_pcm_48000, .sink_method = MF_CONNECT_ALLOW_CONVERTER, .source_method = MF_CONNECT_DIRECT, - .expected_result = S_OK, - .flags = LOADER_EXPECTED_CONVERTER, + .expected_result = S_OK, .converter_class = CLSID_CResamplerMediaObject, }, { /* PCM -> PCM, different enumerated bps, same current type, sink allow converter, force enumerate */ .input_type = &audio_pcm_44100, .output_type = &audio_pcm_48000, .sink_method = MF_CONNECT_ALLOW_CONVERTER, .source_method = -1, .current_input = &audio_pcm_48000, - .expected_result = S_OK, - .flags = LOADER_EXPECTED_CONVERTER | LOADER_SET_ENUMERATE_SOURCE_TYPES, + .expected_result = S_OK, .converter_class = CLSID_CResamplerMediaObject, + .flags = LOADER_SET_ENUMERATE_SOURCE_TYPES, }, { /* PCM -> PCM, different enumerated bps, no current type, sink allow decoder */ .input_type = &audio_pcm_44100, .output_type = &audio_pcm_48000, .sink_method = MF_CONNECT_ALLOW_DECODER, .source_method = MF_CONNECT_DIRECT, - .expected_result = S_OK, - .flags = LOADER_EXPECTED_CONVERTER, + .expected_result = S_OK, .converter_class = CLSID_CResamplerMediaObject, }, { /* PCM -> PCM, different enumerated bps, no current type, default methods */ .input_type = &audio_pcm_44100, .output_type = &audio_pcm_48000, .sink_method = -1, .source_method = -1, - .expected_result = S_OK, - .flags = LOADER_EXPECTED_CONVERTER, + .expected_result = S_OK, .converter_class = CLSID_CResamplerMediaObject, }, { /* PCM -> PCM, different enumerated bps, no current type, source allow converter */ @@ -3198,14 +3227,14 @@ static void test_topology_loader(void) { /* Float -> PCM, refuse input type, add converter */ .input_type = &audio_float_44100, .output_type = &audio_pcm_48000, .sink_method = MF_CONNECT_DIRECT, .source_method = -1, - .expected_result = MF_E_NO_MORE_TYPES, - .flags = LOADER_SET_INVALID_INPUT | LOADER_ADD_RESAMPLER_MFT | LOADER_EXPECTED_CONVERTER, + .expected_result = MF_E_NO_MORE_TYPES, .converter_class = CLSID_CResamplerMediaObject, + .flags = LOADER_SET_INVALID_INPUT | LOADER_ADD_RESAMPLER_MFT, }, { /* Float -> PCM, refuse input type, add converter, allow resampler output type */ .input_type = &audio_float_44100, .output_type = &audio_pcm_48000_resampler, .sink_method = MF_CONNECT_DIRECT, .source_method = -1, - .expected_result = S_OK, - .flags = LOADER_SET_INVALID_INPUT | LOADER_ADD_RESAMPLER_MFT | LOADER_EXPECTED_CONVERTER, + .expected_result = S_OK, .converter_class = CLSID_CResamplerMediaObject, + .flags = LOADER_SET_INVALID_INPUT | LOADER_ADD_RESAMPLER_MFT, }, { @@ -3232,34 +3261,38 @@ static void test_topology_loader(void) /* MP3 -> PCM */ .input_type = &audio_mp3_44100, .output_type = &audio_pcm_44100, .sink_method = MF_CONNECT_ALLOW_DECODER, .source_method = -1, .current_input = &audio_mp3_44100, - .expected_result = S_OK, - .flags = LOADER_EXPECTED_DECODER | LOADER_TODO, + .expected_result = S_OK, .decoder_class = CLSID_CMP3DecMediaObject, + .flags = LOADER_TODO, }, { /* MP3 -> PCM, need both decoder and converter */ .input_type = &audio_mp3_44100, .output_type = &audio_float_48000, .sink_method = MF_CONNECT_ALLOW_DECODER, .source_method = -1, - .current_input = &audio_mp3_44100, - .expected_result = S_OK, - .flags = LOADER_EXPECTED_DECODER | LOADER_EXPECTED_CONVERTER | LOADER_TODO, + .current_input = &audio_mp3_44100, .decoded_type = &audio_float_44100_stereo, + .expected_result = S_OK, .decoder_class = CLSID_CMP3DecMediaObject, .converter_class = CLSID_CResamplerMediaObject, + .flags = LOADER_TODO, }, { /* I420 -> RGB32, Color Convert media type */ .input_type = &video_i420_1280, .output_type = &video_color_convert_1280_rgb32, .sink_method = -1, .source_method = -1, - .expected_result = MF_E_TOPO_CODEC_NOT_FOUND, - .flags = LOADER_NEEDS_VIDEO_PROCESSOR | LOADER_EXPECTED_CONVERTER, + .expected_result = MF_E_TOPO_CODEC_NOT_FOUND, .converter_class = CLSID_CColorConvertDMO, + .flags = LOADER_NEEDS_VIDEO_PROCESSOR, }, { /* I420 -> RGB32, Video Processor media type */ .input_type = &video_i420_1280, .output_type = &video_video_processor_1280_rgb32, .sink_method = -1, .source_method = -1, - .expected_result = S_OK, - .flags = LOADER_EXPECTED_CONVERTER, + .expected_result = S_OK, .converter_class = CLSID_CColorConvertDMO, }, { /* I420 -> RGB32, Video Processor media type without frame size */ .input_type = &video_i420_1280, .output_type = &video_video_processor_rgb32, .sink_method = -1, .source_method = -1, - .expected_result = S_OK, - .flags = LOADER_EXPECTED_CONVERTER, + .expected_result = S_OK, .converter_class = CLSID_CColorConvertDMO, + }, + { + /* H264 -> RGB32, Video Processor media type */ + .input_type = &video_h264_1280, .output_type = &video_video_processor_1280_rgb32, .sink_method = -1, .source_method = -1, + .decoded_type = &video_nv12_1280, + .expected_result = S_OK, .decoder_class = CLSID_CMSH264DecoderMFT, .converter_class = CLSID_CColorConvertDMO, }, { /* RGB32 -> Any Video, no current output type */ @@ -3270,14 +3303,14 @@ static void test_topology_loader(void) { /* RGB32 -> Any Video, no current output type, refuse input type */ .input_type = &video_i420_1280, .output_type = &video_dummy, .sink_method = -1, .source_method = -1, - .expected_result = S_OK, - .flags = LOADER_NO_CURRENT_OUTPUT | LOADER_SET_INVALID_INPUT | LOADER_EXPECTED_CONVERTER, + .expected_result = S_OK, .converter_class = CLSID_CColorConvertDMO, + .flags = LOADER_NO_CURRENT_OUTPUT | LOADER_SET_INVALID_INPUT, }, { /* RGB32 -> Any Video, no current output type, refuse input type */ .input_type = &video_i420_1280, .output_type = &video_video_processor_rgb32, .sink_method = -1, .source_method = -1, - .expected_result = S_OK, - .flags = LOADER_NO_CURRENT_OUTPUT | LOADER_SET_INVALID_INPUT | LOADER_SET_MEDIA_TYPES | LOADER_EXPECTED_CONVERTER, + .expected_result = S_OK, .converter_class = CLSID_CColorConvertDMO, + .flags = LOADER_NO_CURRENT_OUTPUT | LOADER_SET_INVALID_INPUT | LOADER_SET_MEDIA_TYPES, }, }; @@ -3536,14 +3569,14 @@ todo_wine { ok(value == MF_TOPOLOGY_RESOLUTION_SUCCEEDED, "Unexpected value %#x.\n", value); } count = 2; - if (test->flags & LOADER_EXPECTED_DECODER) + if (!IsEqualGUID(&test->decoder_class, &GUID_NULL)) count++; - if (test->flags & LOADER_EXPECTED_CONVERTER) + if (!IsEqualGUID(&test->converter_class, &GUID_NULL)) count++; hr = IMFTopology_GetNodeCount(full_topology, &node_count); ok(hr == S_OK, "Failed to get node count, hr %#lx.\n", hr); - todo_wine_if(test->flags & LOADER_EXPECTED_DECODER) + todo_wine_if(IsEqualGUID(&test->decoder_class, &CLSID_CMP3DecMediaObject)) ok(node_count == count, "Unexpected node count %u.\n", node_count); hr = IMFTopologyNode_GetTopoNodeID(src_node, &node_id); @@ -3558,29 +3591,31 @@ todo_wine { hr = IMFTopology_GetNodeByID(full_topology, node_id, &sink_node2); ok(hr == S_OK, "Failed to get sink in resolved topology, hr %#lx.\n", hr); - if (test->flags & (LOADER_EXPECTED_DECODER | LOADER_EXPECTED_CONVERTER)) + if (!IsEqualGUID(&test->decoder_class, &GUID_NULL)) { + GUID class_id; + hr = IMFTopologyNode_GetOutput(src_node2, 0, &mft_node, &index); - ok(hr == S_OK, "Failed to get transform node in resolved topology, hr %#lx.\n", hr); + ok(hr == S_OK, "Failed to get decoder in resolved topology, hr %#lx.\n", hr); ok(!index, "Unexpected stream index %lu.\n", index); hr = IMFTopologyNode_GetNodeType(mft_node, &node_type); ok(hr == S_OK, "Failed to get transform node type in resolved topology, hr %#lx.\n", hr); ok(node_type == MF_TOPOLOGY_TRANSFORM_NODE, "Unexpected node type %u.\n", node_type); - hr = IMFTopologyNode_GetObject(mft_node, &node_object); - ok(hr == S_OK, "Failed to get object of transform node, hr %#lx.\n", hr); - - if (test->flags & LOADER_EXPECTED_DECODER) - { - value = 0; - hr = IMFTopologyNode_GetUINT32(mft_node, &MF_TOPONODE_DECODER, &value); - ok(hr == S_OK, "Failed to get attribute, hr %#lx.\n", hr); - ok(value == 1, "Unexpected value.\n"); - } + value = 0; + hr = IMFTopologyNode_GetUINT32(mft_node, &MF_TOPONODE_DECODER, &value); + ok(hr == S_OK, "Failed to get attribute, hr %#lx.\n", hr); + ok(value == 1, "Unexpected value.\n"); - hr = IMFTopologyNode_GetItem(mft_node, &MF_TOPONODE_TRANSFORM_OBJECTID, NULL); + class_id = GUID_NULL; + hr = IMFTopologyNode_GetGUID(mft_node, &MF_TOPONODE_TRANSFORM_OBJECTID, &class_id); ok(hr == S_OK, "Failed to get attribute, hr %#lx.\n", hr); + ok(IsEqualGUID(&class_id, &test->decoder_class), "got MF_TOPONODE_TRANSFORM_OBJECTID %s.\n", debugstr_guid(&class_id)); + + hr = IMFTopologyNode_GetObject(mft_node, &node_object); + ok(hr == S_OK, "Failed to get object of transform node, hr %#lx.\n", hr); + IMFTopologyNode_Release(mft_node); hr = IUnknown_QueryInterface(node_object, &IID_IMFTransform, (void **)&transform); ok(hr == S_OK, "Failed to get IMFTransform from transform node's object, hr %#lx.\n", hr); @@ -3588,51 +3623,75 @@ todo_wine { hr = IMFTransform_GetInputCurrentType(transform, 0, &media_type); ok(hr == S_OK, "Failed to get transform input type, hr %#lx.\n", hr); - hr = IMFMediaType_Compare(input_type, (IMFAttributes *)media_type, MF_ATTRIBUTES_MATCH_OUR_ITEMS, &ret); ok(hr == S_OK, "Failed to compare media types, hr %#lx.\n", hr); ok(ret, "Input type of first transform doesn't match source node type.\n"); + IMFMediaType_Release(media_type); - IMFTopologyNode_Release(mft_node); + hr = IMFTransform_GetOutputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Failed to get transform input type, hr %#lx.\n", hr); + if (IsEqualGUID(&test->converter_class, &GUID_NULL)) + { + hr = IMFMediaType_Compare(output_type, (IMFAttributes *)media_type, MF_ATTRIBUTES_MATCH_OUR_ITEMS, &ret); + ok(hr == S_OK, "Failed to compare media types, hr %#lx.\n", hr); + ok(ret, "Output type of first transform doesn't match sink node type.\n"); + } + else if (test->decoded_type) + { + check_media_type(media_type, *test->decoded_type, -1); + } IMFMediaType_Release(media_type); + IMFTransform_Release(transform); + } + + if (!IsEqualGUID(&test->converter_class, &GUID_NULL)) + { + GUID class_id; hr = IMFTopologyNode_GetInput(sink_node2, 0, &mft_node, &index); - ok(hr == S_OK, "Failed to get transform node in resolved topology, hr %#lx.\n", hr); + ok(hr == S_OK, "Failed to get decoder in resolved topology, hr %#lx.\n", hr); ok(!index, "Unexpected stream index %lu.\n", index); hr = IMFTopologyNode_GetNodeType(mft_node, &node_type); ok(hr == S_OK, "Failed to get transform node type in resolved topology, hr %#lx.\n", hr); ok(node_type == MF_TOPOLOGY_TRANSFORM_NODE, "Unexpected node type %u.\n", node_type); - hr = IMFTopologyNode_GetItem(mft_node, &MF_TOPONODE_TRANSFORM_OBJECTID, NULL); + class_id = GUID_NULL; + hr = IMFTopologyNode_GetGUID(mft_node, &MF_TOPONODE_TRANSFORM_OBJECTID, &class_id); ok(hr == S_OK, "Failed to get attribute, hr %#lx.\n", hr); + todo_wine_if(IsEqualGUID(&test->converter_class, &CLSID_CColorConvertDMO)) + ok(IsEqualGUID(&class_id, &test->converter_class), "got MF_TOPONODE_TRANSFORM_OBJECTID %s.\n", debugstr_guid(&class_id)); hr = IMFTopologyNode_GetObject(mft_node, &node_object); ok(hr == S_OK, "Failed to get object of transform node, hr %#lx.\n", hr); + IMFTopologyNode_Release(mft_node); - hr = IUnknown_QueryInterface(node_object, &IID_IMFTransform, (void**) &transform); + hr = IUnknown_QueryInterface(node_object, &IID_IMFTransform, (void **)&transform); ok(hr == S_OK, "Failed to get IMFTransform from transform node's object, hr %#lx.\n", hr); IUnknown_Release(node_object); - hr = IMFTransform_GetOutputCurrentType(transform, 0, &media_type); - ok(hr == S_OK, "Failed to get transform output type, hr %#lx.\n", hr); - hr = IMFMediaType_Compare(output_type, (IMFAttributes *)media_type, MF_ATTRIBUTES_MATCH_OUR_ITEMS, &ret); - ok(hr == S_OK, "Failed to compare media types, hr %#lx.\n", hr); - ok(ret, "Output type of last transform doesn't match sink node type.\n"); - IMFMediaType_Release(media_type); - hr = IMFTransform_GetInputCurrentType(transform, 0, &media_type); ok(hr == S_OK, "Failed to get transform input type, hr %#lx.\n", hr); - if ((test->flags & (LOADER_EXPECTED_CONVERTER | LOADER_EXPECTED_DECODER)) != (LOADER_EXPECTED_CONVERTER | LOADER_EXPECTED_DECODER)) + if (IsEqualGUID(&test->decoder_class, &GUID_NULL)) { hr = IMFMediaType_Compare(input_type, (IMFAttributes *)media_type, MF_ATTRIBUTES_MATCH_OUR_ITEMS, &ret); ok(hr == S_OK, "Failed to compare media types, hr %#lx.\n", hr); - ok(ret, "Input type of transform doesn't match source node type.\n"); + ok(ret, "Input type of last transform doesn't match source node type.\n"); + } + else if (test->decoded_type) + { + check_media_type(media_type, *test->decoded_type, -1); } IMFMediaType_Release(media_type); - IMFTopologyNode_Release(mft_node); + hr = IMFTransform_GetOutputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Failed to get transform input type, hr %#lx.\n", hr); + hr = IMFMediaType_Compare(output_type, (IMFAttributes *)media_type, MF_ATTRIBUTES_MATCH_OUR_ITEMS, &ret); + ok(hr == S_OK, "Failed to compare media types, hr %#lx.\n", hr); + ok(ret, "Output type of last transform doesn't match sink node type.\n"); + IMFMediaType_Release(media_type); + IMFTransform_Release(transform); } diff --git a/dlls/mf/tests/transform.c b/dlls/mf/tests/transform.c index f9e6b3b45cd..468239b0fd4 100644 --- a/dlls/mf/tests/transform.c +++ b/dlls/mf/tests/transform.c @@ -692,25 +692,27 @@ static void check_mft_set_input_type_required_(int line, IMFTransform *transform ok_(__FILE__, line)(!ref, "Release returned %lu\n", ref); } -static void check_mft_set_input_type(IMFTransform *transform, const struct attribute_desc *attributes) +#define check_mft_set_input_type(a, b) check_mft_set_input_type_(__LINE__, a, b, FALSE) +static void check_mft_set_input_type_(int line, IMFTransform *transform, const struct attribute_desc *attributes, BOOL todo) { IMFMediaType *media_type; HRESULT hr; hr = MFCreateMediaType(&media_type); - ok(hr == S_OK, "MFCreateMediaType returned hr %#lx.\n", hr); + ok_(__FILE__, line)(hr == S_OK, "MFCreateMediaType returned hr %#lx.\n", hr); init_media_type(media_type, attributes, -1); hr = IMFTransform_SetInputType(transform, 0, media_type, MFT_SET_TYPE_TEST_ONLY); - ok(hr == S_OK, "SetInputType returned %#lx.\n", hr); + ok_(__FILE__, line)(hr == S_OK, "SetInputType returned %#lx.\n", hr); hr = IMFTransform_SetInputType(transform, 0, media_type, 0); - ok(hr == S_OK, "SetInputType returned %#lx.\n", hr); + todo_wine_if(todo) + ok_(__FILE__, line)(hr == S_OK, "SetInputType returned %#lx.\n", hr); IMFMediaType_Release(media_type); } -#define check_mft_get_input_current_type(a, b) check_mft_get_input_current_type_(a, b, FALSE, FALSE) -static void check_mft_get_input_current_type_(IMFTransform *transform, const struct attribute_desc *attributes, +#define check_mft_get_input_current_type(a, b) check_mft_get_input_current_type_(__LINE__, a, b, FALSE, FALSE) +static void check_mft_get_input_current_type_(int line, IMFTransform *transform, const struct attribute_desc *attributes, BOOL todo_current, BOOL todo_compare) { HRESULT hr, expect_hr = attributes ? S_OK : MF_E_TRANSFORM_TYPE_NOT_SET; @@ -719,19 +721,21 @@ static void check_mft_get_input_current_type_(IMFTransform *transform, const str hr = IMFTransform_GetInputCurrentType(transform, 0, ¤t_type); todo_wine_if(todo_current) - ok(hr == expect_hr, "GetInputCurrentType returned hr %#lx.\n", hr); + ok_(__FILE__, line)(hr == expect_hr, "GetInputCurrentType returned hr %#lx.\n", hr); if (FAILED(hr)) return; hr = MFCreateMediaType(&media_type); - ok(hr == S_OK, "MFCreateMediaType returned hr %#lx.\n", hr); + ok_(__FILE__, line)(hr == S_OK, "MFCreateMediaType returned hr %#lx.\n", hr); init_media_type(media_type, attributes, -1); hr = IMFMediaType_Compare(current_type, (IMFAttributes *)media_type, MF_ATTRIBUTES_MATCH_ALL_ITEMS, &result); - ok(hr == S_OK, "Compare returned hr %#lx.\n", hr); + ok_(__FILE__, line)(hr == S_OK, "Compare returned hr %#lx.\n", hr); todo_wine_if(todo_compare) - ok(result, "got result %u.\n", !!result); + ok_(__FILE__, line)(result, "got result %u.\n", !!result); + + check_attributes_(__FILE__, line, (IMFAttributes *)current_type, attributes, -1); IMFMediaType_Release(media_type); IMFMediaType_Release(current_type); @@ -785,24 +789,31 @@ static void check_mft_set_output_type(IMFTransform *transform, const struct attr IMFMediaType_Release(media_type); } -#define check_mft_get_output_current_type(a, b) check_mft_get_output_current_type_(a, b, FALSE) -static void check_mft_get_output_current_type_(IMFTransform *transform, const struct attribute_desc *attributes, - BOOL todo_current) +#define check_mft_get_output_current_type(a, b) check_mft_get_output_current_type_(__LINE__, a, b, FALSE, FALSE) +static void check_mft_get_output_current_type_(int line, IMFTransform *transform, const struct attribute_desc *attributes, + BOOL todo_current, BOOL todo_compare) { HRESULT hr, expect_hr = attributes ? S_OK : MF_E_TRANSFORM_TYPE_NOT_SET; IMFMediaType *media_type, *current_type; + BOOL result; hr = IMFTransform_GetOutputCurrentType(transform, 0, ¤t_type); todo_wine_if(todo_current) - ok(hr == expect_hr, "GetOutputCurrentType returned hr %#lx.\n", hr); + ok_(__FILE__, line)(hr == expect_hr, "GetOutputCurrentType returned hr %#lx.\n", hr); if (FAILED(hr)) return; hr = MFCreateMediaType(&media_type); - ok(hr == S_OK, "MFCreateMediaType returned hr %#lx.\n", hr); + ok_(__FILE__, line)(hr == S_OK, "MFCreateMediaType returned hr %#lx.\n", hr); init_media_type(media_type, attributes, -1); - check_attributes((IMFAttributes *)current_type, attributes, -1); + hr = IMFMediaType_Compare(current_type, (IMFAttributes *)media_type, + MF_ATTRIBUTES_MATCH_ALL_ITEMS, &result); + ok_(__FILE__, line)(hr == S_OK, "Compare returned hr %#lx.\n", hr); + todo_wine_if(todo_compare) + ok_(__FILE__, line)(result, "got result %u.\n", !!result); + + check_attributes_(__FILE__, line, (IMFAttributes *)current_type, attributes, -1); IMFMediaType_Release(media_type); IMFMediaType_Release(current_type); @@ -3191,11 +3202,11 @@ static void test_wma_decoder(void) /* setting output media type first doesn't work */ check_mft_set_output_type(transform, output_type_desc, MF_E_TRANSFORM_TYPE_NOT_SET); - check_mft_get_output_current_type_(transform, NULL, TRUE); + check_mft_get_output_current_type_(__LINE__, transform, NULL, TRUE, FALSE); check_mft_set_input_type_required(transform, input_type_desc); check_mft_set_input_type(transform, input_type_desc); - check_mft_get_input_current_type_(transform, expect_input_type_desc, TRUE, FALSE); + check_mft_get_input_current_type_(__LINE__, transform, expect_input_type_desc, TRUE, FALSE); check_mft_get_input_stream_info(transform, MF_E_TRANSFORM_TYPE_NOT_SET, NULL); check_mft_get_output_stream_info(transform, MF_E_TRANSFORM_TYPE_NOT_SET, NULL); @@ -3217,7 +3228,7 @@ static void test_wma_decoder(void) check_mft_set_output_type_required(transform, output_type_desc); check_mft_set_output_type(transform, output_type_desc, S_OK); - check_mft_get_output_current_type_(transform, expect_output_type_desc, TRUE); + check_mft_get_output_current_type_(__LINE__, transform, expect_output_type_desc, TRUE, FALSE); check_mft_get_input_stream_info(transform, S_OK, &input_info); check_mft_get_output_stream_info(transform, S_OK, &output_info); @@ -3828,6 +3839,7 @@ static void test_h264_encoder(void) ATTR_RATIO(MF_MT_FRAME_RATE, 30000, 1001), ATTR_UINT32(MF_MT_AVG_BITRATE, 193540), ATTR_BLOB(MF_MT_MPEG_SEQUENCE_HEADER, test_h264_sequence_header, sizeof(test_h264_sequence_header)), + ATTR_UINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive), {0}, }; static const MFT_OUTPUT_STREAM_INFO expect_output_info = {.cbSize = 0x8000}; @@ -4019,11 +4031,11 @@ static void test_h264_decoder(void) ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_H264), ATTR_RATIO(MF_MT_FRAME_SIZE, input_width, input_height), - ATTR_UINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 0), - ATTR_UINT32(MF_MT_DEFAULT_STRIDE, 0), - ATTR_UINT32(MF_MT_FIXED_SIZE_SAMPLES, 0), - ATTR_UINT32(MF_MT_AVG_BIT_ERROR_RATE, 0), - ATTR_UINT32(MF_MT_COMPRESSED, 1), + ATTR_UINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 0, .todo = TRUE), + ATTR_UINT32(MF_MT_DEFAULT_STRIDE, 0, .todo = TRUE), + ATTR_UINT32(MF_MT_FIXED_SIZE_SAMPLES, 0, .todo = TRUE), + ATTR_UINT32(MF_MT_AVG_BIT_ERROR_RATE, 0, .todo = TRUE), + ATTR_UINT32(MF_MT_COMPRESSED, 1, .todo = TRUE), {0}, }; const struct attribute_desc expect_output_type_desc[] = @@ -4262,7 +4274,7 @@ static void test_h264_decoder(void) check_mft_set_input_type_required(transform, input_type_desc); check_mft_set_input_type(transform, input_type_desc); - check_mft_get_input_current_type_(transform, expect_input_type_desc, TRUE, FALSE); + check_mft_get_input_current_type_(__LINE__, transform, expect_input_type_desc, FALSE, TRUE); check_mft_get_input_stream_info(transform, S_OK, &input_info); check_mft_get_output_stream_info(transform, S_OK, &output_info); @@ -4284,7 +4296,7 @@ static void test_h264_decoder(void) check_mft_set_output_type_required(transform, output_type_desc); check_mft_set_output_type(transform, output_type_desc, S_OK); - check_mft_get_output_current_type_(transform, expect_output_type_desc, FALSE); + check_mft_get_output_current_type_(__LINE__, transform, expect_output_type_desc, FALSE, TRUE); /* check that the output media type we've selected don't change the enumeration */ @@ -4382,7 +4394,7 @@ static void test_h264_decoder(void) ok(i == 5, "%lu output media types\n", i); /* current output type is still the one we selected */ - check_mft_get_output_current_type_(transform, expect_output_type_desc, FALSE); + check_mft_get_output_current_type_(__LINE__, transform, expect_output_type_desc, FALSE, TRUE); hr = MFCreateCollection(&output_samples); ok(hr == S_OK, "MFCreateCollection returned %#lx\n", hr); @@ -4412,7 +4424,7 @@ static void test_h264_decoder(void) ret = IMFMediaType_Release(media_type); ok(ret == 1, "Release returned %lu\n", ret); - check_mft_get_output_current_type_(transform, expect_new_output_type_desc, FALSE); + check_mft_get_output_current_type_(__LINE__, transform, expect_new_output_type_desc, FALSE, TRUE); output_sample = create_sample(NULL, actual_width * actual_height * 2); hr = check_mft_process_output(transform, output_sample, &output_status); @@ -4810,8 +4822,8 @@ static void test_audio_convert(void) ATTR_UINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, 22050), ATTR_UINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, 8), ATTR_UINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, 22050 * 8), - ATTR_UINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1), - ATTR_UINT32(MF_MT_AUDIO_CHANNEL_MASK, 3), + ATTR_UINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1, .todo = TRUE), + ATTR_UINT32(MF_MT_AUDIO_CHANNEL_MASK, 3, .todo = TRUE), {0}, }; const struct attribute_desc expect_output_type_desc[] = @@ -4939,7 +4951,7 @@ static void test_audio_convert(void) check_mft_set_input_type_required(transform, input_type_desc); check_mft_set_input_type(transform, input_type_desc); - check_mft_get_input_current_type_(transform, expect_input_type_desc, FALSE, TRUE); + check_mft_get_input_current_type_(__LINE__, transform, expect_input_type_desc, FALSE, TRUE); check_mft_get_input_stream_info(transform, MF_E_TRANSFORM_TYPE_NOT_SET, NULL); check_mft_get_output_stream_info(transform, MF_E_TRANSFORM_TYPE_NOT_SET, NULL); @@ -4961,7 +4973,7 @@ static void test_audio_convert(void) check_mft_set_output_type_required(transform, output_type_desc); check_mft_set_output_type(transform, output_type_desc, S_OK); - check_mft_get_output_current_type_(transform, expect_output_type_desc, FALSE); + check_mft_get_output_current_type_(__LINE__, transform, expect_output_type_desc, FALSE, TRUE); check_mft_get_input_stream_info(transform, S_OK, &input_info); check_mft_get_output_stream_info(transform, S_OK, &output_info); @@ -5414,11 +5426,11 @@ static void test_wmv_encoder(void) check_mft_set_input_type_required(transform, input_type_desc); check_mft_set_input_type(transform, input_type_desc); - check_mft_get_input_current_type_(transform, expect_input_type_desc, FALSE, TRUE); + check_mft_get_input_current_type_(__LINE__, transform, expect_input_type_desc, FALSE, TRUE); check_mft_set_output_type_required(transform, output_type_desc); check_mft_set_output_type(transform, output_type_desc, S_OK); - check_mft_get_output_current_type_(transform, expect_output_type_desc, FALSE); + check_mft_get_output_current_type_(__LINE__, transform, expect_output_type_desc, FALSE, FALSE); check_mft_get_input_stream_info(transform, S_OK, &expect_input_info); check_mft_get_output_stream_info(transform, S_OK, &expect_output_info); @@ -5975,7 +5987,7 @@ static void test_wmv_decoder(void) check_mft_set_input_type_required(transform, input_type_desc); check_mft_set_input_type(transform, input_type_desc); - check_mft_get_input_current_type_(transform, expect_input_type_desc, FALSE, TRUE); + check_mft_get_input_current_type_(__LINE__, transform, expect_input_type_desc, FALSE, TRUE); i = -1; while (SUCCEEDED(hr = IMFTransform_GetOutputAvailableType(transform, 0, ++i, &media_type))) @@ -5998,7 +6010,7 @@ static void test_wmv_decoder(void) check_mft_set_output_type_required(transform, transform_tests[j].output_type_desc); check_mft_set_output_type(transform, transform_tests[j].output_type_desc, S_OK); - check_mft_get_output_current_type_(transform, transform_tests[j].expect_output_type_desc, FALSE); + check_mft_get_output_current_type_(__LINE__, transform, transform_tests[j].expect_output_type_desc, FALSE, FALSE); check_mft_get_input_stream_info(transform, S_OK, transform_tests[j].expect_input_info); check_mft_get_output_stream_info(transform, S_OK, transform_tests[j].expect_output_info); @@ -6914,10 +6926,10 @@ static void test_color_convert(void) ATTR_BLOB(MF_MT_MINIMUM_DISPLAY_APERTURE, &actual_aperture, 16), ATTR_RATIO(MF_MT_FRAME_SIZE, actual_width, actual_height), ATTR_UINT32(MF_MT_DEFAULT_STRIDE, actual_width), - ATTR_UINT32(MF_MT_SAMPLE_SIZE, actual_width * actual_height * 3 / 2), - ATTR_UINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1), - ATTR_UINT32(MF_MT_FIXED_SIZE_SAMPLES, 1), - ATTR_RATIO(MF_MT_PIXEL_ASPECT_RATIO, 1, 1), + ATTR_UINT32(MF_MT_SAMPLE_SIZE, actual_width * actual_height * 3 / 2, .todo = TRUE), + ATTR_UINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1, .todo = TRUE), + ATTR_UINT32(MF_MT_FIXED_SIZE_SAMPLES, 1, .todo = TRUE), + ATTR_RATIO(MF_MT_PIXEL_ASPECT_RATIO, 1, 1, .todo = TRUE), {0}, }; const struct attribute_desc expect_output_type_desc[] = @@ -7077,14 +7089,14 @@ static void test_color_convert(void) check_mft_set_input_type_required(transform, input_type_desc); check_mft_set_input_type(transform, input_type_desc); - check_mft_get_input_current_type_(transform, expect_input_type_desc, FALSE, TRUE); + check_mft_get_input_current_type_(__LINE__, transform, expect_input_type_desc, FALSE, TRUE); for (i = 0; i < ARRAY_SIZE(color_conversion_tests); i++) { winetest_push_context("color conversion #%lu", i); check_mft_set_output_type_required(transform, color_conversion_tests[i].output_type_desc); check_mft_set_output_type(transform, color_conversion_tests[i].output_type_desc, S_OK); - check_mft_get_output_current_type_(transform, color_conversion_tests[i].expect_output_type_desc, FALSE); + check_mft_get_output_current_type_(__LINE__, transform, color_conversion_tests[i].expect_output_type_desc, FALSE, TRUE); check_mft_get_input_stream_info(transform, S_OK, &input_info); check_mft_get_output_stream_info(transform, S_OK, &output_info); @@ -7339,6 +7351,28 @@ static void test_video_processor(void) ATTR_UINT32(MF_MT_DEFAULT_STRIDE, actual_width * 2), {0}, }; + const struct attribute_desc nv12_no_aperture[] = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_NV12), + ATTR_RATIO(MF_MT_FRAME_SIZE, 82, 84), + {0}, + }; + const struct attribute_desc nv12_with_aperture[] = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_NV12), + ATTR_RATIO(MF_MT_FRAME_SIZE, actual_width, actual_height), + ATTR_BLOB(MF_MT_MINIMUM_DISPLAY_APERTURE, &actual_aperture, 16), + {0}, + }; + const struct attribute_desc rgb32_no_aperture[] = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32), + ATTR_RATIO(MF_MT_FRAME_SIZE, 82, 84), + {0}, + }; const MFT_OUTPUT_STREAM_INFO initial_output_info = {0}; const MFT_INPUT_STREAM_INFO initial_input_info = {0}; MFT_OUTPUT_STREAM_INFO output_info = {0}; @@ -7900,6 +7934,29 @@ static void test_video_processor(void) ret = IMFTransform_Release(transform); ok(ret == 0, "Release returned %ld\n", ret); + + /* check that it is possible to change input media type frame size using geometric aperture */ + + hr = CoCreateInstance(class_id, NULL, CLSCTX_INPROC_SERVER, + &IID_IMFTransform, (void **)&transform); + ok(hr == S_OK, "got hr %#lx\n", hr); + + check_mft_set_input_type(transform, nv12_no_aperture); + check_mft_get_input_current_type(transform, nv12_no_aperture); + + check_mft_set_output_type(transform, rgb32_no_aperture, S_OK); + check_mft_get_output_current_type(transform, rgb32_no_aperture); + + check_mft_set_input_type_(__LINE__, transform, nv12_with_aperture, TRUE); + check_mft_get_input_current_type_(__LINE__, transform, nv12_with_aperture, TRUE, FALSE); + + /* output type is the same as before */ + check_mft_get_output_current_type(transform, rgb32_no_aperture); + + ret = IMFTransform_Release(transform); + ok(ret == 0, "Release returned %ld\n", ret); + + failed: winetest_pop_context(); CoUninitialize(); diff --git a/dlls/mf/topology_loader.c b/dlls/mf/topology_loader.c index def7c28089c..a56fb3e3909 100644 --- a/dlls/mf/topology_loader.c +++ b/dlls/mf/topology_loader.c @@ -296,6 +296,8 @@ static HRESULT topology_branch_connect_indirect(IMFTopology *topology, MF_CONNEC { struct topology_branch down_branch = {.up.node = node, .down = branch->down}; struct topology_branch up_branch = {.up = branch->up, .down.node = node}; + MF_CONNECT_METHOD method = method_mask; + IMFMediaType *media_type; if (FAILED(IMFActivate_ActivateObject(activates[i], &IID_IMFTransform, (void **)&transform))) continue; @@ -308,17 +310,22 @@ static HRESULT topology_branch_connect_indirect(IMFTopology *topology, MF_CONNEC hr = topology_branch_connect_down(topology, MF_CONNECT_DIRECT, &up_branch, up_type); if (down_type) { - if (SUCCEEDED(hr)) - hr = topology_branch_fill_media_type(up_type, down_type); - if (SUCCEEDED(hr)) - hr = IMFTransform_SetOutputType(transform, 0, down_type, 0); - if (SUCCEEDED(hr)) - method_mask = MF_CONNECT_DIRECT; + if (SUCCEEDED(topology_branch_fill_media_type(up_type, down_type)) + && SUCCEEDED(IMFTransform_SetOutputType(transform, 0, down_type, 0))) + method = MF_CONNECT_DIRECT; } IMFTransform_Release(transform); + if (SUCCEEDED(hr) && method != MF_CONNECT_DIRECT + && SUCCEEDED(IMFTransform_GetOutputAvailableType(transform, 0, 0, &media_type))) + { + if (SUCCEEDED(topology_branch_fill_media_type(up_type, media_type))) + IMFTransform_SetOutputType(transform, 0, media_type, 0); + IMFMediaType_Release(media_type); + } + if (SUCCEEDED(hr)) - hr = topology_branch_connect(topology, method_mask, &down_branch, !down_type); + hr = topology_branch_connect(topology, method, &down_branch, !down_type); if (SUCCEEDED(hr)) hr = IMFTopology_AddNode(topology, node); if (SUCCEEDED(hr)) @@ -521,8 +528,11 @@ static HRESULT topology_loader_resolve_branches(struct topoloader_context *conte else 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, MF_CONNECT_ALLOW_DECODER, - branch, enumerate_source_types || node_type == MF_TOPOLOGY_TRANSFORM_NODE); + { + hr = topology_branch_connect(context->output_topology, MF_CONNECT_ALLOW_DECODER, branch, enumerate_source_types); + if (hr == MF_E_INVALIDMEDIATYPE && !enumerate_source_types && node_type == MF_TOPOLOGY_TRANSFORM_NODE) + hr = topology_branch_connect(context->output_topology, MF_CONNECT_ALLOW_DECODER, branch, TRUE); + } topology_branch_destroy(branch); if (FAILED(hr)) diff --git a/dlls/mfmediaengine/main.c b/dlls/mfmediaengine/main.c index baa6e8540ff..3e05626f238 100644 --- a/dlls/mfmediaengine/main.c +++ b/dlls/mfmediaengine/main.c @@ -2588,7 +2588,7 @@ static HRESULT WINAPI media_engine_SetBalance(IMFMediaEngineEx *iface, double ba { FIXME("%p, %f stub.\n", iface, balance); - return E_NOTIMPL; + return S_OK; } static BOOL WINAPI media_engine_IsPlaybackRateSupported(IMFMediaEngineEx *iface, double rate) diff --git a/dlls/mfmediaengine/tests/mfmediaengine.c b/dlls/mfmediaengine/tests/mfmediaengine.c index 66a1f0b79e9..f9489f3dcb5 100644 --- a/dlls/mfmediaengine/tests/mfmediaengine.c +++ b/dlls/mfmediaengine/tests/mfmediaengine.c @@ -152,7 +152,7 @@ static void check_rgb32_data_(int line, const WCHAR *filename, const BYTE *data, expect_data = LockResource(LoadResource(GetModuleHandleW(NULL), resource)); diff = compare_rgb32(data, &length, rect, expect_data); - ok_(__FILE__, line)(diff == 0, "Unexpected %lu%% diff\n", diff); + ok_(__FILE__, line)(diff <= 3 /* small difference in wine */, "Unexpected %lu%% diff\n", diff); } static void init_functions(void) diff --git a/dlls/mfplat/main.c b/dlls/mfplat/main.c index 4805812e4d1..3d1fe615a5a 100644 --- a/dlls/mfplat/main.c +++ b/dlls/mfplat/main.c @@ -6296,6 +6296,18 @@ static HRESULT resolver_get_bytestream_url_hint(IMFByteStream *stream, WCHAR con static HRESULT resolver_create_gstreamer_handler(IMFByteStreamHandler **handler) { static const GUID CLSID_GStreamerByteStreamHandler = {0x317df618, 0x5e5a, 0x468a, {0x9f, 0x15, 0xd8, 0x27, 0xa9, 0xa0, 0x81, 0x62}}; + static const GUID CLSID_GStreamerByteStreamHandler2 = {0x317df619, 0x5e5a, 0x468a, {0x9f, 0x15, 0xd8, 0x27, 0xa9, 0xa0, 0x81, 0x62}}; + + const char *env = getenv("WINE_NEW_MEDIA_SOURCE"), *sgi = getenv("SteamGameId"); + if (!env && sgi) + { + if (!strcmp(sgi, "399810") /* Call of Cthulhu */) env = "1"; + if (!strcmp(sgi, "606880") /* Greedfall */) env = "1"; + if (!strcmp(sgi, "692850") /* Bloodstained */) env = "1"; + if (!strcmp(sgi, "934700") /* Dead Island 2 */) env = "1"; + } + if (env && atoi(env)) return CoCreateInstance(&CLSID_GStreamerByteStreamHandler2, NULL, CLSCTX_INPROC_SERVER, &IID_IMFByteStreamHandler, (void **)handler); + return CoCreateInstance(&CLSID_GStreamerByteStreamHandler, NULL, CLSCTX_INPROC_SERVER, &IID_IMFByteStreamHandler, (void **)handler); } diff --git a/dlls/mfplat/mediatype.c b/dlls/mfplat/mediatype.c index c73c92be642..a159ae4b56f 100644 --- a/dlls/mfplat/mediatype.c +++ b/dlls/mfplat/mediatype.c @@ -2965,10 +2965,10 @@ HRESULT WINAPI MFUnwrapMediaType(IMFMediaType *wrapper, IMFMediaType **ret) HRESULT WINAPI MFCreateWaveFormatExFromMFMediaType(IMFMediaType *mediatype, WAVEFORMATEX **ret_format, UINT32 *size, UINT32 flags) { - WAVEFORMATEXTENSIBLE *format_ext = NULL; + UINT32 value, extra_size = 0, user_size; WAVEFORMATEX *format; GUID major, subtype; - UINT32 value; + void *user_data; HRESULT hr; TRACE("%p, %p, %p, %#x.\n", mediatype, ret_format, size, flags); @@ -2982,36 +2982,24 @@ HRESULT WINAPI MFCreateWaveFormatExFromMFMediaType(IMFMediaType *mediatype, WAVE if (!IsEqualGUID(&major, &MFMediaType_Audio)) return E_INVALIDARG; - if (!IsEqualGUID(&subtype, &MFAudioFormat_PCM) && !IsEqualGUID(&subtype, &MFAudioFormat_Float)) + if (FAILED(hr = IMFMediaType_GetBlobSize(mediatype, &MF_MT_USER_DATA, &user_size))) { - FIXME("Unsupported audio format %s.\n", debugstr_guid(&subtype)); - return E_NOTIMPL; + if (!IsEqualGUID(&subtype, &MFAudioFormat_PCM) && !IsEqualGUID(&subtype, &MFAudioFormat_Float)) + return hr; + user_size = 0; } - /* FIXME: probably WAVE_FORMAT_MPEG/WAVE_FORMAT_MPEGLAYER3 should be handled separately. */ if (flags == MFWaveFormatExConvertFlag_ForceExtensible) - { - format_ext = CoTaskMemAlloc(sizeof(*format_ext)); - *size = sizeof(*format_ext); - format = (WAVEFORMATEX *)format_ext; - } - else - { - format = CoTaskMemAlloc(sizeof(*format)); - *size = sizeof(*format); - } + extra_size = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(*format); - if (!format) + *size = sizeof(*format) + user_size + extra_size; + if (!(format = CoTaskMemAlloc(*size))) return E_OUTOFMEMORY; memset(format, 0, *size); - - if (format_ext) - format->wFormatTag = WAVE_FORMAT_EXTENSIBLE; - else if (IsEqualGUID(&subtype, &MFAudioFormat_Float)) - format->wFormatTag = WAVE_FORMAT_IEEE_FLOAT; - else - format->wFormatTag = WAVE_FORMAT_PCM; + format->wFormatTag = subtype.Data1; + format->cbSize = user_size + extra_size; + user_data = format + 1; if (SUCCEEDED(IMFMediaType_GetUINT32(mediatype, &MF_MT_AUDIO_NUM_CHANNELS, &value))) format->nChannels = value; @@ -3023,18 +3011,26 @@ HRESULT WINAPI MFCreateWaveFormatExFromMFMediaType(IMFMediaType *mediatype, WAVE format->nBlockAlign = value; if (SUCCEEDED(IMFMediaType_GetUINT32(mediatype, &MF_MT_AUDIO_BITS_PER_SAMPLE, &value))) format->wBitsPerSample = value; - if (format_ext) + + if (flags == MFWaveFormatExConvertFlag_ForceExtensible) { - format->cbSize = sizeof(*format_ext) - sizeof(*format); + WAVEFORMATEXTENSIBLE *format_ext = CONTAINING_RECORD(format, WAVEFORMATEXTENSIBLE, Format); + + format_ext->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + format_ext->SubFormat = subtype; + user_data = format_ext + 1; if (SUCCEEDED(IMFMediaType_GetUINT32(mediatype, &MF_MT_AUDIO_VALID_BITS_PER_SAMPLE, &value))) format_ext->Samples.wSamplesPerBlock = value; if (SUCCEEDED(IMFMediaType_GetUINT32(mediatype, &MF_MT_AUDIO_CHANNEL_MASK, &value))) format_ext->dwChannelMask = value; - memcpy(&format_ext->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM, sizeof(format_ext->SubFormat)); + else if (format_ext->Format.nChannels < ARRAY_SIZE(default_channel_mask)) + format_ext->dwChannelMask = default_channel_mask[format_ext->Format.nChannels]; } + IMFMediaType_GetBlob(mediatype, &MF_MT_USER_DATA, user_data, user_size, NULL); + *ret_format = format; return S_OK; @@ -3126,6 +3122,15 @@ HRESULT WINAPI MFInitMediaTypeFromWaveFormatEx(IMFMediaType *mediatype, const WA mediatype_set_uint32(mediatype, &MF_MT_ALL_SAMPLES_INDEPENDENT, 1, &hr); } + if (IsEqualGUID(&subtype, &MFAudioFormat_AAC)) + { + HEAACWAVEINFO *info = CONTAINING_RECORD(format, HEAACWAVEINFO, wfx); + if (format->cbSize < sizeof(HEAACWAVEINFO) - sizeof(WAVEFORMATEX)) + return E_INVALIDARG; + mediatype_set_uint32(mediatype, &MF_MT_AAC_AUDIO_PROFILE_LEVEL_INDICATION, info->wAudioProfileLevelIndication, &hr); + mediatype_set_uint32(mediatype, &MF_MT_AAC_PAYLOAD_TYPE, info->wPayloadType, &hr); + } + if (format->cbSize && format->wFormatTag != WAVE_FORMAT_EXTENSIBLE) mediatype_set_blob(mediatype, &MF_MT_USER_DATA, (const UINT8 *)(format + 1), format->cbSize, &hr); diff --git a/dlls/mfplat/tests/mfplat.c b/dlls/mfplat/tests/mfplat.c index c7ee087bb93..f785df89b32 100644 --- a/dlls/mfplat/tests/mfplat.c +++ b/dlls/mfplat/tests/mfplat.c @@ -6953,6 +6953,11 @@ static void validate_media_type(IMFMediaType *mediatype, const WAVEFORMATEX *for } else ok(FAILED(hr), "Unexpected ALL_SAMPLES_INDEPENDENT.\n"); + + hr = IMFMediaType_GetUINT32(mediatype, &MF_MT_FIXED_SIZE_SAMPLES, &value); + ok(FAILED(hr), "Unexpected FIXED_SIZE_SAMPLES.\n"); + hr = IMFMediaType_GetUINT32(mediatype, &MF_MT_COMPRESSED, &value); + ok(FAILED(hr), "Unexpected COMPRESSED.\n"); } static void test_MFInitMediaTypeFromWaveFormatEx(void) @@ -6986,11 +6991,14 @@ static void test_MFInitMediaTypeFromWaveFormatEx(void) { WAVE_FORMAT_WMASPDIF }, }; - UINT8 buff[MPEGLAYER3_WFX_EXTRA_BYTES]; + UINT8 buff[1024]; WAVEFORMATEXTENSIBLE waveformatext; MPEGLAYER3WAVEFORMAT mp3format; + WAVEFORMATEXTENSIBLE *format; + HEAACWAVEFORMAT aacformat; IMFMediaType *mediatype; unsigned int i, size; + UINT32 value; HRESULT hr; hr = MFCreateMediaType(&mediatype); @@ -7043,6 +7051,68 @@ static void test_MFInitMediaTypeFromWaveFormatEx(void) ok(size == mp3format.wfx.cbSize, "Unexpected size %u.\n", size); ok(!memcmp(buff, (WAVEFORMATEX *)&mp3format + 1, size), "Unexpected user data.\n"); + /* HEAACWAVEFORMAT */ + aacformat.wfInfo.wfx.wFormatTag = WAVE_FORMAT_MPEG_HEAAC; + aacformat.wfInfo.wfx.nChannels = 2; + aacformat.wfInfo.wfx.nSamplesPerSec = 44100; + aacformat.wfInfo.wfx.nAvgBytesPerSec = 16000; + aacformat.wfInfo.wfx.nBlockAlign = 1; + aacformat.wfInfo.wfx.wBitsPerSample = 0; + aacformat.wfInfo.wPayloadType = 2; + aacformat.wfInfo.wAudioProfileLevelIndication = 1; + aacformat.pbAudioSpecificConfig[0] = 0xcd; + + /* test with invalid format size */ + aacformat.wfInfo.wfx.cbSize = sizeof(aacformat) - 2 - sizeof(WAVEFORMATEX); + hr = MFInitMediaTypeFromWaveFormatEx(mediatype, (WAVEFORMATEX *)&aacformat, sizeof(aacformat) - 2); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + + aacformat.wfInfo.wfx.cbSize = sizeof(aacformat) - sizeof(WAVEFORMATEX); + hr = MFInitMediaTypeFromWaveFormatEx(mediatype, (WAVEFORMATEX *)&aacformat, sizeof(aacformat)); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + validate_media_type(mediatype, &aacformat.wfInfo.wfx); + + value = 0xdeadbeef; + hr = IMFMediaType_GetUINT32(mediatype, &MF_MT_AAC_AUDIO_PROFILE_LEVEL_INDICATION, &value); + ok(hr == S_OK, "Failed to get attribute, hr %#lx.\n", hr); + ok(value == aacformat.wfInfo.wAudioProfileLevelIndication, "Unexpected AAC_AUDIO_PROFILE_LEVEL_INDICATION %u.\n", value); + value = 0xdeadbeef; + hr = IMFMediaType_GetUINT32(mediatype, &MF_MT_AAC_PAYLOAD_TYPE, &value); + ok(hr == S_OK, "Failed to get attribute, hr %#lx.\n", hr); + ok(value == aacformat.wfInfo.wPayloadType, "Unexpected AAC_PAYLOAD_TYPE %u.\n", value); + + hr = IMFMediaType_GetBlob(mediatype, &MF_MT_USER_DATA, buff, sizeof(buff), &size); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(size == aacformat.wfInfo.wfx.cbSize, "Unexpected size %u.\n", size); + ok(!memcmp(buff, (WAVEFORMATEX *)&aacformat + 1, size), "Unexpected user data.\n"); + + hr = MFCreateWaveFormatExFromMFMediaType(mediatype, (WAVEFORMATEX **)&format, &size, MFWaveFormatExConvertFlag_ForceExtensible); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(format->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE, "got wFormatTag %#x\n", format->Format.wFormatTag); + ok(format->Format.cbSize == aacformat.wfInfo.wfx.cbSize + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX), + "got cbSize %u\n", format->Format.cbSize); + ok(IsEqualGUID(&format->SubFormat, &MFAudioFormat_AAC), "got SubFormat %s\n", debugstr_guid(&format->SubFormat)); + ok(format->dwChannelMask == 3, "got dwChannelMask %#lx\n", format->dwChannelMask); + ok(format->Samples.wSamplesPerBlock == 0, "got wSamplesPerBlock %u\n", format->Samples.wSamplesPerBlock); + ok(!memcmp(format + 1, &aacformat.wfInfo.wfx + 1, aacformat.wfInfo.wfx.cbSize), "Unexpected user data.\n"); + CoTaskMemFree(format); + + /* test with invalid format size */ + aacformat.wfInfo.wfx.cbSize = 1; + hr = IMFMediaType_SetBlob(mediatype, &MF_MT_USER_DATA, buff, aacformat.wfInfo.wfx.cbSize); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = MFCreateWaveFormatExFromMFMediaType(mediatype, (WAVEFORMATEX **)&format, &size, MFWaveFormatExConvertFlag_ForceExtensible); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(format->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE, "got wFormatTag %#x\n", format->Format.wFormatTag); + ok(format->Format.cbSize == aacformat.wfInfo.wfx.cbSize + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX), + "got cbSize %u\n", format->Format.cbSize); + ok(IsEqualGUID(&format->SubFormat, &MFAudioFormat_AAC), "got SubFormat %s\n", debugstr_guid(&format->SubFormat)); + ok(format->dwChannelMask == 3, "got dwChannelMask %#lx\n", format->dwChannelMask); + ok(format->Samples.wSamplesPerBlock == 0, "got wSamplesPerBlock %u\n", format->Samples.wSamplesPerBlock); + ok(!memcmp(format + 1, &aacformat.wfInfo.wfx + 1, aacformat.wfInfo.wfx.cbSize), "Unexpected user data.\n"); + CoTaskMemFree(format); + IMFMediaType_Release(mediatype); } diff --git a/dlls/mfplay/tests/mfplay.c b/dlls/mfplay/tests/mfplay.c index fa66ccae57e..db23bb7de42 100644 --- a/dlls/mfplay/tests/mfplay.c +++ b/dlls/mfplay/tests/mfplay.c @@ -493,7 +493,7 @@ static void test_media_language(void) ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); check_media_language(player, L"test-eng.mp4", L"en"); - check_media_language(player, L"test-ang.mp4", NULL); + todo_check_media_language(player, L"test-ang.mp4", NULL); check_media_language(player, L"test-und.mp4", NULL); check_media_language(player, L"test-en-US.mp4", L"en"); diff --git a/dlls/mfreadwrite/tests/Makefile.in b/dlls/mfreadwrite/tests/Makefile.in index 90bac9e0896..cada1bf22ff 100644 --- a/dlls/mfreadwrite/tests/Makefile.in +++ b/dlls/mfreadwrite/tests/Makefile.in @@ -1,5 +1,5 @@ TESTDLL = mfreadwrite.dll -IMPORTS = ole32 user32 d3d9 dxva2 mfplat mfreadwrite mfuuid +IMPORTS = ole32 user32 d3d9 dxva2 mfplat mfreadwrite mfuuid propsys SOURCES = \ mfplat.c \ diff --git a/dlls/mfreadwrite/tests/mfplat.c b/dlls/mfreadwrite/tests/mfplat.c index c3544a31ac6..2095d2f054d 100644 --- a/dlls/mfreadwrite/tests/mfplat.c +++ b/dlls/mfreadwrite/tests/mfplat.c @@ -38,11 +38,94 @@ DEFINE_GUID(GUID_NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); #include "mfidl.h" #include "mferror.h" #include "mfreadwrite.h" +#include "propvarutil.h" #include "d3d9.h" #include "dxva2api.h" #include "wine/test.h" +struct attribute_desc +{ + const GUID *key; + const char *name; + PROPVARIANT value; + BOOL ratio; + BOOL required; + BOOL todo; + BOOL todo_value; +}; + +#define ATTR_GUID(k, g, ...) {.key = &k, .name = #k, {.vt = VT_CLSID, .puuid = (GUID *)&g}, __VA_ARGS__ } +#define ATTR_UINT32(k, v, ...) {.key = &k, .name = #k, {.vt = VT_UI4, .ulVal = v}, __VA_ARGS__ } +#define ATTR_BLOB(k, p, n, ...) {.key = &k, .name = #k, {.vt = VT_VECTOR | VT_UI1, .caub = {.pElems = (void *)p, .cElems = n}}, __VA_ARGS__ } +#define ATTR_RATIO(k, n, d, ...) {.key = &k, .name = #k, {.vt = VT_UI8, .uhVal = {.HighPart = n, .LowPart = d}}, .ratio = TRUE, __VA_ARGS__ } +#define ATTR_UINT64(k, v, ...) {.key = &k, .name = #k, {.vt = VT_UI8, .uhVal = {.QuadPart = v}}, __VA_ARGS__ } + +#define check_media_type(a, b, c) check_attributes_(__FILE__, __LINE__, (IMFAttributes *)a, b, c) +#define check_attributes(a, b, c) check_attributes_(__FILE__, __LINE__, a, b, c) +void check_attributes_(const char *file, int line, IMFAttributes *attributes, + const struct attribute_desc *desc, ULONG limit) +{ + char buffer[1024], *buf = buffer; + PROPVARIANT value; + int i, j, ret; + HRESULT hr; + + for (i = 0; i < limit && desc[i].key; ++i) + { + hr = IMFAttributes_GetItem(attributes, desc[i].key, &value); + todo_wine_if(desc[i].todo) + ok_(file, line)(hr == S_OK, "%s missing, hr %#lx\n", debugstr_a(desc[i].name), hr); + if (hr != S_OK) continue; + + switch (value.vt) + { + default: sprintf(buffer, "??"); break; + case VT_CLSID: sprintf(buffer, "%s", debugstr_guid(value.puuid)); break; + case VT_UI4: sprintf(buffer, "%lu", value.ulVal); break; + case VT_UI8: + if (desc[i].ratio) + sprintf(buffer, "%lu:%lu", value.uhVal.HighPart, value.uhVal.LowPart); + else + sprintf(buffer, "%I64u", value.uhVal.QuadPart); + break; + case VT_VECTOR | VT_UI1: + buf += sprintf(buf, "size %lu, data {", value.caub.cElems); + for (j = 0; j < 128 && j < value.caub.cElems; ++j) + buf += sprintf(buf, "0x%02x,", value.caub.pElems[j]); + if (value.caub.cElems > 128) + buf += sprintf(buf, "...}"); + else + buf += sprintf(buf - (j ? 1 : 0), "}"); + break; + } + + ret = PropVariantCompareEx(&value, &desc[i].value, 0, 0); + todo_wine_if(desc[i].todo_value) + ok_(file, line)(ret == 0, "%s mismatch, type %u, value %s\n", + debugstr_a(desc[i].name), value.vt, buffer); + PropVariantClear(&value); + } +} + +#define init_media_type(a, b, c) init_attributes_(__FILE__, __LINE__, (IMFAttributes *)a, b, c) +#define init_attributes(a, b, c) init_attributes_(__FILE__, __LINE__, a, b, c) +static void init_attributes_(const char *file, int line, IMFAttributes *attributes, + const struct attribute_desc *desc, ULONG limit) +{ + HRESULT hr; + ULONG i; + + hr = IMFAttributes_DeleteAllItems(attributes); + ok_(file, line)(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + for (i = 0; i < limit && desc[i].key; ++i) + { + hr = IMFAttributes_SetItem(attributes, desc[i].key, &desc[i].value); + ok_(file, line)(hr == S_OK, "SetItem %s returned %#lx\n", debugstr_a(desc[i].name), hr); + } +} + static ULONG get_refcount(void *iface) { IUnknown *unknown = iface; @@ -357,48 +440,10 @@ static HRESULT WINAPI test_source_GetCharacteristics(IMFMediaSource *iface, DWOR static HRESULT WINAPI test_source_CreatePresentationDescriptor(IMFMediaSource *iface, IMFPresentationDescriptor **pd) { struct test_source *source = impl_from_IMFMediaSource(iface); - IMFStreamDescriptor *sds[ARRAY_SIZE(source->streams)]; - IMFMediaType *media_type; HRESULT hr = S_OK; - int i; - - EnterCriticalSection(&source->cs); - - if (source->pd) - { - *pd = source->pd; - IMFPresentationDescriptor_AddRef(*pd); - } - else - { - for (i = 0; i < source->stream_count; ++i) - { - hr = MFCreateMediaType(&media_type); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - - hr = IMFMediaType_SetGUID(media_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Audio); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - hr = IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, &MFAudioFormat_PCM); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_BITS_PER_SAMPLE, 32); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - hr = MFCreateStreamDescriptor(i, 1, &media_type, &sds[i]); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - - IMFMediaType_Release(media_type); - } - - hr = MFCreatePresentationDescriptor(source->stream_count, sds, &source->pd); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - for (i = 0; i < source->stream_count; ++i) - IMFStreamDescriptor_Release(sds[i]); - - *pd = source->pd; - IMFPresentationDescriptor_AddRef(*pd); - } - - LeaveCriticalSection(&source->cs); + *pd = source->pd; + IMFPresentationDescriptor_AddRef(*pd); return hr; } @@ -522,17 +567,22 @@ static struct test_media_stream *create_test_stream(DWORD stream_index, IMFMedia return stream; } -static IMFMediaSource *create_test_source(int stream_count) +static IMFMediaSource *create_test_source(IMFStreamDescriptor **streams, UINT stream_count) { struct test_source *source; + HRESULT hr; int i; source = calloc(1, sizeof(*source)); source->IMFMediaSource_iface.lpVtbl = &test_source_vtbl; source->refcount = 1; source->stream_count = stream_count; - MFCreateEventQueue(&source->event_queue); + hr = MFCreatePresentationDescriptor(stream_count, streams, &source->pd); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = MFCreateEventQueue(&source->event_queue); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); InitializeCriticalSection(&source->cs); + for (i = 0; i < source->stream_count; ++i) source->streams[i] = create_test_stream(i, &source->IMFMediaSource_iface); @@ -899,7 +949,7 @@ static void test_source_reader(const char *filename, bool video) hr = IMFSourceReader_ReadSample(reader, MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, &actual_index, &stream_flags, ×tamp, &sample); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine_if (video) ok(!actual_index, "Unexpected stream index %lu.\n", actual_index); + ok(!actual_index, "Unexpected stream index %lu.\n", actual_index); ok(!(stream_flags & ~MF_SOURCE_READERF_ENDOFSTREAM), "Unexpected stream flags %#lx.\n", stream_flags); if (stream_flags & MF_SOURCE_READERF_ENDOFSTREAM) @@ -920,7 +970,7 @@ static void test_source_reader(const char *filename, bool video) hr = IMFSourceReader_ReadSample(reader, MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, &actual_index, &stream_flags, ×tamp, &sample); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(actual_index == 1, "Unexpected stream index %lu.\n", actual_index); + ok(actual_index == 1, "Unexpected stream index %lu.\n", actual_index); ok(!(stream_flags & ~MF_SOURCE_READERF_ENDOFSTREAM), "Unexpected stream flags %#lx.\n", stream_flags); if (stream_flags & MF_SOURCE_READERF_ENDOFSTREAM) @@ -951,7 +1001,7 @@ static void test_source_reader(const char *filename, bool video) hr = IMFSourceReader_ReadSample(reader, MF_SOURCE_READER_FIRST_AUDIO_STREAM, MF_SOURCE_READER_CONTROLF_DRAIN, &actual_index, &stream_flags, ×tamp, &sample); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine_if (video) ok(actual_index == 0, "Unexpected stream index %lu.\n", actual_index); + ok(actual_index == 0, "Unexpected stream index %lu.\n", actual_index); ok(stream_flags == MF_SOURCE_READERF_ENDOFSTREAM, "Unexpected stream flags %#lx.\n", stream_flags); ok(!sample, "Unexpected sample object.\n"); @@ -972,7 +1022,7 @@ static void test_source_reader(const char *filename, bool video) hr = IMFSourceReader_ReadSample(reader, MF_SOURCE_READER_FIRST_AUDIO_STREAM, MF_SOURCE_READER_CONTROLF_DRAIN, &actual_index, &stream_flags, NULL, &sample); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine_if (video) ok(!actual_index, "Unexpected stream index %lu.\n", actual_index); + ok(!actual_index, "Unexpected stream index %lu.\n", actual_index); ok(stream_flags == MF_SOURCE_READERF_ENDOFSTREAM, "Unexpected stream flags %#lx.\n", stream_flags); ok(!sample, "Unexpected sample object.\n"); @@ -1028,7 +1078,15 @@ static void test_source_reader(const char *filename, bool video) static void test_source_reader_from_media_source(void) { static const DWORD expected_sample_order[10] = {0, 0, 1, 1, 0, 0, 0, 0, 1, 0}; + static const struct attribute_desc audio_stream_type_desc[] = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio), + ATTR_GUID(MF_MT_SUBTYPE, MFAudioFormat_PCM), + ATTR_UINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 32), + {0}, + }; + IMFStreamDescriptor *audio_streams[3]; struct async_callback *callback; IMFSourceReader *reader; IMFMediaSource *source; @@ -1043,7 +1101,18 @@ static void test_source_reader_from_media_source(void) int i; PROPVARIANT pos; - source = create_test_source(3); + for (i = 0; i < ARRAY_SIZE(audio_streams); i++) + { + hr = MFCreateMediaType(&media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + init_media_type(media_type, audio_stream_type_desc, -1); + + hr = MFCreateStreamDescriptor(i, 1, &media_type, &audio_streams[i]); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaType_Release(media_type); + } + + source = create_test_source(audio_streams, 3); ok(!!source, "Failed to create test source.\n"); hr = MFCreateSourceReaderFromMediaSource(source, NULL, &reader); @@ -1122,7 +1191,7 @@ static void test_source_reader_from_media_source(void) IMFSourceReader_Release(reader); IMFMediaSource_Release(source); - source = create_test_source(1); + source = create_test_source(audio_streams, 1); ok(!!source, "Failed to create test source.\n"); hr = MFCreateSourceReaderFromMediaSource(source, NULL, &reader); @@ -1159,7 +1228,7 @@ static void test_source_reader_from_media_source(void) IMFMediaSource_Release(source); /* Request from stream 0. */ - source = create_test_source(3); + source = create_test_source(audio_streams, 3); ok(!!source, "Failed to create test source.\n"); hr = MFCreateSourceReaderFromMediaSource(source, NULL, &reader); @@ -1193,7 +1262,7 @@ static void test_source_reader_from_media_source(void) IMFMediaSource_Release(source); /* Request a non-native bit depth. */ - source = create_test_source(1); + source = create_test_source(audio_streams, 1); ok(!!source, "Failed to create test source.\n"); hr = MFCreateSourceReaderFromMediaSource(source, NULL, &reader); @@ -1241,7 +1310,7 @@ static void test_source_reader_from_media_source(void) IMFMediaSource_Release(source); /* Async mode. */ - source = create_test_source(3); + source = create_test_source(audio_streams, 3); ok(!!source, "Failed to create test source.\n"); callback = create_async_callback(); @@ -1286,7 +1355,7 @@ static void test_source_reader_from_media_source(void) IMFMediaSource_Release(source); /* RequestSample failure. */ - source = create_test_source(3); + source = create_test_source(audio_streams, 3); ok(!!source, "Failed to create test source.\n"); fail_request_sample = TRUE; @@ -1329,7 +1398,7 @@ static void test_source_reader_from_media_source(void) fail_request_sample = FALSE; /* MF_SOURCE_READER_ANY_STREAM with streams of different sample sizes */ - source = create_test_source(2); + source = create_test_source(audio_streams, 2); ok(!!source, "Failed to create test source.\n"); test_source = impl_from_IMFMediaSource(source); @@ -1359,19 +1428,32 @@ static void test_source_reader_from_media_source(void) IMFSourceReader_Release(reader); IMFMediaSource_Release(source); + + for (i = 0; i < ARRAY_SIZE(audio_streams); i++) + IMFStreamDescriptor_Release(audio_streams[i]); } static void test_reader_d3d9(void) { + static const struct attribute_desc audio_stream_type_desc[] = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio), + ATTR_GUID(MF_MT_SUBTYPE, MFAudioFormat_PCM), + ATTR_UINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 32), + {0}, + }; + + IMFStreamDescriptor *audio_streams[3]; IDirect3DDeviceManager9 *d3d9_manager; IDirect3DDevice9 *d3d9_device; IMFAttributes *attributes; + IMFMediaType *media_type; IMFSourceReader *reader; IMFMediaSource *source; IDirect3D9 *d3d9; HWND window; HRESULT hr; - UINT token; + UINT i, token; ULONG refcount; d3d9 = Direct3DCreate9(D3D_SDK_VERSION); @@ -1387,13 +1469,24 @@ static void test_reader_d3d9(void) goto done; } + for (i = 0; i < ARRAY_SIZE(audio_streams); i++) + { + hr = MFCreateMediaType(&media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + init_media_type(media_type, audio_stream_type_desc, -1); + + hr = MFCreateStreamDescriptor(i, 1, &media_type, &audio_streams[i]); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaType_Release(media_type); + } + hr = DXVA2CreateDirect3DDeviceManager9(&token, &d3d9_manager); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); hr = IDirect3DDeviceManager9_ResetDevice(d3d9_manager, d3d9_device, token); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - source = create_test_source(3); + source = create_test_source(audio_streams, 3); ok(!!source, "Failed to create test source.\n"); hr = MFCreateAttributes(&attributes, 1); @@ -1409,11 +1502,13 @@ static void test_reader_d3d9(void) IMFSourceReader_Release(reader); + for (i = 0; i < ARRAY_SIZE(audio_streams); i++) + IMFStreamDescriptor_Release(audio_streams[i]); + refcount = IDirect3DDeviceManager9_Release(d3d9_manager); ok(!refcount, "Unexpected refcount %lu.\n", refcount); IDirect3DDevice9_Release(d3d9_device); - done: IDirect3D9_Release(d3d9); DestroyWindow(window); @@ -1511,12 +1606,34 @@ static void test_sink_writer_mp4(void) static void test_interfaces(void) { + static const struct attribute_desc audio_stream_type_desc[] = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio), + ATTR_GUID(MF_MT_SUBTYPE, MFAudioFormat_PCM), + ATTR_UINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 32), + {0}, + }; + + IMFStreamDescriptor *audio_streams[1]; + IMFMediaType *media_type; IMFSourceReader *reader; IMFMediaSource *source; IUnknown *unk; HRESULT hr; + UINT i; + + for (i = 0; i < ARRAY_SIZE(audio_streams); i++) + { + hr = MFCreateMediaType(&media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + init_media_type(media_type, audio_stream_type_desc, -1); + + hr = MFCreateStreamDescriptor(i, 1, &media_type, &audio_streams[i]); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaType_Release(media_type); + } - source = create_test_source(1); + source = create_test_source(audio_streams, 1); ok(!!source, "Failed to create test source.\n"); hr = MFCreateSourceReaderFromMediaSource(source, NULL, &reader); @@ -1529,6 +1646,596 @@ static void test_interfaces(void) IMFSourceReader_Release(reader); IMFMediaSource_Release(source); + + for (i = 0; i < ARRAY_SIZE(audio_streams); i++) + IMFStreamDescriptor_Release(audio_streams[i]); +} + +static void test_source_reader_transforms(BOOL enable_processing, BOOL enable_advanced) +{ + static const struct attribute_desc h264_stream_type_desc[] = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_H264), + ATTR_RATIO(MF_MT_FRAME_SIZE, 96, 96), + {0}, + }; + static const struct attribute_desc nv12_stream_type_desc[] = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_NV12), + ATTR_RATIO(MF_MT_FRAME_SIZE, 96, 96), + {0}, + }; + static const struct attribute_desc nv12_expect_desc[] = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_NV12), + ATTR_RATIO(MF_MT_FRAME_SIZE, 96, 96), + ATTR_RATIO(MF_MT_FRAME_RATE, 30000, 1001), + ATTR_RATIO(MF_MT_PIXEL_ASPECT_RATIO, 1, 1), + ATTR_UINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1), + ATTR_UINT32(MF_MT_AVG_BIT_ERROR_RATE, 0, .todo = TRUE), + ATTR_UINT32(MF_MT_COMPRESSED, 0, .todo = TRUE), + ATTR_UINT32(MF_MT_DEFAULT_STRIDE, 96), + ATTR_UINT32(MF_MT_FIXED_SIZE_SAMPLES, 1), + ATTR_UINT32(MF_MT_INTERLACE_MODE, 7), + ATTR_UINT32(MF_MT_SAMPLE_SIZE, 13824), + ATTR_UINT32(MF_MT_VIDEO_ROTATION, 0), + {0}, + }; + static const struct attribute_desc nv12_expect_advanced_desc[] = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_NV12, .todo_value = TRUE), + ATTR_RATIO(MF_MT_FRAME_SIZE, 96, 96), + ATTR_UINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1, .todo = TRUE), + ATTR_UINT32(MF_MT_COMPRESSED, 0, .todo = TRUE), + ATTR_UINT32(MF_MT_INTERLACE_MODE, 2, .todo = TRUE), + {0}, + }; + static const struct attribute_desc yuy2_stream_type_desc[] = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_YUY2), + ATTR_RATIO(MF_MT_FRAME_SIZE, 96, 96), + {0}, + }; + static const struct attribute_desc yuy2_expect_desc[] = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_YUY2), + ATTR_RATIO(MF_MT_FRAME_SIZE, 96, 96), + ATTR_RATIO(MF_MT_FRAME_RATE, 30000, 1001), + ATTR_RATIO(MF_MT_PIXEL_ASPECT_RATIO, 1, 1), + ATTR_UINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1), + ATTR_UINT32(MF_MT_AVG_BIT_ERROR_RATE, 0, .todo = TRUE), + ATTR_UINT32(MF_MT_COMPRESSED, 0, .todo = TRUE), + ATTR_UINT32(MF_MT_DEFAULT_STRIDE, 192), + ATTR_UINT32(MF_MT_FIXED_SIZE_SAMPLES, 1), + ATTR_UINT32(MF_MT_INTERLACE_MODE, 7), + ATTR_UINT32(MF_MT_SAMPLE_SIZE, 18432), + ATTR_UINT32(MF_MT_VIDEO_ROTATION, 0), + {0}, + }; + static const struct attribute_desc yuy2_expect_advanced_desc[] = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_YUY2, .todo_value = TRUE), + ATTR_RATIO(MF_MT_FRAME_SIZE, 96, 96), + ATTR_UINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1, .todo = TRUE), + ATTR_UINT32(MF_MT_COMPRESSED, 0, .todo = TRUE), + ATTR_UINT32(MF_MT_INTERLACE_MODE, 2, .todo = TRUE), + {0}, + }; + static const struct attribute_desc rgb32_stream_type_desc[] = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32), + ATTR_RATIO(MF_MT_FRAME_SIZE, 96, 96), + {0}, + }; + static const struct attribute_desc rgb32_expect_desc[] = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32, .todo_value = TRUE), + ATTR_RATIO(MF_MT_FRAME_SIZE, 96, 96), + ATTR_UINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1, .todo = TRUE), + ATTR_UINT32(MF_MT_DEFAULT_STRIDE, 384, .todo = TRUE), + ATTR_UINT32(MF_MT_INTERLACE_MODE, 2, .todo = TRUE), + ATTR_UINT32(MF_MT_SAMPLE_SIZE, 36864, .todo = TRUE), + {0}, + }; + static const struct attribute_desc rgb32_expect_advanced_desc[] = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32, .todo_value = TRUE), + ATTR_RATIO(MF_MT_FRAME_SIZE, 96, 96), + ATTR_UINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1, .todo = TRUE), + ATTR_UINT32(MF_MT_COMPRESSED, 0, .todo = TRUE), + ATTR_UINT32(MF_MT_INTERLACE_MODE, 2, .todo = TRUE), + }; + IMFStreamDescriptor *video_stream; + IMFSourceReaderEx *reader_ex; + IMFAttributes *attributes; + IMFMediaType *media_type; + IMFSourceReader *reader; + IMFTransform *transform; + IMFMediaSource *source; + GUID category; + HRESULT hr; + + winetest_push_context("vp %u adv %u", enable_processing, enable_advanced); + + hr = MFCreateAttributes(&attributes, 1); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFAttributes_SetUINT32(attributes, &MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, enable_processing); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFAttributes_SetUINT32(attributes, &MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, enable_advanced); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* test source reader with a RGB32 source */ + + hr = MFCreateMediaType(&media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + init_media_type(media_type, rgb32_stream_type_desc, -1); + hr = MFCreateStreamDescriptor(0, 1, &media_type, &video_stream); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaType_Release(media_type); + + source = create_test_source(&video_stream, 1); + ok(!!source, "Failed to create test source.\n"); + + hr = MFCreateSourceReaderFromMediaSource(source, attributes, &reader); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFSourceReader_SetStreamSelection(reader, 0, TRUE); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* skip tests on Win7 which misses IMFSourceReaderEx and has uninteresting media types differences */ + hr = IMFSourceReader_QueryInterface(reader, &IID_IMFSourceReaderEx, (void **)&reader_ex); + ok(hr == S_OK || broken(hr == E_NOINTERFACE) /* Win7 */, "Unexpected hr %#lx.\n", hr); + if (broken(hr == E_NOINTERFACE)) + { + win_skip("missing IMFSourceReaderEx interface, skipping tests on Win7\n"); + goto skip_tests; + } + IMFSourceReaderEx_Release(reader_ex); + + hr = IMFSourceReader_GetNativeMediaType(reader, 0, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, rgb32_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + hr = IMFSourceReader_GetCurrentMediaType(reader, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, rgb32_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + /* only one stream type and only one stream */ + hr = IMFSourceReader_GetNativeMediaType(reader, 0, 1, &media_type); + ok(hr == MF_E_NO_MORE_TYPES, "Unexpected hr %#lx.\n", hr); + hr = IMFSourceReader_GetNativeMediaType(reader, 1, 0, &media_type); + ok(hr == MF_E_INVALIDSTREAMNUMBER, "Unexpected hr %#lx.\n", hr); + hr = IMFSourceReader_GetCurrentMediaType(reader, 1, &media_type); + ok(hr == MF_E_INVALIDSTREAMNUMBER, "Unexpected hr %#lx.\n", hr); + + /* cannot request encoding to compressed media type */ + hr = MFCreateMediaType(&media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + init_media_type(media_type, h264_stream_type_desc, -1); + hr = IMFSourceReader_SetCurrentMediaType(reader, 0, NULL, media_type); + ok(hr == MF_E_TOPO_CODEC_NOT_FOUND, "Unexpected hr %#lx.\n", hr); + IMFMediaType_Release(media_type); + + /* SetCurrentMediaType needs major type and subtype */ + hr = MFCreateMediaType(&media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFSourceReader_SetCurrentMediaType(reader, 0, NULL, media_type); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + init_media_type(media_type, nv12_stream_type_desc, 1); + hr = IMFSourceReader_SetCurrentMediaType(reader, 0, NULL, media_type); + todo_wine ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + + /* RGB32 -> NV12 conversion with MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING */ + init_media_type(media_type, nv12_stream_type_desc, 2); /* doesn't need the frame size */ + hr = IMFSourceReader_SetCurrentMediaType(reader, 0, NULL, media_type); + if (enable_advanced) + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + else + ok(hr == MF_E_TOPO_CODEC_NOT_FOUND, "Unexpected hr %#lx.\n", hr); + IMFMediaType_Release(media_type); + + hr = IMFSourceReader_GetCurrentMediaType(reader, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (enable_advanced) + check_media_type(media_type, nv12_expect_advanced_desc, -1); + else + check_media_type(media_type, rgb32_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + /* video processor is accessible with MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING */ + hr = IMFSourceReader_GetServiceForStream(reader, 0, &GUID_NULL, &IID_IMFTransform, (void **)&transform); + if (!enable_advanced) + ok(hr == E_NOINTERFACE, "Unexpected hr %#lx.\n", hr); + else + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + { + hr = IMFTransform_GetInputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, rgb32_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + hr = IMFTransform_GetOutputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, nv12_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + IMFTransform_Release(transform); + } + + IMFSourceReader_Release(reader); + IMFMediaSource_Release(source); + IMFStreamDescriptor_Release(video_stream); + + + /* test source reader with a NV12 source */ + + hr = MFCreateMediaType(&media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + init_media_type(media_type, nv12_stream_type_desc, -1); + hr = MFCreateStreamDescriptor(0, 1, &media_type, &video_stream); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaType_Release(media_type); + + source = create_test_source(&video_stream, 1); + ok(!!source, "Failed to create test source.\n"); + + hr = MFCreateSourceReaderFromMediaSource(source, attributes, &reader); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFSourceReader_SetStreamSelection(reader, 0, TRUE); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFSourceReader_GetNativeMediaType(reader, 0, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, nv12_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + hr = IMFSourceReader_GetCurrentMediaType(reader, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, nv12_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + /* NV12 -> RGB32 conversion with MF_SOURCE_READER_ENABLE_(ADVANCED_)VIDEO_PROCESSING */ + hr = MFCreateMediaType(&media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + init_media_type(media_type, rgb32_stream_type_desc, 2); /* doesn't need the frame size */ + hr = IMFSourceReader_SetCurrentMediaType(reader, 0, NULL, media_type); + if (enable_processing || enable_advanced) + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + else + ok(hr == MF_E_TOPO_CODEC_NOT_FOUND, "Unexpected hr %#lx.\n", hr); + IMFMediaType_Release(media_type); + + hr = IMFSourceReader_GetCurrentMediaType(reader, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (enable_advanced) + check_media_type(media_type, rgb32_expect_advanced_desc, -1); + else if (enable_processing) + check_media_type(media_type, rgb32_expect_desc, -1); + else + check_media_type(media_type, nv12_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + /* convert transform is only exposed with MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING */ + hr = IMFSourceReader_GetServiceForStream(reader, 0, &GUID_NULL, &IID_IMFTransform, (void **)&transform); + if (!enable_advanced) + ok(hr == E_NOINTERFACE, "Unexpected hr %#lx.\n", hr); + else + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + { + hr = IMFTransform_GetInputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, nv12_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + hr = IMFTransform_GetOutputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, rgb32_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + IMFTransform_Release(transform); + } + + /* NV12 -> YUY2 conversion with MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING */ + hr = MFCreateMediaType(&media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + init_media_type(media_type, yuy2_stream_type_desc, 2); /* doesn't need the frame size */ + hr = IMFSourceReader_SetCurrentMediaType(reader, 0, NULL, media_type); + if (enable_advanced) + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + else + ok(hr == MF_E_TOPO_CODEC_NOT_FOUND, "Unexpected hr %#lx.\n", hr); + IMFMediaType_Release(media_type); + + hr = IMFSourceReader_GetCurrentMediaType(reader, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (enable_advanced) + check_media_type(media_type, yuy2_expect_advanced_desc, -1); + else if (enable_processing) + check_media_type(media_type, rgb32_expect_desc, -1); + else + check_media_type(media_type, nv12_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + /* convert transform is only exposed with MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING */ + hr = IMFSourceReader_GetServiceForStream(reader, 0, &GUID_NULL, &IID_IMFTransform, (void **)&transform); + if (!enable_advanced) + ok(hr == E_NOINTERFACE, "Unexpected hr %#lx.\n", hr); + else + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + { + hr = IMFTransform_GetInputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, nv12_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + hr = IMFTransform_GetOutputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, yuy2_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + IMFTransform_Release(transform); + } + + hr = IMFSourceReader_QueryInterface(reader, &IID_IMFSourceReaderEx, (void **)&reader_ex); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFSourceReaderEx_GetTransformForStream(reader_ex, 0, 0, &category, &transform); + if (!enable_advanced) + todo_wine ok(hr == MF_E_INVALIDINDEX, "Unexpected hr %#lx.\n", hr); + else + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + { + hr = IMFTransform_GetInputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, nv12_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + hr = IMFTransform_GetOutputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, yuy2_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + IMFTransform_Release(transform); + } + + hr = IMFSourceReaderEx_GetTransformForStream(reader_ex, 0, 1, &category, &transform); + todo_wine ok(hr == MF_E_INVALIDINDEX, "Unexpected hr %#lx.\n", hr); + IMFSourceReaderEx_Release(reader_ex); + + IMFSourceReader_Release(reader); + IMFMediaSource_Release(source); + IMFStreamDescriptor_Release(video_stream); + + + /* test source reader with a H264 source */ + + hr = MFCreateMediaType(&media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + init_media_type(media_type, h264_stream_type_desc, -1); + hr = MFCreateStreamDescriptor(0, 1, &media_type, &video_stream); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaType_Release(media_type); + + source = create_test_source(&video_stream, 1); + ok(!!source, "Failed to create test source.\n"); + + hr = MFCreateSourceReaderFromMediaSource(source, attributes, &reader); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFSourceReader_SetStreamSelection(reader, 0, TRUE); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFSourceReader_GetNativeMediaType(reader, 0, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, h264_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + hr = IMFSourceReader_GetCurrentMediaType(reader, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, h264_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + /* when H264 output is used, there's no decoder transform */ + hr = IMFSourceReader_GetServiceForStream(reader, 0, &GUID_NULL, &IID_IMFTransform, (void **)&transform); + ok(hr == E_NOINTERFACE, "Unexpected hr %#lx.\n", hr); + + /* H264 -> RGB32 conversion with MF_SOURCE_READER_ENABLE_(ADVANCED_)VIDEO_PROCESSING */ + hr = MFCreateMediaType(&media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + init_media_type(media_type, rgb32_stream_type_desc, 2); /* doesn't need the frame size */ + hr = IMFSourceReader_SetCurrentMediaType(reader, 0, NULL, media_type); + if (enable_processing || enable_advanced) + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + else + todo_wine ok(hr == MF_E_INVALIDMEDIATYPE, "Unexpected hr %#lx.\n", hr); + IMFMediaType_Release(media_type); + + hr = IMFSourceReader_GetCurrentMediaType(reader, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (enable_advanced) + check_media_type(media_type, rgb32_expect_advanced_desc, -1); + else if (enable_processing) + check_media_type(media_type, rgb32_expect_desc, -1); + else + check_media_type(media_type, h264_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + /* the exposed transform is the H264 decoder or the converter with MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING */ + hr = IMFSourceReader_GetServiceForStream(reader, 0, &GUID_NULL, &IID_IMFTransform, (void **)&transform); + if (!enable_processing && !enable_advanced) + ok(hr == E_NOINTERFACE, "Unexpected hr %#lx.\n", hr); + else + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + { + hr = IMFTransform_GetInputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (enable_advanced) + check_media_type(media_type, nv12_stream_type_desc, -1); + else + check_media_type(media_type, h264_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + /* with NV12 output */ + hr = IMFTransform_GetOutputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (enable_advanced) + check_media_type(media_type, rgb32_stream_type_desc, -1); + else + check_media_type(media_type, nv12_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + IMFTransform_Release(transform); + } + + /* H264 decoder transform is also available through the IMFSourceReaderEx interface */ + hr = IMFSourceReader_QueryInterface(reader, &IID_IMFSourceReaderEx, (void **)&reader_ex); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFSourceReaderEx_GetTransformForStream(reader_ex, 0, 0, &category, &transform); + if (!enable_processing && !enable_advanced) + todo_wine ok(hr == MF_E_INVALIDINDEX, "Unexpected hr %#lx.\n", hr); + else + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + { + hr = IMFTransform_GetInputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, h264_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + hr = IMFTransform_GetOutputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, nv12_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + IMFTransform_Release(transform); + } + + /* the video processor can be accessed at index 1 with MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING */ + hr = IMFSourceReaderEx_GetTransformForStream(reader_ex, 0, 1, &category, &transform); + if (!enable_advanced) + todo_wine ok(hr == MF_E_INVALIDINDEX, "Unexpected hr %#lx.\n", hr); + else + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + { + hr = IMFTransform_GetInputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, nv12_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + hr = IMFTransform_GetOutputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, rgb32_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + IMFTransform_Release(transform); + } + + hr = IMFSourceReaderEx_GetTransformForStream(reader_ex, 0, 2, &category, &transform); + todo_wine ok(hr == MF_E_INVALIDINDEX, "Unexpected hr %#lx.\n", hr); + IMFSourceReaderEx_Release(reader_ex); + + /* H264 -> NV12 conversion */ + hr = MFCreateMediaType(&media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + init_media_type(media_type, nv12_stream_type_desc, 2); /* doesn't need the frame size */ + hr = IMFSourceReader_SetCurrentMediaType(reader, 0, NULL, media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaType_Release(media_type); + + hr = IMFSourceReader_GetCurrentMediaType(reader, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, nv12_expect_desc, -1); + IMFMediaType_Release(media_type); + + /* the H264 decoder transform can now be accessed */ + hr = IMFSourceReader_GetServiceForStream(reader, 0, &GUID_NULL, &IID_IMFTransform, (void **)&transform); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFTransform_GetInputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, h264_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + hr = IMFTransform_GetOutputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, nv12_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + IMFTransform_Release(transform); + + /* YUY2 output works too */ + hr = MFCreateMediaType(&media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + init_media_type(media_type, yuy2_stream_type_desc, 2); /* doesn't need the frame size */ + hr = IMFSourceReader_SetCurrentMediaType(reader, 0, NULL, media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaType_Release(media_type); + + hr = IMFSourceReader_GetCurrentMediaType(reader, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, yuy2_expect_desc, -1); + IMFMediaType_Release(media_type); + + /* H264 decoder transform is also available through the IMFSourceReaderEx interface */ + hr = IMFSourceReader_QueryInterface(reader, &IID_IMFSourceReaderEx, (void **)&reader_ex); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFSourceReaderEx_GetTransformForStream(reader_ex, 0, 0, &category, &transform); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + { + hr = IMFTransform_GetInputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, h264_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + hr = IMFTransform_GetOutputCurrentType(transform, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, yuy2_stream_type_desc, -1); + IMFMediaType_Release(media_type); + + /* changing the transform media type doesn't change the reader output type */ + hr = MFCreateMediaType(&media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + init_media_type(media_type, nv12_stream_type_desc, -1); + hr = IMFTransform_SetOutputType(transform, 0, media_type, 0); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaType_Release(media_type); + + hr = IMFSourceReader_GetCurrentMediaType(reader, 0, &media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_media_type(media_type, yuy2_expect_desc, -1); + IMFMediaType_Release(media_type); + + IMFTransform_Release(transform); + } + + hr = IMFSourceReaderEx_GetTransformForStream(reader_ex, 0, 1, &category, &transform); + todo_wine ok(hr == MF_E_INVALIDINDEX, "Unexpected hr %#lx.\n", hr); + IMFSourceReaderEx_Release(reader_ex); + +skip_tests: + IMFSourceReader_Release(reader); + IMFMediaSource_Release(source); + IMFStreamDescriptor_Release(video_stream); + + IMFAttributes_Release(attributes); + + winetest_pop_context(); } START_TEST(mfplat) @@ -1545,6 +2252,9 @@ START_TEST(mfplat) test_source_reader("test.wav", false); test_source_reader("test.mp4", true); test_source_reader_from_media_source(); + test_source_reader_transforms(FALSE, FALSE); + test_source_reader_transforms(TRUE, FALSE); + test_source_reader_transforms(FALSE, TRUE); test_reader_d3d9(); test_sink_writer_create(); test_sink_writer_mp4(); diff --git a/dlls/mlang/mlang.c b/dlls/mlang/mlang.c index f424c87b860..c7e7f360f0b 100644 --- a/dlls/mlang/mlang.c +++ b/dlls/mlang/mlang.c @@ -44,6 +44,9 @@ WINE_DEFAULT_DEBUG_CHANNEL(mlang); #include "initguid.h" +static INIT_ONCE font_link_global_init_once = INIT_ONCE_STATIC_INIT; +static IUnknown *font_link_global = NULL; + static HRESULT MultiLanguage_create(IUnknown *pUnkOuter, LPVOID *ppObj); static HRESULT MLangConvertCharset_create(IUnknown *outer, void **obj); static HRESULT EnumRfc1766_create(LANGID LangId, IEnumRfc1766 **ppEnum); @@ -3333,23 +3336,20 @@ static HRESULT WINAPI fnIMLangFontLink2_GetStrCodePages( IMLangFontLink2* iface, for (i = 0; i < src_len; i++) { - DWORD cp, next_cp = 0; + DWORD cp = 0; HRESULT ret; ret = IMLangFontLink2_GetCharCodePages(iface, src[i], &cp); - if (i + 1 < src_len) - ret = IMLangFontLink2_GetCharCodePages(iface, src[i + 1], &next_cp); if (ret != S_OK) return E_FAIL; if (!cps) cps = cp; - else if ((cps & cp) != 0) cps &= cp; + else if ((cps & cp) != 0 && + !((priority_cp & cps) ^ (priority_cp & cp))) cps &= cp; else { i--; break; } - - if ((priority_cp & cps) && !(priority_cp & next_cp)) break; } if (codepages) *codepages = cps; @@ -3882,11 +3882,25 @@ HRESULT WINAPI DllCanUnloadNow(void) return dll_count == 0 ? S_OK : S_FALSE; } +static BOOL WINAPI allocate_font_link_cb(PINIT_ONCE init_once, PVOID args, PVOID *context) +{ + return SUCCEEDED(MultiLanguage_create(NULL, (void**)&font_link_global)); +} + HRESULT WINAPI GetGlobalFontLinkObject(void **unknown) { + TRACE("%p\n", unknown); + if (!unknown) return E_INVALIDARG; - FIXME("%p: stub\n", unknown); + if (!InitOnceExecuteOnce(&font_link_global_init_once, allocate_font_link_cb, NULL, NULL)) + { + ERR("Failed to create global font link object.\n"); + return E_FAIL; + } - return S_FALSE; + IUnknown_AddRef(font_link_global); + *unknown = font_link_global; + + return S_OK; } diff --git a/dlls/mlang/tests/mlang.c b/dlls/mlang/tests/mlang.c index ee5ef5af738..6bb973dc285 100644 --- a/dlls/mlang/tests/mlang.c +++ b/dlls/mlang/tests/mlang.c @@ -1324,6 +1324,14 @@ static void IMLangFontLink_Test(IMLangFontLink* iMLFL) ok(dwCodePages == dwCmpCodePages, "expected %lx, got %lx\n", dwCmpCodePages, dwCodePages); ok(processed == 2, "expected 2, got %ld\n", processed); + dwCmpCodePages = FS_JISJAPAN; + dwCodePages = 0; + processed = 0; + ret = IMLangFontLink_GetStrCodePages(iMLFL, L"\uff90a", 2, FS_LATIN1, &dwCodePages, &processed); + ok(ret == S_OK, "IMLangFontLink_GetStrCodePages error %lx\n", ret); + ok(dwCodePages == dwCmpCodePages, "expected %lx, got %lx\n", dwCmpCodePages, dwCodePages); + ok(processed == 1, "expected 1, got %ld\n", processed); + dwCodePages = 0xffff; processed = -1; ret = IMLangFontLink_GetStrCodePages(iMLFL, &str[2], 1, 0, &dwCodePages, &processed); @@ -2295,17 +2303,41 @@ static void test_GetGlobalFontLinkObject(void) { HRESULT ret; void *unknown; + LONG refcount; + IMLangFontLink2 *IMLFL2; + IMLangFontLink *IMLFL; + IMLangCodePages *IMLCP; + IMultiLanguage *IML; ret = GetGlobalFontLinkObject(NULL); ok(ret == E_INVALIDARG, "expected E_INVALIDARG got %#lx\n", ret); unknown = (void *)0xdeadbeef; ret = GetGlobalFontLinkObject(&unknown); -todo_wine { ok(ret == S_OK, "expected S_OK got %#lx\n", ret); ok(unknown != NULL && unknown != (void *)0xdeadbeef, "GetGlobalFontLinkObject() returned %p\n", unknown); - } + if (unknown == (void *)0xdeadbeef || !unknown) return; + + ret = IUnknown_QueryInterface((IUnknown*)unknown, &IID_IMLangFontLink2, (void**)&IMLFL2); + todo_wine ok(ret == E_NOINTERFACE, "expected E_NOINTERFACE got %#lx\n", ret); + if (ret == S_OK) IMLangFontLink2_Release(IMLFL2); + + ret = IUnknown_QueryInterface((IUnknown*)unknown, &IID_IMultiLanguage, (void**)&IML); + todo_wine ok(ret == E_NOINTERFACE, "expected E_NOINTERFACE got %#lx\n", ret); + if (ret == S_OK) IMultiLanguage_Release(IML); + + ret = IUnknown_QueryInterface((IUnknown*)unknown, &IID_IMLangFontLink, (void**)&IMLFL); + ok(ret == S_OK, "expected S_OK got %#lx\n", ret); + IMLangFontLink_Release(IMLFL); + + ret = IUnknown_QueryInterface((IUnknown*)unknown, &IID_IMLangCodePages, (void**)&IMLCP); + ok(ret == S_OK, "expected S_OK got %#lx\n", ret); + IMLangCodePages_Release(IMLCP); + + + refcount = IUnknown_Release((IUnknown*)unknown); + ok(refcount == 1, "Got refcount %ld\n", refcount); } static void test_IMLangConvertCharset(IMultiLanguage *ml) @@ -2778,6 +2810,8 @@ START_TEST(mlang) if (!init_function_ptrs()) return; + test_GetGlobalFontLinkObject(); + CoInitialize(NULL); test_Rfc1766ToLcid(); test_LcidToRfc1766(); @@ -2785,8 +2819,6 @@ START_TEST(mlang) test_ConvertINetUnicodeToMultiByte(); test_JapaneseConversion(); - test_GetGlobalFontLinkObject(); - trace("IMultiLanguage\n"); ret = CoCreateInstance(&CLSID_CMultiLanguage, NULL, CLSCTX_INPROC_SERVER, &IID_IMultiLanguage, (void **)&iML); diff --git a/dlls/mmdevapi/spatialaudio.c b/dlls/mmdevapi/spatialaudio.c index 23f3de31428..bc1b085f3cd 100644 --- a/dlls/mmdevapi/spatialaudio.c +++ b/dlls/mmdevapi/spatialaudio.c @@ -49,6 +49,36 @@ static UINT32 AudioObjectType_to_index(AudioObjectType type) return o - 2; } +static const char *debugstr_fmtex(const WAVEFORMATEX *fmt) +{ + static char buf[2048]; + + if (!fmt) + { + strcpy(buf, "(null)"); + } + else if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) + { + const WAVEFORMATEXTENSIBLE *fmtex = (const WAVEFORMATEXTENSIBLE *)fmt; + snprintf(buf, sizeof(buf), "tag: 0x%x (%s), ch: %u (mask: 0x%lx), rate: %lu, depth: %u", + fmt->wFormatTag, debugstr_guid(&fmtex->SubFormat), + fmt->nChannels, fmtex->dwChannelMask, fmt->nSamplesPerSec, + fmt->wBitsPerSample); + } + else + { + snprintf(buf, sizeof(buf), "tag: 0x%x, ch: %u, rate: %lu, depth: %u", + fmt->wFormatTag, fmt->nChannels, fmt->nSamplesPerSec, + fmt->wBitsPerSample); + } + return buf; +} + +static BOOL formats_equal(const WAVEFORMATEX *fmt1, const WAVEFORMATEX *fmt2) +{ + return !memcmp(fmt1, fmt2, sizeof(*fmt1) + fmt1->cbSize); +} + typedef struct SpatialAudioImpl SpatialAudioImpl; typedef struct SpatialAudioStreamImpl SpatialAudioStreamImpl; typedef struct SpatialAudioObjectImpl SpatialAudioObjectImpl; @@ -610,9 +640,20 @@ static HRESULT WINAPI SAC_GetMaxFrameCount(ISpatialAudioClient *iface, static HRESULT WINAPI SAC_IsAudioObjectFormatSupported(ISpatialAudioClient *iface, const WAVEFORMATEX *format) { - SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); - FIXME("(%p)->(%p)\n", This, format); - return E_NOTIMPL; + SpatialAudioImpl *sac = impl_from_ISpatialAudioClient(iface); + + TRACE("sac %p, format %s.\n", sac, debugstr_fmtex(format)); + + if (!format) + return E_POINTER; + + if (!formats_equal(&sac->object_fmtex.Format, format)) + { + FIXME("Reporting format %s as unsupported.\n", debugstr_fmtex(format)); + return E_INVALIDARG; + } + + return S_OK; } static HRESULT WINAPI SAC_IsSpatialAudioStreamAvailable(ISpatialAudioClient *iface, @@ -630,23 +671,6 @@ static WAVEFORMATEX *clone_fmtex(const WAVEFORMATEX *src) return r; } -static const char *debugstr_fmtex(const WAVEFORMATEX *fmt) -{ - static char buf[2048]; - if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ - const WAVEFORMATEXTENSIBLE *fmtex = (const WAVEFORMATEXTENSIBLE *)fmt; - snprintf(buf, sizeof(buf), "tag: 0x%x (%s), ch: %u (mask: 0x%lx), rate: %lu, depth: %u", - fmt->wFormatTag, debugstr_guid(&fmtex->SubFormat), - fmt->nChannels, fmtex->dwChannelMask, fmt->nSamplesPerSec, - fmt->wBitsPerSample); - }else{ - snprintf(buf, sizeof(buf), "tag: 0x%x, ch: %u, rate: %lu, depth: %u", - fmt->wFormatTag, fmt->nChannels, fmt->nSamplesPerSec, - fmt->wBitsPerSample); - } - return buf; -} - static void static_mask_to_channels(AudioObjectType static_mask, WORD *count, DWORD *mask, UINT32 *map) { UINT32 out_chan = 0, map_idx = 0; @@ -776,8 +800,7 @@ static HRESULT WINAPI SAC_ActivateSpatialAudioStream(ISpatialAudioClient *iface, return E_INVALIDARG; } - if(!params->ObjectFormat || - memcmp(params->ObjectFormat, &This->object_fmtex.Format, sizeof(*params->ObjectFormat) + params->ObjectFormat->cbSize)){ + if(!(params->ObjectFormat && formats_equal(params->ObjectFormat, &This->object_fmtex.Format))) { *stream = NULL; return AUDCLNT_E_UNSUPPORTED_FORMAT; } diff --git a/dlls/mmdevapi/tests/spatialaudio.c b/dlls/mmdevapi/tests/spatialaudio.c index a382b57e7a2..4d8073d49ce 100644 --- a/dlls/mmdevapi/tests/spatialaudio.c +++ b/dlls/mmdevapi/tests/spatialaudio.c @@ -64,6 +64,27 @@ static void test_formats(void) ok(fmt->nAvgBytesPerSec == 192000, "Wrong avg bytes per sec, expected 192000 got %lu\n", fmt->nAvgBytesPerSec); ok(fmt->cbSize == 0, "Wrong cbSize for simple format, expected 0, got %hu\n", fmt->cbSize); + hr = ISpatialAudioClient_IsAudioObjectFormatSupported(sac, NULL); + ok(hr == E_POINTER, "Got %#lx.\n", hr); + + memcpy(&format, fmt, sizeof(format)); + hr = ISpatialAudioClient_IsAudioObjectFormatSupported(sac, &format); + ok(hr == S_OK, "Got %#lx.\n", hr); + + format.nBlockAlign *= 2; + hr = ISpatialAudioClient_IsAudioObjectFormatSupported(sac, &format); + todo_wine ok(hr == S_OK, "Got %#lx.\n", hr); + + memcpy(&format, fmt, sizeof(format)); + format.wBitsPerSample *= 2; + hr = ISpatialAudioClient_IsAudioObjectFormatSupported(sac, &format); + ok(hr == E_INVALIDARG, "Got %#lx.\n", hr); + + memcpy(&format, fmt, sizeof(format)); + format.nChannels = 2; + hr = ISpatialAudioClient_IsAudioObjectFormatSupported(sac, &format); + ok(hr == E_INVALIDARG, "Got %#lx.\n", hr); + memcpy(&format, fmt, sizeof(format)); IAudioFormatEnumerator_Release(afe); diff --git a/dlls/mscoree/corruntimehost.c b/dlls/mscoree/corruntimehost.c index c8d384730c0..f931473de7a 100644 --- a/dlls/mscoree/corruntimehost.c +++ b/dlls/mscoree/corruntimehost.c @@ -814,8 +814,12 @@ static ULONG WINAPI CLRRuntimeHost_Release(ICLRRuntimeHost* iface) static HRESULT WINAPI CLRRuntimeHost_Start(ICLRRuntimeHost* iface) { - FIXME("(%p)\n", iface); - return E_NOTIMPL; + RuntimeHost *This = impl_from_ICLRRuntimeHost( iface ); + MonoDomain *dummy; + + TRACE("%p\n", This); + + return RuntimeHost_GetDefaultDomain(This, NULL, &dummy); } static HRESULT WINAPI CLRRuntimeHost_Stop(ICLRRuntimeHost* iface) diff --git a/dlls/mscoree/mscoree_private.h b/dlls/mscoree/mscoree_private.h index 06db53136e1..f27ff95be2d 100644 --- a/dlls/mscoree/mscoree_private.h +++ b/dlls/mscoree/mscoree_private.h @@ -45,7 +45,7 @@ extern HRESULT assembly_get_runtime_version(ASSEMBLY *assembly, LPSTR *version); extern HRESULT assembly_get_vtable_fixups(ASSEMBLY *assembly, VTableFixup **fixups, DWORD *count); extern HRESULT assembly_get_native_entrypoint(ASSEMBLY *assembly, NativeEntryPointFunc *func); -#define WINE_MONO_VERSION "8.1.0" +#define WINE_MONO_VERSION "9.0.0" /* Mono embedding */ typedef struct _MonoDomain MonoDomain; diff --git a/dlls/mshtml/htmlanchor.c b/dlls/mshtml/htmlanchor.c index 7d2ad328890..147a5b9cbe6 100644 --- a/dlls/mshtml/htmlanchor.c +++ b/dlls/mshtml/htmlanchor.c @@ -829,13 +829,13 @@ static void HTMLAnchorElement_unlink(DispatchEx *dispex) unlink_ref(&This->nsanchor); } -static HRESULT HTMLAnchorElement_handle_event(DispatchEx *dispex, eventid_t eid, nsIDOMEvent *event, BOOL *prevent_default) +static HRESULT HTMLAnchorElement_handle_event(DispatchEx *dispex, DOMEvent *event, BOOL *prevent_default) { HTMLAnchorElement *This = impl_from_DispatchEx(dispex); nsAString href_str, target_str; nsresult nsres; - if(eid == EVENTID_CLICK) { + if(event->event_id == EVENTID_CLICK) { nsAString_Init(&href_str, NULL); nsres = nsIDOMHTMLAnchorElement_GetHref(This->nsanchor, &href_str); if (NS_FAILED(nsres)) { @@ -850,14 +850,14 @@ static HRESULT HTMLAnchorElement_handle_event(DispatchEx *dispex, eventid_t eid, goto fallback; } - return handle_link_click_event(&This->element, &href_str, &target_str, event, prevent_default); + return handle_link_click_event(&This->element, &href_str, &target_str, event->nsevent, prevent_default); fallback: nsAString_Finish(&href_str); nsAString_Finish(&target_str); } - return HTMLElement_handle_event(&This->element.node.event_target.dispex, eid, event, prevent_default); + return HTMLElement_handle_event(&This->element.node.event_target.dispex, event, prevent_default); } static const NodeImplVtbl HTMLAnchorElementImplVtbl = { diff --git a/dlls/mshtml/htmlarea.c b/dlls/mshtml/htmlarea.c index c4cb109039a..1b9be40baab 100644 --- a/dlls/mshtml/htmlarea.c +++ b/dlls/mshtml/htmlarea.c @@ -439,13 +439,13 @@ static void HTMLAreaElement_unlink(DispatchEx *dispex) unlink_ref(&This->nsarea); } -static HRESULT HTMLAreaElement_handle_event(DispatchEx *dispex, eventid_t eid, nsIDOMEvent *event, BOOL *prevent_default) +static HRESULT HTMLAreaElement_handle_event(DispatchEx *dispex, DOMEvent *event, BOOL *prevent_default) { HTMLAreaElement *This = impl_from_DispatchEx(dispex); nsAString href_str, target_str; nsresult nsres; - if(eid == EVENTID_CLICK) { + if(event->event_id == EVENTID_CLICK) { nsAString_Init(&href_str, NULL); nsres = nsIDOMHTMLAreaElement_GetHref(This->nsarea, &href_str); if (NS_FAILED(nsres)) { @@ -460,14 +460,14 @@ static HRESULT HTMLAreaElement_handle_event(DispatchEx *dispex, eventid_t eid, n goto fallback; } - return handle_link_click_event(&This->element, &href_str, &target_str, event, prevent_default); + return handle_link_click_event(&This->element, &href_str, &target_str, event->nsevent, prevent_default); fallback: nsAString_Finish(&href_str); nsAString_Finish(&target_str); } - return HTMLElement_handle_event(&This->element.node.event_target.dispex, eid, event, prevent_default); + return HTMLElement_handle_event(&This->element.node.event_target.dispex, event, prevent_default); } static const NodeImplVtbl HTMLAreaElementImplVtbl = { diff --git a/dlls/mshtml/htmldoc.c b/dlls/mshtml/htmldoc.c index 6b5d73c0604..69c739001a6 100644 --- a/dlls/mshtml/htmldoc.c +++ b/dlls/mshtml/htmldoc.c @@ -6333,6 +6333,38 @@ static HRESULT HTMLDocumentNode_location_hook(DispatchEx *dispex, WORD flags, DI DISPID_IHTMLWINDOW2_LOCATION, 0, flags, dp, res, ei, caller); } +static HRESULT HTMLDocumentNode_pre_handle_event(DispatchEx* dispex, DOMEvent *event) +{ + HTMLDocumentNode *doc = impl_from_DispatchEx(dispex); + switch(event->event_id) { + case EVENTID_DOMCONTENTLOADED: { + if(event->trusted && doc->window) + doc->window->dom_content_loaded_event_start_time = get_time_stamp(); + break; + } + default: + break; + } + + return S_OK; +} + +static HRESULT HTMLDocumentNode_handle_event(DispatchEx* dispex, DOMEvent *event, BOOL *prevent_default) +{ + HTMLDocumentNode *doc = impl_from_DispatchEx(dispex); + switch(event->event_id) { + case EVENTID_DOMCONTENTLOADED: { + if(event->trusted && doc->window) + doc->window->dom_content_loaded_event_end_time = get_time_stamp(); + break; + } + default: + break; + } + + return S_OK; +} + static const event_target_vtbl_t HTMLDocumentNode_event_target_vtbl = { { .query_interface = HTMLDocumentNode_query_interface, @@ -6348,8 +6380,10 @@ static const event_target_vtbl_t HTMLDocumentNode_event_target_vtbl = { .get_gecko_target = HTMLDocumentNode_get_gecko_target, .bind_event = HTMLDocumentNode_bind_event, .get_parent_event_target = HTMLDocumentNode_get_parent_event_target, + .pre_handle_event = HTMLDocumentNode_pre_handle_event, + .handle_event = HTMLDocumentNode_handle_event, .get_cp_container = HTMLDocumentNode_get_cp_container, - .set_current_event = HTMLDocumentNode_set_current_event + .set_current_event = HTMLDocumentNode_set_current_event, }; static const NodeImplVtbl HTMLDocumentFragmentImplVtbl = { diff --git a/dlls/mshtml/htmlelem.c b/dlls/mshtml/htmlelem.c index 6fcab0cf89f..cbec1368eaa 100644 --- a/dlls/mshtml/htmlelem.c +++ b/dlls/mshtml/htmlelem.c @@ -7083,16 +7083,16 @@ void HTMLElement_bind_event(DispatchEx *dispex, eventid_t eid) ensure_doc_nsevent_handler(This->node.doc, This->node.nsnode, eid); } -HRESULT HTMLElement_handle_event(DispatchEx *dispex, eventid_t eid, nsIDOMEvent *event, BOOL *prevent_default) +HRESULT HTMLElement_handle_event(DispatchEx *dispex, DOMEvent *event, BOOL *prevent_default) { HTMLElement *This = impl_from_DispatchEx(dispex); - switch(eid) { + switch(event->event_id) { case EVENTID_KEYDOWN: { nsIDOMKeyEvent *key_event; nsresult nsres; - nsres = nsIDOMEvent_QueryInterface(event, &IID_nsIDOMKeyEvent, (void**)&key_event); + nsres = nsIDOMEvent_QueryInterface(event->nsevent, &IID_nsIDOMKeyEvent, (void**)&key_event); if(NS_SUCCEEDED(nsres)) { UINT32 code = 0; diff --git a/dlls/mshtml/htmlevent.c b/dlls/mshtml/htmlevent.c index bba0970d7ce..f6f21d7a9eb 100644 --- a/dlls/mshtml/htmlevent.c +++ b/dlls/mshtml/htmlevent.c @@ -117,7 +117,7 @@ typedef struct { /* Keep these sorted case sensitively */ static const event_info_t event_info[] = { {L"DOMContentLoaded", EVENT_TYPE_EVENT, 0, - EVENT_BUBBLES | EVENT_CANCELABLE}, + EVENT_DEFAULTLISTENER | EVENT_HASDEFAULTHANDLERS | EVENT_BUBBLES | EVENT_CANCELABLE }, {L"abort", EVENT_TYPE_EVENT, DISPID_EVMETH_ONABORT, EVENT_BIND_TO_TARGET}, {L"afterprint", EVENT_TYPE_EVENT, DISPID_EVMETH_ONAFTERPRINT, @@ -5132,6 +5132,18 @@ static HRESULT dispatch_event_object(EventTarget *event_target, DOMEvent *event, IEventTarget_AddRef(&event_target->IEventTarget_iface); event->phase = DEP_CAPTURING_PHASE; + + if(event_info[event->event_id].flags & EVENT_HASDEFAULTHANDLERS) { + for(i = 0; i < chain_cnt; i++) { + vtbl = dispex_get_vtbl(&target_chain[i]->dispex); + if(!vtbl->pre_handle_event) + continue; + hres = vtbl->pre_handle_event(&target_chain[i]->dispex, event); + if(FAILED(hres) || event->stop_propagation) + break; + } + } + i = chain_cnt-1; while(!event->stop_propagation && i) call_event_handlers(target_chain[i--], event, dispatch_mode); @@ -5165,7 +5177,7 @@ static HRESULT dispatch_event_object(EventTarget *event_target, DOMEvent *event, vtbl = dispex_get_vtbl(&target_chain[i]->dispex); if(!vtbl->handle_event) continue; - hres = vtbl->handle_event(&target_chain[i]->dispex, event->event_id, event->nsevent, &prevent_default); + hres = vtbl->handle_event(&target_chain[i]->dispex, event, &prevent_default); if(FAILED(hres) || event->stop_propagation) break; if(prevent_default) diff --git a/dlls/mshtml/htmlevent.h b/dlls/mshtml/htmlevent.h index 2933314292e..f2cbfe18bf2 100644 --- a/dlls/mshtml/htmlevent.h +++ b/dlls/mshtml/htmlevent.h @@ -133,7 +133,8 @@ typedef struct { nsISupports *(*get_gecko_target)(DispatchEx*); void (*bind_event)(DispatchEx*,eventid_t); EventTarget *(*get_parent_event_target)(DispatchEx*); - HRESULT (*handle_event)(DispatchEx*,eventid_t,nsIDOMEvent*,BOOL*); + HRESULT (*pre_handle_event)(DispatchEx*,DOMEvent*); + HRESULT (*handle_event)(DispatchEx*,DOMEvent*,BOOL*); ConnectionPointContainer *(*get_cp_container)(DispatchEx*); IHTMLEventObj *(*set_current_event)(DispatchEx*,IHTMLEventObj*); } event_target_vtbl_t; @@ -143,7 +144,7 @@ IHTMLEventObj *default_set_current_event(HTMLInnerWindow*,IHTMLEventObj*); nsISupports *HTMLElement_get_gecko_target(DispatchEx*); void HTMLElement_bind_event(DispatchEx*,eventid_t); EventTarget *HTMLElement_get_parent_event_target(DispatchEx*); -HRESULT HTMLElement_handle_event(DispatchEx*,eventid_t,nsIDOMEvent*,BOOL*); +HRESULT HTMLElement_handle_event(DispatchEx*,DOMEvent*,BOOL*); ConnectionPointContainer *HTMLElement_get_cp_container(DispatchEx*); IHTMLEventObj *HTMLElement_set_current_event(DispatchEx*,IHTMLEventObj*); diff --git a/dlls/mshtml/htmlform.c b/dlls/mshtml/htmlform.c index 3b8f0cb4e43..019f5cb1a5f 100644 --- a/dlls/mshtml/htmlform.c +++ b/dlls/mshtml/htmlform.c @@ -1061,16 +1061,16 @@ static HRESULT HTMLFormElement_invoke(DispatchEx *dispex, IDispatch *this_obj, D return S_OK; } -static HRESULT HTMLFormElement_handle_event(DispatchEx *dispex, eventid_t eid, nsIDOMEvent *event, BOOL *prevent_default) +static HRESULT HTMLFormElement_handle_event(DispatchEx *dispex, DOMEvent *event, BOOL *prevent_default) { HTMLFormElement *This = impl_from_DispatchEx(dispex); - if(eid == EVENTID_SUBMIT) { + if(event->event_id == EVENTID_SUBMIT) { *prevent_default = TRUE; return IHTMLFormElement_submit(&This->IHTMLFormElement_iface); } - return HTMLElement_handle_event(&This->element.node.event_target.dispex, eid, event, prevent_default); + return HTMLElement_handle_event(&This->element.node.event_target.dispex, event, prevent_default); } static const NodeImplVtbl HTMLFormElementImplVtbl = { diff --git a/dlls/mshtml/nsevents.c b/dlls/mshtml/nsevents.c index c229a2c634f..9a23d031d72 100644 --- a/dlls/mshtml/nsevents.c +++ b/dlls/mshtml/nsevents.c @@ -48,7 +48,6 @@ typedef struct { static nsresult handle_blur(HTMLDocumentNode*,nsIDOMEvent*); static nsresult handle_focus(HTMLDocumentNode*,nsIDOMEvent*); static nsresult handle_keypress(HTMLDocumentNode*,nsIDOMEvent*); -static nsresult handle_dom_content_loaded(HTMLDocumentNode*,nsIDOMEvent*); static nsresult handle_pageshow(HTMLDocumentNode*,nsIDOMEvent*); static nsresult handle_pagehide(HTMLDocumentNode*,nsIDOMEvent*); static nsresult handle_load(HTMLDocumentNode*,nsIDOMEvent*); @@ -68,7 +67,6 @@ static const struct { { EVENTID_BLUR, 0, handle_blur }, { EVENTID_FOCUS, 0, handle_focus }, { EVENTID_KEYPRESS, BUBBLES, handle_keypress }, - { EVENTID_DOMCONTENTLOADED, OVERRIDE, handle_dom_content_loaded }, { EVENTID_PAGESHOW, OVERRIDE, handle_pageshow }, { EVENTID_PAGEHIDE, OVERRIDE, handle_pagehide }, { EVENTID_LOAD, OVERRIDE, handle_load }, @@ -234,26 +232,6 @@ static nsresult handle_keypress(HTMLDocumentNode *doc, nsIDOMEvent *event) return NS_OK; } -static nsresult handle_dom_content_loaded(HTMLDocumentNode *doc, nsIDOMEvent *nsevent) -{ - DOMEvent *event; - HRESULT hres; - - if(doc->window) - doc->window->dom_content_loaded_event_start_time = get_time_stamp(); - - hres = create_event_from_nsevent(nsevent, get_inner_window(doc), dispex_compat_mode(&doc->node.event_target.dispex), &event); - if(SUCCEEDED(hres)) { - dispatch_event(&doc->node.event_target, event); - IDOMEvent_Release(&event->IDOMEvent_iface); - } - - if(doc->window) - doc->window->dom_content_loaded_event_end_time = get_time_stamp(); - - return NS_OK; -} - static nsresult handle_pageshow(HTMLDocumentNode *doc, nsIDOMEvent *nsevent) { HTMLInnerWindow *window; @@ -438,6 +416,7 @@ static nsresult handle_htmlevent(HTMLDocumentNode *doc, nsIDOMEvent *nsevent) nsIDOMEventTarget *event_target; EventTarget *target; nsIDOMNode *nsnode; + HTMLDOMNode *node = NULL; DOMEvent *event; nsresult nsres; HRESULT hres; @@ -458,7 +437,6 @@ static nsresult handle_htmlevent(HTMLDocumentNode *doc, nsIDOMEvent *nsevent) target = &doc->window->event_target; IHTMLWindow2_AddRef(&doc->window->base.IHTMLWindow2_iface); }else { - HTMLDOMNode *node; hres = get_node(nsnode, TRUE, &node); nsIDOMNode_Release(nsnode); if(FAILED(hres)) @@ -466,14 +444,14 @@ static nsresult handle_htmlevent(HTMLDocumentNode *doc, nsIDOMEvent *nsevent) target = &node->event_target; } - hres = create_event_from_nsevent(nsevent, get_inner_window(doc), dispex_compat_mode(&doc->node.event_target.dispex), &event); + hres = create_event_from_nsevent(nsevent, get_inner_window(doc), dispex_compat_mode(&target->dispex), &event); if(FAILED(hres)) { IEventTarget_Release(&target->IEventTarget_iface); return NS_OK; } /* If we fine need for more special cases here, we may consider handling it in a more generic way. */ - if(event->event_id == EVENTID_FOCUS || event->event_id == EVENTID_BLUR) { + if((!node || doc == node->doc) && (event->event_id == EVENTID_FOCUS || event->event_id == EVENTID_BLUR)) { DOMEvent *focus_event; hres = create_document_event(doc, event->event_id == EVENTID_FOCUS ? EVENTID_FOCUSIN : EVENTID_FOCUSOUT, &focus_event); diff --git a/dlls/mshtml/tests/dom.c b/dlls/mshtml/tests/dom.c index 36863d2ac62..ceb59ee8a78 100644 --- a/dlls/mshtml/tests/dom.c +++ b/dlls/mshtml/tests/dom.c @@ -12253,6 +12253,77 @@ static void test_document_mode_lock(void) IHTMLDocument2_Release(doc); } +static void test_document_mode_after_initnew(void) +{ + IHTMLDocument2 *doc; + IHTMLDocument6 *doc6; + IEventTarget *event_target; + IPersistStreamInit *init; + IStream *stream; + HRESULT hres; + HGLOBAL mem; + VARIANT var; + SIZE_T len; + MSG msg; + + notif_doc = doc = create_document(); + if(!doc) + return; + doc_complete = FALSE; + + hres = IHTMLDocument2_QueryInterface(doc, &IID_IEventTarget, (void**)&event_target); + ok(hres == E_NOINTERFACE, "QueryInterface(IID_IEventTarget) returned %08lx.\n", hres); + ok(event_target == NULL, "event_target != NULL\n"); + + len = strlen(doc_blank_ie9); + mem = GlobalAlloc(0, len); + memcpy(mem, doc_blank_ie9, len); + hres = CreateStreamOnHGlobal(mem, TRUE, &stream); + ok(hres == S_OK, "Failed to create stream: %08lx.\n", hres); + + hres = IHTMLDocument2_QueryInterface(doc, &IID_IPersistStreamInit, (void**)&init); + ok(hres == S_OK, "QueryInterface(IID_IPersistStreamInit) failed: %08lx.\n", hres); + + IPersistStreamInit_InitNew(init); + IPersistStreamInit_Load(init, stream); + IPersistStreamInit_Release(init); + IStream_Release(stream); + + set_client_site(doc, TRUE); + do_advise((IUnknown*)doc, &IID_IPropertyNotifySink, (IUnknown*)&PropertyNotifySink); + + hres = IHTMLDocument2_QueryInterface(doc, &IID_IHTMLDocument6, (void**)&doc6); + ok(hres == S_OK, "QueryInterface(IID_IHTMLDocument6) failed: %08lx\n", hres); + + V_VT(&var) = VT_EMPTY; + hres = IHTMLDocument6_get_documentMode(doc6, &var); + ok(hres == S_OK, "get_documentMode failed: %08lx\n", hres); + ok(V_VT(&var) == VT_R4, "V_VT(documentMode) = %u\n", V_VT(&var)); + ok(V_R4(&var) == 5, "documentMode = %f, expected 5\n", V_R4(&var)); + VariantClear(&var); + + while(!doc_complete && GetMessageW(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + hres = IHTMLDocument2_QueryInterface(doc, &IID_IEventTarget, (void**)&event_target); + ok(hres == S_OK, "QueryInterface(IID_IEventTarget) returned %08lx.\n", hres); + ok(event_target != NULL, "event_target == NULL\n"); + IEventTarget_Release(event_target); + + V_VT(&var) = VT_EMPTY; + hres = IHTMLDocument6_get_documentMode(doc6, &var); + ok(hres == S_OK, "get_documentMode failed: %08lx\n", hres); + ok(V_VT(&var) == VT_R4, "V_VT(documentMode) = %u\n", V_VT(&var)); + ok(V_R4(&var) == 9, "documentMode = %f, expected 9\n", V_R4(&var)); + IHTMLDocument6_Release(doc6); + VariantClear(&var); + + set_client_site(doc, FALSE); + IHTMLDocument2_Release(doc); +} + static DWORD WINAPI create_document_proc(void *param) { IHTMLDocument2 *doc = NULL; @@ -12378,6 +12449,7 @@ START_TEST(dom) test_quirks_mode(); test_document_mode_lock(); + test_document_mode_after_initnew(); test_threads(); /* Run this last since it messes with the process-wide user agent */ diff --git a/dlls/nsiproxy.sys/ip.c b/dlls/nsiproxy.sys/ip.c index fe49caa6e57..09f033ac634 100644 --- a/dlls/nsiproxy.sys/ip.c +++ b/dlls/nsiproxy.sys/ip.c @@ -1544,7 +1544,7 @@ struct in6_addr str_to_in6_addr(char *nptr, char **endptr) for (int i = 0; i < sizeof(ret); i++) { - if (!isxdigit( *nptr ) || !isxdigit( *nptr + 1 )) + if (!isxdigit( *nptr ) || !isxdigit( *(nptr + 1) )) { /* invalid hex string */ if (endptr) *endptr = nptr; @@ -1574,7 +1574,7 @@ static NTSTATUS ipv6_forward_enumerate_all( void *key_data, UINT key_size, void #ifdef __linux__ { - char buf[512], *ptr; + char buf[512], *ptr, *end; UINT rtf_flags; FILE *fp; @@ -1582,9 +1582,6 @@ static NTSTATUS ipv6_forward_enumerate_all( void *key_data, UINT key_size, void while ((ptr = fgets( buf, sizeof(buf), fp ))) { - while (!isspace( *ptr )) ptr++; - *ptr++ = '\0'; - entry.prefix = str_to_in6_addr( ptr, &ptr ); entry.prefix_len = strtoul( ptr + 1, &ptr, 16 ); str_to_in6_addr( ptr + 1, &ptr ); /* source network, skip */ @@ -1597,6 +1594,10 @@ static NTSTATUS ipv6_forward_enumerate_all( void *key_data, UINT key_size, void entry.protocol = (rtf_flags & RTF_GATEWAY) ? MIB_IPPROTO_NETMGMT : MIB_IPPROTO_LOCAL; entry.loopback = entry.protocol == MIB_IPPROTO_LOCAL && entry.prefix_len == 32; + while (isspace( *ptr )) ptr++; + end = ptr; + while (*end && !isspace(*end)) ++end; + *end = 0; if (!convert_unix_name_to_luid( ptr, &entry.luid )) continue; if (!convert_luid_to_index( &entry.luid, &entry.if_index )) continue; diff --git a/dlls/ntdll/exception.c b/dlls/ntdll/exception.c index 7a230237f0c..d4b5f901e10 100644 --- a/dlls/ntdll/exception.c +++ b/dlls/ntdll/exception.c @@ -797,6 +797,52 @@ static const struct context_parameters *context_get_parameters( ULONG context_fl return NULL; } +/* offset is from the start of XSAVE_AREA_HEADER. */ +static int next_compacted_xstate_offset( int off, UINT64 compaction_mask, int feature_idx ) +{ + const UINT64 feature_mask = (UINT64)1 << feature_idx; + + if (compaction_mask & feature_mask) off += user_shared_data->XState.Features[feature_idx].Size; + if (user_shared_data->XState.AlignedFeatures & (feature_mask << 1)) + off = (off + 63) & ~63; + return off; +} + +/* size includes XSAVE_AREA_HEADER but not XSAVE_FORMAT (legacy save area). */ +static int xstate_get_compacted_size( UINT64 mask ) +{ + UINT64 compaction_mask; + unsigned int i; + int off; + + compaction_mask = ((UINT64)1 << 63) | mask; + mask >>= 2; + off = sizeof(XSAVE_AREA_HEADER); + i = 2; + while (mask) + { + if (mask == 1) return off + user_shared_data->XState.Features[i].Size; + off = next_compacted_xstate_offset( off, compaction_mask, i ); + mask >>= 1; + ++i; + } + return off; +} + +static int xstate_get_size( UINT64 mask ) +{ + unsigned int i; + + mask >>= 2; + if (!mask) return sizeof(XSAVE_AREA_HEADER); + i = 2; + while (mask != 1) + { + mask >>= 1; + ++i; + } + return user_shared_data->XState.Features[i].Offset + user_shared_data->XState.Features[i].Size - sizeof(XSAVE_FORMAT); +} /********************************************************************** * RtlGetExtendedContextLength2 (NTDLL.@) @@ -822,12 +868,12 @@ NTSTATUS WINAPI RtlGetExtendedContextLength2( ULONG context_flags, ULONG *length if (!(supported_mask = RtlGetEnabledExtendedFeatures( ~(ULONG64)0) )) return STATUS_NOT_SUPPORTED; - compaction_mask &= supported_mask; - - size = p->context_size + p->context_ex_size + offsetof(XSTATE, YmmContext) + 63; + size = p->context_size + p->context_ex_size + 63; - if (compaction_mask & supported_mask & (1 << XSTATE_AVX)) - size += sizeof(YMMCONTEXT); + compaction_mask &= supported_mask & ~(ULONG64)3; + if (user_shared_data->XState.CompactionEnabled) size += xstate_get_compacted_size( compaction_mask ); + else if (compaction_mask) size += xstate_get_size( compaction_mask ); + else size += sizeof(XSAVE_AREA_HEADER); *length = size; return STATUS_SUCCESS; @@ -878,11 +924,11 @@ NTSTATUS WINAPI RtlInitializeExtendedContext2( void *context, ULONG context_flag xs = (XSTATE *)(((ULONG_PTR)c_ex + p->context_ex_size + 63) & ~(ULONG_PTR)63); c_ex->XState.Offset = (ULONG_PTR)xs - (ULONG_PTR)c_ex; - c_ex->XState.Length = offsetof(XSTATE, YmmContext); compaction_mask &= supported_mask; - if (compaction_mask & (1 << XSTATE_AVX)) - c_ex->XState.Length += sizeof(YMMCONTEXT); + if (user_shared_data->XState.CompactionEnabled) c_ex->XState.Length = xstate_get_compacted_size( compaction_mask ); + else if (compaction_mask & ~(ULONG64)3) c_ex->XState.Length = xstate_get_size( compaction_mask ); + else c_ex->XState.Length = sizeof(XSAVE_AREA_HEADER); memset( xs, 0, c_ex->XState.Length ); if (user_shared_data->XState.CompactionEnabled) @@ -916,6 +962,10 @@ NTSTATUS WINAPI RtlInitializeExtendedContext( void *context, ULONG context_flags void * WINAPI RtlLocateExtendedFeature2( CONTEXT_EX *context_ex, ULONG feature_id, XSTATE_CONFIGURATION *xstate_config, ULONG *length ) { + UINT64 feature_mask = (ULONG64)1 << feature_id; + XSAVE_AREA_HEADER *xs; + unsigned int offset, i; + TRACE( "context_ex %p, feature_id %lu, xstate_config %p, length %p.\n", context_ex, feature_id, xstate_config, length ); @@ -931,16 +981,31 @@ void * WINAPI RtlLocateExtendedFeature2( CONTEXT_EX *context_ex, ULONG feature_i return NULL; } - if (feature_id != XSTATE_AVX) + if (feature_id < 2 || feature_id >= 64) return NULL; + xs = (XSAVE_AREA_HEADER *)((BYTE *)context_ex + context_ex->XState.Offset); + if (length) - *length = sizeof(YMMCONTEXT); + *length = xstate_config->Features[feature_id].Size; + + if (xstate_config->CompactionEnabled) + { + if (!(xs->CompactionMask & feature_mask)) return NULL; + offset = sizeof(XSAVE_AREA_HEADER); + for (i = 2; i < feature_id; ++i) + offset = next_compacted_xstate_offset( offset, xs->CompactionMask, i ); + } + else + { + if (!(feature_mask & xstate_config->EnabledFeatures)) return NULL; + offset = xstate_config->Features[feature_id].Offset - sizeof(XSAVE_FORMAT); + } - if (context_ex->XState.Length < sizeof(XSTATE)) + if (context_ex->XState.Length < offset + xstate_config->Features[feature_id].Size) return NULL; - return (BYTE *)context_ex + context_ex->XState.Offset + offsetof(XSTATE, YmmContext); + return (BYTE *)xs + offset; } @@ -1070,8 +1135,9 @@ NTSTATUS WINAPI RtlCopyContext( CONTEXT *dst, DWORD context_flags, CONTEXT *src NTSTATUS WINAPI RtlCopyExtendedContext( CONTEXT_EX *dst, ULONG context_flags, CONTEXT_EX *src ) { const struct context_parameters *p; - XSTATE *dst_xs, *src_xs; + XSAVE_AREA_HEADER *dst_xs, *src_xs; ULONG64 feature_mask; + unsigned int i, off, size; TRACE( "dst %p, context_flags %#lx, src %p.\n", dst, context_flags, src ); @@ -1086,18 +1152,35 @@ NTSTATUS WINAPI RtlCopyExtendedContext( CONTEXT_EX *dst, ULONG context_flags, CO if (!(context_flags & 0x40)) return STATUS_SUCCESS; - if (dst->XState.Length < offsetof(XSTATE, YmmContext)) + if (dst->XState.Length < sizeof(XSAVE_AREA_HEADER)) return STATUS_BUFFER_OVERFLOW; - dst_xs = (XSTATE *)((BYTE *)dst + dst->XState.Offset); - src_xs = (XSTATE *)((BYTE *)src + src->XState.Offset); + dst_xs = (XSAVE_AREA_HEADER *)((BYTE *)dst + dst->XState.Offset); + src_xs = (XSAVE_AREA_HEADER *)((BYTE *)src + src->XState.Offset); - memset(dst_xs, 0, offsetof(XSTATE, YmmContext)); + memset(dst_xs, 0, sizeof(XSAVE_AREA_HEADER)); dst_xs->Mask = (src_xs->Mask & ~(ULONG64)3) & feature_mask; dst_xs->CompactionMask = user_shared_data->XState.CompactionEnabled ? ((ULONG64)1 << 63) | (src_xs->CompactionMask & feature_mask) : 0; - if (dst_xs->Mask & 4 && src->XState.Length >= sizeof(XSTATE) && dst->XState.Length >= sizeof(XSTATE)) - memcpy( &dst_xs->YmmContext, &src_xs->YmmContext, sizeof(dst_xs->YmmContext) ); + + if (dst_xs->CompactionMask) feature_mask &= dst_xs->CompactionMask; + feature_mask = dst_xs->Mask >> 2; + + i = 2; + off = sizeof(XSAVE_AREA_HEADER); + while (1) + { + if (feature_mask & 1) + { + if (!dst_xs->CompactionMask) off = user_shared_data->XState.Features[i].Offset - sizeof(XSAVE_FORMAT); + size = user_shared_data->XState.Features[i].Size; + if (src->XState.Length < off + size || dst->XState.Length < off + size) break; + memcpy( (BYTE *)dst_xs + off, (BYTE *)src_xs + off, size ); + } + if (!(feature_mask >>= 1)) break; + if (dst_xs->CompactionMask) off = next_compacted_xstate_offset( off, dst_xs->CompactionMask, i); + ++i; + } return STATUS_SUCCESS; } diff --git a/dlls/ntdll/heap.c b/dlls/ntdll/heap.c index 7819f8bdfd7..981836f28a7 100644 --- a/dlls/ntdll/heap.c +++ b/dlls/ntdll/heap.c @@ -329,6 +329,7 @@ C_ASSERT( HEAP_MIN_LARGE_BLOCK_SIZE <= HEAP_INITIAL_GROW_SIZE ); #define HEAP_CHECKING_ENABLED 0x80000000 BOOL delay_heap_free = FALSE; +BOOL heap_zero_hack = FALSE; static struct heap *process_heap; /* main process heap */ @@ -1519,6 +1520,9 @@ HANDLE WINAPI RtlCreateHeap( ULONG flags, void *addr, SIZE_T total_size, SIZE_T TRACE( "flags %#lx, addr %p, total_size %#Ix, commit_size %#Ix, unknown %p, definition %p\n", flags, addr, total_size, commit_size, unknown, definition ); + if (heap_zero_hack) + flags |= HEAP_ZERO_MEMORY; + flags &= ~(HEAP_TAIL_CHECKING_ENABLED|HEAP_FREE_CHECKING_ENABLED); if (process_heap) flags |= HEAP_PRIVATE; if (!process_heap || !total_size || (flags & HEAP_SHARED)) flags |= HEAP_GROWABLE; diff --git a/dlls/ntdll/loader.c b/dlls/ntdll/loader.c index 24efd25e8db..8dfe4e21e10 100644 --- a/dlls/ntdll/loader.c +++ b/dlls/ntdll/loader.c @@ -3335,6 +3335,21 @@ static NTSTATUS find_dll_file( const WCHAR *load_path, const WCHAR *libname, UNI return status; } +static void substitute_dll( const WCHAR **libname ) +{ + static int substitute = -1; + + if (substitute == -1) + { + WCHAR env_str[32]; + + if ((substitute = (get_env( L"SteamGameId", env_str, sizeof(env_str)) && !wcsicmp( env_str, L"582660" )))) + FIXME( "HACK: substituting dll name.\n" ); + } + if (!substitute) return; + if (*libname && !wcsicmp( *libname, L"d3dcompiler_46.dll" )) + *libname = L"d3dcompiler_43.dll"; +} /*********************************************************************** * load_dll (internal) @@ -3353,6 +3368,8 @@ static NTSTATUS load_dll( const WCHAR *load_path, const WCHAR *libname, DWORD fl TRACE( "looking for %s in %s\n", debugstr_w(libname), debugstr_w(load_path) ); + substitute_dll( &libname ); + if (system && system_dll_path.Buffer) nts = search_dll_file( system_dll_path.Buffer, libname, &nt_name, pwm, &mapping, &image_info, &id ); @@ -4439,6 +4456,7 @@ void loader_init( CONTEXT *context, void **entry ) WINE_MODREF *kernel32; PEB *peb = NtCurrentTeb()->Peb; WCHAR env_str[16]; + ULONG heap_flags = HEAP_GROWABLE; NtQueryVirtualMemory( GetCurrentProcess(), LdrInitializeThunk, MemoryBasicInformation, &meminfo, sizeof(meminfo), NULL ); @@ -4457,8 +4475,13 @@ void loader_init( CONTEXT *context, void **entry ) delay_heap_free = TRUE; } } + if (get_env( L"WINE_HEAP_ZERO_MEMORY", env_str, sizeof(env_str)) && env_str[0] == L'1') + { + ERR( "Enabling heap zero hack.\n" ); + heap_zero_hack = TRUE; + } - peb->ProcessHeap = RtlCreateHeap( HEAP_GROWABLE, NULL, 0, 0, NULL, NULL ); + peb->ProcessHeap = RtlCreateHeap( heap_flags, NULL, 0, 0, NULL, NULL ); RtlInitializeBitMap( &tls_bitmap, peb->TlsBitmapBits, sizeof(peb->TlsBitmapBits) * 8 ); RtlInitializeBitMap( &tls_expansion_bitmap, peb->TlsExpansionBitmapBits, diff --git a/dlls/ntdll/ntdll_misc.h b/dlls/ntdll/ntdll_misc.h index 623acc1da00..af4d8b57348 100644 --- a/dlls/ntdll/ntdll_misc.h +++ b/dlls/ntdll/ntdll_misc.h @@ -48,6 +48,7 @@ extern UINT_PTR page_size; #endif extern BOOL delay_heap_free; +extern BOOL heap_zero_hack; /* exceptions */ extern LONG call_vectored_handlers( EXCEPTION_RECORD *rec, CONTEXT *context ); diff --git a/dlls/ntdll/tests/exception.c b/dlls/ntdll/tests/exception.c index bb62d4627a5..1d1c9697713 100644 --- a/dlls/ntdll/tests/exception.c +++ b/dlls/ntdll/tests/exception.c @@ -18,20 +18,13 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -#include +#include "ntdll_test.h" + #include -#include "ntstatus.h" -#define WIN32_NO_STATUS -#include "windef.h" -#include "winbase.h" -#include "winnt.h" -#include "winreg.h" #include "winuser.h" -#include "winternl.h" #include "ddk/wdm.h" #include "excpt.h" -#include "wine/test.h" #include "intrin.h" static void *code_mem; @@ -284,6 +277,19 @@ static void test_debugger_xstate(HANDLE thread, CONTEXT *ctx, enum debugger_stag status = pNtSetContextThread(thread, xctx); ok(!status, "NtSetContextThread failed with 0x%lx\n", status); } + +#define check_context_exception_request( a, b ) check_context_exception_request_( a, b, __LINE__ ) +static void check_context_exception_request_( DWORD flags, BOOL hardware_exception, unsigned int line ) +{ + static const DWORD exception_reporting_flags = CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING + | CONTEXT_EXCEPTION_ACTIVE | CONTEXT_SERVICE_ACTIVE; + DWORD expected_flags = CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING; + + if (!(flags & CONTEXT_EXCEPTION_REPORTING)) return; + expected_flags |= hardware_exception ? CONTEXT_EXCEPTION_ACTIVE : CONTEXT_SERVICE_ACTIVE; + ok_(__FILE__, line)( (flags & exception_reporting_flags) == expected_flags, "got %#lx, expected %#lx.\n", + flags, expected_flags ); +} #endif #ifdef __i386__ @@ -1154,13 +1160,16 @@ static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) sizeof(stage), &size_read); ok(!status,"NtReadVirtualMemory failed with 0x%lx\n", status); - ctx.ContextFlags = CONTEXT_FULL | CONTEXT_EXTENDED_REGISTERS; + ctx.ContextFlags = CONTEXT_FULL | CONTEXT_EXTENDED_REGISTERS | CONTEXT_EXCEPTION_REQUEST; status = pNtGetContextThread(pi.hThread, &ctx); ok(!status, "NtGetContextThread failed with 0x%lx\n", status); + ok(ctx.ContextFlags & CONTEXT_EXCEPTION_REPORTING + || broken( !(ctx.ContextFlags & CONTEXT_EXCEPTION_REPORTING) ) /* Win7 WoW64 */, + "got %#lx.\n", ctx.ContextFlags); - trace("exception 0x%lx at %p firstchance=%ld Eip=0x%lx, Eax=0x%lx\n", + trace("exception 0x%lx at %p firstchance=%ld Eip=0x%lx, Eax=0x%lx ctx.ContextFlags %#lx\n", de.u.Exception.ExceptionRecord.ExceptionCode, - de.u.Exception.ExceptionRecord.ExceptionAddress, de.u.Exception.dwFirstChance, ctx.Eip, ctx.Eax); + de.u.Exception.ExceptionRecord.ExceptionAddress, de.u.Exception.dwFirstChance, ctx.Eip, ctx.Eax, ctx.ContextFlags); if (counter > 100) { @@ -1177,6 +1186,7 @@ static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) (char *)ctx.Eip < (char *)ntdll + nt->OptionalHeader.SizeOfImage, "wrong eip %p ntdll %p-%p\n", (void *)ctx.Eip, ntdll, (char *)ntdll + nt->OptionalHeader.SizeOfImage ); + check_context_exception_request( ctx.ContextFlags, TRUE ); } else { @@ -1190,6 +1200,7 @@ static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) ctx.Eax = 0xf00f00f1; /* let the debuggee handle the exception */ continuestatus = DBG_EXCEPTION_NOT_HANDLED; + check_context_exception_request( ctx.ContextFlags, !is_wow64 ); } else if (stage == STAGE_RTLRAISE_HANDLE_LAST_CHANCE) { @@ -1226,6 +1237,7 @@ static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) ctx.Eip, (char *)code_mem_address + 0xb); /* here we handle exception */ } + check_context_exception_request( ctx.ContextFlags, !is_wow64 ); } else if (stage == STAGE_SERVICE_CONTINUE || stage == STAGE_SERVICE_NOT_HANDLED) { @@ -1235,6 +1247,7 @@ static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) "expected Eip = %p, got 0x%lx\n", (char *)code_mem_address + 0x1d, ctx.Eip); if (stage == STAGE_SERVICE_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + check_context_exception_request( ctx.ContextFlags, TRUE ); } else if (stage == STAGE_BREAKPOINT_CONTINUE || stage == STAGE_BREAKPOINT_NOT_HANDLED) { @@ -1244,6 +1257,7 @@ static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) "expected Eip = %p, got 0x%lx\n", (char *)code_mem_address + 2, ctx.Eip); if (stage == STAGE_BREAKPOINT_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + check_context_exception_request( ctx.ContextFlags, TRUE ); } else if (stage == STAGE_EXCEPTION_INVHANDLE_CONTINUE || stage == STAGE_EXCEPTION_INVHANDLE_NOT_HANDLED) { @@ -1254,14 +1268,17 @@ static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) "unexpected number of parameters %ld, expected 0\n", de.u.Exception.ExceptionRecord.NumberParameters); if (stage == STAGE_EXCEPTION_INVHANDLE_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + check_context_exception_request( ctx.ContextFlags, !is_wow64 ); } else if (stage == STAGE_NO_EXCEPTION_INVHANDLE_NOT_HANDLED) { ok(FALSE || broken(TRUE) /* < Win10 */, "should not throw exception\n"); continuestatus = DBG_EXCEPTION_NOT_HANDLED; + check_context_exception_request( ctx.ContextFlags, !is_wow64 ); } else if (stage == STAGE_XSTATE || stage == STAGE_XSTATE_LEGACY_SSE) { + check_context_exception_request( ctx.ContextFlags, TRUE ); test_debugger_xstate(pi.hThread, &ctx, stage); } else if (stage == STAGE_SEGMENTS) @@ -1282,6 +1299,7 @@ static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) ok( !ctx.SegEs, "wrong es %04lx / %04lx\n", ctx.SegEs, ctx.SegSs ); ok( !ctx.SegGs, "wrong gs %04lx / %04lx\n", ctx.SegGs, ctx.SegSs ); } + check_context_exception_request( ctx.ContextFlags, TRUE ); } else ok(FALSE, "unexpected stage %u\n", stage); @@ -4052,9 +4070,10 @@ static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) sizeof(stage), &size_read); ok(!status,"NtReadVirtualMemory failed with 0x%lx\n", status); - ctx.ContextFlags = CONTEXT_FULL | CONTEXT_SEGMENTS; + ctx.ContextFlags = CONTEXT_FULL | CONTEXT_SEGMENTS | CONTEXT_EXCEPTION_REQUEST; status = pNtGetContextThread(pi.hThread, &ctx); ok(!status, "NtGetContextThread failed with 0x%lx\n", status); + ok(ctx.ContextFlags & CONTEXT_EXCEPTION_REPORTING, "got %#lx.\n", ctx.ContextFlags); trace("exception 0x%lx at %p firstchance=%ld Rip=%p, Rax=%p\n", de.u.Exception.ExceptionRecord.ExceptionCode, @@ -4076,6 +4095,7 @@ static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) (char *)ctx.Rip < (char *)ntdll + nt->OptionalHeader.SizeOfImage, "wrong rip %p ntdll %p-%p\n", (void *)ctx.Rip, ntdll, (char *)ntdll + nt->OptionalHeader.SizeOfImage ); + check_context_exception_request( ctx.ContextFlags, TRUE ); } else { @@ -4089,6 +4109,7 @@ static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) ctx.Rax = 0xf00f00f1; /* let the debuggee handle the exception */ continuestatus = DBG_EXCEPTION_NOT_HANDLED; + check_context_exception_request( ctx.ContextFlags, FALSE ); } else if (stage == STAGE_RTLRAISE_HANDLE_LAST_CHANCE) { @@ -4117,6 +4138,7 @@ static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) ctx.Rip, (char *)code_mem_address + 0x10); /* here we handle exception */ } + check_context_exception_request( ctx.ContextFlags, FALSE ); } else if (stage == STAGE_SERVICE_CONTINUE || stage == STAGE_SERVICE_NOT_HANDLED) { @@ -4125,6 +4147,7 @@ static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) ok((char *)ctx.Rip == (char *)code_mem_address + 0x30, "expected Rip = %p, got %p\n", (char *)code_mem_address + 0x30, (char *)ctx.Rip); if (stage == STAGE_SERVICE_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + check_context_exception_request( ctx.ContextFlags, TRUE ); } else if (stage == STAGE_BREAKPOINT_CONTINUE || stage == STAGE_BREAKPOINT_NOT_HANDLED) { @@ -4133,6 +4156,7 @@ static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) ok((char *)ctx.Rip == (char *)code_mem_address + 2, "expected Rip = %p, got %p\n", (char *)code_mem_address + 2, (char *)ctx.Rip); if (stage == STAGE_BREAKPOINT_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + check_context_exception_request( ctx.ContextFlags, TRUE ); } else if (stage == STAGE_EXCEPTION_INVHANDLE_CONTINUE || stage == STAGE_EXCEPTION_INVHANDLE_NOT_HANDLED) { @@ -4143,14 +4167,17 @@ static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) "unexpected number of parameters %ld, expected 0\n", de.u.Exception.ExceptionRecord.NumberParameters); if (stage == STAGE_EXCEPTION_INVHANDLE_NOT_HANDLED) continuestatus = DBG_EXCEPTION_NOT_HANDLED; + check_context_exception_request( ctx.ContextFlags, FALSE ); } else if (stage == STAGE_NO_EXCEPTION_INVHANDLE_NOT_HANDLED) { ok(FALSE || broken(TRUE) /* < Win10 */, "should not throw exception\n"); continuestatus = DBG_EXCEPTION_NOT_HANDLED; + check_context_exception_request( ctx.ContextFlags, FALSE ); } else if (stage == STAGE_XSTATE || stage == STAGE_XSTATE_LEGACY_SSE) { + check_context_exception_request( ctx.ContextFlags, TRUE ); test_debugger_xstate(pi.hThread, &ctx, stage); } else if (stage == STAGE_SEGMENTS) @@ -4175,6 +4202,7 @@ static void test_debugger(DWORD cont_status, BOOL with_WaitForDebugEventEx) ok( ctx.SegEs == ctx.SegSs, "wrong es %04x / %04x\n", ctx.SegEs, ctx.SegSs ); todo_wine ok( ctx.SegFs != ctx.SegSs, "wrong fs %04x / %04x\n", ctx.SegFs, ctx.SegSs ); ok( ctx.SegGs == ctx.SegSs, "wrong gs %04x / %04x\n", ctx.SegGs, ctx.SegSs ); + check_context_exception_request( ctx.ContextFlags, TRUE ); } else ok(FALSE, "unexpected stage %u\n", stage); @@ -4596,9 +4624,21 @@ static void test_wow64_context(void) ret = pRtlWow64GetThreadContext( pi.hThread, &ctx ); ok(ret == STATUS_SUCCESS, "got %#lx\n", ret); ok( ctx.ContextFlags == WOW64_CONTEXT_ALL, "got context flags %#lx\n", ctx.ContextFlags ); + + ctx.ContextFlags = WOW64_CONTEXT_ALL | CONTEXT_EXCEPTION_REQUEST; + ret = pRtlWow64GetThreadContext( pi.hThread, &ctx ); + ok(ret == STATUS_SUCCESS, "got %#lx\n", ret); + ok( (ctx.ContextFlags & CONTEXT_EXCEPTION_REPORTING) || broken( ctx.ContextFlags == WOW64_CONTEXT_ALL ) /*Win 7*/, + "got context flags %#lx\n", ctx.ContextFlags ); + if (context.SegCs == cs32) { trace( "in 32-bit mode %04x\n", context.SegCs ); + if (ctx.ContextFlags & CONTEXT_EXCEPTION_REPORTING) + ok( ctx.ContextFlags == (WOW64_CONTEXT_ALL | CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING) + || ctx.ContextFlags == (WOW64_CONTEXT_ALL | CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING + | CONTEXT_EXCEPTION_ACTIVE), + "got %#lx.\n", ctx.ContextFlags ); ok( ctx.Eip == context.Rip, "cs32: eip %08lx / %p\n", ctx.Eip, (void *)context.Rip ); ok( ctx.Ebp == context.Rbp, "cs32: ebp %08lx / %p\n", ctx.Ebp, (void *)context.Rbp ); ok( ctx.Esp == context.Rsp, "cs32: esp %08lx / %p\n", ctx.Esp, (void *)context.Rsp ); @@ -4673,6 +4713,12 @@ static void test_wow64_context(void) else { trace( "in 64-bit mode %04x\n", context.SegCs ); + if (ctx.ContextFlags & CONTEXT_EXCEPTION_REPORTING) + ok( ctx.ContextFlags == (WOW64_CONTEXT_ALL | CONTEXT_EXCEPTION_REQUEST + | CONTEXT_EXCEPTION_REPORTING | CONTEXT_SERVICE_ACTIVE) + || ctx.ContextFlags == (WOW64_CONTEXT_ALL | CONTEXT_EXCEPTION_REQUEST + | CONTEXT_EXCEPTION_REPORTING | CONTEXT_EXCEPTION_ACTIVE), + "got %#lx.\n", ctx.ContextFlags ); ok( ctx.Eip != context.Rip, "cs64: eip %08lx / %p\n", ctx.Eip, (void *)context.Rip); ok( ctx.SegCs == cs32, "cs64: wrong cs %04lx / %04x\n", ctx.SegCs, cs32 ); ok( ctx.SegDs == context.SegDs, "cs64: wrong ds %04lx / %04x\n", ctx.SegDs, context.SegDs ); @@ -10546,11 +10592,12 @@ static const unsigned test_extended_context_spoil_data2[8] = {0x15, 0x25, 0x35, static BOOL test_extended_context_modified_state; static BOOL xsaveopt_enabled, compaction_enabled; +static ULONG64 xstate_supported_features; static DWORD test_extended_context_handler(EXCEPTION_RECORD *rec, EXCEPTION_REGISTRATION_RECORD *frame, CONTEXT *context, EXCEPTION_REGISTRATION_RECORD **dispatcher) { - static const ULONG64 expected_compaction_mask = 0x8000000000000004; + const ULONG64 expected_compaction_mask = (0x8000000000000000 | xstate_supported_features) & ~(ULONG64)3; CONTEXT_EX *xctx = (CONTEXT_EX *)(context + 1); unsigned int *context_ymm_data; DWORD expected_min_offset; @@ -10588,7 +10635,7 @@ static DWORD test_extended_context_handler(EXCEPTION_RECORD *rec, EXCEPTION_REGI if (compaction_enabled) ok((xs->CompactionMask & (expected_compaction_mask | 3)) == expected_compaction_mask, - "Got compaction mask %#I64x.\n", xs->CompactionMask); + "Got compaction mask %#I64x, expected %#I64x.\n", xs->CompactionMask, expected_compaction_mask); else ok(!xs->CompactionMask, "Got compaction mask %#I64x.\n", xs->CompactionMask); @@ -10600,7 +10647,7 @@ static DWORD test_extended_context_handler(EXCEPTION_RECORD *rec, EXCEPTION_REGI } else { - ok(xs->Mask == (xsaveopt_enabled ? 0 : 4), "Got unexpected Mask %#I64x.\n", xs->Mask); + ok((xs->Mask & 7) == (xsaveopt_enabled ? 0 : 4), "Got unexpected Mask %#I64x.\n", xs->Mask); /* The save area has garbage if xsaveopt is available, so we can't test * its contents. */ @@ -10883,7 +10930,8 @@ static void test_extended_context(void) }; const struct context_parameters *context_arch; - const ULONG64 supported_features = 7, supported_compaction_mask = supported_features | ((ULONG64)1 << 63); + const ULONG64 supported_features = 0xff; + const ULONG64 supported_compaction_mask = supported_features | ((ULONG64)1 << 63); ULONG expected_length, expected_length_xstate, context_flags, expected_offset, max_xstate_length; ULONG64 enabled_features, expected_compaction; DECLSPEC_ALIGN(64) BYTE context_buffer2[4096]; @@ -10922,6 +10970,7 @@ static void test_extended_context(void) xsaveopt_enabled = regs[0] & 1; compaction_enabled = regs[0] & 2; } + xstate_supported_features = enabled_features & supported_features; /* Test context manipulation functions. */ length = 0xdeadbeef; @@ -11392,7 +11441,7 @@ static void test_extended_context(void) xs->Mask = 0xdeadbeef; xs->CompactionMask = 0xdeadbeef; - bret = pSetXStateFeaturesMask(context, 7); + bret = pSetXStateFeaturesMask(context, xstate_supported_features); ok(bret == !!(flags & CONTEXT_NATIVE), "Got unexpected bret %#x.\n", bret); context_flags = *(DWORD *)(context_buffer + context_arch[test].flags_offset); ok(context_flags == (bret ? flags_fpx : flags), @@ -11406,8 +11455,8 @@ static void test_extended_context(void) mask = 0xdeadbeef; bret = pGetXStateFeaturesMask(context, &mask); if (flags & CONTEXT_NATIVE) - ok(bret && mask == (enabled_features & supported_features), - "Got unexpected bret %#x, mask %s, flags %#lx.\n", bret, wine_dbgstr_longlong(mask), flags); + ok(bret && mask == xstate_supported_features, + "Got unexpected bret %#x, mask %s, flags %#lx (enabled_features & supported_features %#I64x).\n", bret, wine_dbgstr_longlong(mask), flags, xstate_supported_features); else ok(!bret && mask == 0xdeadbeef, "Got unexpected bret %#x, mask %s, flags %#lx.\n", bret, wine_dbgstr_longlong(mask), flags); @@ -11632,6 +11681,14 @@ static void test_extended_context(void) &context, &length); memset(&xs->YmmContext, 0xcc, sizeof(xs->YmmContext)); ok(bret, "Got unexpected bret %#x.\n", bret); + + /* clear potentially leftover xstate */ + pSetXStateFeaturesMask(context, 0); + context->ContextFlags = CONTEXT_XSTATE; + SetThreadContext(GetCurrentThread(), context); + + context->ContextFlags = CONTEXT_FULL | CONTEXT_XSTATE | CONTEXT_FLOATING_POINT; + pSetXStateFeaturesMask(context, ~(ULONG64)0); *(void **)(call_func_code_reset_ymm_state + call_func_offsets.func_addr) = GetThreadContext; *(void **)(call_func_code_reset_ymm_state + call_func_offsets.func_param1) = (void *)GetCurrentThread(); @@ -11649,12 +11706,12 @@ static void test_extended_context(void) ok(context->ContextFlags == expected_flags, "Got unexpected ContextFlags %#lx.\n", context->ContextFlags); - expected_compaction = compaction_enabled ? ((ULONG64)1 << 63) | 4 : 0; + expected_compaction = compaction_enabled ? ((ULONG64)1 << 63) | (xstate_supported_features & ~(UINT64)3) : 0; xs = (XSTATE *)((BYTE *)context_ex + context_ex->XState.Offset); ok((xs->Mask & supported_features) == (xsaveopt_enabled ? 0 : 4), "Got unexpected Mask %#I64x.\n", xs->Mask); ok((xs->CompactionMask & (supported_features | ((ULONG64)1 << 63))) == expected_compaction, - "Got unexpected CompactionMask %s.\n", wine_dbgstr_longlong(xs->CompactionMask)); + "Got unexpected CompactionMask %s (expected %#I64x).\n", wine_dbgstr_longlong(xs->CompactionMask), expected_compaction); for (i = 4; i < 8; ++i) ok(!data[i], "Got unexpected data %#x, i %u.\n", data[i], i); @@ -11664,6 +11721,36 @@ static void test_extended_context(void) || broken(((ULONG *)&xs->YmmContext)[i] == test_extended_context_data[i + 4]), "Got unexpected data %#lx, i %u.\n", ((ULONG *)&xs->YmmContext)[i], i); + /* Test setting context which has only part of xstate in CompactionMask. */ + if (compaction_enabled && enabled_features & ((ULONG64)1 << XSTATE_AVX512_KMASK)) + { + *(void **)(call_func_code_set_ymm0 + call_func_offsets.func_addr) = SetThreadContext; + *(void **)(call_func_code_set_ymm0 + call_func_offsets.func_param1) = (void *)GetCurrentThread(); + *(void **)(call_func_code_set_ymm0 + call_func_offsets.func_param2) = context; + *(void **)(call_func_code_set_ymm0 + call_func_offsets.ymm0_save) = data; + memcpy(code_mem, call_func_code_set_ymm0, sizeof(call_func_code_set_ymm0)); + context->ContextFlags = CONTEXT_XSTATE; + xs->CompactionMask = 0x8000000000000000 | ((ULONG64)1 << XSTATE_AVX512_KMASK); + xs->Mask = 0; + memcpy(data, test_extended_context_data, sizeof(data)); + bret = func(); + ok(bret, "Got unexpected bret %#x, GetLastError() %lu.\n", bret, GetLastError()); + /* Setting a context with only part of xstate in CompactionMask doesn't change missing parts. */ + for (i = 4; i < 8; ++i) + ok(data[i] == test_extended_context_data[i], "Got unexpected data %#x, i %u.\n", data[i], i); + + memcpy(data, test_extended_context_data, sizeof(data)); + xs->CompactionMask |= XSTATE_MASK_GSSE; + bret = func(); + ok(bret, "Got unexpected bret %#x, GetLastError() %lu.\n", bret, GetLastError()); + for (i = 4; i < 8; ++i) + ok(!data[i], "Got unexpected data %#x, i %u.\n", data[i], i); + } + else + { + skip("avx512 is not available, skipping test.\n"); + } + /* Test fault exception context. */ memset(data, 0xff, sizeof(data)); xs->Mask = 0; @@ -11713,9 +11800,10 @@ static void test_extended_context(void) bret = GetThreadContext(thread, context); ok(bret, "Got unexpected bret %#x, GetLastError() %lu.\n", bret, GetLastError()); todo_wine_if (!xsaveopt_enabled) - ok((xs->Mask & supported_features) == (xsaveopt_enabled ? 0 : 4), "Got unexpected Mask %#I64x.\n", xs->Mask); + ok((xs->Mask & supported_features) == (xsaveopt_enabled ? 0 : 4), "Got unexpected Mask %#I64x.\n", xs->Mask); ok((xs->CompactionMask & supported_compaction_mask) == expected_compaction, - "Got unexpected CompactionMask %s.\n", wine_dbgstr_longlong(xs->CompactionMask)); + "Got unexpected CompactionMask %I64x, expected %I64x.\n", xs->CompactionMask, + expected_compaction); for (i = 0; i < 16 * 4; ++i) ok(((ULONG *)&xs->YmmContext)[i] == ((xs->Mask & 4) ? 0 : 0xcccccccc), @@ -11796,6 +11884,44 @@ static void test_extended_context(void) "Got unexpected value %#lx, i %u.\n", ((ULONG *)&xs->YmmContext)[i], i); } + if (compaction_enabled && enabled_features & ((ULONG64)1 << XSTATE_AVX512_KMASK)) + { + ULONG64 saved_mask; + ULONG *d; + + saved_mask = xs->CompactionMask; + xs->Mask = XSTATE_MASK_GSSE; + xs->CompactionMask = 0x8000000000000000 | xs->Mask; + *(ULONG *)&xs->YmmContext = 0x11111111; + bret = SetThreadContext(thread, context); + ok(bret, "Got unexpected bret %#x, GetLastError() %lu.\n", bret, GetLastError()); + + xs->Mask = (ULONG64)1 << XSTATE_AVX512_KMASK; + xs->CompactionMask = 0x8000000000000000 | xs->Mask; + *(ULONG *)&xs->YmmContext = 0x22222222; + bret = SetThreadContext(thread, context); + ok(bret, "Got unexpected bret %#x, GetLastError() %lu.\n", bret, GetLastError()); + + xs->CompactionMask = saved_mask; + bret = GetThreadContext(thread, context); + ok(bret, "Got unexpected bret %#x, GetLastError() %lu.\n", bret, GetLastError()); + + todo_wine_if(xs->Mask == XSTATE_MASK_GSSE) + ok((xs->Mask & (XSTATE_MASK_GSSE | ((ULONG64)1 << XSTATE_AVX512_KMASK))) + == (XSTATE_MASK_GSSE | ((ULONG64)1 << XSTATE_AVX512_KMASK)), "got Mask %#I64x.\n", xs->Mask); + d = pLocateXStateFeature(context, XSTATE_AVX, NULL); + ok(!!d, "Got NULL.\n"); + ok(*d == 0x11111111, "got %#lx.\n", *d); + + d = pLocateXStateFeature(context, XSTATE_AVX512_KMASK, NULL); + ok(!!d, "Got NULL.\n"); + todo_wine ok(*d == 0x22222222, "got %#lx.\n", *d); + } + else + { + skip("avx512 is not available, skipping test.\n"); + } + bret = ResumeThread(thread); ok(bret, "Got unexpected bret %#x, GetLastError() %lu.\n", bret, GetLastError()); @@ -12261,6 +12387,254 @@ static void test_set_live_context(void) } #endif +struct context_exception_request_thread_param +{ + LONG volatile sync; + HANDLE event; +}; +static volatile int *p_context_exception_request_value; +struct context_exception_request_thread_param *context_exception_request_param; + +static LONG CALLBACK test_context_exception_request_handler( EXCEPTION_POINTERS *info ) +{ + PEXCEPTION_RECORD rec = info->ExceptionRecord; + CONTEXT *c = info->ContextRecord; + DWORD old_prot; + + ok( !(c->ContextFlags & (CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING | CONTEXT_SERVICE_ACTIVE + | CONTEXT_EXCEPTION_ACTIVE)), "got %#lx.\n", c->ContextFlags ); + + ok( rec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION, "got %#lx.\n", rec->ExceptionCode ); + VirtualProtect( (void *)p_context_exception_request_value, sizeof(*p_context_exception_request_value), + PAGE_READWRITE, &old_prot ); + + WriteRelease( &context_exception_request_param->sync, 5 ); + while (ReadAcquire( &context_exception_request_param->sync ) != 6) + ; + + return EXCEPTION_CONTINUE_EXECUTION; +} + +static DWORD WINAPI test_context_exception_request_thread( void *arg ) +{ +#ifndef _WIN64 + static BYTE wait_sync_x64_code[] = + { + 0x89, 0x11, /* mov %edx,(%rcx) */ + 0x83, 0xc2, 0x01, /* add $0x1,%edx */ + 0x0f, 0x1f, 0x00, /* 1: nopl (%rax) */ + 0x8b, 0x01, /* mov (%rcx),%eax */ + 0x39, 0xd0, /* cmp %edx,%eax */ + 0x75, 0xfa, /* jne 1b */ + 0xc3, /* ret */ + }; + ULONG64 args[2]; +#endif + struct context_exception_request_thread_param *p = arg; + void *vectored_handler; + + context_exception_request_param = p; + vectored_handler = pRtlAddVectoredExceptionHandler( TRUE, test_context_exception_request_handler ); + ok( !!vectored_handler, "failed.\n" ); + + WriteRelease( &p->sync, 1 ); + while (ReadAcquire( &p->sync ) != 2) + ; + + WaitForSingleObject( p->event, INFINITE ); + +#ifndef _WIN64 + memcpy( (char *)code_mem + 1024, wait_sync_x64_code, sizeof(wait_sync_x64_code) ); + args[0] = (ULONG_PTR)&p->sync; + args[1] = 3; + if (is_wow64 && !old_wow64) call_func64( (ULONG64)(ULONG_PTR)code_mem + 1024, ARRAY_SIZE(args), args, code_mem ); +#endif + + p_context_exception_request_value = VirtualAlloc( NULL, sizeof(*p_context_exception_request_value), + MEM_RESERVE | MEM_COMMIT, PAGE_READONLY ); + ok( !!p_context_exception_request_value, "got NULL.\n" ); + *p_context_exception_request_value = 1; + ok( *p_context_exception_request_value == 1, "got %d.\n", *p_context_exception_request_value ); + VirtualFree( (void *)p_context_exception_request_value, 0, MEM_RELEASE ); + pRtlRemoveVectoredExceptionHandler( vectored_handler ); + +#ifndef _WIN64 + args[1] = 7; + if (is_wow64 && !old_wow64) call_func64( (ULONG64)(ULONG_PTR)code_mem + 1024, ARRAY_SIZE(args), args, code_mem ); +#endif + + return 0; +} + +static void test_context_exception_request(void) +{ + struct context_exception_request_thread_param p; + DWORD expected_flags; + HANDLE thread; + CONTEXT c; + BOOL ret; + + if (!pRtlAddVectoredExceptionHandler || !pRtlRemoveVectoredExceptionHandler) + { + skip( "RtlAddVectoredExceptionHandler or RtlRemoveVectoredExceptionHandler not found.\n" ); + return; + } + + c.ContextFlags = CONTEXT_CONTROL; + ret = GetThreadContext( GetCurrentThread(), &c ); + ok( ret, "got error %lu.\n", GetLastError() ); + ok( c.ContextFlags == CONTEXT_CONTROL, "got %#lx.\n", c.ContextFlags ); + + expected_flags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING | CONTEXT_SERVICE_ACTIVE; + + c.ContextFlags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST; + ret = GetThreadContext( GetCurrentThread(), &c ); + ok( ret, "got error %lu.\n", GetLastError() ); + ok( c.ContextFlags == expected_flags || broken( c.ContextFlags == 0x10001 ) /* Win7 WoW64 */, + "got %#lx.\n", c.ContextFlags ); + if (c.ContextFlags == 0x10001) + { + win_skip( "Old WoW64 behaviour, skipping tests.\n" ); + return; + } + + ret = DuplicateHandle( GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &thread, 0, TRUE, DUPLICATE_SAME_ACCESS ); + ok( ret, "got error %lu.\n", GetLastError() ); + c.ContextFlags = expected_flags | CONTEXT_EXCEPTION_REQUEST; + + c.ContextFlags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST; + ret = GetThreadContext( thread, &c ); + ok( ret, "got error %lu.\n", GetLastError() ); + ok( c.ContextFlags == expected_flags, "got %#lx.\n", c.ContextFlags ); + CloseHandle( thread ); + + c.ContextFlags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING | CONTEXT_SERVICE_ACTIVE + | CONTEXT_EXCEPTION_ACTIVE; + ret = GetThreadContext( GetCurrentThread(), &c ); + ok( ret, "got error %lu.\n", GetLastError() ); + ok( c.ContextFlags == expected_flags, "got %#lx.\n", c.ContextFlags ); + + p.event = CreateEventW( NULL, FALSE, FALSE, NULL ); + thread = CreateThread( NULL, 0, test_context_exception_request_thread, &p, CREATE_SUSPENDED, NULL ); + ok( !!thread, "got error %lu.\n", GetLastError() ); + + expected_flags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING | CONTEXT_EXCEPTION_ACTIVE; + + c.ContextFlags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST; + ret = GetThreadContext( thread, &c ); + ok( ret, "got error %lu.\n", GetLastError() ); + ok( c.ContextFlags == expected_flags || broken( c.ContextFlags == (CONTEXT_CONTROL + | CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING)) /* Win7 64 */, "got %#lx.\n", c.ContextFlags ); + + p.sync = 0; + ResumeThread(thread); + + while (ReadAcquire( &p.sync ) != 1) + SwitchToThread(); + /* thread is in user code. */ + SuspendThread( thread ); + + c.ContextFlags = CONTEXT_CONTROL; + ret = GetThreadContext( thread, &c ); + ok( ret, "got error %lu.\n", GetLastError() ); + ok( c.ContextFlags == CONTEXT_CONTROL, "got %#lx.\n", c.ContextFlags ); + + c.ContextFlags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING | CONTEXT_SERVICE_ACTIVE; + ret = SetThreadContext( thread, &c ); + ok( ret, "got error %lu.\n", GetLastError() ); + + expected_flags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING; + + c.ContextFlags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST; + ret = GetThreadContext( thread, &c ); + ok( ret, "got error %lu.\n", GetLastError() ); + ok( c.ContextFlags == expected_flags, "got %#lx.\n", c.ContextFlags ); + + c.ContextFlags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING | CONTEXT_SERVICE_ACTIVE + | CONTEXT_EXCEPTION_ACTIVE; + ret = GetThreadContext( thread, &c ); + ok( ret, "got error %lu.\n", GetLastError() ); + ok( c.ContextFlags == expected_flags, "got %#lx.\n", c.ContextFlags ); + + ResumeThread(thread); + WriteRelease( &p.sync, 2 ); + /* Try to make sure the thread entered WaitForSingleObject(). */ + Sleep(30); + + expected_flags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING | CONTEXT_SERVICE_ACTIVE; + + c.ContextFlags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST; + ret = GetThreadContext( thread, &c ); + ok( ret, "got error %lu.\n", GetLastError() ); + ok( c.ContextFlags == expected_flags, "got %#lx.\n", c.ContextFlags ); + + c.ContextFlags = CONTEXT_CONTROL; + ret = SetThreadContext( thread, &c ); + ok( ret, "got error %lu.\n", GetLastError() ); + + c.ContextFlags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING | CONTEXT_SERVICE_ACTIVE + | CONTEXT_EXCEPTION_ACTIVE; + ret = GetThreadContext( thread, &c ); + ok( ret, "got error %lu.\n", GetLastError() ); + ok( c.ContextFlags == expected_flags, "got %#lx.\n", c.ContextFlags ); + + SetEvent( p.event ); + + if (is_wow64 && !old_wow64) + { + while (ReadAcquire( &p.sync ) != 3) + SwitchToThread(); + /* thread is in x64 code. */ + + expected_flags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING | CONTEXT_EXCEPTION_ACTIVE; + + c.ContextFlags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST; + ret = GetThreadContext( thread, &c ); + ok( ret, "got error %lu.\n", GetLastError() ); + ok( c.ContextFlags == expected_flags, "got %#lx, expected %#lx.\n", c.ContextFlags, expected_flags ); + + WriteRelease( &p.sync, 4 ); + } + + while (ReadAcquire( &p.sync ) != 5) + SwitchToThread(); + + expected_flags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING; + + c.ContextFlags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST; + ret = GetThreadContext( thread, &c ); + ok( ret, "got error %lu.\n", GetLastError() ); + ok( c.ContextFlags == expected_flags, "got %#lx.\n", c.ContextFlags ); + + c.ContextFlags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING | CONTEXT_SERVICE_ACTIVE + | CONTEXT_EXCEPTION_ACTIVE; + ret = GetThreadContext( thread, &c ); + ok( ret, "got error %lu.\n", GetLastError() ); + ok( c.ContextFlags == expected_flags, "got %#lx.\n", c.ContextFlags ); + + WriteRelease( &p.sync, 6 ); + + if (is_wow64 && !old_wow64) + { + while (ReadAcquire( &p.sync ) != 7) + SwitchToThread(); + /* thread is in x64 code. */ + + expected_flags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST | CONTEXT_EXCEPTION_REPORTING | CONTEXT_EXCEPTION_ACTIVE; + + c.ContextFlags = CONTEXT_CONTROL | CONTEXT_EXCEPTION_REQUEST; + ret = GetThreadContext( thread, &c ); + ok( ret, "got error %lu.\n", GetLastError() ); + ok( c.ContextFlags == expected_flags, "got %#lx, expected %#lx.\n", c.ContextFlags, expected_flags ); + + WriteRelease( &p.sync, 8 ); + } + + WaitForSingleObject( thread, INFINITE ); + CloseHandle( thread ); + CloseHandle( p.event ); +} + START_TEST(exception) { HMODULE hkernel32 = GetModuleHandleA("kernel32.dll"); @@ -12537,5 +12911,6 @@ START_TEST(exception) test_suspend_thread(); test_suspend_process(); test_unload_trace(); + test_context_exception_request(); VirtualFree(code_mem, 0, MEM_RELEASE); } diff --git a/dlls/ntdll/tests/info.c b/dlls/ntdll/tests/info.c index 4118aae5e91..c574285abe8 100644 --- a/dlls/ntdll/tests/info.c +++ b/dlls/ntdll/tests/info.c @@ -21,6 +21,7 @@ #include "ntdll_test.h" #include #include +#include static NTSTATUS (WINAPI * pNtQuerySystemInformation)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG); static NTSTATUS (WINAPI * pNtSetSystemInformation)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG); @@ -3758,6 +3759,133 @@ static void test_system_debug_control(void) } } +static void test_process_id(void) +{ + char image_name_buffer[1024 * sizeof(WCHAR)]; + UNICODE_STRING *image_name = (UNICODE_STRING *)image_name_buffer; + SYSTEM_PROCESS_ID_INFORMATION info; + unsigned int i, length; + DWORD pids[2048]; + WCHAR name[2048]; + NTSTATUS status; + HANDLE process; + ULONG len; + BOOL bret; + + status = NtQueryInformationProcess( GetCurrentProcess(), ProcessImageFileName, image_name, + sizeof(image_name_buffer), NULL ); + ok( !status, "got %#lx.\n", status ); + length = image_name->Length; + image_name->Buffer[length] = 0; + + len = 0xdeadbeef; + status = pNtQuerySystemInformation( SystemProcessIdInformation, NULL, 0, &len ); + ok( status == STATUS_INFO_LENGTH_MISMATCH || (is_wow64 && status == STATUS_ACCESS_VIOLATION), "got %#lx.\n", status ); + ok( len == sizeof(info) || (is_wow64 && len == 0xdeadbeef), "got %#lx.\n", len ); + + info.ProcessId = (void *)0xdeadbeef; + info.ImageName.Length = info.ImageName.MaximumLength = 0; + info.ImageName.Buffer = NULL; + status = pNtQuerySystemInformation( SystemProcessIdInformation, &info, sizeof(info), &len ); + ok( status == STATUS_INVALID_CID, "got %#lx.\n", status ); + ok( !info.ImageName.Length, "got %#x.\n", info.ImageName.Length ); + ok( !info.ImageName.MaximumLength, "got %#x.\n", info.ImageName.MaximumLength ); + + info.ProcessId = (void *)(ULONG_PTR)GetCurrentProcessId(); + status = pNtQuerySystemInformation( SystemProcessIdInformation, &info, sizeof(info), &len ); + ok( status == STATUS_INFO_LENGTH_MISMATCH, "got %#lx.\n", status ); + ok( len == sizeof(info), "got %#lx.\n", len ); + ok( !info.ImageName.Length, "got %#x.\n", info.ImageName.Length ); + ok( info.ImageName.MaximumLength == length + 2 || (is_wow64 && !info.ImageName.MaximumLength), + "got %#x.\n", info.ImageName.MaximumLength ); + + info.ImageName.MaximumLength = sizeof(name); + len = 0xdeadbeef; + status = pNtQuerySystemInformation( SystemProcessIdInformation, &info, sizeof(info), &len ); + ok( status == STATUS_ACCESS_VIOLATION, "got %#lx.\n", status ); + ok( len == sizeof(info), "got %#lx.\n", len ); + ok( info.ImageName.Length == length || (is_wow64 && !info.ImageName.Length), + "got %u.\n", info.ImageName.Length ); + ok( info.ImageName.MaximumLength == length + 2 || (is_wow64 && !info.ImageName.Length), + "got %#x.\n", info.ImageName.MaximumLength ); + + info.ProcessId = (void *)0xdeadbeef; + info.ImageName.MaximumLength = sizeof(name); + info.ImageName.Buffer = name; + info.ImageName.Length = 0; + status = pNtQuerySystemInformation( SystemProcessIdInformation, &info, sizeof(info), &len ); + ok( status == STATUS_INVALID_CID, "got %#lx.\n", status ); + ok( !info.ImageName.Length, "got %#x.\n", info.ImageName.Length ); + ok( info.ImageName.MaximumLength == sizeof(name), "got %#x.\n", info.ImageName.MaximumLength ); + ok( info.ImageName.Buffer == name, "got %p, %p.\n", info.ImageName.Buffer, name ); + + info.ProcessId = NULL; + info.ImageName.MaximumLength = sizeof(name); + info.ImageName.Buffer = name; + info.ImageName.Length = 0; + status = pNtQuerySystemInformation( SystemProcessIdInformation, &info, sizeof(info), &len ); + ok( status == STATUS_INVALID_CID, "got %#lx.\n", status ); + ok( !info.ImageName.Length, "got %#x.\n", info.ImageName.Length ); + ok( info.ImageName.MaximumLength == sizeof(name), "got %#x.\n", info.ImageName.MaximumLength ); + ok( info.ImageName.Buffer == name, "got non NULL.\n" ); + + info.ProcessId = NULL; + info.ImageName.MaximumLength = sizeof(name); + info.ImageName.Buffer = name; + info.ImageName.Length = 4; + status = pNtQuerySystemInformation( SystemProcessIdInformation, &info, sizeof(info), &len ); + ok( status == STATUS_INVALID_PARAMETER, "got %#lx.\n", status ); + ok( info.ImageName.Length == 4, "got %#x.\n", info.ImageName.Length ); + ok( info.ImageName.MaximumLength == sizeof(name), "got %#x.\n", info.ImageName.MaximumLength ); + ok( info.ImageName.Buffer == name, "got non NULL.\n" ); + + info.ProcessId = (void *)(ULONG_PTR)GetCurrentProcessId(); + info.ImageName.MaximumLength = sizeof(name); + info.ImageName.Buffer = name; + info.ImageName.Length = 4; + status = pNtQuerySystemInformation( SystemProcessIdInformation, &info, sizeof(info), NULL ); + ok( status == STATUS_INVALID_PARAMETER, "got %#lx.\n", status ); + ok( info.ImageName.Length == 4, "got %#x.\n", info.ImageName.Length ); + ok( info.ImageName.MaximumLength == sizeof(name), "got %#x.\n", info.ImageName.MaximumLength ); + + info.ImageName.Length = 0; + memset( name, 0xcc, sizeof(name) ); + status = pNtQuerySystemInformation( SystemProcessIdInformation, &info, sizeof(info), &len ); + ok( !status, "got %#lx.\n", status ); + ok( info.ImageName.Length == length, "got %#x.\n", info.ImageName.Length ); + ok( len == sizeof(info), "got %#lx.\n", len ); + ok( info.ImageName.MaximumLength == info.ImageName.Length + 2, "got %#x.\n", info.ImageName.MaximumLength ); + ok( !name[info.ImageName.Length / 2], "got %#x.\n", name[info.ImageName.Length / 2] ); + + ok( info.ImageName.Length == image_name->Length, "got %#x, %#x.\n", info.ImageName.Length, image_name->Length ); + ok( !wcscmp( name, image_name->Buffer ), "got %s, %s.\n", debugstr_w(name), debugstr_w(image_name->Buffer) ); + + bret = EnumProcesses( pids, sizeof(pids), &len ); + ok( bret, "got error %lu.\n", GetLastError() ); + for (i = 0; i < len / sizeof(*pids); ++i) + { + process = OpenProcess( PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pids[i] ); + if (pids[i] && !process && GetLastError() != ERROR_ACCESS_DENIED) + { + /* process is gone already. */ + continue; + } + info.ProcessId = (void *)(ULONG_PTR)pids[i]; + info.ImageName.Length = 0; + info.ImageName.MaximumLength = sizeof(name); + info.ImageName.Buffer = name; + status = pNtQuerySystemInformation( SystemProcessIdInformation, &info, sizeof(info), &len ); + ok( info.ImageName.Buffer == name || (!info.ImageName.MaximumLength && !info.ImageName.Length), + "got %p, %p, pid %lu, lengh %u / %u.\n", info.ImageName.Buffer, name, pids[i], + info.ImageName.Length, info.ImageName.MaximumLength ); + if (pids[i]) + ok( !status, "got %#lx, pid %lu.\n", status, pids[i] ); + else + ok( status == STATUS_INVALID_CID, "got %#lx, pid %lu.\n", status, pids[i] ); + if (process) CloseHandle( process ); + } +} + START_TEST(info) { char **argv; @@ -3834,4 +3962,5 @@ START_TEST(info) test_ThreadEnableAlignmentFaultFixup(); test_process_instrumentation_callback(); test_system_debug_control(); + test_process_id(); } diff --git a/dlls/ntdll/tests/ntdll_test.h b/dlls/ntdll/tests/ntdll_test.h index 67278358b29..ed824583870 100644 --- a/dlls/ntdll/tests/ntdll_test.h +++ b/dlls/ntdll/tests/ntdll_test.h @@ -30,3 +30,5 @@ #include "wine/test.h" #include "wine/heap.h" + +extern NTSTATUS call_func64( ULONG64 func64, int nb_args, ULONG64 *args, void *code_mem ); diff --git a/dlls/ntdll/tests/wow64.c b/dlls/ntdll/tests/wow64.c index 01cd0cad035..1d48918165e 100644 --- a/dlls/ntdll/tests/wow64.c +++ b/dlls/ntdll/tests/wow64.c @@ -1440,7 +1440,7 @@ static const BYTE call_func64_code[] = 0xcb, /* lret */ }; -static NTSTATUS call_func64( ULONG64 func64, int nb_args, ULONG64 *args ) +NTSTATUS call_func64( ULONG64 func64, int nb_args, ULONG64 *args, void *code_mem ) { NTSTATUS (WINAPI *func)( ULONG64 func64, int nb_args, ULONG64 *args ) = code_mem; @@ -1940,7 +1940,7 @@ static void test_iosb(void) iosb64.Information = 0xdeadbeef; args[0] = (LONG_PTR)server; - status = call_func64( func, ARRAY_SIZE(args), args ); + status = call_func64( func, ARRAY_SIZE(args), args, code_mem ); ok( status == STATUS_PENDING, "NtFsControlFile returned %lx\n", status ); ok( iosb32.Status == 0x55555555, "status changed to %lx\n", iosb32.Status ); ok( iosb64.Pointer == PtrToUlong(&iosb32), "status changed to %lx\n", iosb64.Status ); @@ -1965,7 +1965,7 @@ static void test_iosb(void) args[8] = (ULONG_PTR)&id; args[9] = sizeof(id); - status = call_func64( func, ARRAY_SIZE(args), args ); + status = call_func64( func, ARRAY_SIZE(args), args, code_mem ); ok( status == STATUS_PENDING || status == STATUS_SUCCESS, "NtFsControlFile returned %lx\n", status ); todo_wine { @@ -1995,7 +1995,7 @@ static void test_iosb(void) id = 0xdeadbeef; args[0] = (LONG_PTR)server; - status = call_func64( func, ARRAY_SIZE(args), args ); + status = call_func64( func, ARRAY_SIZE(args), args, code_mem ); ok( status == STATUS_SUCCESS, "NtFsControlFile returned %lx\n", status ); ok( iosb32.Status == 0x55555555, "status changed to %lx\n", iosb32.Status ); ok( iosb32.Information == 0x55555555, "info changed to %Ix\n", iosb32.Information ); @@ -2018,7 +2018,7 @@ static NTSTATUS invoke_syscall( const char *name, ULONG args32[] ) else win_skip( "syscall thunk %s not recognized\n", name ); - return call_func64( func, ARRAY_SIZE(args64), args64 ); + return call_func64( func, ARRAY_SIZE(args64), args64, code_mem ); } static void test_syscalls(void) @@ -2124,14 +2124,14 @@ static void test_cpu_area(void) ULONG64 context, context_ex; ULONG64 args[] = { (ULONG_PTR)&machine, (ULONG_PTR)&context, (ULONG_PTR)&context_ex }; - status = call_func64( ptr, ARRAY_SIZE(args), args ); + status = call_func64( ptr, ARRAY_SIZE(args), args, code_mem ); ok( !status, "RtlWow64GetCpuAreaInfo failed %lx\n", status ); ok( machine == IMAGE_FILE_MACHINE_I386, "wrong machine %x\n", machine ); ok( context == teb64->TlsSlots[WOW64_TLS_CPURESERVED] + 4, "wrong context %s / %s\n", wine_dbgstr_longlong(context), wine_dbgstr_longlong(teb64->TlsSlots[WOW64_TLS_CPURESERVED]) ); ok( !context_ex, "got context_ex %s\n", wine_dbgstr_longlong(context_ex) ); args[0] = args[1] = args[2] = 0; - status = call_func64( ptr, ARRAY_SIZE(args), args ); + status = call_func64( ptr, ARRAY_SIZE(args), args, code_mem ); ok( !status, "RtlWow64GetCpuAreaInfo failed %lx\n", status ); } else win_skip( "RtlWow64GetCpuAreaInfo not supported\n" ); diff --git a/dlls/ntdll/threadpool.c b/dlls/ntdll/threadpool.c index 4f22114a55e..2887e84b12c 100644 --- a/dlls/ntdll/threadpool.c +++ b/dlls/ntdll/threadpool.c @@ -160,6 +160,7 @@ struct threadpool_object LONG num_pending_callbacks; LONG num_running_callbacks; LONG num_associated_callbacks; + LONG update_serial; /* arguments for callback */ union { @@ -1243,6 +1244,7 @@ static void tp_timerqueue_unlock( struct threadpool_object *timer ) static void CALLBACK waitqueue_thread_proc( void *param ) { struct threadpool_object *objects[MAXIMUM_WAITQUEUE_OBJECTS]; + LONG update_serials[MAXIMUM_WAITQUEUE_OBJECTS]; HANDLE handles[MAXIMUM_WAITQUEUE_OBJECTS + 1]; struct waitqueue_bucket *bucket = param; struct threadpool_object *wait, *next; @@ -1265,6 +1267,7 @@ static void CALLBACK waitqueue_thread_proc( void *param ) u.wait.wait_entry ) { assert( wait->type == TP_OBJECT_TYPE_WAIT ); + assert( wait->u.wait.wait_pending ); if (wait->u.wait.timeout <= now.QuadPart) { /* Wait object timed out. */ @@ -1272,6 +1275,7 @@ static void CALLBACK waitqueue_thread_proc( void *param ) { list_remove( &wait->u.wait.wait_entry ); list_add_tail( &bucket->reserved, &wait->u.wait.wait_entry ); + wait->u.wait.wait_pending = FALSE; } if ((wait->u.wait.flags & (WT_EXECUTEINWAITTHREAD | WT_EXECUTEINIOTHREAD))) { @@ -1293,6 +1297,7 @@ static void CALLBACK waitqueue_thread_proc( void *param ) InterlockedIncrement( &wait->refcount ); objects[num_handles] = wait; handles[num_handles] = wait->u.wait.handle; + update_serials[num_handles] = wait->update_serial; num_handles++; } } @@ -1321,7 +1326,7 @@ static void CALLBACK waitqueue_thread_proc( void *param ) { wait = objects[status - STATUS_WAIT_0]; assert( wait->type == TP_OBJECT_TYPE_WAIT ); - if (wait->u.wait.bucket) + if (wait->u.wait.bucket && wait->update_serial == update_serials[status - STATUS_WAIT_0]) { /* Wait object signaled. */ assert( wait->u.wait.bucket == bucket ); @@ -1329,6 +1334,7 @@ static void CALLBACK waitqueue_thread_proc( void *param ) { list_remove( &wait->u.wait.wait_entry ); list_add_tail( &bucket->reserved, &wait->u.wait.wait_entry ); + wait->u.wait.wait_pending = FALSE; } if ((wait->u.wait.flags & (WT_EXECUTEINWAITTHREAD | WT_EXECUTEINIOTHREAD))) { @@ -1341,7 +1347,10 @@ static void CALLBACK waitqueue_thread_proc( void *param ) else tp_object_submit( wait, TRUE ); } else - WARN("wait object %p triggered while object was destroyed\n", wait); + { + WARN("wait object %p triggered while object was %s.\n", + wait, wait->u.wait.bucket ? "updated" : "destroyed"); + } } /* Release temporary references to wait objects. */ @@ -1914,6 +1923,7 @@ static void tp_object_initialize( struct threadpool_object *object, struct threa object->num_pending_callbacks = 0; object->num_running_callbacks = 0; object->num_associated_callbacks = 0; + object->update_serial = 0; if (environment) { @@ -3048,12 +3058,15 @@ VOID WINAPI TpSetWait( TP_WAIT *wait, HANDLE handle, LARGE_INTEGER *timeout ) { struct threadpool_object *this = impl_from_TP_WAIT( wait ); ULONGLONG timestamp = MAXLONGLONG; + BOOL same_handle; TRACE( "%p %p %p\n", wait, handle, timeout ); RtlEnterCriticalSection( &waitqueue.cs ); assert( this->u.wait.bucket ); + + same_handle = this->u.wait.handle == handle; this->u.wait.handle = handle; if (handle || this->u.wait.wait_pending) @@ -3087,6 +3100,8 @@ VOID WINAPI TpSetWait( TP_WAIT *wait, HANDLE handle, LARGE_INTEGER *timeout ) } /* Wake up the wait queue thread. */ + if (!same_handle) + ++this->update_serial; NtSetEvent( bucket->update_event, NULL ); } diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index c251d9c1955..e5ea71fae80 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -2844,6 +2844,67 @@ static NTSTATUS find_file_in_dir( char *unix_name, int pos, const WCHAR *name, i return STATUS_OBJECT_NAME_NOT_FOUND; } +/* CW-Bug-Id: #23185 Emulate Steam Input native hooks for native SDL */ +static BOOL replace_steam_input_path( OBJECT_ATTRIBUTES *attr, UNICODE_STRING *redir ) +{ + static const WCHAR pipe_prefixW[] = + { + '\\','?','?','\\','p','i','p','e','\\','H','I','D','#','V','I','D','_','0','4','5','E', + '&','P','I','D','_','0','2','8','E','&','I','G','_','0','0', + }; + static const WCHAR hid_prefixW[] = + { + '\\','?','?','\\','h','i','d','#','v','i','d','_','2','8','d','e', + '&','p','i','d','_','1','1','f','f','&','i','g','_','0' + }; + static const WCHAR hid_midW[] = + { + '#','0', + }; + static const WCHAR hid_tailW[] = + { + '&','0','&','0','&','1','#','{','4','d','1','e','5','5','b','2','-','f','1','6','f','-', + '1','1','c','f','-','8','8','c','b','-','0','0','1','1','1','1','0','0','0','0','3','0','}' + }; + UNICODE_STRING *path = attr->ObjectName; + const WCHAR *slot = NULL, *slot_end = NULL, *serial, *serial_end = NULL; + UINT len = 0; + + if (!path || !path->Buffer || path->Length <= sizeof(pipe_prefixW)) return FALSE; + if (wcsnicmp( path->Buffer, pipe_prefixW, ARRAY_SIZE(pipe_prefixW) )) return FALSE; + + serial = path->Buffer + path->Length / sizeof(WCHAR); + while (serial > path->Buffer && *serial != '&') + { + if (*serial == '#') + { + slot_end = serial_end; + serial_end = serial; + slot = serial_end + 1; + } + serial--; + } + if (serial == path->Buffer || *serial != '&' || !slot_end || !serial_end) return FALSE; + + redir->Length = sizeof(hid_prefixW) + sizeof(hid_midW) + sizeof(hid_tailW); + redir->Length += (serial_end - serial + slot_end - slot) * sizeof(WCHAR); + redir->MaximumLength = redir->Length + sizeof(WCHAR); + if (!(redir->Buffer = malloc( redir->MaximumLength ))) return FALSE; + + memcpy( redir->Buffer, hid_prefixW, sizeof(hid_prefixW) ); + len += ARRAY_SIZE(hid_prefixW); + memcpy( redir->Buffer + len, slot, (slot_end - slot) * sizeof(WCHAR) ); + len += slot_end - slot; + memcpy( redir->Buffer + len, hid_midW, sizeof(hid_midW) ); + len += ARRAY_SIZE(hid_midW); + memcpy( redir->Buffer + len, serial, (serial_end - serial) * sizeof(WCHAR) ); + len += serial_end - serial; + memcpy( redir->Buffer + len, hid_tailW, sizeof(hid_tailW) ); + + TRACE( "HACK: %s -> %s\n", debugstr_us(attr->ObjectName), debugstr_us(redir) ); + attr->ObjectName = redir; + return TRUE; +} #ifndef _WIN64 @@ -2945,6 +3006,10 @@ BOOL get_redirect( OBJECT_ATTRIBUTES *attr, UNICODE_STRING *redir ) unsigned int i, prefix_len = 0, len = attr->ObjectName->Length / sizeof(WCHAR); redir->Buffer = NULL; + + /* CW-Bug-Id: #23185 Emulate Steam Input native hooks for native SDL */ + if (replace_steam_input_path( attr, redir )) return TRUE; + if (!NtCurrentTeb64()) return FALSE; if (!len) return FALSE; @@ -3004,7 +3069,8 @@ BOOL get_redirect( OBJECT_ATTRIBUTES *attr, UNICODE_STRING *redir ) BOOL get_redirect( OBJECT_ATTRIBUTES *attr, UNICODE_STRING *redir ) { redir->Buffer = NULL; - return FALSE; + /* CW-Bug-Id: #23185 Emulate Steam Input native hooks for native SDL */ + return replace_steam_input_path( attr, redir ); } #endif @@ -5582,6 +5648,7 @@ struct async_file_read_job LONG cancelled; struct list queue_entry; struct async_file_read_job *next; + ULONG64 queue_time_mcs; }; @@ -5602,7 +5669,9 @@ static void *async_file_read_thread(void *dummy) ULONG buffer_length = 0; void *buffer = NULL; struct list *entry; + struct timespec ts; NTSTATUS status; + ULONG64 delay; ULONG total; int result; @@ -5653,6 +5722,13 @@ static void *async_file_read_thread(void *dummy) break; } + clock_gettime( CLOCK_MONOTONIC, &ts ); + delay = ts.tv_sec * (ULONG64)1000000 + ts.tv_nsec / 1000 - job->queue_time_mcs; + if (delay < 1000) + usleep( 1000 - delay ); + else + usleep( 50 ); + total = result; status = (total || !job->length) ? STATUS_SUCCESS : STATUS_END_OF_FILE; done: @@ -5713,6 +5789,7 @@ static NTSTATUS queue_async_file_read( HANDLE handle, int unix_handle, int needs IO_STATUS_BLOCK *io, void *buffer, ULONG length, LARGE_INTEGER *offset ) { struct async_file_read_job *job; + struct timespec ts; pthread_once( &async_file_read_once, async_file_read_init ); @@ -5744,6 +5821,8 @@ static NTSTATUS queue_async_file_read( HANDLE handle, int unix_handle, int needs job->offset = *offset; job->thread_id = GetCurrentThreadId(); job->cancelled = 0; + clock_gettime( CLOCK_MONOTONIC, &ts ); + job->queue_time_mcs = ts.tv_sec * (ULONG64)1000000 + ts.tv_nsec / 1000; list_add_tail( &async_file_read_queue, &job->queue_entry ); diff --git a/dlls/ntdll/unix/loader.c b/dlls/ntdll/unix/loader.c index 9dec5d15d80..4f8a84c6080 100644 --- a/dlls/ntdll/unix/loader.c +++ b/dlls/ntdll/unix/loader.c @@ -598,6 +598,8 @@ NTSTATUS exec_wineloader( char **argv, int socketfd, const pe_image_info_t *pe_i if (pe_info->wine_fakedll) res_start = res_end = 0; if (pe_info->image_flags & IMAGE_FLAGS_ComPlusNativeReady) machine = native_machine; + unsetenv( "WINE_LD_PRELOAD" ); + /* HACK: Unset LD_PRELOAD before executing explorer.exe to disable buggy gameoverlayrenderer.so */ if (ld_preload && argv[2] && !strcmp( argv[2], "C:\\windows\\system32\\explorer.exe" ) && argv[3] && !strcmp( argv[3], "/desktop" )) @@ -626,8 +628,11 @@ NTSTATUS exec_wineloader( char **argv, int socketfd, const pe_image_info_t *pe_i while (*next); putenv( env ); + ld_preload = NULL; } + if (ld_preload) setenv( "WINE_LD_PRELOAD", ld_preload, 1 ); + signal( SIGPIPE, SIG_DFL ); snprintf( socket_env, sizeof(socket_env), "WINESERVERSOCKET=%u", socketfd ); @@ -2107,7 +2112,8 @@ static void hacks_init(void) || !strcmp(sgi, "2053940") /* Idol Hands 2 */ || !strcmp(sgi, "391150") /* Red Tie Runner */ || !strcmp(sgi, "2152990") /* Dinogen Online */ - || !strcmp(sgi, "2176450"); /* Mr. Hopp's Playhouse 3 */ + || !strcmp(sgi, "2176450") /* Mr. Hopp's Playhouse 3 */ + || !strcmp(sgi, "2361360"); /* Hentai Maid Memories */ if (sgi) wine_allocs_2g_limit = !strcmp(sgi, "359870"); if (wine_allocs_2g_limit) ERR("Allocation 2g limit enabled.\n"); diff --git a/dlls/ntdll/unix/server.c b/dlls/ntdll/unix/server.c index 6148cfddce2..992e5cfdc3d 100644 --- a/dlls/ntdll/unix/server.c +++ b/dlls/ntdll/unix/server.c @@ -762,7 +762,11 @@ unsigned int server_select( const select_op_t *select_op, data_size_t size, UINT if (ret == STATUS_USER_APC) *user_apc = reply_data.call.user; if (reply_size > sizeof(reply_data.call)) + { memcpy( context, reply_data.context, reply_size - sizeof(reply_data.call) ); + context[0].flags &= ~SERVER_CTX_EXEC_SPACE; + context[1].flags &= ~SERVER_CTX_EXEC_SPACE; + } return ret; } diff --git a/dlls/ntdll/unix/signal_arm.c b/dlls/ntdll/unix/signal_arm.c index afa3e71c5e2..dbad562c6d3 100644 --- a/dlls/ntdll/unix/signal_arm.c +++ b/dlls/ntdll/unix/signal_arm.c @@ -1033,6 +1033,7 @@ NTSTATUS WINAPI NtGetContextThread( HANDLE handle, CONTEXT *context ) memcpy( context->D, frame->d, sizeof(frame->d) ); context->ContextFlags |= CONTEXT_FLOATING_POINT; } + set_context_exception_reporting_flags( &context->ContextFlags, CONTEXT_SERVICE_ACTIVE ); return STATUS_SUCCESS; } @@ -1070,7 +1071,7 @@ static void setup_exception( ucontext_t *sigcontext, EXCEPTION_RECORD *rec ) rec->ExceptionAddress = (void *)PC_sig(sigcontext); save_context( &context, sigcontext ); - status = send_debug_event( rec, &context, TRUE ); + status = send_debug_event( rec, &context, TRUE, TRUE ); if (status == DBG_CONTINUE || status == DBG_EXCEPTION_HANDLED) { restore_context( &context, sigcontext ); @@ -1524,7 +1525,7 @@ static void usr1_handler( int signal, siginfo_t *siginfo, void *sigcontext ) if (is_inside_syscall( sigcontext )) { - context.ContextFlags = CONTEXT_FULL; + context.ContextFlags = CONTEXT_FULL | CONTEXT_EXCEPTION_REQUEST; NtGetContextThread( GetCurrentThread(), &context ); wait_suspend( &context ); NtSetContextThread( GetCurrentThread(), &context ); @@ -1532,6 +1533,7 @@ static void usr1_handler( int signal, siginfo_t *siginfo, void *sigcontext ) else { save_context( &context, sigcontext ); + context.ContextFlags |= CONTEXT_EXCEPTION_REPORTING; wait_suspend( &context ); restore_context( &context, sigcontext ); } @@ -1638,7 +1640,11 @@ void call_init_thunk( LPTHREAD_START_ROUTINE entry, void *arg, BOOL suspend, TEB if (context.Pc & 1) context.Cpsr |= 0x20; /* thumb mode */ if ((ctx = get_cpu_area( IMAGE_FILE_MACHINE_ARMNT ))) *ctx = context; - if (suspend) wait_suspend( &context ); + if (suspend) + { + context.ContextFlags |= CONTEXT_EXCEPTION_REPORTING | CONTEXT_EXCEPTION_ACTIVE; + wait_suspend( &context ); + } ctx = (CONTEXT *)((ULONG_PTR)context.Sp & ~15) - 1; *ctx = context; diff --git a/dlls/ntdll/unix/signal_arm64.c b/dlls/ntdll/unix/signal_arm64.c index 389f16f89d9..a4bae5563a8 100644 --- a/dlls/ntdll/unix/signal_arm64.c +++ b/dlls/ntdll/unix/signal_arm64.c @@ -710,6 +710,7 @@ NTSTATUS WINAPI NtGetContextThread( HANDLE handle, CONTEXT *context ) context->ContextFlags |= CONTEXT_FLOATING_POINT; } if (needed_flags & CONTEXT_DEBUG_REGISTERS) FIXME( "debug registers not supported\n" ); + set_context_exception_reporting_flags( &context->ContextFlags, CONTEXT_SERVICE_ACTIVE ); return STATUS_SUCCESS; } @@ -917,6 +918,7 @@ NTSTATUS get_thread_wow64_context( HANDLE handle, void *ctx, ULONG size ) context->Dr7 = wow_frame->Dr7; } /* FIXME: CONTEXT_I386_XSTATE */ + set_context_exception_reporting_flags( &context->ContextFlags, CONTEXT_SERVICE_ACTIVE ); break; } @@ -956,6 +958,7 @@ NTSTATUS get_thread_wow64_context( HANDLE handle, void *ctx, ULONG size ) memcpy( context->D, wow_frame->D, sizeof(wow_frame->D) ); context->ContextFlags |= CONTEXT_FLOATING_POINT; } + set_context_exception_reporting_flags( &context->ContextFlags, CONTEXT_SERVICE_ACTIVE ); break; } @@ -979,7 +982,7 @@ static void setup_exception( ucontext_t *sigcontext, EXCEPTION_RECORD *rec ) rec->ExceptionAddress = (void *)PC_sig(sigcontext); save_context( &context, sigcontext ); - status = send_debug_event( rec, &context, TRUE ); + status = send_debug_event( rec, &context, TRUE, TRUE ); if (status == DBG_CONTINUE || status == DBG_EXCEPTION_HANDLED) { restore_context( &context, sigcontext ); @@ -1491,7 +1494,7 @@ static void usr1_handler( int signal, siginfo_t *siginfo, void *sigcontext ) if (is_inside_syscall( sigcontext )) { - context.ContextFlags = CONTEXT_FULL; + context.ContextFlags = CONTEXT_FULL | CONTEXT_EXCEPTION_REQUEST; NtGetContextThread( GetCurrentThread(), &context ); wait_suspend( &context ); NtSetContextThread( GetCurrentThread(), &context ); @@ -1499,6 +1502,7 @@ static void usr1_handler( int signal, siginfo_t *siginfo, void *sigcontext ) else { save_context( &context, sigcontext ); + context.ContextFlags |= CONTEXT_EXCEPTION_REPORTING; wait_suspend( &context ); restore_context( &context, sigcontext ); } @@ -1684,7 +1688,11 @@ void call_init_thunk( LPTHREAD_START_ROUTINE entry, void *arg, BOOL suspend, TEB if (arm_context->Pc & 1) arm_context->Cpsr |= 0x20; /* thumb mode */ } - if (suspend) wait_suspend( &context ); + if (suspend) + { + context.ContextFlags |= CONTEXT_EXCEPTION_REPORTING | CONTEXT_EXCEPTION_ACTIVE; + wait_suspend( &context ); + } ctx = (CONTEXT *)((ULONG_PTR)context.Sp & ~15) - 1; *ctx = context; diff --git a/dlls/ntdll/unix/signal_i386.c b/dlls/ntdll/unix/signal_i386.c index 5b7d460372c..faf09c05371 100644 --- a/dlls/ntdll/unix/signal_i386.c +++ b/dlls/ntdll/unix/signal_i386.c @@ -151,7 +151,7 @@ typedef struct ucontext #define FPU_sig(context) ((FLOATING_SAVE_AREA*)((context)->uc_mcontext.fpregs)) #define FPUX_sig(context) (FPU_sig(context) && !((context)->uc_mcontext.fpregs->status >> 16) ? (XSAVE_FORMAT *)(FPU_sig(context) + 1) : NULL) -#define XState_sig(fpu) (((unsigned int *)fpu->Reserved4)[12] == FP_XSTATE_MAGIC1 ? (XSTATE *)(fpu + 1) : NULL) +#define XState_sig(fpu) (((unsigned int *)fpu->Reserved4)[12] == FP_XSTATE_MAGIC1 ? (XSAVE_AREA_HEADER *)(fpu + 1) : NULL) #ifdef __ANDROID__ /* custom signal restorer since we may have unmapped the one in vdso, and bionic doesn't check for that */ @@ -443,12 +443,10 @@ struct exc_stack_layout EXCEPTION_RECORD rec; /* 008 */ CONTEXT context; /* 058 */ CONTEXT_EX context_ex; /* 324 */ - BYTE xstate[sizeof(XSTATE)+64]; /* 33c extra space to allow for 64-byte alignment */ - DWORD align; /* 4bc */ + DWORD align; /* 33c */ }; C_ASSERT( offsetof(struct exc_stack_layout, context) == 0x58 ); -C_ASSERT( offsetof(struct exc_stack_layout, xstate) == 0x33c ); -C_ASSERT( sizeof(struct exc_stack_layout) == 0x4c0 ); +C_ASSERT( sizeof(struct exc_stack_layout) == 0x340 ); /* stack layout when calling KiUserApcDispatcher */ struct apc_stack_layout @@ -508,10 +506,10 @@ struct syscall_frame /* Leave space for the whole set of YMM registers. They're not used in * 32-bit mode, but some processors fault if they're not in writable memory. */ - DECLSPEC_ALIGN(64) XSTATE xstate; /* 240 */ + DECLSPEC_ALIGN(64) XSAVE_AREA_HEADER xstate; /* 240 */ }; -C_ASSERT( sizeof(struct syscall_frame) == 0x380 ); +C_ASSERT( sizeof(struct syscall_frame) == 0x280 ); struct x86_thread_data { @@ -525,12 +523,15 @@ struct x86_thread_data UINT dr7; /* 1f0 */ SYSTEM_SERVICE_TABLE *syscall_table; /* 1f4 syscall table */ struct syscall_frame *syscall_frame; /* 1f8 frame pointer on syscall entry */ + UINT64 xstate_features_mask; /* 1fc */ + UINT xstate_features_size; /* 204 */ }; C_ASSERT( sizeof(struct x86_thread_data) <= sizeof(((struct ntdll_thread_data *)0)->cpu_data) ); C_ASSERT( offsetof( TEB, GdiTebBatch ) + offsetof( struct x86_thread_data, gs ) == 0x1d8 ); C_ASSERT( offsetof( TEB, GdiTebBatch ) + offsetof( struct x86_thread_data, syscall_table ) == 0x1f4 ); C_ASSERT( offsetof( TEB, GdiTebBatch ) + offsetof( struct x86_thread_data, syscall_frame ) == 0x1f8 ); +C_ASSERT( offsetof( TEB, GdiTebBatch ) + offsetof( struct x86_thread_data, xstate_features_size ) == 0x204 ); /* flags to control the behavior of the syscall dispatcher */ #define SYSCALL_HAVE_XSAVE 1 @@ -606,17 +607,14 @@ struct xcontext { CONTEXT c; CONTEXT_EX c_ex; - ULONG64 host_compaction_mask; }; -extern BOOL xstate_compaction_enabled; - -static inline XSTATE *xstate_from_context( const CONTEXT *context ) +static inline XSAVE_AREA_HEADER *xstate_from_context( const CONTEXT *context ) { CONTEXT_EX *xctx = (CONTEXT_EX *)(context + 1); if ((context->ContextFlags & CONTEXT_XSTATE) != CONTEXT_XSTATE) return NULL; - return (XSTATE *)((char *)xctx + xctx->XState.Offset); + return (XSAVE_AREA_HEADER *)((char *)xctx + xctx->XState.Offset); } static inline void context_init_xstate( CONTEXT *context, void *xstate_buffer ) @@ -629,7 +627,7 @@ static inline void context_init_xstate( CONTEXT *context, void *xstate_buffer ) if (xstate_buffer) { - xctx->XState.Length = sizeof(XSTATE); + xctx->XState.Length = sizeof(XSAVE_AREA_HEADER) + xstate_features_size; xctx->XState.Offset = (BYTE *)xstate_buffer - (BYTE *)xctx; context->ContextFlags |= CONTEXT_XSTATE; @@ -827,16 +825,12 @@ static inline void save_context( struct xcontext *xcontext, const ucontext_t *si } if (fpux) { - XSTATE *xs; + XSAVE_AREA_HEADER *xs; context->ContextFlags |= CONTEXT_FLOATING_POINT | CONTEXT_EXTENDED_REGISTERS; memcpy( context->ExtendedRegisters, fpux, sizeof(*fpux) ); if (!fpu) fpux_to_fpu( &context->FloatSave, fpux ); - if ((cpu_info.ProcessorFeatureBits & CPU_FEATURE_AVX) && (xs = XState_sig(fpux))) - { - context_init_xstate( context, xs ); - xcontext->host_compaction_mask = xs->CompactionMask; - } + if (xstate_extended_features() && (xs = XState_sig(fpux))) context_init_xstate( context, xs ); } if (!fpu && !fpux) save_fpu( context ); } @@ -877,19 +871,7 @@ static inline void restore_context( const struct xcontext *xcontext, ucontext_t SS_sig(sigcontext) = context->SegSs; if (fpu) *fpu = context->FloatSave; - if (fpux) - { - XSTATE *src_xs, *dst_xs; - - memcpy( fpux, context->ExtendedRegisters, sizeof(*fpux) ); - - if ((dst_xs = XState_sig(fpux)) && (src_xs = xstate_from_context( context ))) - { - memcpy( &dst_xs->YmmContext, &src_xs->YmmContext, sizeof(dst_xs->YmmContext) ); - dst_xs->Mask |= src_xs->Mask; - dst_xs->CompactionMask = xcontext->host_compaction_mask; - } - } + if (fpux) memcpy( fpux, context->ExtendedRegisters, sizeof(*fpux) ); if (!fpu && !fpux) restore_fpu( context ); } @@ -936,15 +918,16 @@ NTSTATUS WINAPI NtSetContextThread( HANDLE handle, const CONTEXT *context ) DWORD flags = context->ContextFlags & ~CONTEXT_i386; BOOL self = (handle == GetCurrentThread()); - if ((flags & CONTEXT_XSTATE) && (cpu_info.ProcessorFeatureBits & CPU_FEATURE_AVX)) + if ((flags & CONTEXT_XSTATE) && xstate_extended_features()) { CONTEXT_EX *context_ex = (CONTEXT_EX *)(context + 1); - XSTATE *xs = (XSTATE *)((char *)context_ex + context_ex->XState.Offset); + XSAVE_AREA_HEADER *xs = (XSAVE_AREA_HEADER *)((char *)context_ex + context_ex->XState.Offset); - if (context_ex->XState.Length < offsetof(XSTATE, YmmContext) || - context_ex->XState.Length > sizeof(XSTATE)) + if (context_ex->XState.Length < sizeof(XSAVE_AREA_HEADER) || + context_ex->XState.Length > sizeof(XSAVE_AREA_HEADER) + xstate_features_size) return STATUS_INVALID_PARAMETER; - if ((xs->Mask & XSTATE_MASK_GSSE) && (context_ex->XState.Length < sizeof(XSTATE))) + if ((xs->Mask & xstate_extended_features()) + && (context_ex->XState.Length < xstate_get_size( xs->CompactionMask, xs->Mask ))) return STATUS_BUFFER_OVERFLOW; } else flags &= ~CONTEXT_XSTATE; @@ -1020,14 +1003,12 @@ NTSTATUS WINAPI NtSetContextThread( HANDLE handle, const CONTEXT *context ) if (flags & CONTEXT_XSTATE) { CONTEXT_EX *context_ex = (CONTEXT_EX *)(context + 1); - XSTATE *xs = (XSTATE *)((char *)context_ex + context_ex->XState.Offset); + XSAVE_AREA_HEADER *xs = (XSAVE_AREA_HEADER *)((char *)context_ex + context_ex->XState.Offset); + UINT64 mask = frame->xstate.Mask; - if (xs->Mask & XSTATE_MASK_GSSE) - { - frame->xstate.Mask |= XSTATE_MASK_GSSE; - frame->xstate.YmmContext = xs->YmmContext; - } - else frame->xstate.Mask &= ~XSTATE_MASK_GSSE; + if (xstate_compaction_enabled) frame->xstate.CompactionMask |= xstate_extended_features(); + copy_xstate( &frame->xstate, xs, xs->Mask ); + if (xs->CompactionMask) frame->xstate.Mask |= mask & ~xs->CompactionMask; } frame->restore_flags |= flags & ~CONTEXT_INTEGER; @@ -1145,20 +1126,24 @@ NTSTATUS WINAPI NtGetContextThread( HANDLE handle, CONTEXT *context ) context->ContextFlags |= CONTEXT_EXTENDED_REGISTERS; } - if ((needed_flags & CONTEXT_XSTATE) && (cpu_info.ProcessorFeatureBits & CPU_FEATURE_AVX)) + if ((needed_flags & CONTEXT_XSTATE) && xstate_extended_features()) { CONTEXT_EX *context_ex = (CONTEXT_EX *)(context + 1); - XSTATE *xstate = (XSTATE *)((char *)context_ex + context_ex->XState.Offset); - unsigned int mask; + XSAVE_AREA_HEADER *xstate = (XSAVE_AREA_HEADER *)((char *)context_ex + context_ex->XState.Offset); + UINT64 mask; - mask = (xstate_compaction_enabled ? xstate->CompactionMask : xstate->Mask) & XSTATE_MASK_GSSE; + if (xstate_compaction_enabled) frame->xstate.CompactionMask |= xstate_extended_features(); + mask = (xstate_compaction_enabled ? xstate->CompactionMask : xstate->Mask) & xstate_extended_features(); xstate->Mask = frame->xstate.Mask & mask; xstate->CompactionMask = xstate_compaction_enabled ? (0x8000000000000000 | mask) : 0; - memset( xstate->Reserved, 0, sizeof(xstate->Reserved) ); + memset( xstate->Reserved2, 0, sizeof(xstate->Reserved2) ); if (xstate->Mask) { - if (context_ex->XState.Length < sizeof(XSTATE)) return STATUS_BUFFER_OVERFLOW; - xstate->YmmContext = frame->xstate.YmmContext; + if (context_ex->XState.Length < xstate_get_size( xstate->CompactionMask, xstate->Mask )) + return STATUS_BUFFER_OVERFLOW; + copy_xstate( xstate, &frame->xstate, xstate->Mask ); + /* copy_xstate may use avx in memcpy, restore xstate not to break the tests. */ + frame->restore_flags |= CONTEXT_XSTATE; } } if (context->ContextFlags & (CONTEXT_DEBUG_REGISTERS & ~CONTEXT_i386)) @@ -1183,6 +1168,7 @@ NTSTATUS WINAPI NtGetContextThread( HANDLE handle, CONTEXT *context ) x86_thread_data()->dr7 = context->Dr7; } } + set_context_exception_reporting_flags( &context->ContextFlags, CONTEXT_SERVICE_ACTIVE ); } if (context->ContextFlags & (CONTEXT_INTEGER & ~CONTEXT_i386)) @@ -1475,9 +1461,11 @@ static void setup_raise_exception( ucontext_t *sigcontext, void *stack_ptr, EXCEPTION_RECORD *rec, struct xcontext *xcontext ) { CONTEXT *context = &xcontext->c; - XSTATE *src_xs; + XSAVE_AREA_HEADER *src_xs; struct exc_stack_layout *stack; - NTSTATUS status = send_debug_event( rec, context, TRUE ); + size_t stack_size; + unsigned int xstate_size; + NTSTATUS status = send_debug_event( rec, context, TRUE, TRUE ); if (status == DBG_CONTINUE || status == DBG_EXCEPTION_HANDLED) { @@ -1488,7 +1476,9 @@ static void setup_raise_exception( ucontext_t *sigcontext, void *stack_ptr, /* fix up instruction pointer in context for EXCEPTION_BREAKPOINT */ if (rec->ExceptionCode == EXCEPTION_BREAKPOINT) context->Eip--; - stack = virtual_setup_exception( stack_ptr, sizeof(*stack), rec ); + xstate_size = sizeof(XSAVE_AREA_HEADER) + xstate_features_size; + stack_size = (ULONG_PTR)stack_ptr - (((ULONG_PTR)stack_ptr - sizeof(*stack) - xstate_size) & ~(ULONG_PTR)63); + stack = virtual_setup_exception( stack_ptr, stack_size, rec ); stack->rec_ptr = &stack->rec; stack->context_ptr = &stack->context; stack->rec = *rec; @@ -1496,16 +1486,13 @@ static void setup_raise_exception( ucontext_t *sigcontext, void *stack_ptr, if ((src_xs = xstate_from_context( context ))) { - XSTATE *dst_xs = (XSTATE *)(((ULONG_PTR)stack->xstate + 63) & ~63); + XSAVE_AREA_HEADER *dst_xs = (XSAVE_AREA_HEADER *)(stack + 1); + assert( !((ULONG_PTR)dst_xs & 63) ); context_init_xstate( &stack->context, dst_xs ); - memset( dst_xs, 0, offsetof(XSTATE, YmmContext) ); - dst_xs->CompactionMask = xstate_compaction_enabled ? 0x8000000000000004 : 0; - if (src_xs->Mask & 4) - { - dst_xs->Mask = 4; - memcpy( &dst_xs->YmmContext, &src_xs->YmmContext, sizeof(dst_xs->YmmContext) ); - } + memset( dst_xs, 0, sizeof(*dst_xs) ); + dst_xs->CompactionMask = xstate_compaction_enabled ? 0x8000000000000000 | xstate_extended_features() : 0; + copy_xstate( dst_xs, src_xs, src_xs->Mask ); } else { @@ -1586,11 +1573,14 @@ NTSTATUS call_user_exception_dispatcher( EXCEPTION_RECORD *rec, CONTEXT *context { struct syscall_frame *frame = x86_thread_data()->syscall_frame; ULONG esp = (frame->esp - sizeof(struct exc_stack_layout)) & ~3; - struct exc_stack_layout *stack = (struct exc_stack_layout *)esp; - XSTATE *src_xs; + struct exc_stack_layout *stack; + XSAVE_AREA_HEADER *src_xs; + unsigned int xstate_size; if (rec->ExceptionCode == EXCEPTION_BREAKPOINT) context->Eip--; + xstate_size = sizeof(XSAVE_AREA_HEADER) + xstate_features_size; + stack = (struct exc_stack_layout *)((esp - sizeof(*stack) - xstate_size) & ~(ULONG_PTR)63); stack->rec_ptr = &stack->rec; stack->context_ptr = &stack->context; stack->rec = *rec; @@ -1598,16 +1588,13 @@ NTSTATUS call_user_exception_dispatcher( EXCEPTION_RECORD *rec, CONTEXT *context if ((src_xs = xstate_from_context( context ))) { - XSTATE *dst_xs = (XSTATE *)(((ULONG_PTR)stack->xstate + 63) & ~63); + XSAVE_AREA_HEADER *dst_xs = (XSAVE_AREA_HEADER *)(stack + 1); context_init_xstate( &stack->context, dst_xs ); - memset( dst_xs, 0, offsetof(XSTATE, YmmContext) ); - dst_xs->CompactionMask = xstate_compaction_enabled ? 0x8000000000000004 : 0; - if (src_xs->Mask & 4) - { - dst_xs->Mask = 4; - memcpy( &dst_xs->YmmContext, &src_xs->YmmContext, sizeof(dst_xs->YmmContext) ); - } + assert( !((ULONG_PTR)dst_xs & 63) ); + memset( dst_xs, 0, sizeof(*dst_xs) ); + dst_xs->CompactionMask = xstate_compaction_enabled ? 0x8000000000000000 | xstate_extended_features() : 0; + copy_xstate( dst_xs, src_xs, src_xs->Mask ); } else { @@ -1639,7 +1626,8 @@ __ASM_GLOBAL_FUNC( call_user_mode_callback, __ASM_CFI(".cfi_rel_offset %edi,-12\n\t") "movl 0x18(%ebp),%edx\n\t" /* teb */ "pushl 0(%edx)\n\t" /* teb->Tib.ExceptionList */ - "subl $0x380,%esp\n\t" /* sizeof(struct syscall_frame) */ + "subl $0x280,%esp\n\t" /* sizeof(struct syscall_frame) */ + "subl 0x204(%edx),%esp\n\t" /* x86_thread_data()->xstate_features_size */ "andl $~63,%esp\n\t" "leal 8(%ebp),%eax\n\t" "movl %eax,0x38(%esp)\n\t" /* frame->syscall_cfa */ @@ -2155,24 +2143,48 @@ static void quit_handler( int signal, siginfo_t *siginfo, void *sigcontext ) */ static void usr1_handler( int signal, siginfo_t *siginfo, void *sigcontext ) { - struct xcontext xcontext; + ucontext_t *ucontext = sigcontext; init_handler( sigcontext ); - if (is_inside_syscall( sigcontext )) + + if (is_inside_syscall( ucontext )) { - DECLSPEC_ALIGN(64) XSTATE xs; - xcontext.c.ContextFlags = CONTEXT_FULL; - context_init_xstate( &xcontext.c, &xs ); + struct syscall_frame *frame = x86_thread_data()->syscall_frame; + ULONG64 saved_compaction = 0; + struct xcontext *context; - NtGetContextThread( GetCurrentThread(), &xcontext.c ); - wait_suspend( &xcontext.c ); - NtSetContextThread( GetCurrentThread(), &xcontext.c ); + context = (struct xcontext *)(((ULONG_PTR)ESP_sig(ucontext) - sizeof(*context)) & ~15); + if ((char *)context < (char *)ntdll_get_thread_data()->kernel_stack) + { + ERR_(seh)( "kernel stack overflow.\n" ); + return; + } + context->c.ContextFlags = CONTEXT_FULL | CONTEXT_EXCEPTION_REQUEST; + NtGetContextThread( GetCurrentThread(), &context->c ); + if (xstate_extended_features()) + { + if (xstate_compaction_enabled) frame->xstate.CompactionMask |= xstate_extended_features(); + context_init_xstate( &context->c, &frame->xstate ); + saved_compaction = frame->xstate.CompactionMask; + } + wait_suspend( &context->c ); + if (xstate_extended_features()) frame->xstate.CompactionMask = saved_compaction; + if (context->c.ContextFlags & 0x40) + { + /* xstate is updated directly in frame's xstate */ + context->c.ContextFlags &= ~0x40; + frame->restore_flags |= 0x40; + } + NtSetContextThread( GetCurrentThread(), &context->c ); } else { - save_context( &xcontext, sigcontext ); - wait_suspend( &xcontext.c ); - restore_context( &xcontext, sigcontext ); + struct xcontext context; + + save_context( &context, ucontext ); + context.c.ContextFlags |= CONTEXT_EXCEPTION_REPORTING; + wait_suspend( &context.c ); + restore_context( &context, ucontext ); } } @@ -2439,6 +2451,7 @@ NTSTATUS signal_alloc_thread( TEB *teb ) else thread_data->fs = gdt_fs_sel; teb->WOW32Reserved = __wine_syscall_dispatcher; + thread_data->xstate_features_size = xstate_features_size; return STATUS_SUCCESS; } @@ -2467,7 +2480,9 @@ void signal_init_process(void) struct sigaction sig_act; void *kernel_stack = (char *)ntdll_get_thread_data()->kernel_stack + kernel_stack_size; - x86_thread_data()->syscall_frame = (struct syscall_frame *)kernel_stack - 1; + x86_thread_data()->syscall_frame = (struct syscall_frame *)((ULONG_PTR)((char *)kernel_stack + - sizeof(struct syscall_frame) - xstate_features_size) & ~(ULONG_PTR)63); + x86_thread_data()->xstate_features_size = xstate_features_size; if (cpu_info.ProcessorFeatureBits & CPU_FEATURE_FXSR) syscall_flags |= SYSCALL_HAVE_FXSAVE; if (cpu_info.ProcessorFeatureBits & CPU_FEATURE_XSAVE) syscall_flags |= SYSCALL_HAVE_XSAVE; @@ -2516,6 +2531,8 @@ void call_init_thunk( LPTHREAD_START_ROUTINE entry, void *arg, BOOL suspend, TEB ldt_set_fs( thread_data->fs, teb ); thread_data->gs = get_gs(); thread_data->syscall_table = KeServiceDescriptorTable; + thread_data->xstate_features_mask = xstate_supported_features_mask; + assert( thread_data->xstate_features_size == xstate_features_size ); context.SegCs = get_cs(); context.SegDs = get_ds(); @@ -2535,6 +2552,7 @@ void call_init_thunk( LPTHREAD_START_ROUTINE entry, void *arg, BOOL suspend, TEB if (suspend) { + context.ContextFlags |= CONTEXT_EXCEPTION_REPORTING | CONTEXT_EXCEPTION_ACTIVE; wait_suspend( &context ); if (context.ContextFlags & CONTEXT_DEBUG_REGISTERS & ~CONTEXT_i386) { @@ -2551,6 +2569,8 @@ void call_init_thunk( LPTHREAD_START_ROUTINE entry, void *arg, BOOL suspend, TEB *ctx = context; ctx->ContextFlags = CONTEXT_FULL | CONTEXT_FLOATING_POINT | CONTEXT_EXTENDED_REGISTERS; memset( frame, 0, sizeof(*frame) ); + if (xstate_compaction_enabled) + frame->xstate.CompactionMask = 0x8000000000000000 | xstate_supported_features_mask; NtSetContextThread( GetCurrentThread(), ctx ); stack = (DWORD *)ctx; @@ -2592,7 +2612,8 @@ __ASM_GLOBAL_FUNC( signal_start_thread, "movl 0x1f8(%ecx),%eax\n\t" /* x86_thread_data()->syscall_frame */ "orl %eax,%eax\n\t" "jnz 1f\n\t" - "leal -0x380(%esp),%eax\n\t" /* sizeof(struct syscall_frame) */ + "leal -0x280(%esp),%eax\n\t" /* sizeof(struct syscall_frame) */ + "subl 0x204(%ecx),%eax\n\t" /* x86_thread_data()->xstate_features_size */ "andl $~63,%eax\n\t" "movl %eax,0x1f8(%ecx)\n" /* x86_thread_data()->syscall_frame */ /* switch to kernel stack */ @@ -2652,26 +2673,28 @@ __ASM_GLOBAL_FUNC( __wine_syscall_dispatcher, "addl %fs:0x1f4,%ebx\n\t" /* x86_thread_data()->syscall_table */ "testl $3,(%ecx)\n\t" /* frame->syscall_flags & (SYSCALL_HAVE_XSAVE | SYSCALL_HAVE_XSAVEC) */ "jz 2f\n\t" - "movl $7,%eax\n\t" + "movl %fs:0x1fc,%eax\n\t" /* x86_thread_data()->xstate_features_mask */ "xorl %edx,%edx\n\t" - "movl %edx,0x240(%ecx)\n\t" - "movl %edx,0x244(%ecx)\n\t" - "movl %edx,0x248(%ecx)\n\t" - "movl %edx,0x24c(%ecx)\n\t" - "movl %edx,0x250(%ecx)\n\t" - "movl %edx,0x254(%ecx)\n\t" + "andl $7,%eax\n\t" + "xorl %edi,%edi\n\t" + "movl %edi,0x240(%ecx)\n\t" + "movl %edi,0x244(%ecx)\n\t" + "movl %edi,0x248(%ecx)\n\t" + "movl %edi,0x24c(%ecx)\n\t" + "movl %edi,0x250(%ecx)\n\t" + "movl %edi,0x254(%ecx)\n\t" "testl $2,(%ecx)\n\t" /* frame->syscall_flags & SYSCALL_HAVE_XSAVEC */ "jz 1f\n\t" - "movl %edx,0x258(%ecx)\n\t" - "movl %edx,0x25c(%ecx)\n\t" - "movl %edx,0x260(%ecx)\n\t" - "movl %edx,0x264(%ecx)\n\t" - "movl %edx,0x268(%ecx)\n\t" - "movl %edx,0x26c(%ecx)\n\t" - "movl %edx,0x270(%ecx)\n\t" - "movl %edx,0x274(%ecx)\n\t" - "movl %edx,0x278(%ecx)\n\t" - "movl %edx,0x27c(%ecx)\n\t" + "movl %edi,0x258(%ecx)\n\t" + "movl %edi,0x25c(%ecx)\n\t" + "movl %edi,0x260(%ecx)\n\t" + "movl %edi,0x264(%ecx)\n\t" + "movl %edi,0x268(%ecx)\n\t" + "movl %edi,0x26c(%ecx)\n\t" + "movl %edi,0x270(%ecx)\n\t" + "movl %edi,0x274(%ecx)\n\t" + "movl %edi,0x278(%ecx)\n\t" + "movl %edi,0x27c(%ecx)\n\t" /* The xsavec instruction is not supported by * binutils < 2.25. */ ".byte 0x0f, 0xc7, 0x61, 0x40\n\t" /* xsavec 0x40(%ecx) */ @@ -2716,8 +2739,8 @@ __ASM_GLOBAL_FUNC( __wine_syscall_dispatcher, "testl $3,%ecx\n\t" /* SYSCALL_HAVE_XSAVE | SYSCALL_HAVE_XSAVEC */ "jz 1f\n\t" "movl %eax,%esi\n\t" - "movl $7,%eax\n\t" - "xorl %edx,%edx\n\t" + "movl %fs:0x1fc,%eax\n\t" /* x86_thread_data()->xstate_features_mask */ + "movl %fs:0x200,%edx\n\t" /* x86_thread_data()->xstate_features_mask high dword */ "xrstor 0x40(%esp)\n\t" "movl %esi,%eax\n\t" "jmp 3f\n" diff --git a/dlls/ntdll/unix/signal_x86_64.c b/dlls/ntdll/unix/signal_x86_64.c index f0467def51f..be9af2c23d1 100644 --- a/dlls/ntdll/unix/signal_x86_64.c +++ b/dlls/ntdll/unix/signal_x86_64.c @@ -151,7 +151,7 @@ __ASM_GLOBAL_FUNC( alloc_fs_sel, #define TRAP_sig(context) ((context)->uc_mcontext.gregs[REG_TRAPNO]) #define ERROR_sig(context) ((context)->uc_mcontext.gregs[REG_ERR]) #define FPU_sig(context) ((XMM_SAVE_AREA32 *)((context)->uc_mcontext.fpregs)) -#define XState_sig(fpu) (((unsigned int *)fpu->Reserved4)[12] == FP_XSTATE_MAGIC1 ? (XSTATE *)(fpu + 1) : NULL) +#define XState_sig(fpu) (((unsigned int *)fpu->Reserved4)[12] == FP_XSTATE_MAGIC1 ? (XSAVE_AREA_HEADER *)(fpu + 1) : NULL) #elif defined(__FreeBSD__) || defined (__FreeBSD_kernel__) @@ -370,11 +370,10 @@ struct exc_stack_layout ULONG64 align; /* 588 */ struct machine_frame machine_frame; /* 590 */ ULONG64 align2; /* 5b8 */ - XSTATE xstate; /* 5c0 */ }; C_ASSERT( offsetof(struct exc_stack_layout, rec) == 0x4f0 ); C_ASSERT( offsetof(struct exc_stack_layout, machine_frame) == 0x590 ); -C_ASSERT( sizeof(struct exc_stack_layout) == 0x700 ); +C_ASSERT( sizeof(struct exc_stack_layout) == 0x5c0 ); /* stack layout when calling KiUserApcDispatcher */ struct apc_stack_layout @@ -435,10 +434,12 @@ struct syscall_frame DWORD restore_flags; /* 00b4 */ DWORD align[2]; /* 00b8 */ XMM_SAVE_AREA32 xsave; /* 00c0 */ - DECLSPEC_ALIGN(64) XSTATE xstate; /* 02c0 */ + DECLSPEC_ALIGN(64) XSAVE_AREA_HEADER xstate; /* 02c0 */ }; -C_ASSERT( sizeof( struct syscall_frame ) == 0x400); +C_ASSERT( offsetof( struct syscall_frame, xsave ) == 0xc0 ); +C_ASSERT( offsetof( struct syscall_frame, xstate ) == 0x2c0 ); +C_ASSERT( sizeof( struct syscall_frame ) == 0x300); struct amd64_thread_data { @@ -452,6 +453,8 @@ struct amd64_thread_data struct syscall_frame *syscall_frame; /* 0328 syscall frame pointer */ SYSTEM_SERVICE_TABLE *syscall_table; /* 0330 syscall table */ DWORD fs; /* 0338 WOW TEB selector */ + DWORD xstate_features_size; /* 033c */ + UINT64 xstate_features_mask; /* 0340 */ }; C_ASSERT( sizeof(struct amd64_thread_data) <= sizeof(((struct ntdll_thread_data *)0)->cpu_data) ); @@ -459,6 +462,8 @@ C_ASSERT( offsetof( TEB, GdiTebBatch ) + offsetof( struct amd64_thread_data, pth C_ASSERT( offsetof( TEB, GdiTebBatch ) + offsetof( struct amd64_thread_data, syscall_frame ) == 0x328 ); C_ASSERT( offsetof( TEB, GdiTebBatch ) + offsetof( struct amd64_thread_data, syscall_table ) == 0x330 ); C_ASSERT( offsetof( TEB, GdiTebBatch ) + offsetof( struct amd64_thread_data, fs ) == 0x338 ); +C_ASSERT( offsetof( TEB, GdiTebBatch ) + offsetof( struct amd64_thread_data, xstate_features_size ) == 0x33c ); +C_ASSERT( offsetof( TEB, GdiTebBatch ) + offsetof( struct amd64_thread_data, xstate_features_mask ) == 0x340 ); static inline struct amd64_thread_data *amd64_thread_data(void) { @@ -485,17 +490,14 @@ struct xcontext { CONTEXT c; CONTEXT_EX c_ex; - ULONG64 host_compaction_mask; }; -extern BOOL xstate_compaction_enabled; - -static inline XSTATE *xstate_from_context( const CONTEXT *context ) +static inline XSAVE_AREA_HEADER *xstate_from_context( const CONTEXT *context ) { CONTEXT_EX *xctx = (CONTEXT_EX *)(context + 1); if ((context->ContextFlags & CONTEXT_XSTATE) != CONTEXT_XSTATE) return NULL; - return (XSTATE *)((char *)xctx + xctx->XState.Offset); + return (XSAVE_AREA_HEADER *)((char *)xctx + xctx->XState.Offset); } static inline void context_init_xstate( CONTEXT *context, void *xstate_buffer ) @@ -508,7 +510,7 @@ static inline void context_init_xstate( CONTEXT *context, void *xstate_buffer ) if (xstate_buffer) { - xctx->XState.Length = sizeof(XSTATE); + xctx->XState.Length = sizeof(XSAVE_AREA_HEADER) + xstate_features_size; xctx->XState.Offset = (BYTE *)xstate_buffer - (BYTE *)xctx; context->ContextFlags |= CONTEXT_XSTATE; @@ -905,18 +907,17 @@ static void save_context( struct xcontext *xcontext, const ucontext_t *sigcontex context->Dr7 = amd64_thread_data()->dr7; if (FPU_sig(sigcontext)) { - XSTATE *xs; + XSAVE_AREA_HEADER *xs; context->ContextFlags |= CONTEXT_FLOATING_POINT; context->FltSave = *FPU_sig(sigcontext); context->MxCsr = context->FltSave.MxCsr; - if ((cpu_info.ProcessorFeatureBits & CPU_FEATURE_AVX) && (xs = XState_sig(FPU_sig(sigcontext)))) + if (xstate_extended_features() && (xs = XState_sig(FPU_sig(sigcontext)))) { /* xcontext and sigcontext are both on the signal stack, so we can * just reference sigcontext without overflowing 32 bit XState.Offset */ context_init_xstate( context, xs ); assert( xcontext->c_ex.XState.Offset == (BYTE *)xs - (BYTE *)&xcontext->c_ex ); - xcontext->host_compaction_mask = xs->CompactionMask; } } } @@ -930,7 +931,6 @@ static void save_context( struct xcontext *xcontext, const ucontext_t *sigcontex static void restore_context( const struct xcontext *xcontext, ucontext_t *sigcontext ) { const CONTEXT *context = &xcontext->c; - XSTATE *xs; amd64_thread_data()->dr0 = context->Dr0; amd64_thread_data()->dr1 = context->Dr1; @@ -940,8 +940,6 @@ static void restore_context( const struct xcontext *xcontext, ucontext_t *sigcon amd64_thread_data()->dr7 = context->Dr7; set_sigcontext( context, sigcontext ); if (FPU_sig(sigcontext)) *FPU_sig(sigcontext) = context->FltSave; - if ((cpu_info.ProcessorFeatureBits & CPU_FEATURE_AVX) && (xs = XState_sig(FPU_sig(sigcontext)))) - xs->CompactionMask = xcontext->host_compaction_mask; leave_handler( sigcontext ); } @@ -989,15 +987,16 @@ NTSTATUS WINAPI NtSetContextThread( HANDLE handle, const CONTEXT *context ) BOOL self = (handle == GetCurrentThread()); struct syscall_frame *frame = amd64_thread_data()->syscall_frame; - if ((flags & CONTEXT_XSTATE) && (cpu_info.ProcessorFeatureBits & CPU_FEATURE_AVX)) + if ((flags & CONTEXT_XSTATE) && xstate_extended_features()) { CONTEXT_EX *context_ex = (CONTEXT_EX *)(context + 1); - XSTATE *xs = (XSTATE *)((char *)context_ex + context_ex->XState.Offset); + XSAVE_AREA_HEADER *xs = (XSAVE_AREA_HEADER *)((char *)context_ex + context_ex->XState.Offset); - if (context_ex->XState.Length < offsetof(XSTATE, YmmContext) || - context_ex->XState.Length > sizeof(XSTATE)) + if (context_ex->XState.Length < sizeof(XSAVE_AREA_HEADER) || + context_ex->XState.Length > sizeof(XSAVE_AREA_HEADER) + xstate_features_size) return STATUS_INVALID_PARAMETER; - if ((xs->Mask & XSTATE_MASK_GSSE) && (context_ex->XState.Length < sizeof(XSTATE))) + if ((xs->Mask & xstate_extended_features()) + && (context_ex->XState.Length < xstate_get_size( xs->CompactionMask, xs->Mask ))) return STATUS_BUFFER_OVERFLOW; } else flags &= ~CONTEXT_XSTATE; @@ -1062,14 +1061,12 @@ NTSTATUS WINAPI NtSetContextThread( HANDLE handle, const CONTEXT *context ) if (flags & CONTEXT_XSTATE) { CONTEXT_EX *context_ex = (CONTEXT_EX *)(context + 1); - XSTATE *xs = (XSTATE *)((char *)context_ex + context_ex->XState.Offset); + XSAVE_AREA_HEADER *xs = (XSAVE_AREA_HEADER *)((char *)context_ex + context_ex->XState.Offset); + UINT64 mask = frame->xstate.Mask; - if (xs->Mask & XSTATE_MASK_GSSE) - { - frame->xstate.Mask |= XSTATE_MASK_GSSE; - memcpy( &frame->xstate.YmmContext, &xs->YmmContext, sizeof(xs->YmmContext) ); - } - else frame->xstate.Mask &= ~XSTATE_MASK_GSSE; + if (xstate_compaction_enabled) frame->xstate.CompactionMask |= xstate_extended_features(); + copy_xstate( &frame->xstate, xs, xs->Mask ); + if (xs->CompactionMask) frame->xstate.Mask |= mask & ~xs->CompactionMask; } frame->restore_flags |= flags & ~CONTEXT_INTEGER; @@ -1174,20 +1171,24 @@ NTSTATUS WINAPI NtGetContextThread( HANDLE handle, CONTEXT *context ) context->MxCsr = context->FltSave.MxCsr; context->ContextFlags |= CONTEXT_FLOATING_POINT; } - if ((needed_flags & CONTEXT_XSTATE) && (cpu_info.ProcessorFeatureBits & CPU_FEATURE_AVX)) + if ((needed_flags & CONTEXT_XSTATE) && xstate_extended_features()) { CONTEXT_EX *context_ex = (CONTEXT_EX *)(context + 1); - XSTATE *xstate = (XSTATE *)((char *)context_ex + context_ex->XState.Offset); - unsigned int mask; + XSAVE_AREA_HEADER *xstate = (XSAVE_AREA_HEADER *)((char *)context_ex + context_ex->XState.Offset); + UINT64 mask; - mask = (xstate_compaction_enabled ? xstate->CompactionMask : xstate->Mask) & XSTATE_MASK_GSSE; + if (xstate_compaction_enabled) frame->xstate.CompactionMask |= xstate_extended_features(); + mask = (xstate_compaction_enabled ? xstate->CompactionMask : xstate->Mask) & xstate_extended_features(); xstate->Mask = frame->xstate.Mask & mask; xstate->CompactionMask = xstate_compaction_enabled ? (0x8000000000000000 | mask) : 0; - memset( xstate->Reserved, 0, sizeof(xstate->Reserved) ); + memset( xstate->Reserved2, 0, sizeof(xstate->Reserved2) ); if (xstate->Mask) { - if (context_ex->XState.Length < sizeof(XSTATE)) return STATUS_BUFFER_OVERFLOW; - memcpy( &xstate->YmmContext, &frame->xstate.YmmContext, sizeof(xstate->YmmContext) ); + if (context_ex->XState.Length < xstate_get_size( xstate->CompactionMask, xstate->Mask )) + return STATUS_BUFFER_OVERFLOW; + copy_xstate( xstate, &frame->xstate, xstate->Mask ); + /* copy_xstate may use avx in memcpy, restore xstate not to break the tests. */ + frame->restore_flags |= CONTEXT_XSTATE; } } if (context->ContextFlags & (CONTEXT_DEBUG_REGISTERS & ~CONTEXT_AMD64)) @@ -1212,6 +1213,7 @@ NTSTATUS WINAPI NtGetContextThread( HANDLE handle, CONTEXT *context ) amd64_thread_data()->dr7 = context->Dr7; } } + set_context_exception_reporting_flags( &context->ContextFlags, CONTEXT_SERVICE_ACTIVE ); return STATUS_SUCCESS; } @@ -1306,14 +1308,10 @@ NTSTATUS set_thread_wow64_context( HANDLE handle, const void *ctx, ULONG size ) if (flags & CONTEXT_I386_XSTATE) { CONTEXT_EX *context_ex = (CONTEXT_EX *)(context + 1); - XSTATE *xs = (XSTATE *)((char *)context_ex + context_ex->XState.Offset); + XSAVE_AREA_HEADER *xs = (XSAVE_AREA_HEADER *)((char *)context_ex + context_ex->XState.Offset); - if (xs->Mask & XSTATE_MASK_GSSE) - { - frame->xstate.Mask |= XSTATE_MASK_GSSE; - memcpy( &frame->xstate.YmmContext, &xs->YmmContext, sizeof(xs->YmmContext) ); - } - else frame->xstate.Mask &= ~XSTATE_MASK_GSSE; + if (xstate_compaction_enabled) frame->xstate.CompactionMask |= xstate_extended_features(); + copy_xstate( &frame->xstate, xs, xs->Mask ); frame->restore_flags |= CONTEXT_XSTATE; } return STATUS_SUCCESS; @@ -1394,26 +1392,31 @@ NTSTATUS get_thread_wow64_context( HANDLE handle, void *ctx, ULONG size ) fpux_to_fpu( &context->FloatSave, &frame->xsave ); context->ContextFlags |= CONTEXT_I386_FLOATING_POINT; } - if ((needed_flags & CONTEXT_I386_XSTATE) && (cpu_info.ProcessorFeatureBits & CPU_FEATURE_AVX)) + if ((needed_flags & CONTEXT_I386_XSTATE) && xstate_extended_features()) { CONTEXT_EX *context_ex = (CONTEXT_EX *)(context + 1); - XSTATE *xstate = (XSTATE *)((char *)context_ex + context_ex->XState.Offset); - unsigned int mask; + XSAVE_AREA_HEADER *xstate = (XSAVE_AREA_HEADER *)((char *)context_ex + context_ex->XState.Offset); + UINT64 mask; - if (context_ex->XState.Length < offsetof(XSTATE, YmmContext) || - context_ex->XState.Length > sizeof(XSTATE)) + if (context_ex->XState.Length < sizeof(XSAVE_AREA_HEADER) || + context_ex->XState.Length > sizeof(XSAVE_AREA_HEADER) + xstate_features_size) return STATUS_INVALID_PARAMETER; - mask = (xstate_compaction_enabled ? xstate->CompactionMask : xstate->Mask) & XSTATE_MASK_GSSE; + if (xstate_compaction_enabled) frame->xstate.CompactionMask |= xstate_extended_features(); + mask = (xstate_compaction_enabled ? xstate->CompactionMask : xstate->Mask) & xstate_extended_features(); xstate->Mask = frame->xstate.Mask & mask; xstate->CompactionMask = xstate_compaction_enabled ? (0x8000000000000000 | mask) : 0; - memset( xstate->Reserved, 0, sizeof(xstate->Reserved) ); + memset( xstate->Reserved2, 0, sizeof(xstate->Reserved2) ); if (xstate->Mask) { - if (context_ex->XState.Length < sizeof(XSTATE)) return STATUS_BUFFER_OVERFLOW; - memcpy( &xstate->YmmContext, &frame->xstate.YmmContext, sizeof(xstate->YmmContext) ); + if (context_ex->XState.Length < xstate_get_size( xstate->CompactionMask, xstate->Mask )) + return STATUS_BUFFER_OVERFLOW; + copy_xstate( xstate, &frame->xstate, xstate->Mask ); + /* copy_xstate may use avx in memcpy, restore xstate not to break the tests. */ + frame->restore_flags |= CONTEXT_XSTATE; } } + set_context_exception_reporting_flags( &context->ContextFlags, CONTEXT_SERVICE_ACTIVE ); return STATUS_SUCCESS; } @@ -1428,7 +1431,8 @@ static void setup_raise_exception( ucontext_t *sigcontext, EXCEPTION_RECORD *rec struct exc_stack_layout *stack; size_t stack_size; NTSTATUS status; - XSTATE *src_xs; + XSAVE_AREA_HEADER *src_xs; + unsigned int xstate_size; if (rec->ExceptionCode == EXCEPTION_SINGLE_STEP) { @@ -1447,7 +1451,7 @@ static void setup_raise_exception( ucontext_t *sigcontext, EXCEPTION_RECORD *rec context->EFlags &= ~0x100; /* clear single-step flag */ } - status = send_debug_event( rec, context, TRUE ); + status = send_debug_event( rec, context, TRUE, TRUE ); if (status == DBG_CONTINUE || status == DBG_EXCEPTION_HANDLED) { restore_context( xcontext, sigcontext ); @@ -1457,7 +1461,8 @@ static void setup_raise_exception( ucontext_t *sigcontext, EXCEPTION_RECORD *rec /* fix up instruction pointer in context for EXCEPTION_BREAKPOINT */ if (rec->ExceptionCode == EXCEPTION_BREAKPOINT) context->Rip--; - stack_size = (ULONG_PTR)stack_ptr - (((ULONG_PTR)stack_ptr - sizeof(*stack)) & ~(ULONG_PTR)63); + xstate_size = sizeof(XSAVE_AREA_HEADER) + xstate_features_size; + stack_size = (ULONG_PTR)stack_ptr - (((ULONG_PTR)stack_ptr - sizeof(*stack) - xstate_size) & ~(ULONG_PTR)63); stack = virtual_setup_exception( stack_ptr, stack_size, rec ); stack->rec = *rec; stack->context = *context; @@ -1466,15 +1471,12 @@ static void setup_raise_exception( ucontext_t *sigcontext, EXCEPTION_RECORD *rec if ((src_xs = xstate_from_context( context ))) { - assert( !((ULONG_PTR)&stack->xstate & 63) ); - context_init_xstate( &stack->context, &stack->xstate ); - memset( &stack->xstate, 0, offsetof(XSTATE, YmmContext) ); - stack->xstate.CompactionMask = xstate_compaction_enabled ? 0x8000000000000004 : 0; - if (src_xs->Mask & 4) - { - stack->xstate.Mask = 4; - memcpy( &stack->xstate.YmmContext, &src_xs->YmmContext, sizeof(stack->xstate.YmmContext) ); - } + XSAVE_AREA_HEADER *dst_xs = (XSAVE_AREA_HEADER *)(stack + 1); + assert( !((ULONG_PTR)dst_xs & 63) ); + context_init_xstate( &stack->context, dst_xs ); + memset( dst_xs, 0, sizeof(*dst_xs) ); + dst_xs->CompactionMask = xstate_compaction_enabled ? 0x8000000000000000 | xstate_extended_features() : 0; + copy_xstate( dst_xs, src_xs, src_xs->Mask ); } else { @@ -1561,16 +1563,19 @@ NTSTATUS call_user_exception_dispatcher( EXCEPTION_RECORD *rec, CONTEXT *context struct syscall_frame *frame = amd64_thread_data()->syscall_frame; struct exc_stack_layout *stack; NTSTATUS status = NtSetContextThread( GetCurrentThread(), context ); + unsigned int xstate_size; if (status) return status; - stack = (struct exc_stack_layout *)((context->Rsp - sizeof(*stack)) & ~(ULONG_PTR)63); + xstate_size = sizeof(XSAVE_AREA_HEADER) + xstate_features_size; + stack = (struct exc_stack_layout *)((context->Rsp - sizeof(*stack) - xstate_size) & ~(ULONG_PTR)63); memmove( &stack->context, context, sizeof(*context) ); if ((context->ContextFlags & CONTEXT_XSTATE) == CONTEXT_XSTATE) { - assert( !((ULONG_PTR)&stack->xstate & 63) ); - context_init_xstate( &stack->context, &stack->xstate ); - memcpy( &stack->xstate, &frame->xstate, sizeof(frame->xstate) ); + XSAVE_AREA_HEADER *dst_xs = (XSAVE_AREA_HEADER *)(stack + 1); + assert( !((ULONG_PTR)dst_xs & 63) ); + context_init_xstate( &stack->context, dst_xs ); + memcpy( dst_xs, &frame->xstate, sizeof(XSAVE_AREA_HEADER) + xstate_features_size ); } else context_init_xstate( &stack->context, NULL ); @@ -1612,13 +1617,15 @@ __ASM_GLOBAL_FUNC( call_user_mode_callback, "fnstcw -0x2c(%rbp)\n\t" "movq %rsi,-0x38(%rbp)\n\t" /* ret_ptr */ "movq %rdx,-0x40(%rbp)\n\t" /* ret_len */ - "subq $0x408,%rsp\n\t" /* sizeof(struct syscall_frame) + exception */ + "subq $0x308,%rsp\n\t" /* sizeof(struct syscall_frame) + exception */ + "movl 0x33c(%r8),%esi\n\t" /* amd64_thread_data()->xstate_features_size */ + "subq %rsi,%rsp\n\t" "andq $~63,%rsp\n\t" "leaq 0x10(%rbp),%rax\n\t" "movq %rax,0xa8(%rsp)\n\t" /* frame->syscall_cfa */ "movq 0x328(%r8),%r10\n\t" /* amd64_thread_data()->syscall_frame */ "movq (%r8),%rax\n\t" /* NtCurrentTeb()->Tib.ExceptionList */ - "movq %rax,0x408(%rsp)\n\t" + "movq %rax,0x300(%rsp,%rsi)\n\t" "movl 0xb0(%r10),%r14d\n\t" /* prev_frame->syscall_flags */ "movl %r14d,0xb0(%rsp)\n\t" /* frame->syscall_flags */ "movq %r10,0xa0(%rsp)\n\t" /* frame->prev_frame */ @@ -1651,7 +1658,8 @@ __ASM_GLOBAL_FUNC( user_mode_callback_return, __ASM_CFI(".cfi_rel_offset %r13,-0x18\n\t") __ASM_CFI(".cfi_rel_offset %r14,-0x20\n\t") __ASM_CFI(".cfi_rel_offset %r15,-0x28\n\t") - "movq 0x408(%r10),%rax\n\t" /* exception list */ + "movl 0x33c(%rcx),%eax\n\t" /* amd64_thread_data()->xstate_features_size */ + "movq 0x300(%r10,%rax),%rax\n\t" /* exception list */ "movq %rax,0(%rcx)\n\t" /* teb->Tib.ExceptionList */ "movq -0x38(%rbp),%r10\n\t" /* ret_ptr */ "movq -0x40(%rbp),%r11\n\t" /* ret_len */ @@ -2495,21 +2503,44 @@ static void quit_handler( int signal, siginfo_t *siginfo, void *sigcontext ) static void usr1_handler( int signal, siginfo_t *siginfo, void *sigcontext ) { ucontext_t *ucontext = init_handler( sigcontext ); - struct xcontext context; if (is_inside_syscall( ucontext )) { - DECLSPEC_ALIGN(64) XSTATE xs; - context.c.ContextFlags = CONTEXT_FULL | CONTEXT_SEGMENTS; - context_init_xstate( &context.c, &xs ); + struct syscall_frame *frame = amd64_thread_data()->syscall_frame; + ULONG64 saved_compaction = 0; + struct xcontext *context; - NtGetContextThread( GetCurrentThread(), &context.c ); - wait_suspend( &context.c ); - NtSetContextThread( GetCurrentThread(), &context.c ); + context = (struct xcontext *)(((ULONG_PTR)RSP_sig(ucontext) - 128 /* red zone */ - sizeof(*context)) & ~15); + if ((char *)context < (char *)ntdll_get_thread_data()->kernel_stack) + { + ERR_(seh)( "kernel stack overflow.\n" ); + return; + } + context->c.ContextFlags = CONTEXT_FULL | CONTEXT_SEGMENTS | CONTEXT_EXCEPTION_REQUEST; + NtGetContextThread( GetCurrentThread(), &context->c ); + if (xstate_extended_features()) + { + if (xstate_compaction_enabled) frame->xstate.CompactionMask |= xstate_extended_features(); + context_init_xstate( &context->c, &frame->xstate ); + saved_compaction = frame->xstate.CompactionMask; + } + wait_suspend( &context->c ); + if (xstate_extended_features()) frame->xstate.CompactionMask = saved_compaction; + if (context->c.ContextFlags & 0x40) + { + /* xstate is updated directly in frame's xstate */ + context->c.ContextFlags &= ~0x40; + frame->restore_flags |= 0x40; + } + NtSetContextThread( GetCurrentThread(), &context->c ); } else { + struct xcontext context; + save_context( &context, ucontext ); + context.c.ContextFlags |= CONTEXT_EXCEPTION_REPORTING; + if (is_wow64() && context.c.SegCs == cs64_sel) context.c.ContextFlags |= CONTEXT_EXCEPTION_ACTIVE; wait_suspend( &context.c ); restore_context( &context, ucontext ); } @@ -2648,6 +2679,7 @@ NTSTATUS signal_alloc_thread( TEB *teb ) } else thread_data->fs = fs32_sel; } + thread_data->xstate_features_size = xstate_features_size; return STATUS_SUCCESS; } @@ -2734,7 +2766,9 @@ void signal_init_process(void) WOW_TEB *wow_teb = get_wow_teb( NtCurrentTeb() ); void *ptr, *kernel_stack = (char *)ntdll_get_thread_data()->kernel_stack + kernel_stack_size; - amd64_thread_data()->syscall_frame = (struct syscall_frame *)kernel_stack - 1; + amd64_thread_data()->syscall_frame = (struct syscall_frame *)((ULONG_PTR)((char *)kernel_stack + - sizeof(struct syscall_frame) - xstate_features_size) & ~(ULONG_PTR)63); + amd64_thread_data()->xstate_features_size = xstate_features_size; /* sneak in a syscall dispatcher pointer at a fixed address (7ffe1000) */ ptr = (char *)user_shared_data + page_size; @@ -2830,6 +2864,8 @@ void call_init_thunk( LPTHREAD_START_ROUTINE entry, void *arg, BOOL suspend, TEB I386_CONTEXT *wow_context; thread_data->syscall_table = KeServiceDescriptorTable; + thread_data->xstate_features_mask = xstate_supported_features_mask; + assert( thread_data->xstate_features_size == xstate_features_size ); #if defined __linux__ arch_prctl( ARCH_SET_GS, teb ); @@ -2851,7 +2887,7 @@ void call_init_thunk( LPTHREAD_START_ROUTINE entry, void *arg, BOOL suspend, TEB # error Please define setting %gs for your architecture #endif - context.ContextFlags = CONTEXT_ALL; + context.ContextFlags = CONTEXT_ALL | CONTEXT_EXCEPTION_REPORTING | CONTEXT_EXCEPTION_ACTIVE; context.Rcx = (ULONG_PTR)entry; context.Rdx = (ULONG_PTR)arg; context.Rsp = (ULONG_PTR)teb->Tib.StackBase - 0x28; @@ -2902,6 +2938,8 @@ void call_init_thunk( LPTHREAD_START_ROUTINE entry, void *arg, BOOL suspend, TEB *ctx = context; ctx->ContextFlags = CONTEXT_FULL; memset( frame, 0, sizeof(*frame) ); + if (xstate_compaction_enabled) + frame->xstate.CompactionMask = 0x8000000000000000 | xstate_supported_features_mask; NtSetContextThread( GetCurrentThread(), ctx ); frame->cs = cs64_sel; @@ -2944,7 +2982,9 @@ __ASM_GLOBAL_FUNC( signal_start_thread, "movq 0x328(%rcx),%r8\n\t" /* amd64_thread_data()->syscall_frame */ "orq %r8,%r8\n\t" "jnz 1f\n\t" - "leaq -0x400(%rsp),%r8\n\t" /* sizeof(struct syscall_frame) */ + "leaq -0x300(%rsp),%r8\n\t" /* sizeof(struct syscall_frame) */ + "movl 0x33c(%rcx),%eax\n\t" /* amd64_thread_data()->xstate_features_size */ + "subq %rax,%r8\n\t" "andq $~63,%r8\n\t" "movq %r8,0x328(%rcx)\n" /* amd64_thread_data()->syscall_frame */ /* switch to kernel stack */ @@ -2999,18 +3039,25 @@ __ASM_GLOBAL_FUNC( __wine_syscall_dispatcher, "movl 0xb0(%rcx),%r14d\n\t" /* frame->syscall_flags */ "testl $3,%r14d\n\t" /* SYSCALL_HAVE_XSAVE | SYSCALL_HAVE_XSAVEC */ "jz 2f\n\t" - "movl $7,%eax\n\t" +#ifdef __APPLE__ + "movq %gs:0x30,%rdx\n\t" + "movl 0x340(%rdx),%eax\n\t" +#else + "movl %gs:0x340,%eax\n\t" /* amd64_thread_data()->xstate_features_mask */ +#endif "xorl %edx,%edx\n\t" - "movq %rdx,0x2c0(%rcx)\n\t" - "movq %rdx,0x2c8(%rcx)\n\t" - "movq %rdx,0x2d0(%rcx)\n\t" + "andl $7,%eax\n\t" + "xorq %rbp,%rbp\n\t" + "movq %rbp,0x2c0(%rcx)\n\t" + "movq %rbp,0x2c8(%rcx)\n\t" + "movq %rbp,0x2d0(%rcx)\n\t" "testl $2,%r14d\n\t" /* SYSCALL_HAVE_XSAVEC */ "jz 1f\n\t" - "movq %rdx,0x2d8(%rcx)\n\t" - "movq %rdx,0x2e0(%rcx)\n\t" - "movq %rdx,0x2e8(%rcx)\n\t" - "movq %rdx,0x2f0(%rcx)\n\t" - "movq %rdx,0x2f8(%rcx)\n\t" + "movq %rbp,0x2d8(%rcx)\n\t" + "movq %rbp,0x2e0(%rcx)\n\t" + "movq %rbp,0x2e8(%rcx)\n\t" + "movq %rbp,0x2f0(%rcx)\n\t" + "movq %rbp,0x2f8(%rcx)\n\t" /* The xsavec instruction is not supported by * binutils < 2.25. */ ".byte 0x48, 0x0f, 0xc7, 0xa1, 0xc0, 0x00, 0x00, 0x00\n\t" /* xsavec64 0xc0(%rcx) */ @@ -3112,8 +3159,14 @@ __ASM_GLOBAL_FUNC( __wine_syscall_dispatcher, "2:\ttestl $3,%r14d\n\t" /* SYSCALL_HAVE_XSAVE | SYSCALL_HAVE_XSAVEC */ "jz 3f\n\t" "movq %rax,%r11\n\t" - "movl $7,%eax\n\t" - "xorl %edx,%edx\n\t" +#ifdef __APPLE__ + "movq %gs:0x30,%rdx\n\t" + "movl 0x340(%rdx),%eax\n\t" + "movl 0x344(%rdx),%edx\n\t" +#else + "movl %gs:0x340,%eax\n\t" /* amd64_thread_data()->xstate_features_mask */ + "movl %gs:0x344,%edx\n\t" /* amd64_thread_data()->xstate_features_mask high dword */ +#endif "xrstor64 0xc0(%rcx)\n\t" "movq %r11,%rax\n\t" "movl 0xb4(%rcx),%edx\n\t" /* frame->restore_flags */ diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c index ed30388b026..23f17e72e08 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -43,6 +43,9 @@ #ifdef HAVE_SCHED_H # include #endif +#ifdef HAVE_SYS_RESOURCE_H +# include +#endif #include #include #include @@ -272,7 +275,6 @@ NTSTATUS WINAPI NtCreateSemaphore( HANDLE *handle, ACCESS_MASK access, const OBJ *handle = 0; if (max <= 0 || initial < 0 || initial > max) return STATUS_INVALID_PARAMETER; - if ((ret = alloc_object_attributes( attr, &objattr, &len ))) return ret; if (do_fsync()) return fsync_create_semaphore( handle, access, attr, initial, max ); @@ -280,6 +282,8 @@ NTSTATUS WINAPI NtCreateSemaphore( HANDLE *handle, ACCESS_MASK access, const OBJ if (do_esync()) return esync_create_semaphore( handle, access, attr, initial, max ); + if ((ret = alloc_object_attributes( attr, &objattr, &len ))) return ret; + SERVER_START_REQ( create_semaphore ) { req->access = access; @@ -1625,7 +1629,17 @@ NTSTATUS WINAPI NtSignalAndWaitForSingleObject( HANDLE signal, HANDLE wait, NTSTATUS WINAPI NtYieldExecution(void) { #ifdef HAVE_SCHED_YIELD +#ifdef RUSAGE_THREAD + struct rusage u1, u2; + int ret; + + ret = getrusage( RUSAGE_THREAD, &u1 ); +#endif sched_yield(); +#ifdef RUSAGE_THREAD + if (!ret) ret = getrusage( RUSAGE_THREAD, &u2 ); + if (!ret && u1.ru_nvcsw == u2.ru_nvcsw && u1.ru_nivcsw == u2.ru_nivcsw) return STATUS_NO_YIELD_PERFORMED; +#endif return STATUS_SUCCESS; #else return STATUS_NO_YIELD_PERFORMED; diff --git a/dlls/ntdll/unix/system.c b/dlls/ntdll/unix/system.c index 4a1e41e7e42..0e77e7d7b14 100644 --- a/dlls/ntdll/unix/system.c +++ b/dlls/ntdll/unix/system.c @@ -242,7 +242,7 @@ static pthread_mutex_t timezone_mutex = PTHREAD_MUTEX_INITIALIZER; static struct { struct cpu_topology_override mapping; - BOOL smt; + ULONG_PTR siblings_mask[MAXIMUM_PROCESSORS]; } cpu_override; @@ -255,6 +255,64 @@ cpu_override; #if defined(__i386__) || defined(__x86_64__) BOOL xstate_compaction_enabled = FALSE; +UINT64 xstate_supported_features_mask; +UINT64 xstate_features_size; + +static int xstate_feature_offset[64]; +static int xstate_feature_size[64]; +static UINT64 xstate_aligned_features; + +static int next_xstate_offset( int off, UINT64 compaction_mask, int feature_idx ) +{ + const UINT64 feature_mask = (UINT64)1 << feature_idx; + + if (!compaction_mask) return xstate_feature_offset[feature_idx + 1] - sizeof(XSAVE_FORMAT); + + if (compaction_mask & feature_mask) off += xstate_feature_size[feature_idx]; + if (xstate_aligned_features & (feature_mask << 1)) + off = (off + 63) & ~63; + return off; +} + +unsigned int xstate_get_size( UINT64 compaction_mask, UINT64 mask ) +{ + unsigned int i; + int off; + + mask >>= 2; + off = sizeof(XSAVE_AREA_HEADER); + i = 2; + while (mask) + { + if (mask == 1) return off + xstate_feature_size[i]; + off = next_xstate_offset( off, compaction_mask, i ); + mask >>= 1; + ++i; + } + return off; +} + +void copy_xstate( XSAVE_AREA_HEADER *dst, XSAVE_AREA_HEADER *src, UINT64 mask ) +{ + unsigned int i; + int src_off, dst_off; + + mask &= xstate_extended_features() & src->Mask; + if (src->CompactionMask) mask &= src->CompactionMask; + if (dst->CompactionMask) mask &= dst->CompactionMask; + dst->Mask = (dst->Mask & ~xstate_extended_features()) | mask; + mask >>= 2; + src_off = dst_off = sizeof(XSAVE_AREA_HEADER); + i = 2; + while (1) + { + if (mask & 1) memcpy( (char *)dst + dst_off, (char *)src + src_off, xstate_feature_size[i] ); + if (!(mask >>= 1)) break; + src_off = next_xstate_offset( src_off, src->CompactionMask, i ); + dst_off = next_xstate_offset( dst_off, dst->CompactionMask, i ); + ++i; + } +} #define AUTH 0x68747541 /* "Auth" */ #define ENTI 0x69746e65 /* "enti" */ @@ -295,6 +353,21 @@ __ASM_GLOBAL_FUNC( do_cpuid, "ret" ) #endif +extern UINT64 do_xgetbv( unsigned int cx); +#ifdef __i386__ +__ASM_GLOBAL_FUNC( do_xgetbv, + "movl 4(%esp),%ecx\n\t" + "xgetbv\n\t" + "ret" ) +#else +__ASM_GLOBAL_FUNC( do_xgetbv, + "movl %edi,%ecx\n\t" + "xgetbv\n\t" + "shlq $32,%rdx\n\t" + "orq %rdx,%rax\n\t" + "ret" ) +#endif + #ifdef __i386__ extern int have_cpuid(void); __ASM_GLOBAL_FUNC( have_cpuid, @@ -353,8 +426,11 @@ static void get_cpuid_name( char *buffer ) static void get_cpuinfo( SYSTEM_CPU_INFORMATION *info ) { + static const ULONG64 wine_xstate_supported_features = 0xff; /* XSTATE_AVX, XSTATE_MPX_BNDREGS, XSTATE_MPX_BNDCSR, + * XSTATE_AVX512_KMASK, XSTATE_AVX512_ZMM_H, XSTATE_AVX512_ZMM */ unsigned int regs[4], regs2[4], regs3[4]; ULONGLONG features; + unsigned int i; #if defined(__i386__) info->ProcessorArchitecture = PROCESSOR_ARCHITECTURE_INTEL; @@ -404,6 +480,25 @@ static void get_cpuinfo( SYSTEM_CPU_INFORMATION *info ) { do_cpuid( 0x0000000d, 1, regs3 ); /* get XSAVE details */ if (regs3[0] & 2) xstate_compaction_enabled = TRUE; + + do_cpuid( 0x0000000d, 0, regs3 ); /* get user xstate features */ + xstate_supported_features_mask = ((ULONG64)regs3[3] << 32) | regs3[0]; + xstate_supported_features_mask &= do_xgetbv( 0 ) & wine_xstate_supported_features; + TRACE("xstate_supported_features_mask %#llx.\n", (long long)xstate_supported_features_mask); + for (i = 2; i < 64; ++i) + { + if (!(xstate_supported_features_mask & ((ULONG64)1 << i))) continue; + do_cpuid( 0x0000000d, i, regs3 ); /* get user xstate features */ + xstate_feature_offset[i] = regs3[1]; + xstate_feature_size[i] = regs3[0]; + if (regs3[2] & 2) xstate_aligned_features |= (ULONG64)1 << i; + TRACE("xstate[%d] offset %d, size %d, aligned %d.\n", i, xstate_feature_offset[i], xstate_feature_size[i], !!(regs3[2] & 2)); + } + xstate_features_size = xstate_get_size( xstate_compaction_enabled ? 0x8000000000000000 + | xstate_supported_features_mask : 0, xstate_supported_features_mask ) + - sizeof(XSAVE_AREA_HEADER); + xstate_features_size = (xstate_features_size + 15) & ~15; + TRACE("xstate_features_size %lld.\n", (long long)xstate_features_size); } if (regs[1] == AUTH && regs[3] == ENTI && regs[2] == CAMD) @@ -574,9 +669,13 @@ static void get_cpuinfo( SYSTEM_CPU_INFORMATION *info ) #endif /* End architecture specific feature detection for CPUs */ +static void fill_performance_core_info(void); +static BOOL sysfs_parse_bitmap(const char *filename, ULONG_PTR *mask); + static void fill_cpu_override(unsigned int host_cpu_count) { const char *env_override = getenv("WINE_CPU_TOPOLOGY"); + BOOL smt = FALSE; unsigned int i; char *s; @@ -593,6 +692,58 @@ static void fill_cpu_override(unsigned int host_cpu_count) goto error; } + if (!*s) + { + /* Auto assign given number of logical CPUs. */ + static const char core_info[] = "/sys/devices/system/cpu/cpu%u/topology/%s"; + char name[MAX_PATH]; + unsigned int attempt, count, j; + ULONG_PTR masks[MAXIMUM_PROCESSORS]; + + if (cpu_override.mapping.cpu_count >= host_cpu_count) + { + TRACE( "Override cpu count %u >= host cpu count %u.\n", cpu_override.mapping.cpu_count, host_cpu_count ); + cpu_override.mapping.cpu_count = 0; + return; + } + + fill_performance_core_info(); + + for (i = 0; i < host_cpu_count; ++i) + { + snprintf(name, sizeof(name), core_info, i, "thread_siblings"); + masks[i] = 0; + sysfs_parse_bitmap(name, &masks[i]); + } + for (attempt = 0; attempt < 3; ++attempt) + { + count = 0; + for (i = 0; i < host_cpu_count && count < cpu_override.mapping.cpu_count; ++i) + { + if (attempt < 2 && performance_cores_capacity) + { + if (i / 32 >= performance_cores_capacity) break; + if (!(performance_cores[i / 32] & (1 << (i % 32)))) goto skip_cpu; + } + cpu_override.mapping.host_cpu_id[count] = i; + cpu_override.siblings_mask[count] = (ULONG_PTR)1 << count; + for (j = 0; j < count; ++j) + { + if (!(masks[cpu_override.mapping.host_cpu_id[j]] & masks[i])) continue; + if (attempt < 1) goto skip_cpu; + cpu_override.siblings_mask[j] |= (ULONG_PTR)1 << count; + cpu_override.siblings_mask[count] |= (ULONG_PTR)1 << j; + } + ++count; +skip_cpu: + ; + } + if (count == cpu_override.mapping.cpu_count) break; + } + assert( count == cpu_override.mapping.cpu_count ); + goto done; + } + if (tolower(*s) == 's') { cpu_override.mapping.cpu_count *= 2; @@ -601,7 +752,7 @@ static void fill_cpu_override(unsigned int host_cpu_count) ERR("Logical CPU count exceeds limit %u.\n", MAXIMUM_PROCESSORS); goto error; } - cpu_override.smt = TRUE; + smt = TRUE; ++s; } if (*s != ':') @@ -624,6 +775,8 @@ static void fill_cpu_override(unsigned int host_cpu_count) } cpu_override.mapping.host_cpu_id[i] = strtol(s, &next, 10); + if (smt) cpu_override.siblings_mask[i] = (ULONG_PTR)3 << (i & ~1); + else cpu_override.siblings_mask[i] = (ULONG_PTR)1 << i; if (next == s) goto error; if (cpu_override.mapping.host_cpu_id[i] >= host_cpu_count) @@ -637,6 +790,7 @@ static void fill_cpu_override(unsigned int host_cpu_count) if (*s) goto error; +done: if (ERR_ON(ntdll)) { MESSAGE("wine: overriding CPU configuration, %u logical CPUs, host CPUs ", cpu_override.mapping.cpu_count); @@ -955,6 +1109,8 @@ static void fill_performance_core_info(void) char op = ','; ULONG *p; + if (performance_cores_capacity) return; + fpcore_list = fopen("/sys/devices/cpu_core/cpus", "r"); if (!fpcore_list) return; @@ -1070,7 +1226,7 @@ static NTSTATUS create_logical_proc_info(void) snprintf(name, sizeof(name), core_info, i, "thread_siblings"); if (cpu_override.mapping.cpu_count) { - thread_mask = cpu_override.smt ? (ULONG_PTR)0x3 << (i & ~1) : (ULONG_PTR)1 << i; + thread_mask = cpu_override.siblings_mask[i]; } else { @@ -1081,7 +1237,9 @@ static NTSTATUS create_logical_proc_info(void) if (cpu_override.mapping.cpu_count) { - phys_core = cpu_override.smt ? i / 2 : i; + assert( thread_mask ); + for (phys_core = 0; ; ++phys_core) + if (thread_mask & ((ULONG_PTR)1 << phys_core)) break; } else { @@ -3341,6 +3499,43 @@ NTSTATUS WINAPI NtQuerySystemInformation( SYSTEM_INFORMATION_CLASS class, break; } + case SystemProcessIdInformation: /* 88 */ + { + SYSTEM_PROCESS_ID_INFORMATION *id = info; + UNICODE_STRING *str = &id->ImageName; + BOOL valid_buffer; + + len = sizeof(*id); + if (len > size) ret = STATUS_INFO_LENGTH_MISMATCH; + else if (id->ImageName.Length) ret = STATUS_INVALID_PARAMETER; + else if (!id->ProcessId) ret = STATUS_INVALID_CID; + + if (ret) break; + + valid_buffer = virtual_check_buffer_for_write( str->Buffer, str->MaximumLength ); + SERVER_START_REQ( get_process_image_name ) + { + req->pid = HandleToULong( id->ProcessId ); + if (valid_buffer) wine_server_set_reply( req, str->Buffer, str->MaximumLength ); + ret = wine_server_call( req ); + if (ret == STATUS_BUFFER_TOO_SMALL) ret = STATUS_INFO_LENGTH_MISMATCH; + if (ret == STATUS_SUCCESS && reply->len + 2 > str->MaximumLength) ret = STATUS_INFO_LENGTH_MISMATCH; + if (ret == STATUS_SUCCESS || ret == STATUS_INFO_LENGTH_MISMATCH) str->MaximumLength = reply->len + 2; + if (!ret && valid_buffer) + { + str->Length = reply->len; + str->Buffer[reply->len / 2] = 0; + } + else if (!valid_buffer && (!ret || ret == STATUS_INFO_LENGTH_MISMATCH)) + { + str->Length = reply->len; + ret = STATUS_ACCESS_VIOLATION; + } + } + SERVER_END_REQ; + break; + } + case SystemDynamicTimeZoneInformation: /* 102 */ { RTL_DYNAMIC_TIME_ZONE_INFORMATION tz; diff --git a/dlls/ntdll/unix/thread.c b/dlls/ntdll/unix/thread.c index 64cad6e7c0e..c3ab1317295 100644 --- a/dlls/ntdll/unix/thread.c +++ b/dlls/ntdll/unix/thread.c @@ -165,12 +165,12 @@ BOOL validate_context_xstate( CONTEXT *context ) { CONTEXT_EX *context_ex; - if (!((context->ContextFlags & 0x40) && (cpu_info.ProcessorFeatureBits & CPU_FEATURE_AVX))) return TRUE; + if (!((context->ContextFlags & 0x40) && xstate_extended_features())) return TRUE; context_ex = (CONTEXT_EX *)(context + 1); - if (context_ex->XState.Length < offsetof(XSTATE, YmmContext) - || context_ex->XState.Length > sizeof(XSTATE)) + if (context_ex->XState.Length < sizeof(XSAVE_AREA_HEADER) || + context_ex->XState.Length > sizeof(XSAVE_AREA_HEADER) + xstate_features_size) return FALSE; if (((ULONG_PTR)context_ex + context_ex->XState.Offset) & 63) return FALSE; @@ -184,7 +184,7 @@ BOOL validate_context_xstate( CONTEXT *context ) */ static unsigned int get_server_context_flags( const void *context, USHORT machine ) { - unsigned int flags, ret = 0; + unsigned int flags = 0, ret = 0; switch (machine) { @@ -222,6 +222,7 @@ static unsigned int get_server_context_flags( const void *context, USHORT machin if (flags & CONTEXT_ARM64_DEBUG_REGISTERS) ret |= SERVER_CTX_DEBUG_REGISTERS; break; } + if (flags & CONTEXT_EXCEPTION_REQUEST) ret |= SERVER_CTX_EXEC_SPACE; return ret; } @@ -236,15 +237,45 @@ static unsigned int get_native_context_flags( USHORT native_machine, USHORT wow_ switch (MAKELONG( native_machine, wow_machine )) { case MAKELONG( IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_I386 ): - return SERVER_CTX_DEBUG_REGISTERS | SERVER_CTX_FLOATING_POINT | SERVER_CTX_YMM_REGISTERS; + return SERVER_CTX_DEBUG_REGISTERS | SERVER_CTX_FLOATING_POINT | SERVER_CTX_YMM_REGISTERS | SERVER_CTX_EXEC_SPACE; case MAKELONG( IMAGE_FILE_MACHINE_ARM64, IMAGE_FILE_MACHINE_ARMNT ): - return SERVER_CTX_DEBUG_REGISTERS | SERVER_CTX_FLOATING_POINT; + return SERVER_CTX_DEBUG_REGISTERS | SERVER_CTX_FLOATING_POINT | SERVER_CTX_EXEC_SPACE; default: - return 0; + return SERVER_CTX_EXEC_SPACE; } } +/*********************************************************************** + * xstate_to_server + * + * Copy xstate to the server format. + */ +static void xstate_to_server( context_t *to, const CONTEXT_EX *xctx ) +{ + const XSTATE *xs = (const XSTATE *)((const char *)xctx + xctx->XState.Offset); + + if (xs->CompactionMask && !(xs->CompactionMask & 4)) return; + to->flags |= SERVER_CTX_YMM_REGISTERS; + if (xs->Mask & 4) memcpy( &to->ymm.regs.ymm_high, &xs->YmmContext, sizeof(xs->YmmContext) ); +} + + +/*********************************************************************** + * exception_request_flags_to_server + * + * Copy exception reporting flags to the server format. + */ +static void exception_request_flags_to_server( context_t *to, DWORD context_flags ) +{ + if (!(context_flags & CONTEXT_EXCEPTION_REPORTING)) return; + to->flags |= SERVER_CTX_EXEC_SPACE; + if (context_flags & CONTEXT_SERVICE_ACTIVE) to->exec_space.space.space = EXEC_SPACE_SYSCALL; + else if (context_flags & CONTEXT_EXCEPTION_ACTIVE) to->exec_space.space.space = EXEC_SPACE_EXCEPTION; + else to->exec_space.space.space = EXEC_SPACE_USERMODE; +} + + /*********************************************************************** * context_to_server * @@ -321,13 +352,8 @@ static NTSTATUS context_to_server( context_t *to, USHORT to_machine, const void memcpy( to->ext.i386_regs, from->ExtendedRegisters, sizeof(to->ext.i386_regs) ); } if (flags & CONTEXT_I386_XSTATE) - { - const CONTEXT_EX *xctx = (const CONTEXT_EX *)(from + 1); - const XSTATE *xs = (const XSTATE *)((const char *)xctx + xctx->XState.Offset); - - to->flags |= SERVER_CTX_YMM_REGISTERS; - if (xs->Mask & 4) memcpy( &to->ymm.regs.ymm_high, &xs->YmmContext, sizeof(xs->YmmContext) ); - } + xstate_to_server( to, (const CONTEXT_EX *)(from + 1) ); + exception_request_flags_to_server( to, flags ); return STATUS_SUCCESS; } @@ -385,13 +411,8 @@ static NTSTATUS context_to_server( context_t *to, USHORT to_machine, const void fpu_to_fpux( (XMM_SAVE_AREA32 *)to->fp.x86_64_regs.fpregs, &from->FloatSave ); } if (flags & CONTEXT_I386_XSTATE) - { - const CONTEXT_EX *xctx = (const CONTEXT_EX *)(from + 1); - const XSTATE *xs = (const XSTATE *)((const char *)xctx + xctx->XState.Offset); - - to->flags |= SERVER_CTX_YMM_REGISTERS; - if (xs->Mask & 4) memcpy( &to->ymm.regs.ymm_high, &xs->YmmContext, sizeof(xs->YmmContext) ); - } + xstate_to_server( to, (const CONTEXT_EX *)(from + 1) ); + exception_request_flags_to_server( to, flags ); return STATUS_SUCCESS; } @@ -452,13 +473,8 @@ static NTSTATUS context_to_server( context_t *to, USHORT to_machine, const void to->debug.x86_64_regs.dr7 = from->Dr7; } if (flags & CONTEXT_AMD64_XSTATE) - { - const CONTEXT_EX *xctx = (const CONTEXT_EX *)(from + 1); - const XSTATE *xs = (const XSTATE *)((const char *)xctx + xctx->XState.Offset); - - to->flags |= SERVER_CTX_YMM_REGISTERS; - if (xs->Mask & 4) memcpy( &to->ymm.regs.ymm_high, &xs->YmmContext, sizeof(xs->YmmContext) ); - } + xstate_to_server( to, (const CONTEXT_EX *)(from + 1) ); + exception_request_flags_to_server( to, flags ); return STATUS_SUCCESS; } @@ -523,13 +539,8 @@ static NTSTATUS context_to_server( context_t *to, USHORT to_machine, const void to->debug.i386_regs.dr7 = from->Dr7; } if (flags & CONTEXT_AMD64_XSTATE) - { - const CONTEXT_EX *xctx = (const CONTEXT_EX *)(from + 1); - const XSTATE *xs = (const XSTATE *)((const char *)xctx + xctx->XState.Offset); - - to->flags |= SERVER_CTX_YMM_REGISTERS; - if (xs->Mask & 4) memcpy( &to->ymm.regs.ymm_high, &xs->YmmContext, sizeof(xs->YmmContext) ); - } + xstate_to_server( to, (const CONTEXT_EX *)(from + 1) ); + exception_request_flags_to_server( to, flags ); return STATUS_SUCCESS; } @@ -577,6 +588,7 @@ static NTSTATUS context_to_server( context_t *to, USHORT to_machine, const void for (i = 0; i < ARM_MAX_WATCHPOINTS; i++) to->debug.arm_regs.wvr[i] = from->Wvr[i]; for (i = 0; i < ARM_MAX_WATCHPOINTS; i++) to->debug.arm_regs.wcr[i] = from->Wcr[i]; } + exception_request_flags_to_server( to, flags ); return STATUS_SUCCESS; } @@ -618,6 +630,7 @@ static NTSTATUS context_to_server( context_t *to, USHORT to_machine, const void for (i = 0; i < ARM64_MAX_WATCHPOINTS; i++) to->debug.arm64_regs.wcr[i] = from->Wcr[i]; for (i = 0; i < ARM64_MAX_WATCHPOINTS; i++) to->debug.arm64_regs.wvr[i] = from->Wvr[i]; } + exception_request_flags_to_server( to, flags ); return STATUS_SUCCESS; } @@ -631,6 +644,48 @@ static NTSTATUS context_to_server( context_t *to, USHORT to_machine, const void } +/*********************************************************************** + * xstate_from_server + * + * Copy xstate from the server format. + */ +static void xstate_from_server( CONTEXT_EX *xctx, const context_t *from ) +{ + XSTATE *xs = (XSTATE *)((char *)xctx + xctx->XState.Offset); + unsigned int i; + + xs->Mask &= ~(ULONG64)4; + + if (xs->CompactionMask) + { + xs->CompactionMask &= ~(UINT64)3; + if (!(xs->CompactionMask & 4)) return; + } + + for (i = 0; i < ARRAY_SIZE( from->ymm.regs.ymm_high); i++) + { + if (!from->ymm.regs.ymm_high[i].low && !from->ymm.regs.ymm_high[i].high) continue; + memcpy( &xs->YmmContext, &from->ymm.regs, sizeof(xs->YmmContext) ); + xs->Mask |= 4; + break; + } +} + + +/*********************************************************************** + * exception_request_flags_from_server + * + * Copy exception reporting flags from the server format. + */ +static void exception_request_flags_from_server( DWORD *context_flags, const context_t *from ) +{ + if (!(*context_flags & CONTEXT_EXCEPTION_REQUEST) || !(from->flags & SERVER_CTX_EXEC_SPACE)) return; + *context_flags = (*context_flags & ~(CONTEXT_SERVICE_ACTIVE | CONTEXT_EXCEPTION_ACTIVE)) | CONTEXT_EXCEPTION_REPORTING; + if (from->exec_space.space.space == EXEC_SPACE_SYSCALL) *context_flags |= CONTEXT_SERVICE_ACTIVE; + else if (from->exec_space.space.space == EXEC_SPACE_EXCEPTION) *context_flags |= CONTEXT_EXCEPTION_ACTIVE; +} + + /*********************************************************************** * context_from_server * @@ -704,20 +759,8 @@ static NTSTATUS context_from_server( void *dst, const context_t *from, USHORT ma memcpy( to->ExtendedRegisters, from->ext.i386_regs, sizeof(to->ExtendedRegisters) ); } if ((from->flags & SERVER_CTX_YMM_REGISTERS) && (to_flags & CONTEXT_I386_XSTATE)) - { - CONTEXT_EX *xctx = (CONTEXT_EX *)(to + 1); - XSTATE *xs = (XSTATE *)((char *)xctx + xctx->XState.Offset); - - xs->Mask &= ~4; - if (user_shared_data->XState.CompactionEnabled) xs->CompactionMask = 0x8000000000000004; - for (i = 0; i < ARRAY_SIZE( from->ymm.regs.ymm_high); i++) - { - if (!from->ymm.regs.ymm_high[i].low && !from->ymm.regs.ymm_high[i].high) continue; - memcpy( &xs->YmmContext, &from->ymm.regs, sizeof(xs->YmmContext) ); - xs->Mask |= 4; - break; - } - } + xstate_from_server( (CONTEXT_EX *)(to + 1), from ); + exception_request_flags_from_server( &to->ContextFlags, from ); return STATUS_SUCCESS; } @@ -778,20 +821,8 @@ static NTSTATUS context_from_server( void *dst, const context_t *from, USHORT ma to->Dr7 = from->debug.x86_64_regs.dr7; } if ((from->flags & SERVER_CTX_YMM_REGISTERS) && (to_flags & CONTEXT_I386_XSTATE)) - { - CONTEXT_EX *xctx = (CONTEXT_EX *)(to + 1); - XSTATE *xs = (XSTATE *)((char *)xctx + xctx->XState.Offset); - - xs->Mask &= ~4; - if (user_shared_data->XState.CompactionEnabled) xs->CompactionMask = 0x8000000000000004; - for (i = 0; i < ARRAY_SIZE( from->ymm.regs.ymm_high); i++) - { - if (!from->ymm.regs.ymm_high[i].low && !from->ymm.regs.ymm_high[i].high) continue; - memcpy( &xs->YmmContext, &from->ymm.regs, sizeof(xs->YmmContext) ); - xs->Mask |= 4; - break; - } - } + xstate_from_server( (CONTEXT_EX *)(to + 1), from ); + exception_request_flags_from_server( &to->ContextFlags, from ); return STATUS_SUCCESS; } @@ -853,20 +884,8 @@ static NTSTATUS context_from_server( void *dst, const context_t *from, USHORT ma to->Dr7 = from->debug.x86_64_regs.dr7; } if ((from->flags & SERVER_CTX_YMM_REGISTERS) && (to_flags & CONTEXT_AMD64_XSTATE)) - { - CONTEXT_EX *xctx = (CONTEXT_EX *)(to + 1); - XSTATE *xs = (XSTATE *)((char *)xctx + xctx->XState.Offset); - - xs->Mask &= ~4; - if (user_shared_data->XState.CompactionEnabled) xs->CompactionMask = 0x8000000000000004; - for (i = 0; i < ARRAY_SIZE( from->ymm.regs.ymm_high); i++) - { - if (!from->ymm.regs.ymm_high[i].low && !from->ymm.regs.ymm_high[i].high) continue; - memcpy( &xs->YmmContext, &from->ymm.regs, sizeof(xs->YmmContext) ); - xs->Mask |= 4; - break; - } - } + xstate_from_server( (CONTEXT_EX *)(to + 1), from ); + exception_request_flags_from_server( &to->ContextFlags, from ); return STATUS_SUCCESS; } @@ -935,20 +954,8 @@ static NTSTATUS context_from_server( void *dst, const context_t *from, USHORT ma to->Dr7 = from->debug.i386_regs.dr7; } if ((from->flags & SERVER_CTX_YMM_REGISTERS) && (to_flags & CONTEXT_AMD64_XSTATE)) - { - CONTEXT_EX *xctx = (CONTEXT_EX *)(to + 1); - XSTATE *xs = (XSTATE *)((char *)xctx + xctx->XState.Offset); - - xs->Mask &= ~4; - if (user_shared_data->XState.CompactionEnabled) xs->CompactionMask = 0x8000000000000004; - for (i = 0; i < ARRAY_SIZE( from->ymm.regs.ymm_high); i++) - { - if (!from->ymm.regs.ymm_high[i].low && !from->ymm.regs.ymm_high[i].high) continue; - memcpy( &xs->YmmContext, &from->ymm.regs, sizeof(xs->YmmContext) ); - xs->Mask |= 4; - break; - } - } + xstate_from_server( (CONTEXT_EX *)(to + 1), from ); + exception_request_flags_from_server( &to->ContextFlags, from ); return STATUS_SUCCESS; } @@ -996,6 +1003,7 @@ static NTSTATUS context_from_server( void *dst, const context_t *from, USHORT ma for (i = 0; i < ARM_MAX_WATCHPOINTS; i++) to->Wvr[i] = from->debug.arm_regs.wvr[i]; for (i = 0; i < ARM_MAX_WATCHPOINTS; i++) to->Wcr[i] = from->debug.arm_regs.wcr[i]; } + exception_request_flags_from_server( &to->ContextFlags, from ); return STATUS_SUCCESS; } @@ -1037,6 +1045,7 @@ static NTSTATUS context_from_server( void *dst, const context_t *from, USHORT ma for (i = 0; i < ARM64_MAX_WATCHPOINTS; i++) to->Wcr[i] = from->debug.arm64_regs.wcr[i]; for (i = 0; i < ARM64_MAX_WATCHPOINTS; i++) to->Wvr[i] = from->debug.arm64_regs.wvr[i]; } + exception_request_flags_from_server( &to->ContextFlags, from ); return STATUS_SUCCESS; } @@ -1497,7 +1506,7 @@ void wait_suspend( CONTEXT *context ) * * Send an EXCEPTION_DEBUG_EVENT event to the debugger. */ -NTSTATUS send_debug_event( EXCEPTION_RECORD *rec, CONTEXT *context, BOOL first_chance ) +NTSTATUS send_debug_event( EXCEPTION_RECORD *rec, CONTEXT *context, BOOL first_chance, BOOL exception ) { unsigned int ret; DWORD i; @@ -1534,6 +1543,8 @@ NTSTATUS send_debug_event( EXCEPTION_RECORD *rec, CONTEXT *context, BOOL first_c select_op.wait.handles[0] = handle; contexts_to_server( server_contexts, context ); + server_contexts[0].flags |= SERVER_CTX_EXEC_SPACE; + server_contexts[0].exec_space.space.space = exception ? EXEC_SPACE_EXCEPTION : EXEC_SPACE_SYSCALL; server_select( &select_op, offsetof( select_op_t, wait.handles[1] ), SELECT_INTERRUPTIBLE, TIMEOUT_INFINITE, server_contexts, NULL ); @@ -1556,7 +1567,7 @@ NTSTATUS send_debug_event( EXCEPTION_RECORD *rec, CONTEXT *context, BOOL first_c */ NTSTATUS WINAPI NtRaiseException( EXCEPTION_RECORD *rec, CONTEXT *context, BOOL first_chance ) { - NTSTATUS status = send_debug_event( rec, context, first_chance ); + NTSTATUS status = send_debug_event( rec, context, first_chance, !(is_win64 || is_wow64() || is_old_wow64()) ); if (status == DBG_CONTINUE || status == DBG_EXCEPTION_HANDLED) return NtContinue( context, FALSE ); @@ -1611,22 +1622,40 @@ NTSTATUS WINAPI NtOpenThread( HANDLE *handle, ACCESS_MASK access, /****************************************************************************** * NtSuspendThread (NTDLL.@) */ -NTSTATUS WINAPI NtSuspendThread( HANDLE handle, ULONG *count ) +NTSTATUS WINAPI NtSuspendThread( HANDLE handle, ULONG *ret_count ) { BOOL self = FALSE; - unsigned int ret; + unsigned int ret, count = 0; + HANDLE wait_handle = NULL; SERVER_START_REQ( suspend_thread ) { req->handle = wine_server_obj_handle( handle ); - if (!(ret = wine_server_call( req ))) + if (!(ret = wine_server_call( req )) || ret == STATUS_PENDING) { self = reply->count & 0x80000000; - if (count) *count = reply->count & 0x7fffffff; + count = reply->count & 0x7fffffff;; + wait_handle = wine_server_ptr_handle( reply->wait_handle ); } } SERVER_END_REQ; + if (self) usleep( 0 ); + + if (ret == STATUS_PENDING && wait_handle) + { + NtWaitForSingleObject( wait_handle, FALSE, NULL ); + + SERVER_START_REQ( suspend_thread ) + { + req->handle = wine_server_obj_handle( handle ); + req->waited_handle = wine_server_obj_handle( wait_handle ); + ret = wine_server_call( req ); + } + SERVER_END_REQ; + } + + if (!ret && ret_count) *ret_count = count; return ret; } diff --git a/dlls/ntdll/unix/uffd_tmp_defs.h b/dlls/ntdll/unix/uffd_tmp_defs.h new file mode 100644 index 00000000000..f79c34c6145 --- /dev/null +++ b/dlls/ntdll/unix/uffd_tmp_defs.h @@ -0,0 +1,75 @@ +#ifndef __UFFD_TMP_DEFS__ +#define __UFFD_TMP_DEFS__ + +#ifdef __x86_64__ +#define __NR_userfaultfd 323 +#else +#define __NR_userfaultfd 374 +#endif + +#ifndef UFFD_FEATURE_WP_ASYNC +#define UFFD_FEATURE_WP_UNPOPULATED (1<<13) +#define UFFD_FEATURE_WP_ASYNC (1<<15) +#endif + +#ifndef PAGEMAP_SCAN +/* Pagemap ioctl */ +#define PAGEMAP_SCAN _IOWR('f', 16, struct pm_scan_arg) + +/* Bits are set in flags of the page_region and masks in pm_scan_args */ +#define PAGE_IS_WPALLOWED (1 << 0) +#define PAGE_IS_WRITTEN (1 << 1) +#define PAGE_IS_FILE (1 << 2) +#define PAGE_IS_PRESENT (1 << 3) +#define PAGE_IS_SWAPPED (1 << 4) +#define PAGE_IS_PFNZERO (1 << 5) +#define PAGE_IS_HUGE (1 << 6) +/* + * struct page_region - Page region with flags + * @start: Start of the region + * @end: End of the region (exclusive) + * @categories: PAGE_IS_* category bitmask for the region + */ +struct page_region { + __u64 start; + __u64 end; + __u64 categories; +}; + +/* Flags for PAGEMAP_SCAN ioctl */ +#define PM_SCAN_WP_MATCHING (1 << 0) /* Write protect the pages matched. */ +#define PM_SCAN_CHECK_WPASYNC (1 << 1) /* Abort the scan when a non-WP-enabled page is found. */ + +/* + * struct pm_scan_arg - Pagemap ioctl argument + * @size: Size of the structure + * @flags: Flags for the IOCTL + * @start: Starting address of the region + * @end: Ending address of the region + * @walk_end Address where the scan stopped (written by kernel). + * walk_end == end informs that the scan completed on entire range. + * @vec: Address of page_region struct array for output + * @vec_len: Length of the page_region struct array + * @max_pages: Optional limit for number of returned pages (0 = disabled) + * @category_inverted: PAGE_IS_* categories which values match if 0 instead of 1 + * @category_mask: Skip pages for which any category doesn't match + * @category_anyof_mask: Skip pages for which no category matches + * @return_mask: PAGE_IS_* categories that are to be reported in `page_region`s returned + */ +struct pm_scan_arg { + __u64 size; + __u64 flags; + __u64 start; + __u64 end; + __u64 walk_end; + __u64 vec; + __u64 vec_len; + __u64 max_pages; + __u64 category_inverted; + __u64 category_mask; + __u64 category_anyof_mask; + __u64 return_mask; +}; +#endif + +#endif diff --git a/dlls/ntdll/unix/unix_private.h b/dlls/ntdll/unix/unix_private.h index cf6b9ab0c53..2b62d5c7754 100644 --- a/dlls/ntdll/unix/unix_private.h +++ b/dlls/ntdll/unix/unix_private.h @@ -227,6 +227,29 @@ extern int server_pipe( int fd[2] ); extern void fpux_to_fpu( I386_FLOATING_SAVE_AREA *fpu, const XSAVE_FORMAT *fpux ); extern void fpu_to_fpux( XSAVE_FORMAT *fpux, const I386_FLOATING_SAVE_AREA *fpu ); + +static inline void set_context_exception_reporting_flags( DWORD *context_flags, DWORD reporting_flag ) +{ + if (!(*context_flags & CONTEXT_EXCEPTION_REQUEST)) + { + *context_flags &= ~(CONTEXT_EXCEPTION_REPORTING | CONTEXT_SERVICE_ACTIVE | CONTEXT_EXCEPTION_ACTIVE); + return; + } + *context_flags = (*context_flags & ~(CONTEXT_SERVICE_ACTIVE | CONTEXT_EXCEPTION_ACTIVE)) + | CONTEXT_EXCEPTION_REPORTING | reporting_flag; +} + +extern BOOL xstate_compaction_enabled; +extern UINT64 xstate_supported_features_mask; +extern UINT64 xstate_features_size; +extern unsigned int xstate_get_size( UINT64 compaction_mask, UINT64 mask ); +extern void copy_xstate( XSAVE_AREA_HEADER *dst, XSAVE_AREA_HEADER *src, UINT64 mask ); + +static inline UINT64 xstate_extended_features(void) +{ + return xstate_supported_features_mask & ~(UINT64)3; +} + extern void *get_cpu_area( USHORT machine ); extern void set_thread_id( TEB *teb, DWORD pid, DWORD tid ); extern NTSTATUS init_thread_stack( TEB *teb, ULONG_PTR limit, SIZE_T reserve_size, SIZE_T commit_size ); @@ -234,7 +257,7 @@ extern void DECLSPEC_NORETURN abort_thread( int status ); extern void DECLSPEC_NORETURN abort_process( int status ); extern void DECLSPEC_NORETURN exit_process( int status ); extern void wait_suspend( CONTEXT *context ); -extern NTSTATUS send_debug_event( EXCEPTION_RECORD *rec, CONTEXT *context, BOOL first_chance ); +extern NTSTATUS send_debug_event( EXCEPTION_RECORD *rec, CONTEXT *context, BOOL first_chance, BOOL exception ); extern BOOL validate_context_xstate( CONTEXT *context ); extern NTSTATUS set_thread_context( HANDLE handle, const void *context, BOOL *self, USHORT machine ); extern NTSTATUS get_thread_context( HANDLE handle, void *context, BOOL *self, USHORT machine ); diff --git a/dlls/ntdll/unix/virtual.c b/dlls/ntdll/unix/virtual.c index 42f572e8479..6c1dc3a8b88 100644 --- a/dlls/ntdll/unix/virtual.c +++ b/dlls/ntdll/unix/virtual.c @@ -51,6 +51,12 @@ #ifdef HAVE_SYS_USER_H # include #endif +#ifdef HAVE_LINK_H +# include +#endif +#ifdef HAVE_SYS_LINK_H +# include +#endif #ifdef HAVE_LIBPROCSTAT_H # include #endif @@ -64,6 +70,10 @@ # include #endif +#include +#include +#include "uffd_tmp_defs.h" + #include #include "ntstatus.h" @@ -82,6 +92,121 @@ WINE_DECLARE_DEBUG_CHANNEL(module); WINE_DECLARE_DEBUG_CHANNEL(virtual_ranges); WINE_DECLARE_DEBUG_CHANNEL(virtstat); +/* Gdb integration, in loader/main.c */ +static struct r_debug *wine_r_debug; + +#if defined(HAVE_LINK_H) || defined(HAVE_SYS_LINK_H) + +struct link_map_entry +{ + struct link_map map; + const void *module; +}; +static struct link_map link_map = {.l_name = (char *)""}; + +static void r_debug_set_state( int state ) +{ + wine_r_debug->r_map = &link_map; + wine_r_debug->r_state = state; + ((void (*)(void))wine_r_debug->r_brk)(); +} + +static char *get_path_from_fd( int fd, int sz ) +{ +#ifdef linux + char *ret = malloc( 32 + sz ); + + if (ret) sprintf( ret, "/proc/self/fd/%u", fd ); + return ret; +#elif defined(F_GETPATH) + char *ret = malloc( PATH_MAX + sz ); + + if (!ret) return NULL; + if (!fcntl( fd, F_GETPATH, ret )) return ret; + free( ret ); + return NULL; +#else + return NULL; +#endif +} + +static char *r_debug_path_from_fd( int fd ) +{ + char *real, *path; + if (!(path = get_path_from_fd( fd, 0 ))) return NULL; + if (!(real = realpath( path, NULL ))) return path; + if (!strncmp( real, "/run/host", 9 )) memmove( real, real + 9, strlen( real ) - 8 ); + free( path ); + return real; +} + +static void r_debug_add_module( void *module, int fd, INT_PTR offset ) +{ + struct link_map *ptr = link_map.l_next, *next; + struct link_map_entry *entry; + + if (!wine_r_debug) return; + + while (ptr) + { + entry = LIST_ENTRY(ptr, struct link_map_entry, map); + if (entry->module == module) break; + ptr = ptr->l_next; + } + + r_debug_set_state( RT_ADD ); + + if (ptr) entry->map.l_addr = offset; + else if ((entry = calloc( 1, sizeof(*entry) ))) + { + entry->module = module; + entry->map.l_addr = offset; + entry->map.l_name = r_debug_path_from_fd( fd ); + + entry->map.l_next = link_map.l_next; + if ((next = entry->map.l_next)) next->l_prev = &entry->map; + entry->map.l_prev = &link_map; + link_map.l_next = &entry->map; + } + + r_debug_set_state( RT_CONSISTENT ); +} + +static void r_debug_remove_module( void *module ) +{ + struct link_map *ptr = link_map.l_next, *next; + struct link_map_entry *entry; + + if (!wine_r_debug) return; + + while (ptr) + { + entry = LIST_ENTRY(ptr, struct link_map_entry, map); + if (entry->module == module) break; + ptr = ptr->l_next; + } + if (!ptr) return; + + r_debug_set_state( RT_DELETE ); + + entry->map.l_prev->l_next = entry->map.l_next; + if ((next = entry->map.l_next)) next->l_prev = entry->map.l_prev; + + r_debug_set_state( RT_CONSISTENT ); + + free( entry->map.l_name ); + free( entry ); +} + +#else /* defined(HAVE_LINK_H) || defined(HAVE_SYS_LINK_H) */ + +#define RT_CONSISTENT 0 +static void r_debug_set_state( int state ) {} +static void r_debug_add_module( void *module, int fd, INT_PTR offset ) {} +static void r_debug_remove_module( void *module ) {} + +#endif /* defined(HAVE_LINK_H) || defined(HAVE_SYS_LINK_H) */ + struct preload_info { void *addr; @@ -211,13 +336,12 @@ static BYTE **pages_vprot; static BYTE *pages_vprot; #endif -static BOOL use_kernel_writewatch; -static int pagemap_fd, pagemap_reset_fd, clear_refs_fd; +static int use_kernel_writewatch; +static int uffd_fd, pagemap_fd; +static int pagemap_reset_fd, clear_refs_fd; #define PAGE_FLAGS_BUFFER_LENGTH 1024 #define PM_SOFT_DIRTY_PAGE (1ull << 57) -static void reset_write_watches( void *base, SIZE_T size ); - static struct file_view *view_block_start, *view_block_end, *next_free_view; #ifdef _WIN64 static const size_t view_block_size = 0x200000; @@ -255,6 +379,243 @@ void *anon_mmap_alloc( size_t size, int prot ) return mmap( NULL, size, prot, MAP_PRIVATE | MAP_ANON, -1, 0 ); } +static void kernel_writewatch_softdirty_init(void) +{ + if ((pagemap_reset_fd = open( "/proc/self/pagemap_reset", O_RDONLY | O_CLOEXEC )) == -1) return; + + if ((pagemap_fd = open( "/proc/self/pagemap", O_RDONLY | O_CLOEXEC )) == -1) + { + ERR( "Could not open pagemap file, error %s.\n", strerror( errno )); + exit(-1); + } + if ((clear_refs_fd = open( "/proc/self/clear_refs", O_WRONLY | O_CLOEXEC )) == -1) + { + ERR( "Could not open clear_refs file, error %s.\n", strerror( errno )); + exit(-1); + } + use_kernel_writewatch = 2; +} + +static void kernel_writewatch_init(void) +{ + struct uffdio_api uffdio_api; + + uffd_fd = syscall( __NR_userfaultfd, O_CLOEXEC | O_NONBLOCK ); + if (uffd_fd == -1) + { + kernel_writewatch_softdirty_init(); + return; + } + + uffdio_api.api = UFFD_API; + uffdio_api.features = UFFD_FEATURE_WP_ASYNC | UFFD_FEATURE_WP_UNPOPULATED; + if (ioctl( uffd_fd, UFFDIO_API, &uffdio_api ) || uffdio_api.api != UFFD_API) + { + close( uffd_fd ); + kernel_writewatch_softdirty_init(); + return; + } + pagemap_fd = open( "/proc/self/pagemap", O_CLOEXEC | O_RDONLY ); + if (pagemap_fd == -1) + { + ERR("Error opening /proc/self/pagemap.\n"); + close( uffd_fd ); + return; + } + use_kernel_writewatch = 1; +} + +static void kernel_writewatch_reset( void *start, SIZE_T len ) +{ + struct pm_scan_arg arg = { 0 }; + + if (use_kernel_writewatch == 2) + { + char buffer[17]; + ssize_t ret; + + memset(buffer, 0, sizeof(buffer)); + buffer[0] = '6'; + *(ULONG64 *)&buffer[1] = (ULONG_PTR)start; + *(ULONG64 *)&buffer[1 + 8] = (ULONG_PTR)start + len; + + if ((ret = write(clear_refs_fd, buffer, sizeof(buffer))) != sizeof(buffer)) + ERR("Could not clear soft dirty bits, ret %zd, error %s.\n", ret, strerror(errno)); + return; + } + + arg.size = sizeof(arg); + arg.start = (UINT_PTR)start; + arg.end = arg.start + len; + arg.flags = PM_SCAN_WP_MATCHING; + arg.category_mask = PAGE_IS_WRITTEN; + arg.return_mask = PAGE_IS_WRITTEN; + if (ioctl( pagemap_fd, PAGEMAP_SCAN, &arg ) < 0) + ERR( "ioctl(PAGEMAP_SCAN) failed, err %s.\n", strerror(errno) ); +} + +static void kernel_writewatch_register_range( struct file_view *view, void *base, size_t size ) +{ + struct uffdio_register uffdio_register; + struct uffdio_writeprotect wp; + + if (!(view->protect & VPROT_WRITEWATCH) || !use_kernel_writewatch) return; + + madvise( base, size, MADV_NOHUGEPAGE ); + if (use_kernel_writewatch == 2) + { + kernel_writewatch_reset( base, size ); + return; + } + + uffdio_register.range.start = (UINT_PTR)base; + uffdio_register.range.len = size; + uffdio_register.mode = UFFDIO_REGISTER_MODE_WP; + if (ioctl( uffd_fd, UFFDIO_REGISTER, &uffdio_register ) == -1) + { + ERR( "ioctl( UFFDIO_REGISTER ) failed, %s.\n", strerror(errno) ); + return; + } + + if (!(uffdio_register.ioctls & UFFDIO_WRITEPROTECT)) + { + ERR( "uffdio_register.ioctls %s.\n", wine_dbgstr_longlong(uffdio_register.ioctls) ); + return; + } + wp.range.start = (UINT_PTR)base; + wp.range.len = size; + wp.mode = UFFDIO_WRITEPROTECT_MODE_WP; + + if (ioctl(uffd_fd, UFFDIO_WRITEPROTECT, &wp) == -1) + { + perror("ioctl(UFFDIO_WRITEPROTECT)"); + exit(-1); + } +} + +static NTSTATUS kernel_soft_dirty_get_write_watches( void *base, SIZE_T size, void **addresses, ULONG_PTR *count, BOOL reset ) +{ + static UINT64 buffer[PAGE_FLAGS_BUFFER_LENGTH]; + char *addr = base; + char *end = addr + size; + unsigned int i, length; + ssize_t read_length; + ULONG_PTR pos = 0; + + if (reset) + { + if (is_win64) + { + addresses[0] = end; + if ((read_length = pread(pagemap_reset_fd, addresses, *count * sizeof(*addresses), + ((ULONG_PTR)addr >> page_shift) * sizeof(*addresses))) == -1) + { + ERR("Error reading page flags, read_length %zd, error %s.\n", read_length, strerror(errno)); + return STATUS_INVALID_ADDRESS; + } + *count = read_length / sizeof(*addresses); + return STATUS_SUCCESS; + } + + while (pos < *count && addr < end) + { + length = min(PAGE_FLAGS_BUFFER_LENGTH, *count - pos); + + buffer[0] = (ULONG_PTR)end; + if ((read_length = pread(pagemap_reset_fd, buffer, length * sizeof(*buffer), + ((ULONG_PTR)addr >> page_shift) * sizeof(*buffer))) == -1) + { + ERR("Error reading page flags, read_length %zd, error %s.\n", read_length, strerror(errno)); + return STATUS_INVALID_ADDRESS; + } + read_length /= sizeof(*buffer); + for (i = 0; i < read_length; ++i) + { + assert(pos < *count); + addresses[pos++] = (void *)(ULONG_PTR)buffer[i]; + } + if (read_length < length) + break; + addr = (char *)(ULONG_PTR)buffer[read_length - 1] + page_size; + } + *count = pos; + return STATUS_SUCCESS; + } + + while (pos < *count && addr < end) + { + length = min(PAGE_FLAGS_BUFFER_LENGTH, (end - addr) >> page_shift); + + if ((read_length = pread(pagemap_fd, buffer, length * sizeof(*buffer), + ((ULONG_PTR)addr >> page_shift) * sizeof(*buffer))) != length * sizeof(*buffer)) + { + ERR("Error reading page flags, read_length %zd, error %s.\n", read_length, strerror(errno)); + return STATUS_INVALID_ADDRESS; + } + for (i = 0; i < length && pos < *count; ++i) + { + if (buffer[i] & PM_SOFT_DIRTY_PAGE) + addresses[pos++] = addr; + + addr += page_size; + } + } + *count = pos; + return STATUS_SUCCESS; +} + +static NTSTATUS kernel_get_write_watches( void *base, SIZE_T size, void **buffer, ULONG_PTR *count, BOOL reset ) +{ + SIZE_T buffer_len = count ? *count : 0; + struct pm_scan_arg arg = { 0 }; + char *addr = base, *next_addr; + struct page_region rgns[256]; + int rgn_count, i; + size_t c_addr; + + assert( !(size & page_mask) ); + + if (use_kernel_writewatch == 2) return kernel_soft_dirty_get_write_watches( base, size, buffer, count, reset ); + + arg.size = sizeof(arg); + arg.vec = (UINT_PTR)rgns; + arg.vec_len = ARRAY_SIZE(rgns); + if (reset) + arg.flags |= PM_SCAN_WP_MATCHING; + arg.category_mask = PAGE_IS_WRITTEN; + arg.return_mask = PAGE_IS_WRITTEN; + + *count = 0; + while (1) + { + arg.start = (UINT_PTR)addr; + arg.end = arg.start + size; + arg.max_pages = buffer_len; + + if ((rgn_count = ioctl( pagemap_fd, PAGEMAP_SCAN, &arg )) < 0) + { + ERR( "ioctl( PAGEMAP_SCAN ) failed, error %s.\n", strerror(errno) ); + return STATUS_INTERNAL_ERROR; + } + if (!rgn_count) break; + + assert( rgn_count <= ARRAY_SIZE(rgns) ); + for (i = 0; i < rgn_count; ++i) + { + assert( rgns[i].categories == PAGE_IS_WRITTEN ); + assert( !buffer || buffer_len >= ((rgns[i].end - rgns[i].start) >> page_shift) ); + for (c_addr = rgns[i].start; buffer_len && c_addr != rgns[i].end; c_addr += page_size, --buffer_len) + buffer[(*count)++] = (void *)c_addr; + } + if (!buffer_len || rgn_count < arg.vec_len) break; + next_addr = (void *)(UINT_PTR)arg.walk_end; + assert( size >= next_addr - addr ); + if (!(size -= next_addr - addr)) break; + addr = next_addr; + } + return STATUS_SUCCESS; +} + static void mmap_add_reserved_area( void *addr, SIZE_T size ) { @@ -649,6 +1010,30 @@ void *get_builtin_so_handle( void *module ) return ret; } +static void load_steam_overlay(const char *unix_lib_path) +{ + const char *preload, *p; + char path[PATH_MAX]; + unsigned int len; + void *handle; + + if (!strstr(unix_lib_path, "winex11.so")) return; + if (getenv("LD_PRELOAD") || !(preload = getenv("WINE_LD_PRELOAD"))) return; + + p = preload; + while (*(preload = p)) + { + p = strchrnul( preload, ':' ); + len = p - preload; + if (*p) ++p; + if (len + 1 > sizeof(path)) continue; + memcpy( path, preload, len ); + path[len] = 0; + if (!strstr( path, "gameoverlayrenderer.so" )) continue; + handle = dlopen( path, RTLD_NOW | RTLD_GLOBAL ); + FIXME( "HACK: tried to load %s, handle %p.\n", debugstr_a(path), handle ); + } +} /*********************************************************************** * get_builtin_unix_funcs @@ -665,7 +1050,10 @@ static NTSTATUS get_builtin_unix_funcs( void *module, BOOL wow, const void **fun { if (builtin->module != module) continue; if (builtin->unix_path && !builtin->unix_handle) + { + load_steam_overlay(builtin->unix_path); builtin->unix_handle = dlopen( builtin->unix_path, RTLD_NOW ); + } if (builtin->unix_handle) { *funcs = dlsym( builtin->unix_handle, ptr_name ); @@ -693,7 +1081,11 @@ NTSTATUS load_builtin_unixlib( void *module, const char *name ) if (builtin->module != module) continue; if (!builtin->unix_path) builtin->unix_path = strdup( name ); else status = STATUS_IMAGE_ALREADY_LOADED; - if (!builtin->unix_handle) builtin->unix_handle = dlopen( builtin->unix_path, RTLD_NOW ); + if (!builtin->unix_handle) + { + load_steam_overlay(builtin->unix_path); + builtin->unix_handle = dlopen( builtin->unix_path, RTLD_NOW ); + } break; } server_leave_uninterrupted_section( &virtual_mutex, &sigset ); @@ -1552,12 +1944,7 @@ static NTSTATUS create_view( struct file_view **view_ret, void *base, size_t siz mprotect( base, size, unix_prot | PROT_EXEC ); } - if (vprot & VPROT_WRITEWATCH && use_kernel_writewatch) - { - madvise( view->base, view->size, MADV_NOHUGEPAGE ); - reset_write_watches( view->base, view->size ); - } - + kernel_writewatch_register_range( view, view->base, view->size ); return STATUS_SUCCESS; } @@ -1758,19 +2145,7 @@ static void update_write_watches( void *base, size_t size, size_t accessed_size */ static void reset_write_watches( void *base, SIZE_T size ) { - if (use_kernel_writewatch) - { - char buffer[17]; - ssize_t ret; - - memset(buffer, 0, sizeof(buffer)); - buffer[0] = '6'; - *(void **)&buffer[1] = base; - *(void **)&buffer[1 + 8] = (char *)base + size; - - if ((ret = write(clear_refs_fd, buffer, sizeof(buffer))) != sizeof(buffer)) - ERR("Could not clear soft dirty bits, ret %zd, error %s.\n", ret, strerror(errno)); - } + if (use_kernel_writewatch) kernel_writewatch_reset( base, size ); else { set_page_vprot_bits( base, size, VPROT_WRITEWATCH, 0 ); @@ -2111,7 +2486,7 @@ static NTSTATUS map_view( struct file_view **view_ret, void *base, size_t size, set_vprot( view, base, size, vprot ); if (vprot & VPROT_WRITEWATCH) { - madvise( base, size, MADV_NOHUGEPAGE ); + kernel_writewatch_register_range( view, base, size ); reset_write_watches( base, size ); } *view_ret = view; @@ -2282,10 +2657,8 @@ static NTSTATUS decommit_pages( struct file_view *view, size_t start, size_t siz if (!size) size = view->size; if (anon_mmap_fixed( (char *)view->base + start, size, PROT_NONE, 0 ) != MAP_FAILED) { - if (use_kernel_writewatch && view->protect & VPROT_WRITEWATCH) - madvise( view->base, view->size, MADV_NOHUGEPAGE ); - set_page_vprot_bits( (char *)view->base + start, size, 0, VPROT_COMMITTED ); + kernel_writewatch_register_range( view, (char *)view->base + start, size ); return STATUS_SUCCESS; } return STATUS_NO_MEMORY; @@ -3020,6 +3393,7 @@ static NTSTATUS map_image_into_view( struct file_view *view, const WCHAR *filena #ifdef VALGRIND_LOAD_PDB_DEBUGINFO VALGRIND_LOAD_PDB_DEBUGINFO(fd, ptr, total_size, ptr - (char *)wine_server_get_ptr( image_info->base )); #endif + r_debug_add_module( ptr, fd, ptr - (char *)wine_server_get_ptr( image_info->base ) ); return STATUS_SUCCESS; } @@ -3373,12 +3747,15 @@ static void *alloc_virtual_heap( SIZE_T size ) void virtual_init(void) { const struct preload_info **preload_info = dlsym( RTLD_DEFAULT, "wine_main_preload_info" ); + struct r_debug **r_debug = dlsym( RTLD_DEFAULT, "wine_r_debug" ); const char *preload = getenv( "WINEPRELOADRESERVE" ); size_t size; int i; pthread_mutexattr_t attr; const char *env_var; + if (r_debug && (wine_r_debug = *r_debug)) r_debug_set_state( RT_CONSISTENT ); + pthread_mutexattr_init( &attr ); pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_RECURSIVE ); pthread_mutex_init( &virtual_mutex, &attr ); @@ -3391,23 +3768,11 @@ void virtual_init(void) host_addr_space_limit = address_space_limit; #endif - if (!((env_var = getenv("WINE_DISABLE_KERNEL_WRITEWATCH")) && atoi(env_var)) - && (pagemap_reset_fd = open("/proc/self/pagemap_reset", O_RDONLY | O_CLOEXEC)) != -1) - { - use_kernel_writewatch = TRUE; - if ((pagemap_fd = open("/proc/self/pagemap", O_RDONLY | O_CLOEXEC)) == -1) - { - ERR("Could not open pagemap file, error %s.\n", strerror(errno)); - exit(-1); - } - if ((clear_refs_fd = open("/proc/self/clear_refs", O_WRONLY | O_CLOEXEC)) == -1) - { - ERR("Could not open clear_refs file, error %s.\n", strerror(errno)); - exit(-1); - } - if (ERR_ON(virtual)) - MESSAGE("wine: using kernel write watches (experimental).\n"); - } + if (!((env_var = getenv( "WINE_DISABLE_KERNEL_WRITEWATCH" )) && atoi( env_var ))) + kernel_writewatch_init(); + + if (use_kernel_writewatch) + MESSAGE( "wine: using kernel write watches, use_kernel_writewatch %d.\n", use_kernel_writewatch ); if (preload_info && *preload_info) for (i = 0; (*preload_info)[i].size; i++) @@ -4280,7 +4645,7 @@ ssize_t virtual_locked_read( int fd, void *addr, size_t size ) int err = EFAULT; ssize_t ret = read( fd, addr, size ); - if (ret != -1 || errno != EFAULT) return ret; + if (ret != -1 || use_kernel_writewatch || errno != EFAULT) return ret; server_enter_uninterrupted_section( &virtual_mutex, &sigset ); if (!check_write_access( addr, size, &has_write_watch )) @@ -4305,7 +4670,7 @@ ssize_t virtual_locked_pread( int fd, void *addr, size_t size, off_t offset ) int err = EFAULT; ssize_t ret = pread( fd, addr, size, offset ); - if (ret != -1 || errno != EFAULT) return ret; + if (ret != -1 || use_kernel_writewatch || errno != EFAULT) return ret; server_enter_uninterrupted_section( &virtual_mutex, &sigset ); if (!check_write_access( addr, size, &has_write_watch )) @@ -4331,7 +4696,7 @@ ssize_t virtual_locked_recvmsg( int fd, struct msghdr *hdr, int flags ) int err = EFAULT; ssize_t ret = recvmsg( fd, hdr, flags ); - if (ret != -1 || errno != EFAULT) return ret; + if (ret != -1 || use_kernel_writewatch || errno != EFAULT) return ret; server_enter_uninterrupted_section( &virtual_mutex, &sigset ); for (i = 0; i < hdr->msg_iovlen; i++) @@ -4633,22 +4998,6 @@ static NTSTATUS allocate_virtual_memory( void **ret, SIZE_T *size_ptr, ULONG typ SIZE_T size = *size_ptr; NTSTATUS status = STATUS_SUCCESS; - if (type & MEM_WRITE_WATCH) - { - static int disable = -1; - - if (disable == -1) - { - const char *env_var; - - if ((disable = (env_var = getenv("WINE_DISABLE_WRITE_WATCH")) && atoi(env_var))) - FIXME("Disabling write watch support.\n"); - } - - if (disable) - return STATUS_NOT_SUPPORTED; - } - /* Round parameters to a page boundary */ if (is_beyond_limit( 0, size, working_set_limit )) return STATUS_WORKING_SET_LIMIT_RANGE; @@ -5934,7 +6283,11 @@ static NTSTATUS unmap_view_of_section( HANDLE process, PVOID addr, ULONG flags ) SERVER_END_REQ; if (!status) { - if (view->protect & SEC_IMAGE) release_builtin_module( view->base ); + if (view->protect & SEC_IMAGE) + { + r_debug_remove_module( view->base ); + release_builtin_module( view->base ); + } if (flags & MEM_PRESERVE_PLACEHOLDER) free_pages_preserve_placeholder( view, view->base, view->size ); else delete_view( view ); } @@ -6139,82 +6492,17 @@ NTSTATUS WINAPI NtGetWriteWatch( HANDLE process, ULONG flags, PVOID base, SIZE_T if (use_kernel_writewatch) { - static UINT64 buffer[PAGE_FLAGS_BUFFER_LENGTH]; - unsigned int i, length; - ssize_t read_length; - - if (flags & WRITE_WATCH_FLAG_RESET) - { - if (is_win64) - { - addresses[0] = end; - if ((read_length = pread(pagemap_reset_fd, addresses, *count * sizeof(*addresses), - ((ULONG_PTR)addr >> page_shift) * sizeof(*addresses))) == -1) - { - ERR("Error reading page flags, read_length %zd, error %s.\n", read_length, strerror(errno)); - status = STATUS_INVALID_ADDRESS; - goto done; - } - *count = read_length / sizeof(*addresses); - *granularity = page_size; - goto done; - } - - while (pos < *count && addr < end) - { - length = min(PAGE_FLAGS_BUFFER_LENGTH, *count - pos); - - buffer[0] = (ULONG_PTR)end; - if ((read_length = pread(pagemap_reset_fd, buffer, length * sizeof(*buffer), - ((ULONG_PTR)addr >> page_shift) * sizeof(*buffer))) == -1) - { - ERR("Error reading page flags, read_length %zd, error %s.\n", read_length, strerror(errno)); - status = STATUS_INVALID_ADDRESS; - goto done; - } - read_length /= sizeof(*buffer); - for (i = 0; i < read_length; ++i) - { - assert(pos < *count); - addresses[pos++] = (void *)(ULONG_PTR)buffer[i]; - } - if (read_length < length) - break; - addr = (char *)(ULONG_PTR)buffer[read_length - 1] + page_size; - } - } - else - { - while (pos < *count && addr < end) - { - length = min(PAGE_FLAGS_BUFFER_LENGTH, (end - addr) >> page_shift); - - if ((read_length = pread(pagemap_fd, buffer, length * sizeof(*buffer), - ((ULONG_PTR)addr >> page_shift) * sizeof(*buffer))) != length * sizeof(*buffer)) - { - ERR("Error reading page flags, read_length %zd, error %s.\n", read_length, strerror(errno)); - status = STATUS_INVALID_ADDRESS; - goto done; - } - for (i = 0; i < length && pos < *count; ++i) - { - if (buffer[i] & PM_SOFT_DIRTY_PAGE) - addresses[pos++] = addr; - - addr += page_size; - } - } - } + if (!(status = kernel_get_write_watches( base, size, addresses, count, flags & WRITE_WATCH_FLAG_RESET ))) + *granularity = page_size; + goto done; } - else + + while (pos < *count && addr < end) { - while (pos < *count && addr < end) - { - if (!(get_page_vprot( addr ) & VPROT_WRITEWATCH)) addresses[pos++] = addr; - addr += page_size; - } - if (flags & WRITE_WATCH_FLAG_RESET) reset_write_watches( base, addr - (char *)base ); + if (!(get_page_vprot( addr ) & VPROT_WRITEWATCH)) addresses[pos++] = addr; + addr += page_size; } + if (flags & WRITE_WATCH_FLAG_RESET) reset_write_watches( base, addr - (char *)base ); *count = pos; *granularity = page_size; } @@ -6267,13 +6555,21 @@ NTSTATUS WINAPI NtReadVirtualMemory( HANDLE process, const void *addr, void *buf int unix_pid; ssize_t ret; - SERVER_START_REQ( read_process_memory ) + if (process == NtCurrentProcess()) { - req->handle = wine_server_obj_handle( process ); - status = wine_server_call( req ); - unix_pid = reply->unix_pid; + unix_pid = getpid(); + status = STATUS_SUCCESS; + } + else + { + SERVER_START_REQ( read_process_memory ) + { + req->handle = wine_server_obj_handle( process ); + status = wine_server_call( req ); + unix_pid = reply->unix_pid; + } + SERVER_END_REQ; } - SERVER_END_REQ; if (status) { diff --git a/dlls/opengl32/tests/opengl.c b/dlls/opengl32/tests/opengl.c index e3ca82a9b03..74aff6b4bbd 100644 --- a/dlls/opengl32/tests/opengl.c +++ b/dlls/opengl32/tests/opengl.c @@ -2006,6 +2006,55 @@ static void test_copy_context(HDC hdc) ok(ret, "wglMakeCurrent failed, last error %#lx.\n", GetLastError()); } +static void test_child_window(HWND hwnd, PIXELFORMATDESCRIPTOR *pfd) +{ + int pixel_format; + DWORD t1, t; + HGLRC hglrc; + HWND child; + HDC hdc; + int res; + + child = CreateWindowA("static", "Title", WS_CHILDWINDOW | WS_VISIBLE, 50, 50, 100, 100, hwnd, NULL, NULL, NULL); + ok(!!child, "got error %lu.\n", GetLastError()); + + hdc = GetDC(child); + pixel_format = ChoosePixelFormat(hdc, pfd); + res = SetPixelFormat(hdc, pixel_format, pfd); + ok(res, "got error %lu.\n", GetLastError()); + + hglrc = wglCreateContext(hdc); + ok(!!hglrc, "got error %lu.\n", GetLastError()); + + /* Test SwapBuffers with NULL context. */ + + glDrawBuffer(GL_BACK); + + /* Currently blit happening for child window in winex11 may not be updated with the latest GL frame + * even on glXWaitForSbcOML() path. So simulate continuous present for the test purpose. */ + trace("Child window rectangle should turn from red to green now.\n"); + t1 = GetTickCount(); + while ((t = GetTickCount()) - t1 < 3000) + { + res = wglMakeCurrent(hdc, hglrc); + ok(res, "got error %lu.\n", GetLastError()); + if (t - t1 > 1500) + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + else + glClearColor(1.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + res = wglMakeCurrent(NULL, NULL); + ok(res, "got error %lu.\n", GetLastError()); + SwapBuffers(hdc); + } + + res = wglDeleteContext(hglrc); + ok(res, "got error %lu.\n", GetLastError()); + + ReleaseDC(child, hdc); + DestroyWindow(child); +} + START_TEST(opengl) { HWND hwnd; @@ -2138,6 +2187,9 @@ START_TEST(opengl) else skip("WGL_EXT_swap_control not supported, skipping test\n"); + if (winetest_interactive) + test_child_window(hwnd, &pfd); + cleanup: ReleaseDC(hwnd, hdc); DestroyWindow(hwnd); diff --git a/dlls/opengl32/wgl.c b/dlls/opengl32/wgl.c index 7d77296f199..2a308152735 100644 --- a/dlls/opengl32/wgl.c +++ b/dlls/opengl32/wgl.c @@ -1297,21 +1297,67 @@ static char *fixup_shader( GLsizei count, const GLchar *const*string, const GLin static int needs_fixup = -1; static unsigned int once; - const char add_ext[] = "#version 120\r\n" - "#extension GL_ARB_explicit_uniform_location : enable\r\n" - "#extension GL_ARB_explicit_attrib_location : enable\r\n"; - const char search_str[] = "uniform mat4 boneMatrices[NBONES];"; - const char prepend_str[] = "layout(location = 2) "; - unsigned int search_len, new_len; + static const struct + { + const char *gameid; + const char *add_ext; + const char *search_str; + const char *prepend_str; + } + replace[] = + { + { + "333420", + + /* add_ext */ + "#version 120\r\n" + "#extension GL_ARB_explicit_uniform_location : enable\r\n" + "#extension GL_ARB_explicit_attrib_location : enable\r\n", + /* search_str */ + "uniform mat4 boneMatrices[NBONES];", + /* replace_str */ + "layout(location = 2) ", + }, + { + "242110", + /* add_ext */ + "#version 120\r\n", + }, + { + "229890", + /* add_ext */ + "#version 120\r\n", + }, + }; + static const char *add_ext; + static const char *search_str; + static const char *prepend_str; + static unsigned int add_ext_len, search_len, prepend_len; + unsigned int new_len; const char *p, *next; BOOL found = FALSE; char *new, *out; if (needs_fixup == -1) { - const char *sgi = getenv("SteamGameId"); + const char *sgi = getenv("SteamGameId"); + unsigned int i; - needs_fixup = sgi && !strcmp( sgi, "333420" ); + if (!sgi) return NULL; + + for (i = 0; i < ARRAY_SIZE(replace); ++i) + if (!strcmp( sgi, replace[i].gameid )) break; + + needs_fixup = i < ARRAY_SIZE(replace); + if (needs_fixup) + { + add_ext = replace[i].add_ext; + add_ext_len = add_ext ? strlen(add_ext) : 0; + search_str = replace[i].search_str; + search_len = search_str ? strlen(search_str) : 0; + prepend_str = replace[i].prepend_str; + prepend_len = prepend_str ? strlen(prepend_str) : 0; + } } if (!needs_fixup) return NULL; @@ -1322,12 +1368,13 @@ static char *fixup_shader( GLsizei count, const GLchar *const*string, const GLin FIXME( "HACK: Fixing up shader.\n" ); TRACE( "Appending extension string.\n" ); - new_len = strlen( *string ) + sizeof(prepend_str) - 1 + sizeof(add_ext); + new_len = strlen( *string ) + prepend_len + add_ext_len + 1; new = out = malloc( new_len ); - memcpy( out, add_ext, sizeof(add_ext) - 1 ); - out += sizeof(add_ext) - 1; + memcpy( out, add_ext, add_ext_len ); + out += add_ext_len; + + if (!search_str) goto skip_search; - search_len = sizeof(search_str) - 1; next = *string; while (*(p = next)) { @@ -1338,8 +1385,8 @@ static char *fixup_shader( GLsizei count, const GLchar *const*string, const GLin TRACE( "Adding explicit location.\n" ); memcpy( out, *string, p - *string ); out += p - *string; - memcpy( out, prepend_str, sizeof(prepend_str) - 1 ); - out += sizeof(prepend_str) - 1; + memcpy( out, prepend_str, prepend_len ); + out += prepend_len; strcpy( out, p ); found = TRUE; break; @@ -1347,6 +1394,8 @@ static char *fixup_shader( GLsizei count, const GLchar *const*string, const GLin while (*next == '\n' || *next == '\r') ++next; } + +skip_search: if (!found) strcpy( out, *string ); diff --git a/dlls/sapi/Makefile.in b/dlls/sapi/Makefile.in index 008d0f74545..a6c4d86037e 100644 --- a/dlls/sapi/Makefile.in +++ b/dlls/sapi/Makefile.in @@ -1,10 +1,11 @@ MODULE = sapi.dll -IMPORTS = uuid ole32 user32 advapi32 +IMPORTS = uuid ole32 oleaut32 user32 advapi32 DELAYIMPORTS = winmm SOURCES = \ async.c \ automation.c \ + dispatch.c \ main.c \ mmaudio.c \ resource.c \ diff --git a/dlls/sapi/dispatch.c b/dlls/sapi/dispatch.c new file mode 100644 index 00000000000..c1f0e842788 --- /dev/null +++ b/dlls/sapi/dispatch.c @@ -0,0 +1,87 @@ +/* + * ITypeInfo cache for IDispatch + * + * Copyright 2019 Zebediah Figura + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "wine/debug.h" + +#define COBJMACROS + +#include "objbase.h" +#include "sapiddk.h" + +#include "sapi_private.h" + +WINE_DEFAULT_DEBUG_CHANNEL(sapi); + +static ITypeLib *typelib; +static ITypeInfo *typeinfos[last_tid]; + +static REFIID tid_id[] = +{ + &IID_ISpeechObjectToken, + &IID_ISpeechObjectTokens, + &IID_ISpeechVoice, +}; + +HRESULT get_typeinfo(enum type_id tid, ITypeInfo **ret) +{ + HRESULT hr; + + if (!typelib) + { + ITypeLib *tl; + + hr = LoadRegTypeLib(&LIBID_SpeechLib, 5, 4, LOCALE_SYSTEM_DEFAULT, &tl); + if (FAILED(hr)) + { + ERR("Failed to load typelib, hr %#lx.\n", hr); + return hr; + } + if (InterlockedCompareExchangePointer((void **)&typelib, tl, NULL)) + ITypeLib_Release(tl); + } + if (!typeinfos[tid]) + { + ITypeInfo *typeinfo; + + hr = ITypeLib_GetTypeInfoOfGuid(typelib, tid_id[tid], &typeinfo); + if (FAILED(hr)) + { + ERR("Failed to get type info for %s, hr %#lx.\n", debugstr_guid(tid_id[tid]), hr); + return hr; + } + if (InterlockedCompareExchangePointer((void **)(typeinfos + tid), typeinfo, NULL)) + ITypeInfo_Release(typeinfo); + } + ITypeInfo_AddRef(*ret = typeinfos[tid]); + return S_OK; +} + +void release_typelib(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(typeinfos); ++i) + { + if (typeinfos[i]) + ITypeInfo_Release(typeinfos[i]); + } + if (typelib) + ITypeLib_Release(typelib); +} diff --git a/dlls/sapi/main.c b/dlls/sapi/main.c index 108db7d13d8..8adcc7449fb 100644 --- a/dlls/sapi/main.c +++ b/dlls/sapi/main.c @@ -145,3 +145,21 @@ HRESULT WINAPI DllGetClassObject( REFCLSID clsid, REFIID iid, void **obj ) return IClassFactory_QueryInterface( cf, iid, obj ); } + +/****************************************************************** + * DllMain + */ +BOOL WINAPI DllMain( HINSTANCE hinst, DWORD reason, void *reserved ) +{ + switch (reason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls( hinst ); + break; + case DLL_PROCESS_DETACH: + if (reserved) break; + release_typelib(); + break; + } + return TRUE; +} diff --git a/dlls/sapi/sapi_private.h b/dlls/sapi/sapi_private.h index f6a5e966461..219436f88c1 100644 --- a/dlls/sapi/sapi_private.h +++ b/dlls/sapi/sapi_private.h @@ -52,3 +52,14 @@ HRESULT mmaudio_out_create( IUnknown *outer, REFIID iid, void **obj ); HRESULT token_category_create( IUnknown *outer, REFIID iid, void **obj ); HRESULT token_enum_create( IUnknown *outer, REFIID iid, void **obj ); HRESULT token_create( IUnknown *outer, REFIID iid, void **obj ); + +enum type_id +{ + ISpeechObjectToken_tid, + ISpeechObjectTokens_tid, + ISpeechVoice_tid, + last_tid +}; + +HRESULT get_typeinfo( enum type_id tid, ITypeInfo **typeinfo ); +void release_typelib( void ); diff --git a/dlls/sapi/tests/Makefile.in b/dlls/sapi/tests/Makefile.in index 4c107d5e994..4e792f2daae 100644 --- a/dlls/sapi/tests/Makefile.in +++ b/dlls/sapi/tests/Makefile.in @@ -1,5 +1,5 @@ TESTDLL = sapi.dll -IMPORTS = ole32 user32 advapi32 winmm +IMPORTS = ole32 oleaut32 user32 advapi32 winmm SOURCES = \ automation.c \ diff --git a/dlls/sapi/tests/token.c b/dlls/sapi/tests/token.c index a7d164389d9..c7067718c45 100644 --- a/dlls/sapi/tests/token.c +++ b/dlls/sapi/tests/token.c @@ -26,6 +26,8 @@ #include "wine/test.h" +DEFINE_GUID(GUID_NULL,0,0,0,0,0,0,0,0,0,0,0); + static void test_data_key(void) { ISpRegDataKey *data_key; @@ -203,6 +205,16 @@ static void test_token_category(void) IEnumSpObjectTokens_Release( enum_tokens ); + hr = ISpObjectTokenCategory_EnumTokens( cat, L"", NULL, &enum_tokens ); + ok( hr == S_OK, "got %08lx\n", hr ); + + count = 0xdeadbeef; + hr = IEnumSpObjectTokens_GetCount( enum_tokens, &count ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( count == 5, "got %lu\n", count ); + + IEnumSpObjectTokens_Release( enum_tokens ); + hr = ISpObjectTokenCategory_EnumTokens( cat, L"Language=409", NULL, &enum_tokens ); ok( hr == S_OK, "got %08lx\n", hr ); @@ -246,12 +258,46 @@ static void test_token_enum(void) { ISpObjectTokenEnumBuilder *token_enum; HRESULT hr; + IDispatch *disp; + ISpeechObjectTokens *speech_tokens; + IUnknown *unk; + IEnumVARIANT *enumvar; ISpObjectToken *tokens[5]; ISpObjectToken *out_tokens[5]; WCHAR token_id[MAX_PATH]; ULONG count; + VARIANT vars[3], ret; + DISPPARAMS params; int i; + hr = CoCreateInstance( &CLSID_SpObjectTokenEnum, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectTokenEnumBuilder, (void **)&token_enum ); + ok( hr == S_OK, "got %08lx\n", hr ); + + hr = ISpObjectTokenEnumBuilder_QueryInterface( token_enum, + &IID_IDispatch, (void **)&disp ); + ok( hr == S_OK, "got %08lx\n", hr ); + IDispatch_Release( disp ); + + hr = ISpObjectTokenEnumBuilder_QueryInterface( token_enum, + &IID_ISpeechObjectTokens, + (void **)&speech_tokens ); + ok( hr == S_OK, "got %08lx\n", hr ); + ISpeechObjectTokens_Release( speech_tokens ); + + ISpObjectTokenEnumBuilder_Release( token_enum ); + + hr = CoCreateInstance( &CLSID_SpObjectTokenEnum, NULL, CLSCTX_INPROC_SERVER, + &IID_IDispatch, (void **)&disp ); + ok( hr == S_OK, "got %08lx\n", hr ); + IDispatch_Release( disp ); + + hr = CoCreateInstance( &CLSID_SpObjectTokenEnum, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpeechObjectTokens, (void **)&speech_tokens ); + ok( hr == S_OK, "got %08lx\n", hr ); + ISpeechObjectTokens_Release( speech_tokens ); + + hr = CoCreateInstance( &CLSID_SpObjectTokenEnum, NULL, CLSCTX_INPROC_SERVER, &IID_ISpObjectTokenEnumBuilder, (void **)&token_enum ); ok( hr == S_OK, "got %08lx\n", hr ); @@ -318,6 +364,70 @@ static void test_token_enum(void) &IID_ISpObjectTokenEnumBuilder, (void **)&token_enum ); ok( hr == S_OK, "got %08lx\n", hr ); + hr = ISpObjectTokenEnumBuilder_QueryInterface( token_enum, + &IID_ISpeechObjectTokens, + (void **)&speech_tokens ); + ok( hr == S_OK, "got %08lx\n", hr ); + + hr = ISpObjectTokenEnumBuilder_SetAttribs( token_enum, NULL, NULL ); + ok( hr == S_OK, "got %08lx\n", hr ); + hr = ISpObjectTokenEnumBuilder_AddTokens( token_enum, 3, tokens ); + ok( hr == S_OK, "got %08lx\n", hr ); + + count = 0xdeadbeef; + hr = ISpeechObjectTokens_get_Count( speech_tokens, (LONG *)&count ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( count == 3, "got %lu\n", count ); + + hr = ISpeechObjectTokens_get__NewEnum( speech_tokens, &unk ); + ok( hr == S_OK, "got %08lx\n", hr ); + hr = IUnknown_QueryInterface( unk, &IID_IEnumVARIANT, (void **)&enumvar ); + ok( hr == S_OK, "got %08lx\n", hr ); + IUnknown_Release( unk ); + + V_VT( &vars[0] ) = VT_ILLEGAL; + V_DISPATCH( &vars[0] ) = (IDispatch *)0xdeadbeef; + hr = IEnumVARIANT_Next( enumvar, 1, vars, NULL ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( V_VT( &vars[0] ) == VT_DISPATCH, "got %#x\n", V_VT( &vars[0] ) ); + ok( V_DISPATCH( &vars[0] ) != (IDispatch *)0xdeadbeef && V_DISPATCH( &vars[0] ) != NULL, + "got %p\n", V_DISPATCH( &vars[0] ) ); + VariantClear( &vars[0] ); + + for ( i = 0; i < 3; i++ ) { + V_VT( &vars[i] ) = VT_ILLEGAL; + V_DISPATCH( &vars[i] ) = (IDispatch *)0xdeadbeef; + } + count = 0xdeadbeef; + + hr = IEnumVARIANT_Next( enumvar, 3, vars, &count ); + ok( hr == S_FALSE, "got %08lx\n", hr ); + ok( count == 2, "got %lu\n", count ); + for ( i = 0; i < 2; i++ ) { + ok( V_VT( &vars[i] ) == VT_DISPATCH, "got %#x\n", V_VT( &vars[i] ) ); + ok( V_DISPATCH( &vars[i] ) != (IDispatch *)0xdeadbeef && V_DISPATCH( &vars[i] ) != NULL, + "got %p\n", V_DISPATCH( &vars[i] ) ); + VariantClear( &vars[i] ); + } + ok( V_VT( &vars[2] ) == VT_ILLEGAL, "got %#x\n", V_VT( &vars[2] ) ); + + IEnumVARIANT_Release( enumvar ); + + memset( ¶ms, 0, sizeof(params) ); + VariantInit( &ret ); + hr = ISpeechObjectTokens_Invoke( speech_tokens, DISPID_SOTsCount, &IID_NULL, 0, + DISPATCH_PROPERTYGET, ¶ms, &ret, NULL, NULL ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( V_VT( &ret ) == VT_I4, "got %#x\n", V_VT( &ret ) ); + ok( V_I4( &ret ) == 3, "got %ld\n", V_I4( &ret ) ); + + ISpeechObjectTokens_Release( speech_tokens ); + ISpObjectTokenEnumBuilder_Release( token_enum ); + + hr = CoCreateInstance( &CLSID_SpObjectTokenEnum, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectTokenEnumBuilder, (void **)&token_enum ); + ok( hr == S_OK, "got %08lx\n", hr ); + /* Vendor attribute must exist */ hr = ISpObjectTokenEnumBuilder_SetAttribs( token_enum, L"Vendor", NULL ); ok( hr == S_OK, "got %08lx\n", hr ); @@ -601,15 +711,47 @@ static IClassFactory test_class_cf = { &ClassFactoryVtbl }; static void test_object_token(void) { - static const WCHAR test_token_id[] = L"HKEY_LOCAL_MACHINE\\Software\\Wine\\Winetest\\sapi\\TestToken"; + static const WCHAR test_token_id[] = L"HKEY_LOCAL_MACHINE\\Software\\Winetest\\sapi\\TestToken"; + static const WCHAR *get_description = L"GetDescription"; ISpObjectToken *token; + IDispatch *disp; + ISpeechObjectToken *speech_token; ISpDataKey *sub_key; HRESULT hr; LPWSTR tempW, token_id; + BSTR tempB; ISpObjectTokenCategory *cat; DWORD regid; IUnknown *obj; + DISPID dispid; + DISPPARAMS params; + VARIANT arg, ret; + + hr = CoCreateInstance( &CLSID_SpObjectToken, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectToken, (void **)&token ); + ok( hr == S_OK, "got %08lx\n", hr ); + + hr = ISpObjectToken_QueryInterface( token, &IID_IDispatch, (void **)&disp ); + ok( hr == S_OK, "got %08lx\n", hr ); + IDispatch_Release( disp ); + + hr = ISpObjectToken_QueryInterface( token, &IID_ISpeechObjectToken, (void **)&speech_token ); + ok( hr == S_OK, "got %08lx\n", hr ); + ISpeechObjectToken_Release( speech_token ); + + ISpObjectToken_Release( token ); + + hr = CoCreateInstance( &CLSID_SpObjectToken, NULL, CLSCTX_INPROC_SERVER, + &IID_IDispatch, (void **)&disp ); + ok( hr == S_OK, "got %08lx\n", hr ); + IDispatch_Release( disp ); + + hr = CoCreateInstance( &CLSID_SpObjectToken, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpeechObjectToken, (void **)&speech_token ); + ok( hr == S_OK, "got %08lx\n", hr ); + ISpeechObjectToken_Release( speech_token ); + hr = CoCreateInstance( &CLSID_SpObjectToken, NULL, CLSCTX_INPROC_SERVER, &IID_ISpObjectToken, (void **)&token ); @@ -780,11 +922,85 @@ static void test_object_token(void) CoTaskMemFree( tempW ); } + hr = ISpObjectToken_SetStringValue( token, L"409", L"409 - TestToken" ); + ok( hr == S_OK, "got %08lx\n", hr ); + hr = ISpObjectToken_SetStringValue( token, L"407", L"407 - TestToken" ); + ok( hr == S_OK, "got %08lx\n", hr ); + hr = ISpObjectToken_SetStringValue( token, L"E40C", L"E40C - TestToken" ); + ok( hr == S_OK, "got %08lx\n", hr ); + + hr = ISpObjectToken_QueryInterface( token, &IID_ISpeechObjectToken, (void **)&speech_token ); + ok( hr == S_OK, "got %08lx\n", hr ); + + hr = ISpeechObjectToken_GetDescription( speech_token, 0x409, NULL ); + ok( hr == E_POINTER, "got %08lx\n", hr ); + + tempB = NULL; + hr = ISpeechObjectToken_GetDescription( speech_token, 0x409, &tempB ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( tempB && !wcscmp( tempB, L"409 - TestToken" ), "got %s\n", wine_dbgstr_w( tempB ) ); + SysFreeString( tempB ); + + tempB = NULL; + hr = ISpeechObjectToken_GetDescription( speech_token, 0x10407, &tempB ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( tempB && !wcscmp( tempB, L"407 - TestToken" ), "got %s\n", wine_dbgstr_w( tempB ) ); + SysFreeString( tempB ); + + tempB = NULL; + hr = ISpeechObjectToken_GetDescription( speech_token, 0xE40C, &tempB ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( tempB && !wcscmp( tempB, L"E40C - TestToken" ), "got %s\n", wine_dbgstr_w( tempB ) ); + SysFreeString( tempB ); + + tempB = (BSTR)0xdeadbeef; + hr = ISpeechObjectToken_GetDescription( speech_token, 0x406, &tempB ); + ok( hr == SPERR_NOT_FOUND, "got %08lx\n", hr ); + ok( tempB == (BSTR)0xdeadbeef || broken(tempB == NULL) /* < win7 */, "got %p\n", tempB ); + + hr = ISpObjectToken_SetStringValue( token, NULL, L"TestToken" ); + ok( hr == S_OK, "got %08lx\n", hr ); + + tempB = NULL; + hr = ISpeechObjectToken_GetDescription( speech_token, 0x406, &tempB ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( tempB && !wcscmp( tempB, L"TestToken" ), "got %s\n", wine_dbgstr_w( tempB ) ); + SysFreeString( tempB ); + + tempB = NULL; + hr = ISpeechObjectToken_GetDescription( speech_token, 0x0, &tempB ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( tempB && !wcscmp( tempB, L"TestToken" ), "got %s\n", wine_dbgstr_w( tempB ) ); + SysFreeString( tempB ); + + dispid = 0xdeadbeef; + hr = ISpeechObjectToken_GetIDsOfNames( speech_token, &IID_NULL, (WCHAR **)&get_description, 1, 0x409, &dispid ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( dispid == DISPID_SOTGetDescription, "got %08lx\n", dispid ); + + memset( ¶ms, 0, sizeof(params) ); + params.cArgs = 1; + params.cNamedArgs = 0; + params.rgvarg = &arg; + VariantInit( &arg ); + V_VT( &arg ) = VT_I4; + V_I4( &arg ) = 0x409; + VariantInit( &ret ); + hr = ISpeechObjectToken_Invoke( speech_token, DISPID_SOTGetDescription, &IID_NULL, + 0, DISPATCH_METHOD, ¶ms, &ret, NULL, NULL ); + ok( hr == S_OK, "got %08lx\n", hr ); + ok( V_VT( &ret ) == VT_BSTR, "got %#x\n", V_VT( &ret ) ); + ok( V_BSTR( &ret ) && !wcscmp( V_BSTR( &ret ), L"409 - TestToken" ), + "got %s\n", wine_dbgstr_w( V_BSTR( &ret ) ) ); + VariantClear( &ret ); + + ISpeechObjectToken_Release( speech_token ); + ISpObjectToken_Release( test_class_token ); IUnknown_Release( obj ); ISpObjectToken_Release( token ); - RegDeleteTreeA( HKEY_LOCAL_MACHINE, "Software\\Wine\\Winetest\\sapi" ); + RegDeleteTreeA( HKEY_LOCAL_MACHINE, "Software\\Winetest\\sapi" ); } START_TEST(token) diff --git a/dlls/sapi/tests/tts.c b/dlls/sapi/tests/tts.c index 38ec1d31bfc..38c69e6144a 100644 --- a/dlls/sapi/tests/tts.c +++ b/dlls/sapi/tests/tts.c @@ -416,14 +416,15 @@ static IClassFactory test_engine_cf = { &ClassFactoryVtbl }; static void test_spvoice(void) { - static const WCHAR test_token_id[] = L"HKEY_LOCAL_MACHINE\\Software\\Wine\\Winetest\\sapi\\tts\\TestEngine"; + static const WCHAR test_token_id[] = L"HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Speech\\Voices\\Tokens\\WinetestVoice"; static const WCHAR test_text[] = L"Hello! This is a test sentence."; + static const WCHAR *get_voices = L"GetVoices"; ISpVoice *voice; IUnknown *dummy; ISpMMSysAudio *audio_out; ISpObjectTokenCategory *token_cat; - ISpObjectToken *token; + ISpObjectToken *token, *token2; WCHAR *token_id = NULL, *default_token_id = NULL; ISpDataKey *attrs_key; LONG rate; @@ -431,6 +432,17 @@ static void test_spvoice(void) ULONG stream_num; DWORD regid; DWORD start, duration; + ISpeechVoice *speech_voice; + ISpeechObjectTokens *speech_tokens; + LONG count, volume_long; + ISpeechObjectToken *speech_token; + BSTR req = NULL, opt = NULL; + UINT info_count; + ITypeInfo *typeinfo; + TYPEATTR *typeattr; + DISPID dispid; + DISPPARAMS params; + VARIANT args[2], ret; HRESULT hr; if (waveOutGetNumDevs() == 0) { @@ -438,6 +450,8 @@ static void test_spvoice(void) return; } + RegDeleteTreeA(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Speech\\Voices\\WinetestVoice"); + check_apttype(); ok(test_apt_data.type == APTTYPE_UNITIALIZED, "got apt type %d.\n", test_apt_data.type); @@ -587,6 +601,7 @@ static void test_spvoice(void) hr = ISpObjectToken_CreateKey(token, L"Attributes", &attrs_key); ok(hr == S_OK, "got %#lx.\n", hr); ISpDataKey_SetStringValue(attrs_key, L"Language", L"409"); + ISpDataKey_SetStringValue(attrs_key, L"Vendor", L"Winetest"); ISpDataKey_Release(attrs_key); hr = ISpVoice_SetVoice(voice, token); @@ -681,13 +696,112 @@ static void test_spvoice(void) ok(hr == S_OK, "got %#lx.\n", hr); ok(duration < 300, "took %lu ms.\n", duration); + hr = ISpVoice_QueryInterface(voice, &IID_ISpeechVoice, (void **)&speech_voice); + ok(hr == S_OK, "got %#lx.\n", hr); + + count = -1; + hr = ISpeechVoice_GetVoices(speech_voice, NULL, NULL, &speech_tokens); + ok(hr == S_OK, "got %#lx.\n", hr); + hr = ISpeechObjectTokens_get_Count(speech_tokens, &count); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(count > 0, "got %ld.\n", count); + ISpeechObjectTokens_Release(speech_tokens); + + req = SysAllocString(L"Vendor=Winetest"); + opt = SysAllocString(L"Language=409;Gender=Male"); + + count = 0xdeadbeef; + hr = ISpeechVoice_GetVoices(speech_voice, req, opt, &speech_tokens); + ok(hr == S_OK, "got %#lx.\n", hr); + hr = ISpeechObjectTokens_get_Count(speech_tokens, &count); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(count == 1, "got %ld.\n", count); + ISpeechObjectTokens_Release(speech_tokens); + + volume_long = 0xdeadbeef; + hr = ISpeechVoice_put_Volume(speech_voice, 80); + ok(hr == S_OK, "got %#lx.\n", hr); + hr = ISpeechVoice_get_Volume(speech_voice, &volume_long); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(volume_long == 80, "got %ld.\n", volume_long); + + hr = ISpObjectToken_QueryInterface(token, &IID_ISpeechObjectToken, (void **)&speech_token); + ok(hr == S_OK, "got %#lx.\n", hr); + hr = ISpeechVoice_putref_Voice(speech_voice, speech_token); + ok(hr == S_OK, "got %#lx.\n", hr); + ISpeechObjectToken_Release(speech_token); + + speech_token = (ISpeechObjectToken *)0xdeadbeef; + hr = ISpeechVoice_get_Voice(speech_voice, &speech_token); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(speech_token && speech_token != (ISpeechObjectToken *)0xdeadbeef, "got %p.\n", speech_token); + hr = ISpeechObjectToken_QueryInterface(speech_token, &IID_ISpObjectToken, (void **)&token2); + ok(hr == S_OK, "got %#lx.\n", hr); + token_id = NULL; + hr = ISpObjectToken_GetId(token2, &token_id); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(!wcscmp(token_id, test_token_id), "got %s.\n", wine_dbgstr_w(token_id)); + CoTaskMemFree(token_id); + ISpObjectToken_Release(token2); + + hr = ISpeechVoice_Speak(speech_voice, NULL, SVSFPurgeBeforeSpeak, NULL); + ok(hr == S_OK, "got %#lx.\n", hr); + + info_count = 0xdeadbeef; + hr = ISpeechVoice_GetTypeInfoCount(speech_voice, &info_count); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(info_count == 1, "got %u.\n", info_count); + + typeinfo = NULL; + typeattr = NULL; + hr = ISpeechVoice_GetTypeInfo(speech_voice, 0, 0, &typeinfo); + ok(hr == S_OK, "got %#lx.\n", hr); + hr = ITypeInfo_GetTypeAttr(typeinfo, &typeattr); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(typeattr->typekind == TKIND_DISPATCH, "got %u.\n", typeattr->typekind); + ok(IsEqualGUID(&typeattr->guid, &IID_ISpeechVoice), "got %s.\n", wine_dbgstr_guid(&typeattr->guid)); + ITypeInfo_ReleaseTypeAttr(typeinfo, typeattr); + ITypeInfo_Release(typeinfo); + + dispid = 0xdeadbeef; + hr = ISpeechVoice_GetIDsOfNames(speech_voice, &IID_NULL, (WCHAR **)&get_voices, 1, 0x409, &dispid); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(dispid == DISPID_SVGetVoices, "got %#lx.\n", dispid); + + memset(¶ms, 0, sizeof(params)); + params.cArgs = 2; + params.cNamedArgs = 0; + params.rgvarg = args; + VariantInit(&args[0]); + VariantInit(&args[1]); + V_VT(&args[0]) = VT_BSTR; + V_VT(&args[1]) = VT_BSTR; + V_BSTR(&args[0]) = opt; + V_BSTR(&args[1]) = req; + VariantInit(&ret); + hr = ISpeechVoice_Invoke(speech_voice, dispid, &IID_NULL, 0, DISPATCH_METHOD, ¶ms, &ret, NULL, NULL); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(V_VT(&ret) == VT_DISPATCH, "got %#x.\n", V_VT(&ret)); + hr = IDispatch_QueryInterface(V_DISPATCH(&ret), &IID_ISpeechObjectTokens, (void **)&speech_tokens); + ok(hr == S_OK, "got %#lx.\n", hr); + count = -1; + hr = ISpeechObjectTokens_get_Count(speech_tokens, &count); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(count == 1, "got %ld.\n", count); + ISpeechObjectTokens_Release(speech_tokens); + VariantClear(&ret); + + ISpeechVoice_Release(speech_voice); + done: reset_engine_params(&test_engine); ISpVoice_Release(voice); ISpObjectToken_Release(token); ISpMMSysAudio_Release(audio_out); + SysFreeString(req); + SysFreeString(opt); - RegDeleteTreeA( HKEY_LOCAL_MACHINE, "Software\\Wine\\Winetest\\sapi" ); + RegDeleteTreeA(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Speech\\Voices\\WinetestVoice"); } START_TEST(tts) diff --git a/dlls/sapi/token.c b/dlls/sapi/token.c index f599bdb6b14..65d35dc96ff 100644 --- a/dlls/sapi/token.c +++ b/dlls/sapi/token.c @@ -51,6 +51,7 @@ static struct data_key *impl_from_ISpRegDataKey( ISpRegDataKey *iface ) struct object_token { ISpObjectToken ISpObjectToken_iface; + ISpeechObjectToken ISpeechObjectToken_iface; LONG ref; ISpRegDataKey *data_key; @@ -62,6 +63,11 @@ static struct object_token *impl_from_ISpObjectToken( ISpObjectToken *iface ) return CONTAINING_RECORD( iface, struct object_token, ISpObjectToken_iface ); } +static struct object_token *impl_from_ISpeechObjectToken( ISpeechObjectToken *iface ) +{ + return CONTAINING_RECORD( iface, struct object_token, ISpeechObjectToken_iface ); +} + static HRESULT WINAPI data_key_QueryInterface( ISpRegDataKey *iface, REFIID iid, void **obj ) { struct data_key *This = impl_from_ISpRegDataKey( iface ); @@ -573,6 +579,7 @@ struct token_with_score struct token_enum { ISpObjectTokenEnumBuilder ISpObjectTokenEnumBuilder_iface; + ISpeechObjectTokens ISpeechObjectTokens_iface; LONG ref; BOOL init; @@ -587,6 +594,25 @@ static struct token_enum *impl_from_ISpObjectTokenEnumBuilder( ISpObjectTokenEnu return CONTAINING_RECORD( iface, struct token_enum, ISpObjectTokenEnumBuilder_iface ); } +static struct token_enum *impl_from_ISpeechObjectTokens( ISpeechObjectTokens *iface ) +{ + return CONTAINING_RECORD( iface, struct token_enum, ISpeechObjectTokens_iface ); +} + +struct enum_var +{ + IEnumVARIANT IEnumVARIANT_iface; + LONG ref; + + ISpObjectTokenEnumBuilder *token_enum; + ULONG index; +}; + +static struct enum_var *impl_from_IEnumVARIANT( IEnumVARIANT *iface ) +{ + return CONTAINING_RECORD( iface, struct enum_var, IEnumVARIANT_iface ); +} + static HRESULT WINAPI token_category_EnumTokens( ISpObjectTokenCategory *iface, LPCWSTR req, LPCWSTR opt, IEnumSpObjectTokens **enum_tokens ) @@ -759,15 +785,19 @@ static HRESULT WINAPI token_enum_QueryInterface( ISpObjectTokenEnumBuilder *ifac if (IsEqualIID( iid, &IID_IUnknown ) || IsEqualIID( iid, &IID_IEnumSpObjectTokens ) || IsEqualIID( iid, &IID_ISpObjectTokenEnumBuilder )) + *obj = &This->ISpObjectTokenEnumBuilder_iface; + else if (IsEqualIID( iid, &IID_IDispatch ) || + IsEqualIID( iid, &IID_ISpeechObjectTokens )) + *obj = &This->ISpeechObjectTokens_iface; + else { - ISpObjectTokenEnumBuilder_AddRef( iface ); - *obj = iface; - return S_OK; + *obj = NULL; + FIXME( "interface %s not implemented\n", debugstr_guid( iid ) ); + return E_NOINTERFACE; } - FIXME( "interface %s not implemented\n", debugstr_guid( iid ) ); - *obj = NULL; - return E_NOINTERFACE; + IUnknown_AddRef( (IUnknown *)*obj ); + return S_OK; } static ULONG WINAPI token_enum_AddRef( ISpObjectTokenEnumBuilder *iface ) @@ -917,7 +947,7 @@ static HRESULT score_attributes( ISpObjectToken *token, const WCHAR *attrs, unsigned int i, j; HRESULT hr; - if (!attrs) + if (!attrs || !*attrs) { *score = 1; return S_OK; @@ -1129,6 +1159,257 @@ const struct ISpObjectTokenEnumBuilderVtbl token_enum_vtbl = token_enum_Sort }; +static HRESULT WINAPI enum_var_QueryInterface( IEnumVARIANT *iface, + REFIID iid, void **obj ) +{ + struct enum_var *This = impl_from_IEnumVARIANT( iface ); + + TRACE( "(%p)->(%s %p)\n", This, debugstr_guid( iid ), obj ); + + if (IsEqualIID( iid, &IID_IUnknown ) || + IsEqualIID( iid, &IID_IEnumVARIANT )) + { + IEnumVARIANT_AddRef( iface ); + *obj = iface; + return S_OK; + } + + *obj = NULL; + FIXME( "interface %s not implemented\n", debugstr_guid( iid ) ); + return E_NOINTERFACE; +} + +static ULONG WINAPI enum_var_AddRef( IEnumVARIANT *iface ) +{ + struct enum_var *This = impl_from_IEnumVARIANT( iface ); + ULONG ref = InterlockedIncrement( &This->ref ); + + TRACE( "(%p) ref = %lu\n", This, ref ); + return ref; +} + +static ULONG WINAPI enum_var_Release( IEnumVARIANT *iface ) +{ + struct enum_var *This = impl_from_IEnumVARIANT( iface ); + ULONG ref = InterlockedDecrement( &This->ref ); + + TRACE( "(%p) ref = %lu\n", This, ref ); + + if (!ref) + { + ISpObjectTokenEnumBuilder_Release( This->token_enum ); + free( This ); + } + return ref; +} + +static HRESULT WINAPI enum_var_Next( IEnumVARIANT *iface, ULONG count, + VARIANT *vars, ULONG *fetched ) +{ + struct enum_var *This = impl_from_IEnumVARIANT( iface ); + ULONG i, total; + HRESULT hr; + + TRACE( "(%p)->(%lu %p %p)\n", This, count, vars, fetched ); + + if (fetched) *fetched = 0; + + if (FAILED(hr = ISpObjectTokenEnumBuilder_GetCount( This->token_enum, &total ))) + return hr; + + for ( i = 0; i < count && This->index < total; i++, This->index++ ) + { + ISpObjectToken *token; + IDispatch *disp; + + if (FAILED(hr = ISpObjectTokenEnumBuilder_Item( This->token_enum, This->index, &token ))) + goto fail; + + hr = ISpObjectToken_QueryInterface( token, &IID_IDispatch, (void **)&disp ); + ISpObjectToken_Release( token ); + if (FAILED(hr)) goto fail; + + VariantInit( &vars[i] ); + V_VT( &vars[i] ) = VT_DISPATCH; + V_DISPATCH( &vars[i] ) = disp; + } + + if (fetched) *fetched = i; + return i == count ? S_OK : S_FALSE; + +fail: + while (i--) + VariantClear( &vars[i] ); + return hr; +} + +static HRESULT WINAPI enum_var_Skip( IEnumVARIANT *iface, ULONG count ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI enum_var_Reset( IEnumVARIANT *iface ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI enum_var_Clone( IEnumVARIANT *iface, IEnumVARIANT **new_enum ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static const IEnumVARIANTVtbl enum_var_vtbl = +{ + enum_var_QueryInterface, + enum_var_AddRef, + enum_var_Release, + enum_var_Next, + enum_var_Skip, + enum_var_Reset, + enum_var_Clone +}; + +static HRESULT WINAPI speech_tokens_QueryInterface( ISpeechObjectTokens *iface, + REFIID iid, void **obj ) +{ + struct token_enum *This = impl_from_ISpeechObjectTokens( iface ); + + TRACE( "(%p)->(%s %p)\n", This, debugstr_guid( iid ), obj ); + + return ISpObjectTokenEnumBuilder_QueryInterface( + &This->ISpObjectTokenEnumBuilder_iface, iid, obj ); +} + +static ULONG WINAPI speech_tokens_AddRef( ISpeechObjectTokens *iface ) +{ + struct token_enum *This = impl_from_ISpeechObjectTokens( iface ); + + TRACE( "(%p)\n", This ); + + return ISpObjectTokenEnumBuilder_AddRef( &This->ISpObjectTokenEnumBuilder_iface ); +} + +static ULONG WINAPI speech_tokens_Release( ISpeechObjectTokens *iface ) +{ + struct token_enum *This = impl_from_ISpeechObjectTokens( iface ); + + TRACE( "(%p)\n", This ); + + return ISpObjectTokenEnumBuilder_Release( &This->ISpObjectTokenEnumBuilder_iface ); +} + +static HRESULT WINAPI speech_tokens_GetTypeInfoCount( ISpeechObjectTokens *iface, + UINT *count ) +{ + + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI speech_tokens_GetTypeInfo( ISpeechObjectTokens *iface, + UINT index, + LCID lcid, + ITypeInfo **type_info ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI speech_tokens_GetIDsOfNames( ISpeechObjectTokens *iface, + REFIID iid, + LPOLESTR *names, + UINT count, + LCID lcid, + DISPID *dispids ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI speech_tokens_Invoke( ISpeechObjectTokens *iface, + DISPID dispid, + REFIID iid, + LCID lcid, + WORD flags, + DISPPARAMS *params, + VARIANT *result, + EXCEPINFO *excepinfo, + UINT *argerr ) +{ + ITypeInfo *ti; + HRESULT hr; + + TRACE( "(%p)->(%ld %s %#lx %#x %p %p %p %p)\n", iface, dispid, + debugstr_guid( iid ), lcid, flags, params, result, excepinfo, argerr ); + + if (FAILED(hr = get_typeinfo( ISpeechObjectTokens_tid, &ti ))) + return hr; + hr = ITypeInfo_Invoke( ti, iface, dispid, flags, params, result, excepinfo, argerr ); + ITypeInfo_Release( ti ); + + return hr; +} + +static HRESULT WINAPI speech_tokens_get_Count( ISpeechObjectTokens *iface, + LONG *count ) +{ + struct token_enum *This = impl_from_ISpeechObjectTokens( iface ); + + TRACE( "(%p)->(%p)\n", This, count ); + + return ISpObjectTokenEnumBuilder_GetCount( &This->ISpObjectTokenEnumBuilder_iface, (ULONG *)count ); +} + +static HRESULT WINAPI speech_tokens_Item( ISpeechObjectTokens *iface, + LONG index, ISpeechObjectToken **token ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI speech_tokens_get__NewEnum( ISpeechObjectTokens *iface, + IUnknown **new_enum ) +{ + struct enum_var *enum_var; + HRESULT hr; + + TRACE( "(%p)->(%p)\n", iface, new_enum ); + + if (!new_enum) return E_POINTER; + if (!(enum_var = malloc( sizeof(*enum_var) ))) return E_OUTOFMEMORY; + + enum_var->IEnumVARIANT_iface.lpVtbl = &enum_var_vtbl; + enum_var->ref = 1; + enum_var->index = 0; + if (FAILED(hr = ISpeechObjectTokens_QueryInterface( iface, &IID_ISpObjectTokenEnumBuilder, + (void **)&enum_var->token_enum ))) + { + free( enum_var ); + return hr; + } + + *new_enum = (IUnknown *)&enum_var->IEnumVARIANT_iface; + + return S_OK; +} + +static const ISpeechObjectTokensVtbl speech_tokens_vtbl = +{ + speech_tokens_QueryInterface, + speech_tokens_AddRef, + speech_tokens_Release, + speech_tokens_GetTypeInfoCount, + speech_tokens_GetTypeInfo, + speech_tokens_GetIDsOfNames, + speech_tokens_Invoke, + speech_tokens_get_Count, + speech_tokens_Item, + speech_tokens_get__NewEnum +}; + HRESULT token_enum_create( IUnknown *outer, REFIID iid, void **obj ) { struct token_enum *This = malloc( sizeof(*This) ); @@ -1136,6 +1417,7 @@ HRESULT token_enum_create( IUnknown *outer, REFIID iid, void **obj ) if (!This) return E_OUTOFMEMORY; This->ISpObjectTokenEnumBuilder_iface.lpVtbl = &token_enum_vtbl; + This->ISpeechObjectTokens_iface.lpVtbl = &speech_tokens_vtbl; This->ref = 1; This->req = NULL; This->opt = NULL; @@ -1160,15 +1442,19 @@ static HRESULT WINAPI token_QueryInterface( ISpObjectToken *iface, if (IsEqualIID( iid, &IID_IUnknown ) || IsEqualIID( iid, &IID_ISpDataKey ) || IsEqualIID( iid, &IID_ISpObjectToken )) + *obj = &This->ISpObjectToken_iface; + else if (IsEqualIID( iid, &IID_IDispatch ) || + IsEqualIID( iid, &IID_ISpeechObjectToken )) + *obj = &This->ISpeechObjectToken_iface; + else { - ISpObjectToken_AddRef( iface ); - *obj = iface; - return S_OK; + *obj = NULL; + FIXME( "interface %s not implemented\n", debugstr_guid( iid ) ); + return E_NOINTERFACE; } - FIXME( "interface %s not implemented\n", debugstr_guid( iid ) ); - *obj = NULL; - return E_NOINTERFACE; + IUnknown_AddRef( (IUnknown *)*obj ); + return S_OK; } static ULONG WINAPI token_AddRef( ISpObjectToken *iface ) @@ -1495,6 +1781,247 @@ const struct ISpObjectTokenVtbl token_vtbl = token_MatchesAttributes }; +static HRESULT WINAPI speech_token_QueryInterface( ISpeechObjectToken *iface, + REFIID iid, void **obj ) +{ + struct object_token *This = impl_from_ISpeechObjectToken( iface ); + + TRACE( "(%p)->(%s %p)\n", This, debugstr_guid( iid ), obj ); + + return ISpObjectToken_QueryInterface( &This->ISpObjectToken_iface, iid, obj ); +} + +static ULONG WINAPI speech_token_AddRef( ISpeechObjectToken *iface ) +{ + struct object_token *This = impl_from_ISpeechObjectToken( iface ); + + TRACE( "(%p)\n", This ); + + return ISpObjectToken_AddRef( &This->ISpObjectToken_iface ); +} + +static ULONG WINAPI speech_token_Release( ISpeechObjectToken *iface ) +{ + struct object_token *This = impl_from_ISpeechObjectToken( iface ); + + TRACE( "(%p)\n", This ); + + return ISpObjectToken_Release( &This->ISpObjectToken_iface ); +} + +static HRESULT WINAPI speech_token_GetTypeInfoCount( ISpeechObjectToken *iface, + UINT *count ) +{ + + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI speech_token_GetTypeInfo( ISpeechObjectToken *iface, + UINT index, + LCID lcid, + ITypeInfo **type_info ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI speech_token_GetIDsOfNames( ISpeechObjectToken *iface, + REFIID iid, + LPOLESTR *names, + UINT count, + LCID lcid, + DISPID *dispids ) +{ + ITypeInfo *ti; + HRESULT hr; + + TRACE( "(%p)->(%s %p %u %#lx %p)\n", + iface, debugstr_guid( iid ), names, count, lcid, dispids ); + + if (FAILED(hr = get_typeinfo( ISpeechObjectToken_tid, &ti ))) + return hr; + hr = ITypeInfo_GetIDsOfNames( ti, names, count, dispids ); + ITypeInfo_Release( ti ); + + return hr; +} + +static HRESULT WINAPI speech_token_Invoke( ISpeechObjectToken *iface, + DISPID dispid, + REFIID iid, + LCID lcid, + WORD flags, + DISPPARAMS *params, + VARIANT *result, + EXCEPINFO *excepinfo, + UINT *argerr ) +{ + ITypeInfo *ti; + HRESULT hr; + + TRACE( "(%p)->(%ld %s %#lx %#x %p %p %p %p)\n", iface, dispid, + debugstr_guid( iid ), lcid, flags, params, result, excepinfo, argerr ); + + if (FAILED(hr = get_typeinfo( ISpeechObjectToken_tid, &ti ))) + return hr; + hr = ITypeInfo_Invoke( ti, iface, dispid, flags, params, result, excepinfo, argerr ); + ITypeInfo_Release( ti ); + + return hr; +} + +static HRESULT WINAPI speech_token_get_Id( ISpeechObjectToken *iface, + BSTR *id ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI speech_token_get_DataKey( ISpeechObjectToken *iface, + ISpeechDataKey **key ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI speech_token_get_Category( ISpeechObjectToken *iface, + ISpeechObjectTokenCategory **cat ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI speech_token_GetDescription( ISpeechObjectToken *iface, + LONG locale, BSTR *desc ) +{ + struct object_token *This = impl_from_ISpeechObjectToken( iface ); + WCHAR langid[5]; + WCHAR *desc_wstr = NULL; + HRESULT hr; + + TRACE( "(%p)->(%#lx %p)\n", This, locale, desc ); + + if (!desc) return E_POINTER; + + swprintf( langid, ARRAY_SIZE( langid ), L"%X", LANGIDFROMLCID( locale ) ); + + hr = ISpObjectToken_GetStringValue( &This->ISpObjectToken_iface, langid, &desc_wstr ); + if (hr == SPERR_NOT_FOUND) + hr = ISpObjectToken_GetStringValue( &This->ISpObjectToken_iface, NULL, &desc_wstr ); + if (FAILED(hr)) + return hr; + + *desc = SysAllocString( desc_wstr ); + + CoTaskMemFree( desc_wstr ); + return *desc ? S_OK : E_OUTOFMEMORY; +} + +static HRESULT WINAPI speech_token_SetId( ISpeechObjectToken *iface, + BSTR id, BSTR category_id, + VARIANT_BOOL create ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI speech_token_GetAttribute( ISpeechObjectToken *iface, + BSTR name, BSTR *value ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI speech_token_CreateInstance( ISpeechObjectToken *iface, + IUnknown *outer, + SpeechTokenContext clsctx, + IUnknown **object ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI speech_token_Remove( ISpeechObjectToken *iface, + BSTR clsid ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI speech_token_GetStorageFileName( ISpeechObjectToken *iface, + BSTR clsid, + BSTR key, + BSTR name, + SpeechTokenShellFolder folder, + BSTR *path ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI speech_token_RemoveStorageFileName( ISpeechObjectToken *iface, + BSTR clsid, + BSTR key, + VARIANT_BOOL remove ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI speech_token_IsUISupported( ISpeechObjectToken *iface, + const BSTR type, + const VARIANT *data, + IUnknown *object, + VARIANT_BOOL *supported ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI speech_token_DisplayUI( ISpeechObjectToken *iface, + LONG hwnd, + BSTR title, + const BSTR type, + const VARIANT *data, + IUnknown *object ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +static HRESULT WINAPI speech_token_MatchesAttributes( ISpeechObjectToken *iface, + const BSTR attributes, + VARIANT_BOOL *matches ) +{ + FIXME( "stub\n" ); + return E_NOTIMPL; +} + +const struct ISpeechObjectTokenVtbl speech_token_vtbl = +{ + speech_token_QueryInterface, + speech_token_AddRef, + speech_token_Release, + speech_token_GetTypeInfoCount, + speech_token_GetTypeInfo, + speech_token_GetIDsOfNames, + speech_token_Invoke, + speech_token_get_Id, + speech_token_get_DataKey, + speech_token_get_Category, + speech_token_GetDescription, + speech_token_SetId, + speech_token_GetAttribute, + speech_token_CreateInstance, + speech_token_Remove, + speech_token_GetStorageFileName, + speech_token_RemoveStorageFileName, + speech_token_IsUISupported, + speech_token_DisplayUI, + speech_token_MatchesAttributes +}; + HRESULT token_create( IUnknown *outer, REFIID iid, void **obj ) { struct object_token *This = malloc( sizeof(*This) ); @@ -1502,6 +2029,7 @@ HRESULT token_create( IUnknown *outer, REFIID iid, void **obj ) if (!This) return E_OUTOFMEMORY; This->ISpObjectToken_iface.lpVtbl = &token_vtbl; + This->ISpeechObjectToken_iface.lpVtbl = &speech_token_vtbl; This->ref = 1; This->data_key = NULL; diff --git a/dlls/sapi/tts.c b/dlls/sapi/tts.c index 410a57b4bfc..c474a118981 100644 --- a/dlls/sapi/tts.c +++ b/dlls/sapi/tts.c @@ -43,6 +43,7 @@ struct speech_voice LONG ref; ISpStreamFormat *output; + ISpObjectToken *engine_token; ISpTTSEngine *engine; LONG cur_stream_num; DWORD actions; @@ -81,6 +82,15 @@ static inline struct tts_engine_site *impl_from_ISpTTSEngineSite(ISpTTSEngineSit return CONTAINING_RECORD(iface, struct tts_engine_site, ISpTTSEngineSite_iface); } +static HRESULT create_token_category(const WCHAR *cat_id, ISpObjectTokenCategory **cat) +{ + HRESULT hr; + if (FAILED(hr = CoCreateInstance(&CLSID_SpObjectTokenCategory, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectTokenCategory, (void **)cat))) + return hr; + return ISpObjectTokenCategory_SetId(*cat, cat_id, FALSE); +} + static HRESULT create_default_token(const WCHAR *cat_id, ISpObjectToken **token) { ISpObjectTokenCategory *cat; @@ -89,17 +99,13 @@ static HRESULT create_default_token(const WCHAR *cat_id, ISpObjectToken **token) TRACE("(%s, %p).\n", debugstr_w(cat_id), token); - if (FAILED(hr = CoCreateInstance(&CLSID_SpObjectTokenCategory, NULL, CLSCTX_INPROC_SERVER, - &IID_ISpObjectTokenCategory, (void **)&cat))) + if (FAILED(hr = create_token_category(cat_id, &cat))) return hr; - if (FAILED(hr = ISpObjectTokenCategory_SetId(cat, cat_id, FALSE)) || - FAILED(hr = ISpObjectTokenCategory_GetDefaultTokenId(cat, &default_token_id))) - { - ISpObjectTokenCategory_Release(cat); - return hr; - } + hr = ISpObjectTokenCategory_GetDefaultTokenId(cat, &default_token_id); ISpObjectTokenCategory_Release(cat); + if (FAILED(hr)) + return hr; if (FAILED(hr = CoCreateInstance(&CLSID_SpObjectToken, NULL, CLSCTX_INPROC_SERVER, &IID_ISpObjectToken, (void **)token))) @@ -163,6 +169,7 @@ static ULONG WINAPI speech_voice_Release(ISpeechVoice *iface) { async_cancel_queue(&This->queue); if (This->output) ISpStreamFormat_Release(This->output); + if (This->engine_token) ISpObjectToken_Release(This->engine_token); if (This->engine) ISpTTSEngine_Release(This->engine); DeleteCriticalSection(&This->cs); @@ -172,37 +179,51 @@ static ULONG WINAPI speech_voice_Release(ISpeechVoice *iface) return ref; } -static HRESULT WINAPI speech_voice_GetTypeInfoCount(ISpeechVoice *iface, UINT *info) +static HRESULT WINAPI speech_voice_GetTypeInfoCount(ISpeechVoice *iface, UINT *count) { - FIXME("(%p, %p): stub.\n", iface, info); - - return E_NOTIMPL; + TRACE("(%p, %p).\n", iface, count); + *count = 1; + return S_OK; } -static HRESULT WINAPI speech_voice_GetTypeInfo(ISpeechVoice *iface, UINT info, LCID lcid, +static HRESULT WINAPI speech_voice_GetTypeInfo(ISpeechVoice *iface, UINT index, LCID lcid, ITypeInfo **type_info) { - FIXME("(%p, %u, %lu, %p): stub.\n", iface, info, lcid, type_info); - - return E_NOTIMPL; + TRACE("(%p, %u, %#lx, %p).\n", iface, index, lcid, type_info); + if (index != 0) return DISP_E_BADINDEX; + return get_typeinfo(ISpeechVoice_tid, type_info); } static HRESULT WINAPI speech_voice_GetIDsOfNames(ISpeechVoice *iface, REFIID riid, LPOLESTR *names, - UINT count, LCID lcid, DISPID *dispid) + UINT count, LCID lcid, DISPID *dispids) { - FIXME("(%p, %s, %p, %u, %lu, %p): stub.\n", iface, debugstr_guid(riid), names, count, lcid, dispid); + ITypeInfo *typeinfo; + HRESULT hr; - return E_NOTIMPL; + TRACE("(%p, %s, %p, %u, %#lx, %p).\n", iface, debugstr_guid(riid), names, count, lcid, dispids); + + if (FAILED(hr = get_typeinfo(ISpeechVoice_tid, &typeinfo))) + return hr; + hr = ITypeInfo_GetIDsOfNames(typeinfo, names, count, dispids); + ITypeInfo_Release(typeinfo); + return hr; } static HRESULT WINAPI speech_voice_Invoke(ISpeechVoice *iface, DISPID dispid, REFIID riid, LCID lcid, WORD flags, DISPPARAMS *params, VARIANT *result, EXCEPINFO *excepinfo, UINT *argerr) { - FIXME("(%p, %ld, %s, %#lx, %#x, %p, %p, %p, %p): stub.\n", iface, dispid, debugstr_guid(riid), + ITypeInfo *typeinfo; + HRESULT hr; + + TRACE("(%p, %ld, %s, %#lx, %#x, %p, %p, %p, %p).\n", iface, dispid, debugstr_guid(riid), lcid, flags, params, result, excepinfo, argerr); - return E_NOTIMPL; + if (FAILED(hr = get_typeinfo(ISpeechVoice_tid, &typeinfo))) + return hr; + hr = ITypeInfo_Invoke(typeinfo, iface, dispid, flags, params, result, excepinfo, argerr); + ITypeInfo_Release(typeinfo); + return hr; } static HRESULT WINAPI speech_voice_get_Status(ISpeechVoice *iface, ISpeechVoiceStatus **status) @@ -214,16 +235,34 @@ static HRESULT WINAPI speech_voice_get_Status(ISpeechVoice *iface, ISpeechVoiceS static HRESULT WINAPI speech_voice_get_Voice(ISpeechVoice *iface, ISpeechObjectToken **voice) { - FIXME("(%p, %p): stub.\n", iface, voice); + struct speech_voice *This = impl_from_ISpeechVoice(iface); + ISpObjectToken *token; + HRESULT hr; - return E_NOTIMPL; + TRACE("(%p, %p).\n", iface, voice); + + if (!voice) return E_POINTER; + if (FAILED(hr = ISpVoice_GetVoice(&This->ISpVoice_iface, &token))) + return hr; + hr = ISpObjectToken_QueryInterface(token, &IID_ISpeechObjectToken, (void **)voice); + ISpObjectToken_Release(token); + return hr; } static HRESULT WINAPI speech_voice_putref_Voice(ISpeechVoice *iface, ISpeechObjectToken *voice) { - FIXME("(%p, %p): stub.\n", iface, voice); + struct speech_voice *This = impl_from_ISpeechVoice(iface); + ISpObjectToken *token; + HRESULT hr; - return E_NOTIMPL; + TRACE("(%p, %p).\n", iface, voice); + + if (!voice) return E_INVALIDARG; + if (FAILED(hr = ISpeechObjectToken_QueryInterface(voice, &IID_ISpObjectToken, (void **)&token))) + return hr; + hr = ISpVoice_SetVoice(&This->ISpVoice_iface, token); + ISpObjectToken_Release(token); + return hr; } static HRESULT WINAPI speech_voice_get_AudioOutput(ISpeechVoice *iface, ISpeechObjectToken **output) @@ -270,16 +309,25 @@ static HRESULT WINAPI speech_voice_put_Rate(ISpeechVoice *iface, LONG rate) static HRESULT WINAPI speech_voice_get_Volume(ISpeechVoice *iface, LONG *volume) { - FIXME("(%p, %p): stub.\n", iface, volume); + struct speech_voice *This = impl_from_ISpeechVoice(iface); + USHORT res = 0; + HRESULT hr; - return E_NOTIMPL; + TRACE("(%p, %p).\n", iface, volume); + + if (!volume) return E_POINTER; + hr = ISpVoice_GetVolume(&This->ISpVoice_iface, &res); + *volume = res; + return hr; } static HRESULT WINAPI speech_voice_put_Volume(ISpeechVoice *iface, LONG volume) { - FIXME("(%p, %ld): stub.\n", iface, volume); + struct speech_voice *This = impl_from_ISpeechVoice(iface); - return E_NOTIMPL; + TRACE("(%p, %ld).\n", iface, volume); + + return ISpVoice_SetVolume(&This->ISpVoice_iface, (USHORT)volume); } static HRESULT WINAPI speech_voice_put_AllowAudioOutputFormatChangesOnNextSet(ISpeechVoice *iface, @@ -355,9 +403,11 @@ static HRESULT WINAPI speech_voice_get_SynchronousSpeakTimeout(ISpeechVoice *ifa static HRESULT WINAPI speech_voice_Speak(ISpeechVoice *iface, BSTR text, SpeechVoiceSpeakFlags flags, LONG *number) { - FIXME("(%p, %s, %#x, %p): stub.\n", iface, debugstr_w(text), flags, number); + struct speech_voice *This = impl_from_ISpeechVoice(iface); - return E_NOTIMPL; + TRACE("(%p, %s, %#x, %p).\n", iface, debugstr_w(text), flags, number); + + return ISpVoice_Speak(&This->ISpVoice_iface, text, flags, (ULONG *)number); } static HRESULT WINAPI speech_voice_SpeakStream(ISpeechVoice *iface, ISpeechBaseStream *stream, @@ -392,9 +442,26 @@ static HRESULT WINAPI speech_voice_Skip(ISpeechVoice *iface, const BSTR type, LO static HRESULT WINAPI speech_voice_GetVoices(ISpeechVoice *iface, BSTR required, BSTR optional, ISpeechObjectTokens **tokens) { - FIXME("(%p, %s, %s, %p): stub.\n", iface, debugstr_w(required), debugstr_w(optional), tokens); - return E_NOTIMPL; + ISpObjectTokenCategory *cat; + IEnumSpObjectTokens *token_enum; + HRESULT hr; + + TRACE("(%p, %s, %s, %p).\n", iface, debugstr_w(required), debugstr_w(optional), tokens); + + if (!tokens) return E_POINTER; + + if (FAILED(hr = create_token_category(SPCAT_VOICES, &cat))) + return hr; + + if (SUCCEEDED(hr = ISpObjectTokenCategory_EnumTokens(cat, required, optional, &token_enum))) + { + hr = IEnumSpObjectTokens_QueryInterface(token_enum, &IID_ISpeechObjectTokens, (void **)tokens); + IEnumSpObjectTokens_Release(token_enum); + } + + ISpObjectTokenCategory_Release(cat); + return hr; } static HRESULT WINAPI speech_voice_GetAudioOutputs(ISpeechVoice *iface, BSTR required, BSTR optional, @@ -660,7 +727,7 @@ static HRESULT WINAPI spvoice_Resume(ISpVoice *iface) static HRESULT WINAPI spvoice_SetVoice(ISpVoice *iface, ISpObjectToken *token) { struct speech_voice *This = impl_from_ISpVoice(iface); - ISpTTSEngine *engine; + WCHAR *id = NULL, *old_id = NULL; HRESULT hr; TRACE("(%p, %p).\n", iface, token); @@ -673,27 +740,37 @@ static HRESULT WINAPI spvoice_SetVoice(ISpVoice *iface, ISpObjectToken *token) else ISpObjectToken_AddRef(token); - hr = ISpObjectToken_CreateInstance(token, NULL, CLSCTX_ALL, &IID_ISpTTSEngine, (void **)&engine); - ISpObjectToken_Release(token); - if (FAILED(hr)) - return hr; - EnterCriticalSection(&This->cs); + if (This->engine_token && + SUCCEEDED(ISpObjectToken_GetId(token, &id)) && + SUCCEEDED(ISpObjectToken_GetId(This->engine_token, &old_id)) && + !wcscmp(id, old_id)) + { + ISpObjectToken_Release(token); + goto done; + } + + if (This->engine_token) + ISpObjectToken_Release(This->engine_token); + This->engine_token = token; + if (This->engine) + { ISpTTSEngine_Release(This->engine); - This->engine = engine; + This->engine = NULL; + } +done: LeaveCriticalSection(&This->cs); - + CoTaskMemFree(id); + CoTaskMemFree(old_id); return S_OK; } static HRESULT WINAPI spvoice_GetVoice(ISpVoice *iface, ISpObjectToken **token) { struct speech_voice *This = impl_from_ISpVoice(iface); - ISpObjectWithToken *engine_token_iface; - HRESULT hr; TRACE("(%p, %p).\n", iface, token); @@ -702,21 +779,18 @@ static HRESULT WINAPI spvoice_GetVoice(ISpVoice *iface, ISpObjectToken **token) EnterCriticalSection(&This->cs); - if (!This->engine) + if (!This->engine_token) { LeaveCriticalSection(&This->cs); return create_default_token(SPCAT_VOICES, token); } - if (SUCCEEDED(hr = ISpTTSEngine_QueryInterface(This->engine, &IID_ISpObjectWithToken, (void **)&engine_token_iface))) - { - hr = ISpObjectWithToken_GetObjectToken(engine_token_iface, token); - ISpObjectWithToken_Release(engine_token_iface); - } + ISpObjectToken_AddRef(This->engine_token); + *token = This->engine_token; LeaveCriticalSection(&This->cs); - return hr; + return S_OK; } struct async_result @@ -731,6 +805,7 @@ struct speak_task struct async_result *result; struct speech_voice *voice; + ISpTTSEngine *engine; SPVTEXTFRAG *frag_list; ISpTTSEngineSite *site; DWORD flags; @@ -773,7 +848,6 @@ static void speak_proc(struct async_task *task) struct speech_voice *This = speak_task->voice; GUID fmtid; WAVEFORMATEX *wfx = NULL; - ISpTTSEngine *engine = NULL; ISpAudio *audio = NULL; HRESULT hr; @@ -794,8 +868,6 @@ static void speak_proc(struct async_task *task) ERR("failed setting output format: %#lx.\n", hr); goto done; } - engine = This->engine; - ISpTTSEngine_AddRef(engine); if (SUCCEEDED(ISpStreamFormat_QueryInterface(This->output, &IID_ISpAudio, (void **)&audio))) ISpAudio_SetState(audio, SPAS_RUN, 0); @@ -804,7 +876,7 @@ static void speak_proc(struct async_task *task) LeaveCriticalSection(&This->cs); - hr = ISpTTSEngine_Speak(engine, speak_task->flags, &fmtid, wfx, speak_task->frag_list, speak_task->site); + hr = ISpTTSEngine_Speak(speak_task->engine, speak_task->flags, &fmtid, wfx, speak_task->frag_list, speak_task->site); if (SUCCEEDED(hr)) { ISpStreamFormat_Commit(This->output, STGC_DEFAULT); @@ -821,7 +893,7 @@ static void speak_proc(struct async_task *task) ISpAudio_Release(audio); } CoTaskMemFree(wfx); - if (engine) ISpTTSEngine_Release(engine); + ISpTTSEngine_Release(speak_task->engine); free(speak_task->frag_list); ISpTTSEngineSite_Release(speak_task->site); @@ -838,6 +910,7 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR { struct speech_voice *This = impl_from_ISpVoice(iface); ISpTTSEngineSite *site = NULL; + ISpTTSEngine *engine = NULL; SPVTEXTFRAG *frag; struct speak_task *speak_task = NULL; struct async_result *result = NULL; @@ -891,12 +964,28 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR return hr; } - if (!This->engine) + EnterCriticalSection(&This->cs); + + if (!This->engine_token) { - /* Create a new engine with the default voice. */ + /* Set the engine token to default. */ if (FAILED(hr = ISpVoice_SetVoice(iface, NULL))) + { + LeaveCriticalSection(&This->cs); return hr; + } } + if (!This->engine && + FAILED(hr = ISpObjectToken_CreateInstance(This->engine_token, NULL, CLSCTX_ALL, &IID_ISpTTSEngine, (void **)&This->engine))) + { + LeaveCriticalSection(&This->cs); + ERR("Failed to create engine: %#lx.\n", hr); + return hr; + } + engine = This->engine; + ISpTTSEngine_AddRef(engine); + + LeaveCriticalSection(&This->cs); if (!(frag = malloc(sizeof(*frag) + contents_size))) return E_OUTOFMEMORY; @@ -920,6 +1009,7 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR speak_task->task.proc = speak_proc; speak_task->result = NULL; speak_task->voice = This; + speak_task->engine = engine; speak_task->frag_list = frag; speak_task->site = site; speak_task->flags = flags & SPF_NLP_SPEAK_PUNC; @@ -958,6 +1048,7 @@ static HRESULT WINAPI spvoice_Speak(ISpVoice *iface, const WCHAR *contents, DWOR fail: if (site) ISpTTSEngineSite_Release(site); + if (engine) ISpTTSEngine_Release(engine); free(frag); free(speak_task); if (result) @@ -1397,6 +1488,7 @@ HRESULT speech_voice_create(IUnknown *outer, REFIID iid, void **obj) This->ref = 1; This->output = NULL; + This->engine_token = NULL; This->engine = NULL; This->cur_stream_num = 0; This->actions = SPVES_CONTINUE; diff --git a/dlls/user32/sysparams.c b/dlls/user32/sysparams.c index b941f126a58..e91737cbcc2 100644 --- a/dlls/user32/sysparams.c +++ b/dlls/user32/sysparams.c @@ -999,3 +999,10 @@ LONG WINAPI SetDisplayConfig(UINT32 path_info_count, DISPLAYCONFIG_PATH_INFO *pa return ERROR_SUCCESS; } + +LONG WINAPI GetDisplayConfigBufferSizes( UINT32 flags, UINT32 *num_path_info, + UINT32 *num_mode_info ) +{ + flags |= 0x40000000; /* HACK: avoid triggering display updates in NtUserGetDisplayConfigBufferSizes(). */ + return NtUserGetDisplayConfigBufferSizes(flags, num_path_info, num_mode_info); +} diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c index 2fd31be3314..8f5d1c0b973 100644 --- a/dlls/user32/tests/win.c +++ b/dlls/user32/tests/win.c @@ -1796,7 +1796,7 @@ static void test_shell_window(void) WaitForSingleObject(hthread, INFINITE); - DeleteObject(hthread); + CloseHandle(hthread); CloseDesktop(hdesk); } @@ -13030,6 +13030,111 @@ static void test_WM_NCCALCSIZE(void) DestroyWindow(hwnd); } +#define TRAY_MINIMIZE_ALL 419 +#define TRAY_MINIMIZE_ALL_UNDO 416 + +static void test_shell_tray(void) +{ + HWND hwnd, traywnd; + + if (!(traywnd = FindWindowA( "Shell_TrayWnd", NULL ))) + { + skip( "Shell_TrayWnd not found, skipping tests.\n" ); + return; + } + + hwnd = CreateWindowW( L"static", L"parent", WS_OVERLAPPEDWINDOW|WS_VISIBLE, + 100, 100, 200, 200, 0, 0, 0, NULL ); + ok( !!hwnd, "failed, error %lu.\n", GetLastError() ); + flush_events( TRUE ); + + ok( !IsIconic( hwnd ), "window is minimized.\n" ); + + SendMessageA( traywnd, WM_COMMAND, TRAY_MINIMIZE_ALL, 0xdeadbeef ); + flush_events( TRUE ); + todo_wine ok( IsIconic( hwnd ), "window is not minimized.\n" ); + + SendMessageA( traywnd, WM_COMMAND, TRAY_MINIMIZE_ALL_UNDO, 0xdeadbeef ); + flush_events( TRUE ); + ok( !IsIconic( hwnd ), "window is minimized.\n" ); + + DestroyWindow(hwnd); +} + +static int wm_mousemove_count; +static BOOL do_release_capture; + +static LRESULT WINAPI test_ReleaseCapture_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) +{ + if (msg == WM_MOUSEMOVE) + { + wm_mousemove_count++; + if (wm_mousemove_count >= 100) + return 1; + + if (do_release_capture) + ReleaseCapture(); + return 0; + } + return DefWindowProcA(hwnd, msg, wp, lp); +} + +static void test_ReleaseCapture(void) +{ + WNDCLASSA cls = {0}; + ATOM atom; + HWND hwnd; + POINT pt; + BOOL ret; + + cls.lpfnWndProc = test_ReleaseCapture_proc; + cls.hInstance = GetModuleHandleA(0); + cls.hCursor = LoadCursorA(0, (LPCSTR)IDC_ARROW); + cls.hbrBackground = GetStockObject(BLACK_BRUSH); + cls.lpszClassName = "test_ReleaseCapture_class"; + atom = RegisterClassA(&cls); + ok(!!atom, "RegisterClassA failed, error %#lx.\n", GetLastError()); + + hwnd = CreateWindowExA(WS_EX_TOPMOST, cls.lpszClassName, "", WS_POPUP | WS_VISIBLE, 100, 100, + 100, 100, NULL, NULL, 0, NULL); + ok(!!hwnd, "CreateWindowA failed, error %#lx.\n", GetLastError()); + ret = SetForegroundWindow(hwnd); + ok(ret, "SetForegroundWindow failed, error %#lx.\n", GetLastError()); + GetCursorPos(&pt); + ret = SetCursorPos(150, 150); + ok(ret, "SetCursorPos failed, error %#lx.\n", GetLastError()); + flush_events(TRUE); + + /* Test a Wine bug that WM_MOUSEMOVE is post too many times when calling ReleaseCapture() during + * handling WM_MOUSEMOVE and the cursor is on the window */ + do_release_capture = TRUE; + ret = ReleaseCapture(); + ok(ret, "ReleaseCapture failed, error %#lx.\n", GetLastError()); + flush_events(TRUE); + do_release_capture = FALSE; + ok(wm_mousemove_count < 10, "Got too many WM_MOUSEMOVE.\n"); + + /* Test that ReleaseCapture() should send a WM_MOUSEMOVE if a window is captured */ + SetCapture(hwnd); + wm_mousemove_count = 0; + ret = ReleaseCapture(); + ok(ret, "ReleaseCapture failed, error %#lx.\n", GetLastError()); + flush_events(TRUE); + ok(wm_mousemove_count == 1, "Got no WM_MOUSEMOVE.\n"); + + /* Test that ReleaseCapture() shouldn't send WM_MOUSEMOVE if no window is captured */ + wm_mousemove_count = 0; + ret = ReleaseCapture(); + ok(ret, "ReleaseCapture failed, error %#lx.\n", GetLastError()); + flush_events(TRUE); + ok(wm_mousemove_count == 0, "Got WM_MOUSEMOVE.\n"); + + ret = SetCursorPos(pt.x, pt.y); + ok(ret, "SetCursorPos failed, error %#lx.\n", GetLastError()); + DestroyWindow(hwnd); + UnregisterClassA(cls.lpszClassName, GetModuleHandleA(0)); +} + START_TEST(win) { char **argv; @@ -13212,6 +13317,7 @@ START_TEST(win) test_cancel_mode(); test_DragDetect(); test_WM_NCCALCSIZE(); + test_ReleaseCapture(); /* add the tests above this line */ if (hhook) UnhookWindowsHookEx(hhook); @@ -13226,4 +13332,5 @@ START_TEST(win) test_topmost(); test_shell_window(); + test_shell_tray(); } diff --git a/dlls/user32/user32.spec b/dlls/user32/user32.spec index e8d94f86af6..ffe5af2daa0 100644 --- a/dlls/user32/user32.spec +++ b/dlls/user32/user32.spec @@ -292,7 +292,7 @@ @ stdcall GetDesktopWindow() @ stdcall GetDialogBaseUnits() @ stdcall GetDisplayAutoRotationPreferences(ptr) -@ stdcall GetDisplayConfigBufferSizes(long ptr ptr) NtUserGetDisplayConfigBufferSizes +@ stdcall GetDisplayConfigBufferSizes(long ptr ptr) @ stdcall GetDlgCtrlID(long) @ stdcall GetDlgItem(long long) @ stdcall GetDlgItemInt(long long ptr long) diff --git a/dlls/win32u/dce.c b/dlls/win32u/dce.c index d7aa75b3039..3f1e25ae9e2 100644 --- a/dlls/win32u/dce.c +++ b/dlls/win32u/dce.c @@ -1248,7 +1248,10 @@ static BOOL send_erase( HWND hwnd, UINT flags, HRGN client_rgn, { /* don't erase if the clip box is empty */ if (type != NULLREGION) - need_erase = !send_message( hwnd, WM_ERASEBKGND, (WPARAM)hdc, 0 ); + { + need_erase = !send_message_timeout( hwnd, WM_ERASEBKGND, (WPARAM)hdc, 0, SMTO_ABORTIFHUNG, 1000, FALSE ); + if (need_erase && RtlGetLastWin32Error() == ERROR_TIMEOUT) ERR( "timeout.\n" ); + } } if (!hdc_ret) release_dc( hwnd, hdc, TRUE ); } diff --git a/dlls/win32u/font.c b/dlls/win32u/font.c index 6094ea0261b..08060e0a9b7 100644 --- a/dlls/win32u/font.c +++ b/dlls/win32u/font.c @@ -55,6 +55,7 @@ struct font_physdev { struct gdi_physdev dev; struct gdi_font *font; + UINT aa_flags; }; static inline struct font_physdev *get_font_dev( PHYSDEV dev ) @@ -3850,7 +3851,7 @@ static UINT get_glyph_index_linked( struct gdi_font **font, UINT glyph ) static DWORD get_glyph_outline( struct gdi_font *font, UINT glyph, UINT format, GLYPHMETRICS *gm_ret, ABC *abc_ret, DWORD buflen, void *buf, - const MAT2 *mat ) + const MAT2 *mat, UINT aa_flags ) { GLYPHMETRICS gm; ABC abc; @@ -3884,7 +3885,7 @@ static DWORD get_glyph_outline( struct gdi_font *font, UINT glyph, UINT format, if (format == GGO_METRICS && !mat && get_gdi_font_glyph_metrics( font, index, &gm, &abc )) goto done; - ret = font_funcs->get_glyph_outline( font, index, format, &gm, &abc, buflen, buf, mat, tategaki ); + ret = font_funcs->get_glyph_outline( font, index, format, &gm, &abc, buflen, buf, mat, tategaki, aa_flags ); if (ret == GDI_ERROR) return ret; if (format == GGO_METRICS && !mat) @@ -3933,7 +3934,7 @@ static BOOL font_GetCharABCWidths( PHYSDEV dev, UINT first, UINT count, WCHAR *c for (i = 0; i < count; i++) { c = chars ? chars[i] : first + i; - get_glyph_outline( physdev->font, c, GGO_METRICS, NULL, &buffer[i], 0, NULL, NULL ); + get_glyph_outline( physdev->font, c, GGO_METRICS, NULL, &buffer[i], 0, NULL, NULL, physdev->aa_flags ); } pthread_mutex_unlock( &font_lock ); return TRUE; @@ -3959,7 +3960,7 @@ static BOOL font_GetCharABCWidthsI( PHYSDEV dev, UINT first, UINT count, WORD *g pthread_mutex_lock( &font_lock ); for (c = 0; c < count; c++, buffer++) get_glyph_outline( physdev->font, gi ? gi[c] : first + c, GGO_METRICS | GGO_GLYPH_INDEX, - NULL, buffer, 0, NULL, NULL ); + NULL, buffer, 0, NULL, NULL, physdev->aa_flags ); pthread_mutex_unlock( &font_lock ); return TRUE; } @@ -3986,7 +3987,7 @@ static BOOL font_GetCharWidth( PHYSDEV dev, UINT first, UINT count, const WCHAR for (i = 0; i < count; i++) { c = chars ? chars[i] : i + first; - if (get_glyph_outline( physdev->font, c, GGO_METRICS, NULL, &abc, 0, NULL, NULL ) == GDI_ERROR) + if (get_glyph_outline( physdev->font, c, GGO_METRICS, NULL, &abc, 0, NULL, NULL, physdev->aa_flags ) == GDI_ERROR) buffer[i] = 0; else buffer[i] = abc.abcA + abc.abcB + abc.abcC; @@ -4165,7 +4166,7 @@ static DWORD font_GetGlyphOutline( PHYSDEV dev, UINT glyph, UINT format, return dev->funcs->pGetGlyphOutline( dev, glyph, format, gm, buflen, buf, mat ); } pthread_mutex_lock( &font_lock ); - ret = get_glyph_outline( physdev->font, glyph, format, gm, NULL, buflen, buf, mat ); + ret = get_glyph_outline( physdev->font, glyph, format, gm, NULL, buflen, buf, mat, physdev->aa_flags ); pthread_mutex_unlock( &font_lock ); return ret; } @@ -4345,7 +4346,7 @@ static BOOL font_GetTextExtentExPoint( PHYSDEV dev, const WCHAR *str, INT count, pthread_mutex_lock( &font_lock ); for (i = pos = 0; i < count; i++) { - get_glyph_outline( physdev->font, str[i], GGO_METRICS, NULL, &abc, 0, NULL, NULL ); + get_glyph_outline( physdev->font, str[i], GGO_METRICS, NULL, &abc, 0, NULL, NULL, physdev->aa_flags ); pos += abc.abcA + abc.abcB + abc.abcC; dxs[i] = pos; } @@ -4375,7 +4376,7 @@ static BOOL font_GetTextExtentExPointI( PHYSDEV dev, const WORD *indices, INT co for (i = pos = 0; i < count; i++) { get_glyph_outline( physdev->font, indices[i], GGO_METRICS | GGO_GLYPH_INDEX, - NULL, &abc, 0, NULL, NULL ); + NULL, &abc, 0, NULL, NULL, physdev->aa_flags ); pos += abc.abcA + abc.abcB + abc.abcC; dxs[i] = pos; } @@ -4689,7 +4690,7 @@ static HFONT font_SelectFont( PHYSDEV dev, HFONT hfont, UINT *aa_flags ) *aa_flags = font_smoothing; } *aa_flags = font_funcs->get_aa_flags( font, *aa_flags, antialias_fakes ); - font->aa_flags = *aa_flags; + physdev->aa_flags = *aa_flags; } TRACE( "%p %s %d aa %x\n", hfont, debugstr_w(lf.lfFaceName), (int)lf.lfHeight, *aa_flags ); pthread_mutex_unlock( &font_lock ); diff --git a/dlls/win32u/freetype.c b/dlls/win32u/freetype.c index b6f85bf2594..880b1f8603b 100644 --- a/dlls/win32u/freetype.c +++ b/dlls/win32u/freetype.c @@ -3459,7 +3459,7 @@ static FT_Int get_load_flags( UINT format, BOOL vertical_metrics, BOOL force_no_ */ static UINT freetype_get_glyph_outline( struct gdi_font *font, UINT glyph, UINT format, GLYPHMETRICS *lpgm, ABC *abc, UINT buflen, void *buf, - const MAT2 *lpmat, BOOL tategaki ) + const MAT2 *lpmat, BOOL tategaki, UINT aa_flags ) { struct gdi_font *base_font = font->base_font ? font->base_font : font; FT_Face ft_face = get_ft_face( font ); @@ -3479,8 +3479,8 @@ static UINT freetype_get_glyph_outline( struct gdi_font *font, UINT glyph, UINT matrices = get_transform_matrices( font, tategaki, lpmat, transform_matrices ); - if ((format & ~GGO_GLYPH_INDEX) == GGO_METRICS) - effective_format = font->aa_flags | (format & GGO_GLYPH_INDEX); + if (aa_flags && (format & ~GGO_GLYPH_INDEX) == GGO_METRICS) + effective_format = aa_flags | (format & GGO_GLYPH_INDEX); vertical_metrics = (tategaki && FT_HAS_VERTICAL(ft_face)); /* there is a freetype bug where vertical metrics are only properly scaled and correct in 2.4.0 or greater */ diff --git a/dlls/win32u/input.c b/dlls/win32u/input.c index 69bc57f3ca4..9acdeb40388 100644 --- a/dlls/win32u/input.c +++ b/dlls/win32u/input.c @@ -1803,10 +1803,13 @@ HWND WINAPI NtUserSetCapture( HWND hwnd ) */ BOOL release_capture(void) { - BOOL ret = set_capture_window( 0, 0, NULL ); + HWND previous = NULL; + BOOL ret; + + ret = set_capture_window( 0, 0, &previous ); /* Somebody may have missed some mouse movements */ - if (ret) + if (ret && previous) { INPUT input = { .type = INPUT_MOUSE }; input.mi.dwFlags = MOUSEEVENTF_MOVE; diff --git a/dlls/win32u/message.c b/dlls/win32u/message.c index 7bb3c393483..c0b009d8eb4 100644 --- a/dlls/win32u/message.c +++ b/dlls/win32u/message.c @@ -471,7 +471,7 @@ static inline void push_string( struct packed_message *data, LPCWSTR str ) /* make sure that there is space for 'size' bytes in buffer, growing it if needed */ static inline void *get_buffer_space( void **buffer, size_t size, size_t prev_size ) { - if (prev_size < size) *buffer = malloc( size ); + if (prev_size < size) *buffer = realloc( *buffer, size ); return *buffer; } @@ -522,7 +522,7 @@ BOOL set_keyboard_auto_repeat( BOOL enable ) * Unpack a message received from another process. */ static BOOL unpack_message( HWND hwnd, UINT message, WPARAM *wparam, LPARAM *lparam, - void **buffer, size_t size ) + void **buffer, size_t size, size_t buffer_size ) { size_t minsize = 0; union packed_structs *ps = *buffer; @@ -585,7 +585,7 @@ static BOOL unpack_message( HWND hwnd, UINT message, WPARAM *wparam, LPARAM *lpa break; case WM_GETTEXT: case WM_ASKCBFORMATNAME: - if (!get_buffer_space( buffer, (*wparam * sizeof(WCHAR)), size )) return FALSE; + if (!get_buffer_space( buffer, (*wparam * sizeof(WCHAR)), buffer_size )) return FALSE; break; case WM_WININICHANGE: if (!*lparam) return TRUE; @@ -726,17 +726,19 @@ static BOOL unpack_message( HWND hwnd, UINT message, WPARAM *wparam, LPARAM *lpa minsize = sizeof(SCROLLINFO); break; case SBM_GETSCROLLINFO: - if (!get_buffer_space( buffer, sizeof(SCROLLINFO), size )) return FALSE; + case WM_WINE_GETSCROLLINFO: + if (!get_buffer_space( buffer, sizeof(SCROLLINFO), buffer_size )) return FALSE; break; case SBM_GETSCROLLBARINFO: - if (!get_buffer_space( buffer, sizeof(SCROLLBARINFO), size )) return FALSE; + case WM_WINE_GETSCROLLBARINFO: + if (!get_buffer_space( buffer, sizeof(SCROLLBARINFO), buffer_size )) return FALSE; break; case EM_GETSEL: case SBM_GETRANGE: case CB_GETEDITSEL: if (*wparam || *lparam) { - if (!get_buffer_space( buffer, 2 * sizeof(DWORD), size )) return FALSE; + if (!get_buffer_space( buffer, 2 * sizeof(DWORD), buffer_size )) return FALSE; if (*wparam) *wparam = (WPARAM)*buffer; if (*lparam) *lparam = (LPARAM)((DWORD *)*buffer + 1); } @@ -744,7 +746,7 @@ static BOOL unpack_message( HWND hwnd, UINT message, WPARAM *wparam, LPARAM *lpa case EM_GETRECT: case LB_GETITEMRECT: case CB_GETDROPPEDCONTROLRECT: - if (!get_buffer_space( buffer, sizeof(RECT), size )) return FALSE; + if (!get_buffer_space( buffer, sizeof(RECT), buffer_size )) return FALSE; break; case EM_SETRECT: case EM_SETRECTNP: @@ -755,7 +757,7 @@ static BOOL unpack_message( HWND hwnd, UINT message, WPARAM *wparam, LPARAM *lpa WORD *len_ptr, len; if (size < sizeof(WORD)) return FALSE; len = *(WORD *)*buffer; - if (!get_buffer_space( buffer, (len + 1) * sizeof(WCHAR), size )) return FALSE; + if (!get_buffer_space( buffer, (len + 1) * sizeof(WCHAR), buffer_size )) return FALSE; len_ptr = *buffer; len_ptr[0] = len_ptr[1] = len; *lparam = (LPARAM)(len_ptr + 1); @@ -780,26 +782,24 @@ static BOOL unpack_message( HWND hwnd, UINT message, WPARAM *wparam, LPARAM *lpa break; case CB_GETLBTEXT: { - size_t prev_size = size; if (combobox_has_strings( hwnd )) size = (send_message( hwnd, CB_GETLBTEXTLEN, *wparam, 0 ) + 1) * sizeof(WCHAR); else size = sizeof(ULONG_PTR); - if (!get_buffer_space( buffer, size, prev_size )) return FALSE; + if (!get_buffer_space( buffer, size, buffer_size )) return FALSE; break; } case LB_GETTEXT: { - size_t prev_size = size; if (listbox_has_strings( hwnd )) size = (send_message( hwnd, LB_GETTEXTLEN, *wparam, 0 ) + 1) * sizeof(WCHAR); else size = sizeof(ULONG_PTR); - if (!get_buffer_space( buffer, size, prev_size )) return FALSE; + if (!get_buffer_space( buffer, size, buffer_size )) return FALSE; break; } case LB_GETSELITEMS: - if (!get_buffer_space( buffer, *wparam * sizeof(UINT), size )) return FALSE; + if (!get_buffer_space( buffer, *wparam * sizeof(UINT), buffer_size )) return FALSE; break; case WM_NEXTMENU: { @@ -814,7 +814,7 @@ static BOOL unpack_message( HWND hwnd, UINT message, WPARAM *wparam, LPARAM *lpa case WM_SIZING: case WM_MOVING: minsize = sizeof(RECT); - if (!get_buffer_space( buffer, sizeof(RECT), size )) return FALSE; + if (!get_buffer_space( buffer, sizeof(RECT), buffer_size )) return FALSE; break; case WM_MDICREATE: { @@ -880,7 +880,7 @@ static BOOL unpack_message( HWND hwnd, UINT message, WPARAM *wparam, LPARAM *lpa } case WM_MDIGETACTIVE: if (!*lparam) return TRUE; - if (!get_buffer_space( buffer, sizeof(BOOL), size )) return FALSE; + if (!get_buffer_space( buffer, sizeof(BOOL), buffer_size )) return FALSE; break; case WM_DEVICECHANGE: if (!(*wparam & 0x8000)) return TRUE; @@ -1126,9 +1126,11 @@ static size_t pack_message( HWND hwnd, UINT message, WPARAM wparam, LPARAM lpara push_data( data, (SCROLLINFO *)lparam, sizeof(SCROLLINFO) ); return 0; case SBM_GETSCROLLINFO: + case WM_WINE_GETSCROLLINFO: push_data( data, (SCROLLINFO *)lparam, sizeof(SCROLLINFO) ); return sizeof(SCROLLINFO); case SBM_GETSCROLLBARINFO: + case WM_WINE_GETSCROLLBARINFO: { const SCROLLBARINFO *info = (const SCROLLBARINFO *)lparam; size_t size = min( info->cbSize, sizeof(SCROLLBARINFO) ); @@ -1363,8 +1365,13 @@ static void pack_reply( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam, break; } case SBM_GETSCROLLINFO: + case WM_WINE_GETSCROLLINFO: push_data( data, (SCROLLINFO *)lparam, sizeof(SCROLLINFO) ); break; + case SBM_GETSCROLLBARINFO: + case WM_WINE_GETSCROLLBARINFO: + push_data( data, (SCROLLBARINFO *)lparam, sizeof(SCROLLBARINFO) ); + break; case EM_GETRECT: case LB_GETITEMRECT: case CB_GETDROPPEDCONTROLRECT: @@ -1503,9 +1510,11 @@ static void unpack_reply( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam, } break; case SBM_GETSCROLLINFO: + case WM_WINE_GETSCROLLINFO: memcpy( (SCROLLINFO *)lparam, buffer, min( sizeof(SCROLLINFO), size )); break; case SBM_GETSCROLLBARINFO: + case WM_WINE_GETSCROLLBARINFO: memcpy( (SCROLLBARINFO *)lparam, buffer, min( sizeof(SCROLLBARINFO), size )); break; case EM_GETRECT: @@ -1707,9 +1716,11 @@ size_t user_message_size( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam, break; case SBM_SETSCROLLINFO: case SBM_GETSCROLLINFO: + case WM_WINE_GETSCROLLINFO: size = sizeof(SCROLLINFO); break; case SBM_GETSCROLLBARINFO: + case WM_WINE_GETSCROLLBARINFO: size = sizeof(SCROLLBARINFO); break; case EM_GETSEL: @@ -1949,9 +1960,11 @@ static void copy_user_result( void *buffer, size_t size, LRESULT result, UINT me break; case SBM_SETSCROLLINFO: case SBM_GETSCROLLINFO: + case WM_WINE_GETSCROLLINFO: copy_size = sizeof(SCROLLINFO); break; case SBM_GETSCROLLBARINFO: + case WM_WINE_GETSCROLLBARINFO: copy_size = sizeof(SCROLLBARINFO); break; case EM_GETSEL: @@ -2123,6 +2136,10 @@ static LRESULT handle_internal_message( HWND hwnd, UINT msg, WPARAM wparam, LPAR case WM_WINE_UPDATEWINDOWSTATE: update_window_state( hwnd ); return 0; + case WM_WINE_GETSCROLLBARINFO: + return get_scroll_bar_info( hwnd, (LONG)wparam, (SCROLLBARINFO *)lparam ); + case WM_WINE_GETSCROLLINFO: + return get_scroll_info( hwnd, (int)wparam, (SCROLLINFO *)lparam ); default: if (msg >= WM_WINE_FIRST_DRIVER_MSG && msg <= WM_WINE_LAST_DRIVER_MSG) return user_driver->pWindowMessage( hwnd, msg, wparam, lparam ); @@ -2899,7 +2916,7 @@ static int peek_message( MSG *msg, HWND hwnd, UINT first, UINT last, UINT flags, memcpy( buffer, buffer_init, buffer_size ); } if (!unpack_message( info.msg.hwnd, info.msg.message, &info.msg.wParam, - &info.msg.lParam, &buffer, size )) + &info.msg.lParam, &buffer, size, buffer_size )) continue; break; case MSG_CALLBACK: @@ -2983,7 +3000,7 @@ static int peek_message( MSG *msg, HWND hwnd, UINT first, UINT last, UINT flags, memcpy( buffer, buffer_init, buffer_size ); } if (!unpack_message( info.msg.hwnd, info.msg.message, &info.msg.wParam, - &info.msg.lParam, &buffer, size )) + &info.msg.lParam, &buffer, size, buffer_size )) { /* ignore it */ reply_message( &info, 0, &info.msg ); diff --git a/dlls/win32u/ntgdi_private.h b/dlls/win32u/ntgdi_private.h index 5c4f8279c87..eb82df750ba 100644 --- a/dlls/win32u/ntgdi_private.h +++ b/dlls/win32u/ntgdi_private.h @@ -327,7 +327,7 @@ struct font_backend_funcs UINT (*get_default_glyph)( struct gdi_font *gdi_font ); UINT (*get_glyph_outline)( struct gdi_font *font, UINT glyph, UINT format, GLYPHMETRICS *gm, ABC *abc, UINT buflen, void *buf, - const MAT2 *mat, BOOL tategaki ); + const MAT2 *mat, BOOL tategaki, UINT aa_flags ); UINT (*get_unicode_ranges)( struct gdi_font *font, GLYPHSET *gs ); BOOL (*get_char_width_info)( struct gdi_font *font, struct char_width_info *info ); BOOL (*set_outline_text_metrics)( struct gdi_font *font ); diff --git a/dlls/win32u/rawinput.c b/dlls/win32u/rawinput.c index e5eab049fa3..1e071e30a94 100644 --- a/dlls/win32u/rawinput.c +++ b/dlls/win32u/rawinput.c @@ -531,6 +531,30 @@ UINT WINAPI NtUserGetRawInputDeviceList( RAWINPUTDEVICELIST *device_list, UINT * return count; } +static BOOL steam_input_get_vid_pid( UINT slot, UINT16 *vid, UINT16 *pid ) +{ + const char *info = getenv( "SteamVirtualGamepadInfo" ); + char buffer[256]; + UINT current; + FILE *file; + + TRACE( "reading SteamVirtualGamepadInfo %s\n", debugstr_a(info) ); + + if (!info || !(file = fopen( info, "r" ))) return FALSE; + while (fscanf( file, "%255[^\n]\n", buffer ) == 1) + { + if (sscanf( buffer, "[slot %d]", ¤t )) continue; + if (current < slot) continue; + if (current > slot) break; + if (sscanf( buffer, "VID=0x%hx", vid )) continue; + if (sscanf( buffer, "PID=0x%hx", pid )) continue; + } + + fclose( file ); + + return TRUE; +} + /********************************************************************** * NtUserGetRawInputDeviceInfo (win32u.@) */ @@ -568,10 +592,51 @@ UINT WINAPI NtUserGetRawInputDeviceInfo( HANDLE handle, UINT command, void *data switch (command) { case RIDI_DEVICENAME: - if ((len = wcslen( device->path ) + 1) <= data_len && data) - memcpy( data, device->path, len * sizeof(WCHAR) ); + { + static const WCHAR steam_input_idW[] = + { + '\\','\\','?','\\','H','I','D','#','V','I','D','_','2','8','D','E','&','P','I','D','_','1','1','F','F','&','I','G','_',0 + }; + const WCHAR *device_path; + WCHAR bufferW[MAX_PATH]; + + /* CW-Bug-Id: #23185 Emulate Steam Input native hooks for native SDL */ + if (wcsnicmp( device->path, steam_input_idW, 29 )) device_path = device->path; + else + { + char buffer[MAX_PATH]; + UINT size = 0, slot; + const WCHAR *tmpW; + UINT16 vid, pid; + + tmpW = device->path + 29; + while (*tmpW && *tmpW != '#' && size < ARRAY_SIZE(buffer)) buffer[size++] = *tmpW++; + buffer[size] = 0; + if (sscanf( buffer, "%02u", &slot ) != 1) slot = 0; + + if (!steam_input_get_vid_pid( slot, &vid, &pid )) + { + vid = 0x045e; + pid = 0x028e; + } + + size = snprintf( buffer, ARRAY_SIZE(buffer), "\\\\.\\pipe\\HID#VID_045E&PID_028E&IG_00#%04X&%04X", vid, pid ); + if ((tmpW = wcschr( device->path + 29, '&' ))) + { + do buffer[size++] = *tmpW++; + while (*tmpW != '&' && size < ARRAY_SIZE(buffer)); + } + size += snprintf( buffer + size, ARRAY_SIZE(buffer) - size, "#%d#%u", slot, (UINT)GetCurrentProcessId() ); + + ntdll_umbstowcs( buffer, size + 1, bufferW, sizeof(bufferW) ); + device_path = bufferW; + } + + if ((len = wcslen( device_path ) + 1) <= data_len && data) + memcpy( data, device_path, len * sizeof(WCHAR) ); *data_size = len; break; + } case RIDI_DEVICEINFO: if ((len = sizeof(info)) <= data_len && data) @@ -611,6 +676,7 @@ UINT WINAPI NtUserGetRawInputDeviceInfo( HANDLE handle, UINT command, void *data */ UINT WINAPI NtUserGetRawInputBuffer( RAWINPUT *data, UINT *data_size, UINT header_size ) { + static int cached_clear_qs_rawinput = -1; unsigned int count = 0, remaining, rawinput_size, next_size, overhead; struct rawinput_thread_data *thread_data; struct hardware_msg_data *msg_data; @@ -643,6 +709,7 @@ UINT WINAPI NtUserGetRawInputBuffer( RAWINPUT *data, UINT *data_size, UINT heade { req->rawinput_size = rawinput_size; req->buffer_size = 0; + req->clear_qs_rawinput = 0; if (wine_server_call( req )) return ~0u; *data_size = reply->next_size; } @@ -653,12 +720,20 @@ UINT WINAPI NtUserGetRawInputBuffer( RAWINPUT *data, UINT *data_size, UINT heade if (!(thread_data = get_rawinput_thread_data())) return ~0u; rawinput = thread_data->buffer; + if (cached_clear_qs_rawinput == -1) + { + const char *sgi; + + cached_clear_qs_rawinput = (sgi = getenv( "SteamGameId" )) && !strcmp( sgi, "1172470" ); + } + /* first RAWINPUT block in the buffer is used for WM_INPUT message data */ msg_data = (struct hardware_msg_data *)NEXTRAWINPUTBLOCK(rawinput); SERVER_START_REQ( get_rawinput_buffer ) { req->rawinput_size = rawinput_size; req->buffer_size = *data_size; + req->clear_qs_rawinput = cached_clear_qs_rawinput; wine_server_set_reply( req, msg_data, RAWINPUT_BUFFER_SIZE - rawinput->header.dwSize ); if (wine_server_call( req )) return ~0u; next_size = reply->next_size; diff --git a/dlls/win32u/scroll.c b/dlls/win32u/scroll.c index 19a9a1379f4..40a9b1a6605 100644 --- a/dlls/win32u/scroll.c +++ b/dlls/win32u/scroll.c @@ -165,6 +165,16 @@ static BOOL show_scroll_bar( HWND hwnd, int bar, BOOL show_horz, BOOL show_vert /* frame has been changed, let the window redraw itself */ NtUserSetWindowPos( hwnd, 0, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED ); + + if ((set_bits & WS_HSCROLL) && !(old_style & WS_HSCROLL)) + NtUserNotifyWinEvent( EVENT_OBJECT_SHOW, hwnd, OBJID_HSCROLL, 0 ); + if ((set_bits & WS_VSCROLL) && !(old_style & WS_VSCROLL)) + NtUserNotifyWinEvent( EVENT_OBJECT_SHOW, hwnd, OBJID_VSCROLL, 0 ); + if ((clear_bits & WS_HSCROLL) && (old_style & WS_HSCROLL)) + NtUserNotifyWinEvent( EVENT_OBJECT_HIDE, hwnd, OBJID_HSCROLL, 0 ); + if ((clear_bits & WS_VSCROLL) && (old_style & WS_VSCROLL)) + NtUserNotifyWinEvent( EVENT_OBJECT_HIDE, hwnd, OBJID_VSCROLL, 0 ); + return TRUE; } return FALSE; /* no frame changes */ @@ -892,7 +902,13 @@ BOOL get_scroll_info( HWND hwnd, int bar, SCROLLINFO *info ) struct scroll_info *scroll; /* handle invalid data structure */ - if (!validate_scroll_info( info ) || !(scroll = get_scroll_info_ptr( hwnd, bar, FALSE ))) + if (!validate_scroll_info( info )) + return FALSE; + + if (bar != SB_CTL && !is_current_thread_window( hwnd )) + return send_message( hwnd, WM_WINE_GETSCROLLINFO, (WPARAM)bar, (LPARAM)info ); + + if (!(scroll = get_scroll_info_ptr( hwnd, bar, FALSE ))) return FALSE; /* fill in the desired scroll info structure */ @@ -1043,12 +1059,25 @@ static int set_scroll_info( HWND hwnd, int bar, const SCROLLINFO *info, BOOL red refresh_scroll_bar( hwnd, bar, TRUE, TRUE ); else if (action & SA_SSI_REPAINT_ARROWS) refresh_scroll_bar( hwnd, bar, TRUE, FALSE ); + + switch (bar) + { + case SB_CTL: + NtUserNotifyWinEvent( EVENT_OBJECT_VALUECHANGE, hwnd, OBJID_CLIENT, 0 ); + break; + case SB_HORZ: + NtUserNotifyWinEvent( EVENT_OBJECT_VALUECHANGE, hwnd, OBJID_HSCROLL, 0 ); + break; + case SB_VERT: + NtUserNotifyWinEvent( EVENT_OBJECT_VALUECHANGE, hwnd, OBJID_VSCROLL, 0 ); + break; + } } return ret; /* Return current position */ } -static BOOL get_scroll_bar_info( HWND hwnd, LONG id, SCROLLBARINFO *info ) +BOOL get_scroll_bar_info( HWND hwnd, LONG id, SCROLLBARINFO *info ) { struct scroll_info *scroll; int bar, dummy; @@ -1067,6 +1096,9 @@ static BOOL get_scroll_bar_info( HWND hwnd, LONG id, SCROLLBARINFO *info ) /* handle invalid data structure */ if (info->cbSize != sizeof(*info)) return FALSE; + if (bar != SB_CTL && !is_current_thread_window( hwnd )) + return send_message( hwnd, WM_WINE_GETSCROLLBARINFO, (WPARAM)id, (LPARAM)info ); + get_scroll_bar_rect( hwnd, bar, &info->rcScrollBar, &dummy, &info->dxyLineButton, &info->xyThumbTop ); /* rcScrollBar needs to be in screen coordinates */ diff --git a/dlls/win32u/sysparams.c b/dlls/win32u/sysparams.c index d505bde85f3..2e6e0915091 100644 --- a/dlls/win32u/sysparams.c +++ b/dlls/win32u/sysparams.c @@ -1406,7 +1406,7 @@ static void add_gpu( const struct gdi_gpu *gpu, void *param ) break; /* Nvidia */ case 0x10de: - sprintf( buffer, "31.0.15.3625" ); + sprintf( buffer, "31.0.15.5212" ); break; /* Default value for any other vendor. */ default: @@ -2427,6 +2427,7 @@ LONG WINAPI NtUserGetDisplayConfigBufferSizes( UINT32 flags, UINT32 *num_path_in volatile struct global_shared_memory *global_shared; struct monitor *monitor; UINT32 count = 0; + BOOL skip_update = FALSE; TRACE( "(0x%x %p %p)\n", flags, num_path_info, num_mode_info ); @@ -2435,6 +2436,12 @@ LONG WINAPI NtUserGetDisplayConfigBufferSizes( UINT32 flags, UINT32 *num_path_in *num_path_info = 0; + if (flags & 0x40000000) + { + flags &= ~0x40000000; + skip_update = TRUE; + } + switch (flags) { case QDC_ALL_PATHS: @@ -2450,7 +2457,7 @@ LONG WINAPI NtUserGetDisplayConfigBufferSizes( UINT32 flags, UINT32 *num_path_in FIXME( "only returning active paths\n" ); /* NtUserGetDisplayConfigBufferSizes() is called by display drivers to trigger display settings update. */ - if ((global_shared = get_global_shared_memory())) + if (!skip_update && (global_shared = get_global_shared_memory())) InterlockedIncrement( (LONG *)&global_shared->display_settings_serial ); if (lock_display_devices()) diff --git a/dlls/win32u/win32u_private.h b/dlls/win32u/win32u_private.h index a5d2a196932..dc65ca858cb 100644 --- a/dlls/win32u/win32u_private.h +++ b/dlls/win32u/win32u_private.h @@ -153,6 +153,7 @@ extern BOOL rawinput_device_get_usages( HANDLE handle, USHORT *usage_page, USHOR /* scroll.c */ extern void draw_nc_scrollbar( HWND hwnd, HDC hdc, BOOL draw_horizontal, BOOL draw_vertical ); +extern BOOL get_scroll_bar_info( HWND hwnd, LONG id, SCROLLBARINFO *info ); extern BOOL get_scroll_info( HWND hwnd, INT bar, SCROLLINFO *info ); extern void handle_scroll_event( HWND hwnd, INT bar, UINT msg, POINT pt ); extern LRESULT scroll_bar_window_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, diff --git a/dlls/win32u/window.c b/dlls/win32u/window.c index d3ce35ebb86..2dcc3e20d3e 100644 --- a/dlls/win32u/window.c +++ b/dlls/win32u/window.c @@ -873,6 +873,7 @@ BOOL enable_window( HWND hwnd, BOOL enable ) send_message( hwnd, WM_ENABLE, FALSE, 0 ); } } + NtUserNotifyWinEvent( EVENT_OBJECT_STATECHANGE, hwnd, OBJID_CLIENT, 0 ); return ret; } @@ -3544,6 +3545,10 @@ BOOL set_window_pos( WINDOWPOS *winpos, int parent_x, int parent_y ) winpos->cy = new_window_rect.bottom - new_window_rect.top; send_message( winpos->hwnd, WM_WINDOWPOSCHANGED, 0, (LPARAM)winpos ); } + + if ((winpos->flags & (SWP_NOMOVE|SWP_NOSIZE)) != (SWP_NOMOVE|SWP_NOSIZE)) + NtUserNotifyWinEvent( EVENT_OBJECT_LOCATIONCHANGE, winpos->hwnd, OBJID_WINDOW, 0 ); + ret = TRUE; done: SetThreadDpiAwarenessContext( context ); @@ -4688,6 +4693,7 @@ static void send_destroy_message( HWND hwnd ) if (hwnd == NtUserGetClipboardOwner()) release_clipboard_owner( hwnd ); send_message( hwnd, WM_DESTROY, 0, 0); + NtUserNotifyWinEvent( EVENT_OBJECT_DESTROY, hwnd, OBJID_WINDOW, 0 ); /* * This WM_DESTROY message can trigger re-entrant calls to DestroyWindow diff --git a/dlls/win32u/winstation.c b/dlls/win32u/winstation.c index 2887e104c4a..fbede26d8a8 100644 --- a/dlls/win32u/winstation.c +++ b/dlls/win32u/winstation.c @@ -42,12 +42,17 @@ WINE_DECLARE_DEBUG_CHANNEL(win); BOOL is_virtual_desktop(void) { - HANDLE desktop = NtUserGetThreadDesktop( GetCurrentThreadId() ); - USEROBJECTFLAGS flags = {0}; - DWORD len; + const desktop_shm_t *desktop = get_desktop_shared_memory(); + unsigned int flags; + + if (!desktop) return FALSE; + SHARED_READ_BEGIN( desktop, desktop_shm_t ) + { + flags = desktop->flags; + } + SHARED_READ_END - if (!NtUserGetObjectInformation( desktop, UOI_FLAGS, &flags, sizeof(flags), &len )) return FALSE; - return !!(flags.dwFlags & DF_WINE_CREATE_DESKTOP); + return !!(flags & DF_WINE_CREATE_DESKTOP); } /*********************************************************************** diff --git a/dlls/windows.gaming.input/controller.c b/dlls/windows.gaming.input/controller.c index bd3d441c445..1adbc5cce0b 100644 --- a/dlls/windows.gaming.input/controller.c +++ b/dlls/windows.gaming.input/controller.c @@ -61,6 +61,7 @@ struct controller IGameControllerImpl IGameControllerImpl_iface; IGameControllerInputSink IGameControllerInputSink_iface; IRawGameController IRawGameController_iface; + IRawGameController2 IRawGameController2_iface; IGameController *IGameController_outer; LONG ref; @@ -99,6 +100,12 @@ static HRESULT WINAPI controller_QueryInterface( IGameControllerImpl *iface, REF return S_OK; } + if (IsEqualGUID( iid, &IID_IRawGameController2 )) + { + IInspectable_AddRef( (*out = &impl->IRawGameController2_iface) ); + return S_OK; + } + FIXME( "%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid( iid ) ); *out = NULL; return E_NOINTERFACE; @@ -330,6 +337,58 @@ static const struct IRawGameControllerVtbl raw_controller_vtbl = raw_controller_GetSwitchKind, }; +DEFINE_IINSPECTABLE_OUTER( raw_controller_2, IRawGameController2, struct controller, IGameController_outer ) + +static HRESULT WINAPI raw_controller_2_get_SimpleHapticsControllers( IRawGameController2 *iface, IVectorView_SimpleHapticsController** value) +{ + static const struct vector_iids iids = + { + .vector = &IID_IVector_SimpleHapticsController, + .view = &IID_IVectorView_SimpleHapticsController, + .iterable = &IID_IIterable_SimpleHapticsController, + .iterator = &IID_IIterator_SimpleHapticsController, + }; + IVector_SimpleHapticsController *vector; + HRESULT hr; + + FIXME( "iface %p, value %p stub!\n", iface, value ); + + if (SUCCEEDED(hr = vector_create( &iids, (void **)&vector ))) + { + hr = IVector_SimpleHapticsController_GetView( vector, value ); + IVector_SimpleHapticsController_Release( vector ); + } + + return hr; +} + +static HRESULT WINAPI raw_controller_2_get_NonRoamableId( IRawGameController2 *iface, HSTRING* value ) +{ + struct controller *impl = impl_from_IRawGameController2( iface ); + return IWineGameControllerProvider_get_NonRoamableId( impl->wine_provider, value ); +} + +static HRESULT WINAPI raw_controller_2_get_DisplayName( IRawGameController2 *iface, HSTRING* value ) +{ + FIXME( "iface %p, value %p stub!\n", iface, value ); + return E_NOTIMPL; +} + +static const struct IRawGameController2Vtbl raw_controller_2_vtbl = +{ + raw_controller_2_QueryInterface, + raw_controller_2_AddRef, + raw_controller_2_Release, + /* IInspectable methods */ + raw_controller_2_GetIids, + raw_controller_2_GetRuntimeClassName, + raw_controller_2_GetTrustLevel, + /* IRawGameController2 methods */ + raw_controller_2_get_SimpleHapticsControllers, + raw_controller_2_get_NonRoamableId, + raw_controller_2_get_DisplayName, +}; + struct controller_statics { IActivationFactory IActivationFactory_iface; @@ -525,6 +584,7 @@ static HRESULT WINAPI controller_factory_CreateGameController( ICustomGameContro impl->IGameControllerImpl_iface.lpVtbl = &controller_vtbl; impl->IGameControllerInputSink_iface.lpVtbl = &input_sink_vtbl; impl->IRawGameController_iface.lpVtbl = &raw_controller_vtbl; + impl->IRawGameController2_iface.lpVtbl = &raw_controller_2_vtbl; impl->ref = 1; TRACE( "created RawGameController %p\n", impl ); diff --git a/dlls/windows.gaming.input/provider.c b/dlls/windows.gaming.input/provider.c index d0472727224..3515d5c9ba6 100644 --- a/dlls/windows.gaming.input/provider.c +++ b/dlls/windows.gaming.input/provider.c @@ -148,6 +148,65 @@ static BOOL CALLBACK count_ffb_axes( const DIDEVICEOBJECTINSTANCEW *obj, void *a return DIENUM_CONTINUE; } +static BOOL steam_input_get_vid_pid( UINT slot, UINT16 *vid, UINT16 *pid ) +{ + const char *info = getenv( "SteamVirtualGamepadInfo" ); + char buffer[256]; + UINT current; + FILE *file; + + TRACE( "reading SteamVirtualGamepadInfo %s\n", debugstr_a(info) ); + + if (!info || !(file = fopen( info, "r" ))) return FALSE; + while (fscanf( file, "%255[^\n]\n", buffer ) == 1) + { + if (sscanf( buffer, "[slot %d]", ¤t )) continue; + if (current < slot) continue; + if (current > slot) break; + if (sscanf( buffer, "VID=0x%hx", vid )) continue; + if (sscanf( buffer, "PID=0x%hx", pid )) continue; + } + + fclose( file ); + + return TRUE; +} + +static HRESULT WINAPI wine_provider_get_NonRoamableId( IWineGameControllerProvider *iface, HSTRING *value ) +{ + struct provider *impl = impl_from_IWineGameControllerProvider( iface ); + WCHAR buffer[1024]; + UINT16 vid, pid; + HRESULT hr; + + FIXME( "iface %p, value %p stub!\n", iface, value ); + + if (FAILED(hr = IGameControllerProvider_get_HardwareVendorId( &impl->IGameControllerProvider_iface, &vid ))) return hr; + if (FAILED(hr = IGameControllerProvider_get_HardwareProductId( &impl->IGameControllerProvider_iface, &pid ))) return hr; + + /* CW-Bug-Id: #23185 Emulate Steam Input native hooks for native SDL */ + if (vid == 0x28de && pid == 0x11ff) + { + const WCHAR *tmp; + char serial[256]; + UINT32 len, slot; + + if (!(tmp = wcschr( impl->device_path + 8, '#' )) || wcsnicmp( tmp - 6, L"&XI_", 4 )) return E_NOTIMPL; + if (swscanf( tmp - 2, L"%02u#%*u&%255[^&]&", &slot, serial ) != 2) return E_NOTIMPL; + + if (!steam_input_get_vid_pid( slot, &vid, &pid )) + { + vid = 0x045e; + pid = 0x028e; + } + + len = swprintf( buffer, ARRAY_SIZE(buffer), L"{wgi/nrid/:steam-%04X&%04X&%s#%d#%u}", vid, pid, serial, slot, GetCurrentProcessId() ); + return WindowsCreateString( buffer, len, value ); + } + + return E_NOTIMPL; +} + static HRESULT WINAPI wine_provider_get_Type( IWineGameControllerProvider *iface, WineGameControllerType *value ) { struct provider *impl = impl_from_IWineGameControllerProvider( iface ); @@ -356,6 +415,7 @@ static const struct IWineGameControllerProviderVtbl wine_provider_vtbl = wine_provider_GetRuntimeClassName, wine_provider_GetTrustLevel, /* IWineGameControllerProvider methods */ + wine_provider_get_NonRoamableId, wine_provider_get_Type, wine_provider_get_AxisCount, wine_provider_get_ButtonCount, diff --git a/dlls/windows.gaming.input/provider.idl b/dlls/windows.gaming.input/provider.idl index e7b6e96b8aa..a6fcc6e84f3 100644 --- a/dlls/windows.gaming.input/provider.idl +++ b/dlls/windows.gaming.input/provider.idl @@ -173,6 +173,8 @@ namespace Windows.Gaming.Input.Custom { interface IWineGameControllerProvider : IInspectable requires Windows.Gaming.Input.Custom.IGameControllerProvider { + [propget] HRESULT NonRoamableId([out, retval] HSTRING *value); + [propget] HRESULT Type([out, retval] WineGameControllerType *value); [propget] HRESULT AxisCount([out, retval] INT32 *value); [propget] HRESULT ButtonCount([out, retval] INT32 *value); diff --git a/dlls/windows.media.speech/Makefile.in b/dlls/windows.media.speech/Makefile.in index 5be66d8367e..ea1e4272139 100644 --- a/dlls/windows.media.speech/Makefile.in +++ b/dlls/windows.media.speech/Makefile.in @@ -1,5 +1,6 @@ MODULE = windows.media.speech.dll -IMPORTS = combase uuid +UNIXLIB = windows.media.speech.so +IMPORTS = combase uuid user32 SOURCES = \ async.c \ @@ -9,4 +10,5 @@ SOURCES = \ main.c \ recognizer.c \ synthesizer.c \ + unixlib.c \ vector.c diff --git a/dlls/windows.media.speech/main.c b/dlls/windows.media.speech/main.c index e772a791588..d53e1599eb8 100644 --- a/dlls/windows.media.speech/main.c +++ b/dlls/windows.media.speech/main.c @@ -20,10 +20,36 @@ #include "initguid.h" #include "private.h" +#include "unixlib.h" + #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(speech); +BOOL WINAPI DllMain( HINSTANCE instance, DWORD reason, void *reserved ) +{ + NTSTATUS status; + + switch (reason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(instance); + + if ((status = __wine_init_unix_call())) + ERR("loading the unixlib failed with status %#lx.\n", status); + + if ((status = WINE_UNIX_CALL(unix_process_attach, NULL))) + WARN("initializing the unixlib failed with status %#lx.\n", status); + + break; + case DLL_PROCESS_DETACH: + WINE_UNIX_CALL(unix_process_detach, NULL); + break; + } + + return TRUE; +} + HRESULT WINAPI DllGetClassObject(REFCLSID clsid, REFIID riid, void **out) { FIXME("clsid %s, riid %s, out %p stub!\n", debugstr_guid(clsid), debugstr_guid(riid), out); diff --git a/dlls/windows.media.speech/private.h b/dlls/windows.media.speech/private.h index d03fe0e773e..60d09c9f7d1 100644 --- a/dlls/windows.media.speech/private.h +++ b/dlls/windows.media.speech/private.h @@ -22,11 +22,16 @@ #include +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "winerror.h" +#include "winternl.h" #define COBJMACROS #include "corerror.h" #include "windef.h" #include "winbase.h" #include "winstring.h" +#include "winuser.h" #include "objbase.h" #include "activation.h" @@ -43,6 +48,8 @@ #include "wine/list.h" +#define SPERR_WINRT_INTERNAL_ERROR 0x800455a0 + /* * * Windows.Media.SpeechRecognition diff --git a/dlls/windows.media.speech/recognizer.c b/dlls/windows.media.speech/recognizer.c index 790d127fc64..c898ea7723f 100644 --- a/dlls/windows.media.speech/recognizer.c +++ b/dlls/windows.media.speech/recognizer.c @@ -25,8 +25,610 @@ #include "wine/debug.h" +#include "unixlib.h" +#include "wine/unixlib.h" + WINE_DEFAULT_DEBUG_CHANNEL(speech); +struct map_view_hstring_vector_view_hstring +{ + IMapView_HSTRING_IVectorView_HSTRING IMapView_HSTRING_IVectorView_HSTRING_iface; + LONG ref; +}; + +static inline struct map_view_hstring_vector_view_hstring *impl_from_IMapView_HSTRING_IVectorView_HSTRING( IMapView_HSTRING_IVectorView_HSTRING *iface ) +{ + return CONTAINING_RECORD(iface, struct map_view_hstring_vector_view_hstring, IMapView_HSTRING_IVectorView_HSTRING_iface); +} + +HRESULT WINAPI map_view_hstring_vector_view_hstring_QueryInterface( IMapView_HSTRING_IVectorView_HSTRING *iface, REFIID iid, void **out ) +{ + struct map_view_hstring_vector_view_hstring *impl = impl_from_IMapView_HSTRING_IVectorView_HSTRING(iface); + + TRACE("iface %p, iid %s, out %p.\n", iface, debugstr_guid(iid), out); + + if (IsEqualGUID(iid, &IID_IUnknown) || + IsEqualGUID(iid, &IID_IInspectable) || + IsEqualGUID(iid, &IID_IMapView_HSTRING_IVectorView_HSTRING)) + { + IInspectable_AddRef((*out = &impl->IMapView_HSTRING_IVectorView_HSTRING_iface)); + return S_OK; + } + + WARN("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); + *out = NULL; + return E_NOINTERFACE; +} + +ULONG WINAPI map_view_hstring_vector_view_hstring_AddRef( IMapView_HSTRING_IVectorView_HSTRING *iface ) +{ + struct map_view_hstring_vector_view_hstring *impl = impl_from_IMapView_HSTRING_IVectorView_HSTRING(iface); + ULONG ref = InterlockedIncrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + return ref; +} + +ULONG WINAPI map_view_hstring_vector_view_hstring_Release( IMapView_HSTRING_IVectorView_HSTRING *iface ) +{ + struct map_view_hstring_vector_view_hstring *impl = impl_from_IMapView_HSTRING_IVectorView_HSTRING(iface); + + ULONG ref = InterlockedDecrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + + if(!ref) + free(impl); + + return ref; +} + +HRESULT WINAPI map_view_hstring_vector_view_hstring_GetIids( IMapView_HSTRING_IVectorView_HSTRING *iface, ULONG *iidCount, IID **iids ) +{ + FIXME("iface %p stub!\n", iface); + return E_NOTIMPL; +} + +HRESULT WINAPI map_view_hstring_vector_view_hstring_GetRuntimeClassName( IMapView_HSTRING_IVectorView_HSTRING *iface, HSTRING *className ) +{ + FIXME("iface %p stub!\n", iface); + return E_NOTIMPL; +} + +HRESULT WINAPI map_view_hstring_vector_view_hstring_GetTrustLevel( IMapView_HSTRING_IVectorView_HSTRING *iface, TrustLevel *trustLevel ) +{ + FIXME("iface %p stub!\n", iface); + return E_NOTIMPL; +} + +HRESULT WINAPI map_view_hstring_vector_view_hstring_Lookup( IMapView_HSTRING_IVectorView_HSTRING *iface, HSTRING key, IVectorView_HSTRING **value ) +{ + FIXME("iface %p stub!\n", iface); + return E_NOTIMPL; +} + +HRESULT WINAPI map_view_hstring_vector_view_hstring_get_Size( IMapView_HSTRING_IVectorView_HSTRING *iface, unsigned int *size ) +{ + FIXME("iface %p stub!\n", iface); + *size = 0; + return S_OK; +} + +HRESULT WINAPI map_view_hstring_vector_view_hstring_HasKey( IMapView_HSTRING_IVectorView_HSTRING *iface, HSTRING key, boolean *found ) +{ + FIXME("iface %p stub!\n", iface); + return E_NOTIMPL; +} + +HRESULT WINAPI map_view_hstring_vector_view_hstring_Split( IMapView_HSTRING_IVectorView_HSTRING *iface, IMapView_HSTRING_IVectorView_HSTRING **first, IMapView_HSTRING_IVectorView_HSTRING **second ) +{ + FIXME("iface %p stub!\n", iface); + return E_NOTIMPL; +} + +static const struct IMapView_HSTRING_IVectorView_HSTRINGVtbl map_view_hstring_vector_view_hstring_vtbl = +{ + /* IUnknown methods */ + map_view_hstring_vector_view_hstring_QueryInterface, + map_view_hstring_vector_view_hstring_AddRef, + map_view_hstring_vector_view_hstring_Release, + /* IInspectable methods */ + map_view_hstring_vector_view_hstring_GetIids, + map_view_hstring_vector_view_hstring_GetRuntimeClassName, + map_view_hstring_vector_view_hstring_GetTrustLevel, + /* IMapView* > methods */ + map_view_hstring_vector_view_hstring_Lookup, + map_view_hstring_vector_view_hstring_get_Size, + map_view_hstring_vector_view_hstring_HasKey, + map_view_hstring_vector_view_hstring_Split +}; + + +static HRESULT map_view_hstring_vector_view_hstring_create( IMapView_HSTRING_IVectorView_HSTRING **out ) +{ + struct map_view_hstring_vector_view_hstring *impl; + + TRACE("out %p.\n", out); + + if (!(impl = calloc(1, sizeof(*impl)))) + { + *out = NULL; + return E_OUTOFMEMORY; + } + + impl->IMapView_HSTRING_IVectorView_HSTRING_iface.lpVtbl = &map_view_hstring_vector_view_hstring_vtbl; + impl->ref = 1; + + *out = &impl->IMapView_HSTRING_IVectorView_HSTRING_iface; + TRACE("created %p\n", *out); + return S_OK; +} + +struct semantic_interpretation +{ + ISpeechRecognitionSemanticInterpretation ISpeechRecognitionSemanticInterpretation_iface; + LONG ref; +}; + +static inline struct semantic_interpretation *impl_from_ISpeechRecognitionSemanticInterpretation( ISpeechRecognitionSemanticInterpretation *iface ) +{ + return CONTAINING_RECORD(iface, struct semantic_interpretation, ISpeechRecognitionSemanticInterpretation_iface); +} + +HRESULT WINAPI semantic_interpretation_QueryInterface( ISpeechRecognitionSemanticInterpretation *iface, REFIID iid, void **out ) +{ + struct semantic_interpretation *impl = impl_from_ISpeechRecognitionSemanticInterpretation(iface); + + TRACE("iface %p, iid %s, out %p.\n", iface, debugstr_guid(iid), out); + + if (IsEqualGUID(iid, &IID_IUnknown) || + IsEqualGUID(iid, &IID_IInspectable) || + IsEqualGUID(iid, &IID_ISpeechRecognitionSemanticInterpretation)) + { + IInspectable_AddRef((*out = &impl->ISpeechRecognitionSemanticInterpretation_iface)); + return S_OK; + } + + WARN("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); + *out = NULL; + return E_NOINTERFACE; +} + +ULONG WINAPI semantic_interpretation_AddRef( ISpeechRecognitionSemanticInterpretation *iface ) +{ + struct semantic_interpretation *impl = impl_from_ISpeechRecognitionSemanticInterpretation(iface); + ULONG ref = InterlockedIncrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + return ref; +} + +ULONG WINAPI semantic_interpretation_Release( ISpeechRecognitionSemanticInterpretation *iface ) +{ + struct semantic_interpretation *impl = impl_from_ISpeechRecognitionSemanticInterpretation(iface); + + ULONG ref = InterlockedDecrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + + if(!ref) + free(impl); + + return ref; +} + +HRESULT WINAPI semantic_interpretation_GetIids( ISpeechRecognitionSemanticInterpretation *iface, ULONG *iid_count, IID **iids ) +{ + FIXME("iface %p, iid_count %p, iids %p stub!\n", iface, iid_count, iids); + return E_NOTIMPL; +} + +HRESULT WINAPI semantic_interpretation_GetRuntimeClassName( ISpeechRecognitionSemanticInterpretation *iface, HSTRING *class_name ) +{ + FIXME("iface %p, class_name %p stub!\n", iface, class_name); + return E_NOTIMPL; +} + +HRESULT WINAPI semantic_interpretation_GetTrustLevel( ISpeechRecognitionSemanticInterpretation *iface, TrustLevel *trust_level ) +{ + FIXME("iface %p, trust_level %p stub!\n", iface, trust_level); + return E_NOTIMPL; +} + +HRESULT WINAPI semantic_interpretation_get_Properties( ISpeechRecognitionSemanticInterpretation *iface, IMapView_HSTRING_IVectorView_HSTRING **value ) +{ + FIXME("iface %p stub!\n", iface); + return map_view_hstring_vector_view_hstring_create(value); +} + +static const struct ISpeechRecognitionSemanticInterpretationVtbl semantic_interpretation_vtbl = +{ + /* IUnknown methods */ + semantic_interpretation_QueryInterface, + semantic_interpretation_AddRef, + semantic_interpretation_Release, + /* IInspectable methods */ + semantic_interpretation_GetIids, + semantic_interpretation_GetRuntimeClassName, + semantic_interpretation_GetTrustLevel, + /* ISpeechRecognitionSemanticInterpretation methods */ + semantic_interpretation_get_Properties +}; + + +static HRESULT semantic_interpretation_create( ISpeechRecognitionSemanticInterpretation **out ) +{ + struct semantic_interpretation *impl; + + TRACE("out %p.\n", out); + + if (!(impl = calloc(1, sizeof(*impl)))) + { + *out = NULL; + return E_OUTOFMEMORY; + } + + impl->ISpeechRecognitionSemanticInterpretation_iface.lpVtbl = &semantic_interpretation_vtbl; + impl->ref = 1; + + *out = &impl->ISpeechRecognitionSemanticInterpretation_iface; + TRACE("created %p\n", *out); + return S_OK; +} + +struct recognition_result +{ + ISpeechRecognitionResult ISpeechRecognitionResult_iface; + ISpeechRecognitionResult2 ISpeechRecognitionResult2_iface; + LONG ref; + + ISpeechRecognitionConstraint *constraint; + HSTRING text; +}; + +static inline struct recognition_result *impl_from_ISpeechRecognitionResult( ISpeechRecognitionResult *iface ) +{ + return CONTAINING_RECORD(iface, struct recognition_result, ISpeechRecognitionResult_iface); +} + +static HRESULT WINAPI recognition_result_QueryInterface( ISpeechRecognitionResult *iface, REFIID iid, void **out ) +{ + struct recognition_result *impl = impl_from_ISpeechRecognitionResult(iface); + + TRACE("iface %p, iid %s, out %p.\n", iface, debugstr_guid(iid), out); + + if (IsEqualGUID(iid, &IID_IUnknown) || + IsEqualGUID(iid, &IID_IInspectable) || + IsEqualGUID(iid, &IID_IAgileObject) || + IsEqualGUID(iid, &IID_ISpeechRecognitionResult)) + { + IInspectable_AddRef((*out = &impl->ISpeechRecognitionResult_iface)); + return S_OK; + } + + if (IsEqualGUID(iid, &IID_ISpeechRecognitionResult2)) + { + IInspectable_AddRef((*out = &impl->ISpeechRecognitionResult2_iface)); + return S_OK; + } + + WARN("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); + *out = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI recognition_result_AddRef( ISpeechRecognitionResult *iface ) +{ + struct recognition_result *impl = impl_from_ISpeechRecognitionResult(iface); + ULONG ref = InterlockedIncrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + return ref; +} + +static ULONG WINAPI recognition_result_Release( ISpeechRecognitionResult *iface ) +{ + struct recognition_result *impl = impl_from_ISpeechRecognitionResult(iface); + + ULONG ref = InterlockedDecrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + + if(!ref) + { + ISpeechRecognitionConstraint_Release(impl->constraint); + WindowsDeleteString(impl->text); + free(impl); + } + + return ref; +} + +static HRESULT WINAPI recognition_result_GetIids( ISpeechRecognitionResult *iface, ULONG *iid_count, IID **iids ) +{ + FIXME("iface %p, iid_count %p, iids %p stub!\n", iface, iid_count, iids); + return E_NOTIMPL; +} + +static HRESULT WINAPI recognition_result_GetRuntimeClassName( ISpeechRecognitionResult *iface, HSTRING *class_name ) +{ + FIXME("iface %p, class_name %p stub!\n", iface, class_name); + return E_NOTIMPL; +} + +static HRESULT WINAPI recognition_result_GetTrustLevel( ISpeechRecognitionResult *iface, TrustLevel *trust_level ) +{ + FIXME("iface %p, trust_level %p stub!\n", iface, trust_level); + return E_NOTIMPL; +} + +static HRESULT WINAPI recognition_result_get_Status( ISpeechRecognitionResult *iface, SpeechRecognitionResultStatus *value ) +{ + FIXME("iface %p, operation %p stub!\n", iface, value); + *value = SpeechRecognitionResultStatus_Success; + return S_OK; +} + +static HRESULT WINAPI recognition_result_get_Text( ISpeechRecognitionResult *iface, HSTRING *value ) +{ + struct recognition_result *impl = impl_from_ISpeechRecognitionResult(iface); + TRACE("iface %p, operation %p, text: %s.\n", iface, value, debugstr_hstring(impl->text)); + return WindowsDuplicateString(impl->text, value); +} + +static HRESULT WINAPI recognition_result_get_Confidence( ISpeechRecognitionResult *iface, SpeechRecognitionConfidence *value ) +{ + FIXME("iface %p, operation %p semi stub!\n", iface, value); + *value = SpeechRecognitionConfidence_High; + return S_OK; +} + +static HRESULT WINAPI recognition_result_get_SemanticInterpretation( ISpeechRecognitionResult *iface, + ISpeechRecognitionSemanticInterpretation **value ) +{ + FIXME("iface %p, operation %p stub!\n", iface, value); + return semantic_interpretation_create(value); +} + +static HRESULT WINAPI recognition_result_GetAlternates( ISpeechRecognitionResult *iface, + UINT32 max_amount, + IVectorView_SpeechRecognitionResult **results ) +{ + IVector_IInspectable *vector; + struct vector_iids constraints_iids = + { + .iterable = &IID_IVectorView_SpeechRecognitionResult, + .iterator = &IID_IVectorView_SpeechRecognitionResult, + .vector = &IID_IVector_IInspectable, + .view = &IID_IVectorView_SpeechRecognitionResult, + }; + + FIXME("iface %p, max_amount %u, results %p stub!\n", iface, max_amount, results); + + vector_inspectable_create(&constraints_iids, (IVector_IInspectable **)&vector); + IVector_IInspectable_GetView(vector, (IVectorView_IInspectable **)results); + IVector_IInspectable_Release(vector); + return S_OK; +} + +static HRESULT WINAPI recognition_result_get_Constraint( ISpeechRecognitionResult *iface, ISpeechRecognitionConstraint **value ) +{ + struct recognition_result *impl = impl_from_ISpeechRecognitionResult(iface); + TRACE("iface %p, operation %p.\n", iface, value); + ISpeechRecognitionConstraint_AddRef((*value = impl->constraint)); + return S_OK; +} + +static HRESULT WINAPI recognition_result_get_RulePath( ISpeechRecognitionResult *iface, IVectorView_HSTRING **value ) +{ + FIXME("iface %p stub!\n", iface); + return E_NOTIMPL; +} + +static HRESULT WINAPI recognition_result_get_RawConfidence( ISpeechRecognitionResult *iface, DOUBLE *value ) +{ + FIXME("iface %p stub!\n", iface); + return E_NOTIMPL; +} + +static const struct ISpeechRecognitionResultVtbl recognition_result_vtbl = +{ + /* IUnknown methods */ + recognition_result_QueryInterface, + recognition_result_AddRef, + recognition_result_Release, + /* IInspectable methods */ + recognition_result_GetIids, + recognition_result_GetRuntimeClassName, + recognition_result_GetTrustLevel, + /* ISpeechRecognitionResult methods */ + recognition_result_get_Status, + recognition_result_get_Text, + recognition_result_get_Confidence, + recognition_result_get_SemanticInterpretation, + recognition_result_GetAlternates, + recognition_result_get_Constraint, + recognition_result_get_RulePath, + recognition_result_get_RawConfidence +}; + +DEFINE_IINSPECTABLE(recognition_result2, ISpeechRecognitionResult2, struct recognition_result, ISpeechRecognitionResult_iface) + +static HRESULT WINAPI recognition_result2_get_PhraseStartTime( ISpeechRecognitionResult2 *iface, DateTime *value ) +{ + DateTime dt = { .UniversalTime = 0 }; + FIXME("iface %p, value %p stub!\n", iface, value); + *value = dt; + return S_OK; +} + + +static HRESULT WINAPI recognition_result2_get_PhraseDuration( ISpeechRecognitionResult2 *iface, TimeSpan *value ) +{ + TimeSpan ts = { .Duration = 50000000LL }; /* Use 5 seconds as stub value. */ + FIXME("iface %p, value %p stub!\n", iface, value); + *value = ts; + return S_OK; +} + +static const struct ISpeechRecognitionResult2Vtbl recognition_result2_vtbl = +{ + /* IUnknown methods */ + recognition_result2_QueryInterface, + recognition_result2_AddRef, + recognition_result2_Release, + /* IInspectable methods */ + recognition_result2_GetIids, + recognition_result2_GetRuntimeClassName, + recognition_result2_GetTrustLevel, + /* ISpeechRecognitionResult2 methods */ + recognition_result2_get_PhraseStartTime, + recognition_result2_get_PhraseDuration +}; + +static HRESULT WINAPI recognition_result_create( ISpeechRecognitionConstraint *constraint, + HSTRING result_text, + ISpeechRecognitionResult **out ) +{ + struct recognition_result *impl; + + TRACE("out %p.\n", out); + + if (!(impl = calloc(1, sizeof(*impl)))) + { + *out = NULL; + return E_OUTOFMEMORY; + } + + impl->ISpeechRecognitionResult_iface.lpVtbl = &recognition_result_vtbl; + impl->ISpeechRecognitionResult2_iface.lpVtbl = &recognition_result2_vtbl; + impl->ref = 1; + + if (constraint) ISpeechRecognitionConstraint_AddRef((impl->constraint = constraint)); + WindowsDuplicateString(result_text, &impl->text); + + *out = &impl->ISpeechRecognitionResult_iface; + + TRACE("created %p.\n", *out); + + return S_OK; +} + +struct recognition_result_event_args +{ + ISpeechContinuousRecognitionResultGeneratedEventArgs ISpeechContinuousRecognitionResultGeneratedEventArgs_iface; + LONG ref; + + ISpeechRecognitionResult *result; +}; + +static inline struct recognition_result_event_args *impl_from_ISpeechContinuousRecognitionResultGeneratedEventArgs( ISpeechContinuousRecognitionResultGeneratedEventArgs *iface ) +{ + return CONTAINING_RECORD(iface, struct recognition_result_event_args, ISpeechContinuousRecognitionResultGeneratedEventArgs_iface); +} + +static HRESULT WINAPI recognition_result_event_args_QueryInterface( ISpeechContinuousRecognitionResultGeneratedEventArgs *iface, REFIID iid, void **out ) +{ + struct recognition_result_event_args *impl = impl_from_ISpeechContinuousRecognitionResultGeneratedEventArgs(iface); + + TRACE("iface %p, iid %s, out %p.\n", iface, debugstr_guid(iid), out); + + if (IsEqualGUID(iid, &IID_IUnknown) || + IsEqualGUID(iid, &IID_IInspectable) || + IsEqualGUID(iid, &IID_IAgileObject) || + IsEqualGUID(iid, &IID_ISpeechContinuousRecognitionResultGeneratedEventArgs)) + { + IInspectable_AddRef((*out = &impl->ISpeechContinuousRecognitionResultGeneratedEventArgs_iface)); + return S_OK; + } + + WARN("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); + *out = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI recognition_result_event_args_AddRef( ISpeechContinuousRecognitionResultGeneratedEventArgs *iface ) +{ + struct recognition_result_event_args *impl = impl_from_ISpeechContinuousRecognitionResultGeneratedEventArgs(iface); + ULONG ref = InterlockedIncrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + return ref; +} + +static ULONG WINAPI recognition_result_event_args_Release( ISpeechContinuousRecognitionResultGeneratedEventArgs *iface ) +{ + struct recognition_result_event_args *impl = impl_from_ISpeechContinuousRecognitionResultGeneratedEventArgs(iface); + + ULONG ref = InterlockedDecrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); + + if (!ref) + { + if (impl->result) ISpeechRecognitionResult_Release(impl->result); + free(impl); + } + + return ref; +} + +static HRESULT WINAPI recognition_result_event_args_GetIids( ISpeechContinuousRecognitionResultGeneratedEventArgs *iface, ULONG *iid_count, IID **iids ) +{ + FIXME("iface %p, iid_count %p, iids %p stub!\n", iface, iid_count, iids); + return E_NOTIMPL; +} + +static HRESULT WINAPI recognition_result_event_args_GetRuntimeClassName( ISpeechContinuousRecognitionResultGeneratedEventArgs *iface, HSTRING *class_name ) +{ + FIXME("iface %p, class_name %p stub!\n", iface, class_name); + return E_NOTIMPL; +} + +static HRESULT WINAPI recognition_result_event_args_GetTrustLevel( ISpeechContinuousRecognitionResultGeneratedEventArgs *iface, TrustLevel *trust_level ) +{ + FIXME("iface %p, trust_level %p stub!\n", iface, trust_level); + return E_NOTIMPL; +} + +static HRESULT WINAPI recognition_result_event_args_get_Result( ISpeechContinuousRecognitionResultGeneratedEventArgs *iface, + ISpeechRecognitionResult **value ) +{ + struct recognition_result_event_args *impl = impl_from_ISpeechContinuousRecognitionResultGeneratedEventArgs(iface); + FIXME("iface %p value %p stub!\n", iface, value); + ISpeechRecognitionResult_AddRef((*value = impl->result)); + return S_OK; +} + +static const struct ISpeechContinuousRecognitionResultGeneratedEventArgsVtbl recognition_result_event_args_vtbl = +{ + /* IUnknown methods */ + recognition_result_event_args_QueryInterface, + recognition_result_event_args_AddRef, + recognition_result_event_args_Release, + /* IInspectable methods */ + recognition_result_event_args_GetIids, + recognition_result_event_args_GetRuntimeClassName, + recognition_result_event_args_GetTrustLevel, + /* ISpeechContinuousRecognitionResultGeneratedEventArgs methods */ + recognition_result_event_args_get_Result +}; + +static HRESULT WINAPI recognition_result_event_args_create( ISpeechRecognitionResult *result, + ISpeechContinuousRecognitionResultGeneratedEventArgs **out ) +{ + struct recognition_result_event_args *impl; + + TRACE("out %p.\n", out); + + if (!(impl = calloc(1, sizeof(*impl)))) + { + *out = NULL; + return E_OUTOFMEMORY; + } + + impl->ISpeechContinuousRecognitionResultGeneratedEventArgs_iface.lpVtbl = &recognition_result_event_args_vtbl; + impl->ref = 1; + if (result) ISpeechRecognitionResult_AddRef((impl->result = result)); + + *out = &impl->ISpeechContinuousRecognitionResultGeneratedEventArgs_iface; + + TRACE("created %p.\n", *out); + return S_OK; +} + /* * * ISpeechRecognitionCompilationResult @@ -171,6 +773,8 @@ struct session IAudioCaptureClient *capture_client; WAVEFORMATEX capture_wfx; + speech_recognizer_handle unix_handle; + HANDLE worker_thread, worker_control_event, audio_buf_event; BOOLEAN worker_running, worker_paused; CRITICAL_SECTION cs; @@ -187,15 +791,107 @@ static inline struct session *impl_from_ISpeechContinuousRecognitionSession( ISp return CONTAINING_RECORD(iface, struct session, ISpeechContinuousRecognitionSession_iface); } +static HRESULT session_find_constraint_by_string(struct session *session, WCHAR *str, HSTRING *hstr_out, ISpeechRecognitionConstraint **out) +{ + ISpeechRecognitionListConstraint *list_constraint; + IIterable_IInspectable *constraints_iterable; + IIterator_IInspectable *constraints_iterator; + ISpeechRecognitionConstraint *constraint; + IIterable_HSTRING *commands_iterable; + IIterator_HSTRING *commands_iterator; + BOOLEAN has_constraint, has_command; + IVector_HSTRING *commands; + const WCHAR *command_str; + HSTRING command; + HRESULT hr; + + TRACE("session %p, str %s, out %p.\n", session, debugstr_w(str), out); + + if (FAILED(hr = IVector_ISpeechRecognitionConstraint_QueryInterface(session->constraints, &IID_IIterable_ISpeechRecognitionConstraint, (void **)&constraints_iterable))) + return hr; + + if (FAILED(hr = IIterable_IInspectable_First(constraints_iterable, &constraints_iterator))) + { + IIterable_IInspectable_Release(constraints_iterable); + return hr; + } + + *out = NULL; + + for (hr = IIterator_IInspectable_get_HasCurrent(constraints_iterator, &has_constraint); SUCCEEDED(hr) && has_constraint && !(*out); hr = IIterator_IInspectable_MoveNext(constraints_iterator, &has_constraint)) + { + list_constraint = NULL; + commands_iterable = NULL; + commands_iterator = NULL; + commands = NULL; + + if (FAILED(IIterator_IInspectable_get_Current(constraints_iterator, (IInspectable **)&constraint))) + goto skip; + + if (FAILED(ISpeechRecognitionConstraint_QueryInterface(constraint, &IID_ISpeechRecognitionListConstraint, (void**)&list_constraint))) + goto skip; + + if (FAILED(ISpeechRecognitionListConstraint_get_Commands(list_constraint, &commands))) + goto skip; + + if (FAILED(IVector_HSTRING_QueryInterface(commands, &IID_IIterable_HSTRING, (void **)&commands_iterable))) + goto skip; + + if (FAILED(IIterable_HSTRING_First(commands_iterable, &commands_iterator))) + goto skip; + + for (hr = IIterator_HSTRING_get_HasCurrent(commands_iterator, &has_command); SUCCEEDED(hr) && has_command && !(*out); hr = IIterator_HSTRING_MoveNext(commands_iterator, &has_command)) + { + if (FAILED(IIterator_HSTRING_get_Current(commands_iterator, &command))) + continue; + + command_str = WindowsGetStringRawBuffer(command, NULL); + + TRACE("Comparing str %s to command_str %s.\n", debugstr_w(str), debugstr_w(command_str)); + + if (!wcsicmp(str, command_str)) + { + TRACE("constraint %p has str %s.\n", constraint, debugstr_w(str)); + ISpeechRecognitionConstraint_AddRef((*out = constraint)); + WindowsDuplicateString(command, hstr_out); + } + + WindowsDeleteString(command); + } + +skip: + if (commands_iterator) IIterator_HSTRING_Release(commands_iterator); + if (commands_iterable) IIterable_HSTRING_Release(commands_iterable); + if (commands) IVector_HSTRING_Release(commands); + + if (list_constraint) ISpeechRecognitionListConstraint_Release(list_constraint); + if (constraint) ISpeechRecognitionConstraint_Release(constraint); + } + + IIterator_IInspectable_Release(constraints_iterator); + IIterable_IInspectable_Release(constraints_iterable); + + hr = (*out) ? S_OK : COR_E_KEYNOTFOUND; + return hr; +} + static DWORD CALLBACK session_worker_thread_cb( void *args ) { ISpeechContinuousRecognitionSession *iface = args; struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + struct speech_get_recognition_result_params recognition_result_params; + struct speech_recognize_audio_params recognize_audio_params; + ISpeechContinuousRecognitionResultGeneratedEventArgs *event_args; + ISpeechRecognitionConstraint *constraint; + ISpeechRecognitionResult *result; BOOLEAN running = TRUE, paused = FALSE; UINT32 frame_count, tmp_buf_size; BYTE *audio_buf, *tmp_buf = NULL; + WCHAR *recognized_text; DWORD flags, status; + NTSTATUS nt_status; HANDLE events[2]; + HSTRING hstring; HRESULT hr; SetThreadDescription(GetCurrentThread(), L"wine_speech_recognition_session_worker"); @@ -245,6 +941,7 @@ static DWORD CALLBACK session_worker_thread_cb( void *args ) { SIZE_T packet_size = 0, tmp_buf_offset = 0; UINT32 frames_available = 0; + INT recognized_text_len = 0; while (tmp_buf_offset < tmp_buf_size && IAudioCaptureClient_GetBuffer(impl->capture_client, &audio_buf, &frames_available, &flags, NULL, NULL) == S_OK) @@ -264,7 +961,69 @@ static DWORD CALLBACK session_worker_thread_cb( void *args ) IAudioCaptureClient_ReleaseBuffer(impl->capture_client, frames_available); } - /* TODO: Send mic data to recognizer and handle results. */ + recognize_audio_params.handle = impl->unix_handle; + recognize_audio_params.samples = tmp_buf; + recognize_audio_params.samples_size = tmp_buf_offset; + recognize_audio_params.status = RECOGNITION_STATUS_EXCEPTION; + + if (NT_ERROR(nt_status = WINE_UNIX_CALL(unix_speech_recognize_audio, &recognize_audio_params))) + WARN("unix_speech_recognize_audio failed with status %#lx.\n", nt_status); + + if (recognize_audio_params.status != RECOGNITION_STATUS_RESULT_AVAILABLE) + continue; + + recognition_result_params.handle = impl->unix_handle; + recognition_result_params.result_buf = NULL; + recognition_result_params.result_buf_size = 512; + + do + { + recognition_result_params.result_buf = realloc(recognition_result_params.result_buf, recognition_result_params.result_buf_size); + } + while (WINE_UNIX_CALL(unix_speech_get_recognition_result, &recognition_result_params) == STATUS_BUFFER_TOO_SMALL && + recognition_result_params.result_buf); + + if (!recognition_result_params.result_buf) + { + WARN("memory allocation failed.\n"); + break; + } + + /* Silence was recognized. */ + if (!strcmp(recognition_result_params.result_buf, "")) + { + free(recognition_result_params.result_buf); + continue; + } + + recognized_text_len = MultiByteToWideChar(CP_UTF8, 0, recognition_result_params.result_buf, -1, NULL, 0); + + if (!(recognized_text = malloc(recognized_text_len * sizeof(WCHAR)))) + { + free(recognition_result_params.result_buf); + WARN("memory allocation failed.\n"); + break; + } + + MultiByteToWideChar(CP_UTF8, 0, recognition_result_params.result_buf, -1, recognized_text, recognized_text_len); + + if (SUCCEEDED(hr = session_find_constraint_by_string(impl, recognized_text, &hstring, &constraint))) + { + recognition_result_create(constraint, hstring, &result); + recognition_result_event_args_create(result, &event_args); + + typed_event_handlers_notify(&impl->result_handlers, + (IInspectable *)&impl->ISpeechContinuousRecognitionSession_iface, + (IInspectable *)event_args); + + ISpeechContinuousRecognitionResultGeneratedEventArgs_Release(event_args); + ISpeechRecognitionResult_Release(result); + WindowsDeleteString(hstring); + ISpeechRecognitionConstraint_Release(constraint); + } + + free(recognized_text); + free(recognition_result_params.result_buf); } else { @@ -319,7 +1078,9 @@ static ULONG WINAPI session_AddRef( ISpeechContinuousRecognitionSession *iface ) static ULONG WINAPI session_Release( ISpeechContinuousRecognitionSession *iface ) { struct session *impl = impl_from_ISpeechContinuousRecognitionSession(iface); + struct speech_release_recognizer_params release_params; ULONG ref = InterlockedDecrement(&impl->ref); + TRACE("iface %p, ref %lu.\n", iface, ref); if (!ref) @@ -345,6 +1106,9 @@ static ULONG WINAPI session_Release( ISpeechContinuousRecognitionSession *iface impl->cs.DebugInfo->Spare[0] = 0; DeleteCriticalSection(&impl->cs); + release_params.handle = impl->unix_handle; + WINE_UNIX_CALL(unix_speech_release_recognizer, &release_params); + IVector_ISpeechRecognitionConstraint_Release(impl->constraints); free(impl); } @@ -725,17 +1489,155 @@ static HRESULT WINAPI recognizer_get_UIOptions( ISpeechRecognizer *iface, ISpeec return E_NOTIMPL; } +static HRESULT recognizer_create_unix_instance( struct session *session, const char **grammar, UINT32 grammar_size ) +{ + struct speech_create_recognizer_params create_params = { 0 }; + WCHAR locale[LOCALE_NAME_MAX_LENGTH]; + NTSTATUS status; + INT len; + + if (!(len = GetUserDefaultLocaleName(locale, LOCALE_NAME_MAX_LENGTH))) + return E_FAIL; + + if (CharLowerBuffW(locale, len) != len) + return E_FAIL; + + if (!WideCharToMultiByte(CP_ACP, 0, locale, len, create_params.locale, ARRAY_SIZE(create_params.locale), NULL, NULL)) + return HRESULT_FROM_WIN32(GetLastError()); + + create_params.sample_rate = (FLOAT)session->capture_wfx.nSamplesPerSec; + create_params.grammar = grammar; + create_params.grammar_size = grammar_size; + + if ((status = WINE_UNIX_CALL(unix_speech_create_recognizer, &create_params))) + { + ERR("Unable to create Vosk instance for locale %s, status %#lx. Speech recognition won't work.\n", debugstr_a(create_params.locale), status); + return SPERR_WINRT_INTERNAL_ERROR; + } + + session->unix_handle = create_params.handle; + + return S_OK; +} + static HRESULT recognizer_compile_constraints_async( IInspectable *invoker, IInspectable **result ) { - return compilation_result_create(SpeechRecognitionResultStatus_Success, (ISpeechRecognitionCompilationResult **) result); + struct recognizer *impl = impl_from_ISpeechRecognizer((ISpeechRecognizer *)invoker); + struct session *session = impl_from_ISpeechContinuousRecognitionSession(impl->session); + struct speech_release_recognizer_params release_params; + ISpeechRecognitionListConstraint *list_constraint; + IIterable_IInspectable *constraints_iterable; + IIterator_IInspectable *constraints_iterator; + ISpeechRecognitionConstraint *constraint; + IIterable_HSTRING *commands_iterable; + IIterator_HSTRING *commands_iterator; + BOOLEAN has_constraint, has_command; + IVector_HSTRING *commands; + const WCHAR *command_str; + UINT32 grammar_size = 0, i = 0; + char **grammar = NULL; + HSTRING command; + UINT32 size = 0; + HRESULT hr; + + if (FAILED(hr = IVector_ISpeechRecognitionConstraint_QueryInterface(session->constraints, &IID_IIterable_ISpeechRecognitionConstraint, (void **)&constraints_iterable))) + return hr; + + if (FAILED(hr = IIterable_IInspectable_First(constraints_iterable, &constraints_iterator))) + { + IIterable_IInspectable_Release(constraints_iterable); + return hr; + } + + for (hr = IIterator_IInspectable_get_HasCurrent(constraints_iterator, &has_constraint); SUCCEEDED(hr) && has_constraint; hr = IIterator_IInspectable_MoveNext(constraints_iterator, &has_constraint)) + { + list_constraint = NULL; + commands_iterable = NULL; + commands_iterator = NULL; + commands = NULL; + + if (FAILED(IIterator_IInspectable_get_Current(constraints_iterator, (IInspectable **)&constraint))) + goto skip; + + if (FAILED(ISpeechRecognitionConstraint_QueryInterface(constraint, &IID_ISpeechRecognitionListConstraint, (void**)&list_constraint))) + goto skip; + + if (FAILED(ISpeechRecognitionListConstraint_get_Commands(list_constraint, &commands))) + goto skip; + + if (FAILED(IVector_HSTRING_QueryInterface(commands, &IID_IIterable_HSTRING, (void **)&commands_iterable))) + goto skip; + + if (FAILED(IIterable_HSTRING_First(commands_iterable, &commands_iterator))) + goto skip; + + if (FAILED(IVector_HSTRING_get_Size(commands, &size))) + goto skip; + + grammar_size += size; + grammar = realloc(grammar, grammar_size * sizeof(char *)); + + for (hr = IIterator_HSTRING_get_HasCurrent(commands_iterator, &has_command); SUCCEEDED(hr) && has_command; hr = IIterator_HSTRING_MoveNext(commands_iterator, &has_command)) + { + if (FAILED(IIterator_HSTRING_get_Current(commands_iterator, &command))) + continue; + + command_str = WindowsGetStringRawBuffer(command, NULL); + + if (command_str) + { + WCHAR *wstr = wcsdup(command_str); + size_t len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, grammar[i], 0, NULL, NULL); + grammar[i] = malloc(len * sizeof(char)); + + CharLowerW(wstr); + WideCharToMultiByte(CP_UTF8, 0, wstr, -1, grammar[i], len, NULL, NULL); + free(wstr); + i++; + } + + WindowsDeleteString(command); + } + +skip: + if (commands_iterator) IIterator_HSTRING_Release(commands_iterator); + if (commands_iterable) IIterable_HSTRING_Release(commands_iterable); + if (commands) IVector_HSTRING_Release(commands); + + if (list_constraint) ISpeechRecognitionListConstraint_Release(list_constraint); + if (constraint) ISpeechRecognitionConstraint_Release(constraint); + } + + IIterator_IInspectable_Release(constraints_iterator); + IIterable_IInspectable_Release(constraints_iterable); + + if (session->unix_handle) + { + release_params.handle = session->unix_handle; + WINE_UNIX_CALL(unix_speech_release_recognizer, &release_params); + session->unix_handle = 0; + } + + hr = recognizer_create_unix_instance(session, (const char **)grammar, grammar_size); + + for(i = 0; i < grammar_size; ++i) + free(grammar[i]); + free(grammar); + + if (FAILED(hr)) + { + WARN("Failed to created recognizer instance with grammar.\n"); + return compilation_result_create(SpeechRecognitionResultStatus_GrammarCompilationFailure, (ISpeechRecognitionCompilationResult **) result); + } + else return compilation_result_create(SpeechRecognitionResultStatus_Success, (ISpeechRecognitionCompilationResult **) result); } static HRESULT WINAPI recognizer_CompileConstraintsAsync( ISpeechRecognizer *iface, IAsyncOperation_SpeechRecognitionCompilationResult **operation ) { IAsyncOperation_IInspectable **value = (IAsyncOperation_IInspectable **)operation; - FIXME("iface %p, operation %p semi-stub!\n", iface, operation); - return async_operation_inspectable_create(&IID_IAsyncOperation_SpeechRecognitionCompilationResult, NULL, recognizer_compile_constraints_async, value); + TRACE("iface %p, operation %p semi-stub!\n", iface, operation); + return async_operation_inspectable_create(&IID_IAsyncOperation_SpeechRecognitionCompilationResult, (IInspectable *)iface, recognizer_compile_constraints_async, value); } static HRESULT WINAPI recognizer_RecognizeAsync( ISpeechRecognizer *iface, diff --git a/dlls/windows.media.speech/tests/speech.c b/dlls/windows.media.speech/tests/speech.c index ade056a0a39..a6c7d56e1e1 100644 --- a/dlls/windows.media.speech/tests/speech.c +++ b/dlls/windows.media.speech/tests/speech.c @@ -42,7 +42,6 @@ #define AsyncStatus_Closed 4 #define SPERR_WINRT_INTERNAL_ERROR 0x800455a0 -#define SPERR_WINRT_INCORRECT_FORMAT 0x80131537 #define IHandler_RecognitionResult ITypedEventHandler_SpeechContinuousRecognitionSession_SpeechContinuousRecognitionResultGeneratedEventArgs #define IHandler_RecognitionResultVtbl ITypedEventHandler_SpeechContinuousRecognitionSession_SpeechContinuousRecognitionResultGeneratedEventArgsVtbl @@ -203,7 +202,23 @@ HRESULT WINAPI recognition_result_handler_Invoke( IHandler_RecognitionResult *if ISpeechContinuousRecognitionSession *sender, ISpeechContinuousRecognitionResultGeneratedEventArgs *args ) { - trace("iface %p, sender %p, args %p.\n", iface, sender, args); + ISpeechRecognitionResult *result; + HSTRING hstring; + HRESULT hr; + + if (!args) return S_OK; + + hr = ISpeechContinuousRecognitionResultGeneratedEventArgs_get_Result(args, &result); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + hr = ISpeechRecognitionResult_get_Text(result, &hstring); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + trace("iface %p, sender %p, args %p, text %s.\n", iface, sender, args, debugstr_w(WindowsGetStringRawBuffer(hstring, NULL))); + + WindowsDeleteString(hstring); + ISpeechRecognitionResult_Release(result); + return S_OK; } @@ -1082,7 +1097,7 @@ static void test_SpeechSynthesizer(void) operation_ss_stream = (void *)0xdeadbeef; hr = ISpeechSynthesizer_SynthesizeSsmlToStreamAsync(synthesizer, str, &operation_ss_stream); /* Broken on Win 8 + 8.1 */ - ok(hr == S_OK || broken(hr == SPERR_WINRT_INCORRECT_FORMAT), "ISpeechSynthesizer_SynthesizeSsmlToStreamAsync failed, hr %#lx\n", hr); + ok(hr == S_OK || broken(hr == COR_E_FORMAT), "ISpeechSynthesizer_SynthesizeSsmlToStreamAsync failed, hr %#lx\n", hr); if (hr == S_OK) { @@ -1308,7 +1323,7 @@ static void test_SpeechRecognizer(void) ok(ref == 1, "Got unexpected ref %lu.\n", ref); hr = RoActivateInstance(hstr, &inspectable); - ok(hr == S_OK || broken(hr == SPERR_WINRT_INTERNAL_ERROR), "Got unexpected hr %#lx.\n", hr); + ok(hr == S_OK || hr == SPERR_WINRT_INTERNAL_ERROR, "Got unexpected hr %#lx.\n", hr); if (hr == S_OK) { @@ -1527,7 +1542,7 @@ static void test_SpeechRecognizer(void) } else if (hr == SPERR_WINRT_INTERNAL_ERROR) /* Not sure when this triggers. Probably if a language pack is not installed. */ { - win_skip("Could not init SpeechRecognizer with default language!\n"); + skip("Could not init SpeechRecognizer with default language!\n"); } done: @@ -1703,7 +1718,7 @@ static void test_Recognition(void) static const WCHAR *list_constraint_name = L"Windows.Media.SpeechRecognition.SpeechRecognitionListConstraint"; static const WCHAR *recognizer_name = L"Windows.Media.SpeechRecognition.SpeechRecognizer"; static const WCHAR *speech_constraint_tag = L"test_message"; - static const WCHAR *speech_constraints[] = { L"This is a test.", L"Number 5!", L"What time is it?" }; + static const WCHAR *speech_constraints[] = { L"This is a test", L"Number 5", L"What time is it" }; ISpeechRecognitionListConstraintFactory *listconstraint_factory = NULL; IAsyncOperation_SpeechRecognitionCompilationResult *operation = NULL; IVector_ISpeechRecognitionConstraint *constraints = NULL; @@ -1744,12 +1759,12 @@ static void test_Recognition(void) ok(hr == S_OK, "WindowsCreateString failed, hr %#lx.\n", hr); hr = RoActivateInstance(hstr, &inspectable); - ok(hr == S_OK || broken(hr == SPERR_WINRT_INTERNAL_ERROR || hr == REGDB_E_CLASSNOTREG), "Got unexpected hr %#lx.\n", hr); + ok(hr == S_OK || hr == SPERR_WINRT_INTERNAL_ERROR || broken(hr == REGDB_E_CLASSNOTREG), "Got unexpected hr %#lx.\n", hr); WindowsDeleteString(hstr); - if (FAILED(hr)) /* Win 8 and 8.1 and Win10 without enabled SR. */ + if (FAILED(hr)) /* Win 8 and 8.1 and Win10 without enabled SR. Wine with missing Unix side dependencies. */ { - win_skip("SpeechRecognizer cannot be activated!\n"); + skip("SpeechRecognizer cannot be activated!\n"); goto done; } @@ -1866,6 +1881,8 @@ static void test_Recognition(void) ok(hr == S_OK, "ISpeechRecognizer2_get_State failed, hr %#lx.\n", hr); ok(recog_state == SpeechRecognizerState_Capturing || broken(recog_state == SpeechRecognizerState_Idle), "recog_state was %u.\n", recog_state); + + Sleep(10000); /* * TODO: Use a loopback device together with prerecorded audio files to test the recognizer's functionality. */ diff --git a/dlls/windows.media.speech/unixlib.c b/dlls/windows.media.speech/unixlib.c new file mode 100644 index 00000000000..55e48a550ff --- /dev/null +++ b/dlls/windows.media.speech/unixlib.c @@ -0,0 +1,485 @@ +/* + * Unixlib for Windows.Media.Speech + * + * Copyright 2023 Bernhard Kölbl for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SONAME_LIBVOSK +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-prototypes" +#include +#pragma GCC diagnostic pop +#endif + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "winerror.h" +#include "winternl.h" + +#include "wine/debug.h" + +#include "unixlib.h" + +WINE_DEFAULT_DEBUG_CHANNEL(speech); +#ifdef SONAME_LIBVOSK +WINE_DECLARE_DEBUG_CHANNEL(winediag); + +static void *vosk_handle; +#define MAKE_FUNCPTR( f ) static typeof(f) * p_##f +MAKE_FUNCPTR(vosk_model_new); +MAKE_FUNCPTR(vosk_model_free); +MAKE_FUNCPTR(vosk_recognizer_new); +MAKE_FUNCPTR(vosk_recognizer_new_grm); +MAKE_FUNCPTR(vosk_recognizer_free); +MAKE_FUNCPTR(vosk_recognizer_accept_waveform); +MAKE_FUNCPTR(vosk_recognizer_final_result); +MAKE_FUNCPTR(vosk_recognizer_reset); +#undef MAKE_FUNCPTR + +static NTSTATUS process_attach( void *args ) +{ + if (!(vosk_handle = dlopen(SONAME_LIBVOSK, RTLD_NOW))) + { + ERR_(winediag)("Wine is unable to load the Unix side dependencies for speech recognition. " + "Make sure Vosk is installed and up to date on your system and try again.\n"); + return STATUS_DLL_NOT_FOUND; + } + +#define LOAD_FUNCPTR( f ) \ + if (!(p_##f = dlsym(vosk_handle, #f))) \ + { \ + ERR("failed to load %s\n", #f); \ + goto error; \ + } + LOAD_FUNCPTR(vosk_model_new) + LOAD_FUNCPTR(vosk_recognizer_new) + LOAD_FUNCPTR(vosk_recognizer_new_grm) + LOAD_FUNCPTR(vosk_model_free) + LOAD_FUNCPTR(vosk_recognizer_new) + LOAD_FUNCPTR(vosk_recognizer_free) + LOAD_FUNCPTR(vosk_recognizer_accept_waveform) + LOAD_FUNCPTR(vosk_recognizer_final_result) + LOAD_FUNCPTR(vosk_recognizer_reset) +#undef LOAD_FUNCPTR + + return STATUS_SUCCESS; + +error: + dlclose(vosk_handle); + vosk_handle = NULL; + return STATUS_DLL_NOT_FOUND; +} + +static NTSTATUS process_detach( void *args ) +{ + if (vosk_handle) + { + dlclose(vosk_handle); + vosk_handle = NULL; + } + return STATUS_SUCCESS; +} + +static inline speech_recognizer_handle vosk_recognizer_to_handle( VoskRecognizer *recognizer ) +{ + return (speech_recognizer_handle)(UINT_PTR)recognizer; +} + +static inline VoskRecognizer *vosk_recognizer_from_handle( speech_recognizer_handle handle ) +{ + return (VoskRecognizer *)(UINT_PTR)handle; +} + +static const char* map_lang_to_phasmophobia_dir(const char* lang, size_t len) +{ + if (!strncmp(lang, "ar", len)) + return "Arabic"; + if (!strncmp(lang, "ca", len)) + return "Catalan"; + if (!strncmp(lang, "zn", len)) + return "Chinese"; + if (!strncmp(lang, "cs", len)) + return "Czech"; + if (!strncmp(lang, "nl", len)) + return "Dutch"; + if (!strncmp(lang, "en", len)) + return "English"; + if (!strncmp(lang, "fr", len)) + return "French"; + if (!strncmp(lang, "de", len)) + return "German"; + if (!strncmp(lang, "de", len)) + return "German"; + if (!strncmp(lang, "el", len)) + return "Greek"; + if (!strncmp(lang, "it", len)) + return "Italian"; + if (!strncmp(lang, "ja", len)) + return "Japanese"; + if (!strncmp(lang, "pt", len)) + return "Portuguese"; + if (!strncmp(lang, "ru", len)) + return "Russian"; + if (!strncmp(lang, "es", len)) + return "Spanish"; + if (!strncmp(lang, "sw", len)) + return "Swedish"; + if (!strncmp(lang, "tr", len)) + return "Turkish"; + if (!strncmp(lang, "uk", len)) + return "Ukrainian"; + + return ""; +} + +static NTSTATUS find_model_by_locale_and_path( const char *path, const char *locale, VoskModel **model ) +{ + static const char *vosk_model_identifier_small = "vosk-model-small-"; + static const char *vosk_model_identifier = "vosk-model-"; + size_t ident_small_len = strlen(vosk_model_identifier_small); + size_t ident_len = strlen(vosk_model_identifier); + char *ent_name, *model_path, *best_match, *delim, *appid = getenv("SteamAppId"), *str = NULL; + NTSTATUS status = STATUS_UNSUCCESSFUL; + struct dirent *dirent; + size_t path_len, len; + DIR *dir; + + TRACE("path %s, locale %s, model %p.\n", path, debugstr_a(locale), model); + + if (!path || !locale || (len = strlen(locale)) < 4) + return STATUS_UNSUCCESSFUL; + + if (!(dir = opendir(path))) + return STATUS_UNSUCCESSFUL; + + delim = strchr(locale, '-'); + path_len = strlen(path); + best_match = NULL; + *model = NULL; + + while ((dirent = readdir(dir))) + { + ent_name = dirent->d_name; + + if (!strncmp(ent_name, vosk_model_identifier_small, ident_small_len)) + ent_name += ident_small_len; + else if (!strncmp(ent_name, vosk_model_identifier, ident_len)) + ent_name += ident_len; + else if (strcmp(appid, "739630") != 0) + continue; + + /* + * Find the first matching model for lang and region (en-us). + * If there isn't any, pick the first one just matching lang (en). + */ + if (!strncmp(ent_name, locale, len)) + { + if (best_match) free(best_match); + best_match = strdup(dirent->d_name); + break; + } + + if (!best_match && !strncmp(ent_name, locale, delim - locale)) + best_match = strdup(dirent->d_name); + + if (!best_match && !strcmp(appid, "739630")) + { + if ((str = (char *)map_lang_to_phasmophobia_dir(locale, delim - locale))) + best_match = strdup(str); + } + } + + closedir(dir); + + if (!best_match) + return STATUS_UNSUCCESSFUL; + + if (!(model_path = malloc(path_len + 1 /* '/' */ + strlen(best_match) + 1))) + { + status = STATUS_NO_MEMORY; + goto done; + } + + sprintf(model_path, "%s/%s", path, best_match); + + TRACE("trying to load Vosk model %s.\n", debugstr_a(model_path)); + + if ((*model = p_vosk_model_new(model_path)) != NULL) + status = STATUS_SUCCESS; + +done: + free(model_path); + free(best_match); + + return status; +} + +static NTSTATUS find_model_by_locale( const char *locale, VoskModel **model ) +{ + const char *suffix = NULL; + char *env, *path = NULL, *appid = getenv("SteamAppId"); + NTSTATUS status; + + TRACE("locale %s, model %p.\n", debugstr_a(locale), model); + + if (!model) + return STATUS_UNSUCCESSFUL; + + if (!find_model_by_locale_and_path(getenv("VOSK_MODEL_PATH"), locale, model)) + return STATUS_SUCCESS; + if (!find_model_by_locale_and_path("/usr/share/vosk", locale, model)) + return STATUS_SUCCESS; + + if ((env = getenv("XDG_CACHE_HOME"))) + suffix = "/vosk"; + else if ((env = getenv("HOME"))) + suffix = "/.cache/vosk"; + else + return STATUS_UNSUCCESSFUL; + + if (!(path = malloc(strlen(env) + strlen(suffix) + 1))) + return STATUS_NO_MEMORY; + + sprintf(path, "%s%s", env, suffix); + status = find_model_by_locale_and_path(path, locale, model); + free(path); + + /* Hack to load Vosk models from Phasmophobia, so they don't need to be downloaded separately.*/ + if (status && appid && !strcmp(appid, "739630") && (env = getenv("PWD"))) + { + suffix = "/Phasmophobia_Data/StreamingAssets/LanguageModels"; + + if (!(path = malloc(strlen(env) + strlen(suffix) + 1))) + return STATUS_NO_MEMORY; + + sprintf(path, "%s%s", env, suffix); + status = find_model_by_locale_and_path(path, locale, model); + free(path); + } + + return status; +} + +static NTSTATUS grammar_to_json_array(const char **grammar, UINT32 grammar_size, const char **array) +{ + size_t buf_size = strlen("[]") + 1, len; + char *buf; + UINT32 i; + + for (i = 0; i < grammar_size; ++i) + { + buf_size += strlen(grammar[i]) + 4; /* (4) - two double quotes, a comma and a space */ + } + + if (!(buf = malloc(buf_size))) + return STATUS_NO_MEMORY; + + *array = buf; + + *buf = '['; + buf++; + + for (i = 0; i < grammar_size; ++i) + { + *buf = '\"'; + buf++; + len = strlen(grammar[i]); + memcpy(buf, grammar[i], len); + buf += len; + *buf = '\"'; + buf++; + if (i < (grammar_size - 1)) + { + *buf = ','; + buf++; + *buf = ' '; + buf++; + } + } + + *buf = ']'; + buf++; + *buf = '\0'; + + return STATUS_SUCCESS; +} + +static NTSTATUS speech_create_recognizer( void *args ) +{ + struct speech_create_recognizer_params *params = args; + VoskRecognizer *recognizer = NULL; + VoskModel *model = NULL; + NTSTATUS status = STATUS_SUCCESS; + const char *grammar_json; + + TRACE("args %p.\n", args); + + if (!vosk_handle) + return STATUS_NOT_SUPPORTED; + + if ((status = find_model_by_locale(params->locale, &model))) + return status; + + if (params->grammar && grammar_to_json_array(params->grammar, params->grammar_size, &grammar_json) == STATUS_SUCCESS) + { + if (!(recognizer = p_vosk_recognizer_new_grm(model, params->sample_rate, grammar_json))) + status = STATUS_UNSUCCESSFUL; + } + else + { + if (!(recognizer = p_vosk_recognizer_new(model, params->sample_rate))) + status = STATUS_UNSUCCESSFUL; + } + + /* VoskModel is reference-counted. A VoskRecognizer keeps a reference to its model. */ + p_vosk_model_free(model); + + params->handle = vosk_recognizer_to_handle(recognizer); + return status; +} + +static NTSTATUS speech_release_recognizer( void *args ) +{ + struct speech_release_recognizer_params *params = args; + + TRACE("args %p.\n", args); + + if (!vosk_handle) + return STATUS_NOT_SUPPORTED; + + p_vosk_recognizer_free(vosk_recognizer_from_handle(params->handle)); + + return STATUS_SUCCESS; +} + +static NTSTATUS speech_recognize_audio( void *args ) +{ + struct speech_recognize_audio_params *params = args; + VoskRecognizer *recognizer = vosk_recognizer_from_handle(params->handle); + + if (!vosk_handle) + return STATUS_NOT_SUPPORTED; + + if (!recognizer) + return STATUS_UNSUCCESSFUL; + + params->status = p_vosk_recognizer_accept_waveform(recognizer, (const char *)params->samples, params->samples_size); + + return STATUS_SUCCESS; +} + +static NTSTATUS speech_get_recognition_result( void* args ) +{ + struct speech_get_recognition_result_params *params = args; + VoskRecognizer *recognizer = vosk_recognizer_from_handle(params->handle); + static const char *result_json_start = "{\n \"text\" : \""; + const size_t json_start_len = strlen(result_json_start); + static size_t last_result_len = 0; + static char *last_result = NULL; + const char *tmp = NULL; + + if (!vosk_handle) + return STATUS_NOT_SUPPORTED; + + if (!recognizer) + return STATUS_UNSUCCESSFUL; + + if (!last_result) + { + if ((tmp = p_vosk_recognizer_final_result(recognizer))) + { + last_result = strdup(tmp); + tmp = last_result; + + /* Operations to remove the JSON wrapper "{\n \"text\" : \"some recognized text\"\n}" -> "some recognized text\0" */ + memmove(last_result, last_result + json_start_len, strlen(last_result) - json_start_len + 1); + last_result = strrchr(last_result, '\"'); + last_result[0] = '\0'; + + last_result = (char *)tmp; + last_result_len = strlen(last_result); + } + else return STATUS_NOT_FOUND; + } + else if (params->result_buf_size >= last_result_len + 1) + { + memcpy(params->result_buf, last_result, last_result_len + 1); + p_vosk_recognizer_reset(recognizer); + + free (last_result); + last_result = NULL; + + return STATUS_SUCCESS; + } + + params->result_buf_size = last_result_len + 1; + return STATUS_BUFFER_TOO_SMALL; +} + +#else /* SONAME_LIBVOSK */ + +#define MAKE_UNSUPPORTED_FUNC( f ) \ + static NTSTATUS f( void *args ) \ + { \ + ERR("wine was compiled without Vosk support. Speech recognition won't work.\n"); \ + return STATUS_NOT_SUPPORTED; \ + } + +MAKE_UNSUPPORTED_FUNC(process_attach) +MAKE_UNSUPPORTED_FUNC(process_detach) +MAKE_UNSUPPORTED_FUNC(speech_create_recognizer) +MAKE_UNSUPPORTED_FUNC(speech_release_recognizer) +MAKE_UNSUPPORTED_FUNC(speech_recognize_audio) +MAKE_UNSUPPORTED_FUNC(speech_get_recognition_result) +#undef MAKE_UNSUPPORTED_FUNC + +#endif /* SONAME_LIBVOSK */ + +const unixlib_entry_t __wine_unix_call_funcs[] = +{ + process_attach, + process_detach, + speech_create_recognizer, + speech_release_recognizer, + speech_recognize_audio, + speech_get_recognition_result, +}; + +const unixlib_entry_t __wine_unix_call_wow64_funcs[] = +{ + process_attach, + process_detach, + speech_create_recognizer, + speech_release_recognizer, + speech_recognize_audio, + speech_get_recognition_result, +}; diff --git a/dlls/windows.media.speech/unixlib.h b/dlls/windows.media.speech/unixlib.h new file mode 100644 index 00000000000..ad2fab738b9 --- /dev/null +++ b/dlls/windows.media.speech/unixlib.h @@ -0,0 +1,81 @@ +/* + * Unix library interface for Windows.Media.Speech + * + * Copyright 2023 Bernhard Kölbl for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef __WINE_WINDOWS_MEDIA_SPEECH_UNIXLIB_H +#define __WINE_WINDOWS_MEDIA_SPEECH_UNIXLIB_H + +#include +#include + +#include "windef.h" +#include "winternl.h" +#include "wtypes.h" + +#include "wine/unixlib.h" + +typedef UINT64 speech_recognizer_handle; + +struct speech_create_recognizer_params +{ + speech_recognizer_handle handle; + CHAR locale[LOCALE_NAME_MAX_LENGTH]; + FLOAT sample_rate; + const char **grammar; + unsigned int grammar_size; +}; + +struct speech_release_recognizer_params +{ + speech_recognizer_handle handle; +}; + +enum speech_recognition_status +{ + RECOGNITION_STATUS_CONTINUING, + RECOGNITION_STATUS_RESULT_AVAILABLE, + RECOGNITION_STATUS_EXCEPTION, +}; + +struct speech_recognize_audio_params +{ + speech_recognizer_handle handle; + const BYTE *samples; + UINT32 samples_size; + enum speech_recognition_status status; +}; + +struct speech_get_recognition_result_params +{ + speech_recognizer_handle handle; + char *result_buf; + UINT32 result_buf_size; +}; + +enum vosk_funcs +{ + unix_process_attach, + unix_process_detach, + unix_speech_create_recognizer, + unix_speech_release_recognizer, + unix_speech_recognize_audio, + unix_speech_get_recognition_result, +}; + +#endif diff --git a/dlls/windows.perception.stub/Makefile.in b/dlls/windows.perception.stub/Makefile.in index 18c90e697c4..9583b64bb89 100644 --- a/dlls/windows.perception.stub/Makefile.in +++ b/dlls/windows.perception.stub/Makefile.in @@ -1,5 +1,5 @@ MODULE = windows.perception.stub.dll -IMPORTS = combase +IMPORTS = combase user32 SOURCES = \ classes.idl \ diff --git a/dlls/windows.perception.stub/holographicspace.c b/dlls/windows.perception.stub/holographicspace.c index 8a12147f4a8..f51be10a9c1 100644 --- a/dlls/windows.perception.stub/holographicspace.c +++ b/dlls/windows.perception.stub/holographicspace.c @@ -27,6 +27,7 @@ struct holographicspace IActivationFactory IActivationFactory_iface; IHolographicSpaceStatics2 IHolographicSpaceStatics2_iface; IHolographicSpaceStatics3 IHolographicSpaceStatics3_iface; + IHolographicSpaceInterop IHolographicSpaceInterop_iface; LONG ref; }; @@ -65,6 +66,13 @@ static HRESULT WINAPI factory_QueryInterface( IActivationFactory *iface, REFIID return S_OK; } + if (IsEqualGUID( iid, &IID_IHolographicSpaceInterop )) + { + *out = &impl->IHolographicSpaceInterop_iface; + IInspectable_AddRef( *out ); + return S_OK; + } + FIXME( "%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid( iid ) ); *out = NULL; return E_NOINTERFACE; @@ -193,11 +201,39 @@ static const struct IHolographicSpaceStatics3Vtbl holographicspace_statics3_vtbl holographicspace_statics3_get_IsConfigured, }; +DEFINE_IINSPECTABLE( holographicspace_interop, IHolographicSpaceInterop, struct holographicspace, IActivationFactory_iface ) + +static HRESULT WINAPI holographicspace_interop_CreateForWindow( IHolographicSpaceInterop *iface, + HWND window, REFIID iid, void **holographic_space ) +{ + FIXME( "iface %p, window %p, iid %s, holographic_space %p.\n", iface, window, debugstr_guid( iid ), holographic_space ); + + FIXME( "HACK: Setting WS_EX_NOACTIVATE for %p.\n", window ); + SetWindowLongW( window, GWL_EXSTYLE, WS_EX_NOACTIVATE ); + + *holographic_space = NULL; + return E_NOTIMPL; +} + +static const struct IHolographicSpaceInteropVtbl holographicspace_interop_vtbl = +{ + holographicspace_interop_QueryInterface, + holographicspace_interop_AddRef, + holographicspace_interop_Release, + /* IInspectable methods */ + holographicspace_interop_GetIids, + holographicspace_interop_GetRuntimeClassName, + holographicspace_interop_GetTrustLevel, + /* IHolographicSpaceInterop methods */ + holographicspace_interop_CreateForWindow, +}; + static struct holographicspace holographicspace_statics = { {&factory_vtbl}, {&holographicspace_statics2_vtbl}, {&holographicspace_statics3_vtbl}, + {&holographicspace_interop_vtbl}, 1, }; diff --git a/dlls/windows.perception.stub/private.h b/dlls/windows.perception.stub/private.h index a6ae62916e1..ceaa2944174 100644 --- a/dlls/windows.perception.stub/private.h +++ b/dlls/windows.perception.stub/private.h @@ -36,6 +36,7 @@ #include "windows.perception.spatial.surfaces.h" #define WIDL_using_Windows_Graphics_Holographic #include "windows.graphics.holographic.h" +#include "holographicspaceinterop.h" extern IActivationFactory *observer_factory; extern IActivationFactory *holographicspace_factory; diff --git a/dlls/windows.perception.stub/tests/perception.c b/dlls/windows.perception.stub/tests/perception.c index d0bee4eb247..8714f44be4a 100644 --- a/dlls/windows.perception.stub/tests/perception.c +++ b/dlls/windows.perception.stub/tests/perception.c @@ -32,6 +32,7 @@ #include "windows.perception.spatial.surfaces.h" #define WIDL_using_Windows_Graphics_Holographic #include "windows.graphics.holographic.h" +#include "holographicspaceinterop.h" #include "wine/test.h" @@ -121,6 +122,7 @@ static void test_HolographicSpaceStatics(void) check_interface( factory, &IID_IUnknown ); check_interface( factory, &IID_IInspectable ); check_interface( factory, &IID_IAgileObject ); + check_interface( factory, &IID_IHolographicSpaceInterop ); hr = IActivationFactory_QueryInterface( factory, &IID_IHolographicSpaceStatics2, (void **)&holographicspace_statics2 ); if (hr == E_NOINTERFACE) /* win1607 */ diff --git a/dlls/winebus.sys/bus_sdl.c b/dlls/winebus.sys/bus_sdl.c index 674da28f8e4..f37c2593f2b 100644 --- a/dlls/winebus.sys/bus_sdl.c +++ b/dlls/winebus.sys/bus_sdl.c @@ -115,7 +115,7 @@ MAKE_FUNCPTR(SDL_RegisterEvents); MAKE_FUNCPTR(SDL_PushEvent); MAKE_FUNCPTR(SDL_GetTicks); MAKE_FUNCPTR(SDL_LogSetPriority); -MAKE_FUNCPTR(SDL_SetHint); +MAKE_FUNCPTR(SDL_SetHintWithPriority); static int (*pSDL_JoystickRumble)(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms); static int (*pSDL_JoystickRumbleTriggers)(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble, Uint32 duration_ms); static Uint16 (*pSDL_JoystickGetProduct)(SDL_Joystick * joystick); @@ -964,7 +964,8 @@ static void sdl_add_device(unsigned int index) SDL_JoystickID id; SDL_JoystickType joystick_type; SDL_GameController *controller = NULL; - const char *product, *sdl_serial, *str; + const char *product, *sdl_serial, *str, *env; + BOOL expose_steam_controller = (env = getenv("PROTON_EXPOSE_STEAM_CONTROLLER")) && atoi(env) == 1; char guid_str[33], buffer[ARRAY_SIZE(desc.product)]; int axis_count, axis_offset; @@ -1013,7 +1014,7 @@ static void sdl_add_device(unsigned int index) return; } - if (desc.vid == 0x28de && desc.pid == 0x11ff) + if (desc.vid == 0x28de && desc.pid == 0x11ff && !expose_steam_controller) { TRACE("Steam virtual controller, pretending it's an Xbox 360 controller\n"); desc.vid = 0x045e; @@ -1021,8 +1022,12 @@ static void sdl_add_device(unsigned int index) } /* CW-Bug-Id: #20528 Check steam virtual controller indexes to keep them ordered */ - if ((str = pSDL_JoystickName(joystick)) && sscanf(str, "Microsoft X-Box 360 pad %u", &desc.input) == 1) desc.input++; - else desc.input = -1; + /* CW-Bug-Id: #23185 Emulate Steam Input native hooks for native SDL */ + if ((str = pSDL_JoystickName(joystick)) && sscanf(str, "Microsoft X-Box 360 pad %u", &desc.input) == 1) + { + if (!expose_steam_controller) desc.input++; + desc.version = 0; /* keep version fixed as 0 so we can hardcode it in ntdll rawinput pipe redirection */ + } if (pSDL_JoystickGetSerial && (sdl_serial = pSDL_JoystickGetSerial(joystick))) { @@ -1033,8 +1038,15 @@ static void sdl_add_device(unsigned int index) /* Overcooked! All You Can Eat only adds controllers with unique serial numbers * Prefer keeping serial numbers unique over keeping them consistent across runs */ pSDL_JoystickGetGUIDString(pSDL_JoystickGetGUID(joystick), guid_str, sizeof(guid_str)); - snprintf(buffer, sizeof(buffer), "%s.%d", guid_str, index); - TRACE("Making up serial number for %s: %s\n", product, buffer); + + /* CW-Bug-Id: #23185 Emulate Steam Input native hooks for native SDL */ + if (desc.input != -1) snprintf(buffer, sizeof(buffer), "%s", guid_str); + else + { + snprintf(buffer, sizeof(buffer), "%s.%d", guid_str, index); + TRACE("Making up serial number for %s: %s\n", product, buffer); + } + ntdll_umbstowcs(buffer, strlen(buffer) + 1, desc.serialnumber, ARRAY_SIZE(desc.serialnumber)); } @@ -1185,7 +1197,7 @@ NTSTATUS sdl_bus_init(void *args) LOAD_FUNCPTR(SDL_PushEvent); LOAD_FUNCPTR(SDL_GetTicks); LOAD_FUNCPTR(SDL_LogSetPriority); - LOAD_FUNCPTR(SDL_SetHint); + LOAD_FUNCPTR(SDL_SetHintWithPriority); #undef LOAD_FUNCPTR pSDL_JoystickRumble = dlsym(sdl_handle, "SDL_JoystickRumble"); pSDL_JoystickRumbleTriggers = dlsym(sdl_handle, "SDL_JoystickRumbleTriggers"); @@ -1197,7 +1209,7 @@ NTSTATUS sdl_bus_init(void *args) /* CW-Bug-Id: #23185: Disable SDL 2.30 new behavior, we need the steam virtual * controller name to figure which slot number it represents. */ - pSDL_SetHint("SteamVirtualGamepadInfo", ""); + pSDL_SetHintWithPriority("SteamVirtualGamepadInfo", "", SDL_HINT_OVERRIDE); if (pSDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) < 0) { diff --git a/dlls/winegstreamer/Makefile.in b/dlls/winegstreamer/Makefile.in index 02b1b04fdf9..f76a7fb35f2 100644 --- a/dlls/winegstreamer/Makefile.in +++ b/dlls/winegstreamer/Makefile.in @@ -7,13 +7,13 @@ UNIX_CFLAGS = $(GSTREAMER_CFLAGS) UNIX_LIBS = $(GSTREAMER_LIBS) $(PTHREAD_LIBS) SOURCES = \ - aac_decoder.c \ + audio_decoder.c \ color_convert.c \ - h264_decoder.c \ main.c \ media_sink.c \ media_source.c \ mfplat.c \ + new_media_source.c \ quartz_parser.c \ quartz_transform.c \ resampler.c \ @@ -26,8 +26,17 @@ SOURCES = \ wg_muxer.c \ wg_parser.c \ wg_sample.c \ + wg_source.c \ + wg_task_pool.c \ wg_transform.c \ winegstreamer_classes.idl \ wm_reader.c \ wma_decoder.c \ - wmv_decoder.c + wmv_decoder.c \ + media-converter/audioconv.c \ + media-converter/audioconvbin.c \ + media-converter/fossilize.c \ + media-converter/lib.c \ + media-converter/murmur3.c \ + media-converter/protondemuxer.c \ + media-converter/videoconv.c diff --git a/dlls/winegstreamer/aac_decoder.c b/dlls/winegstreamer/audio_decoder.c similarity index 68% rename from dlls/winegstreamer/aac_decoder.c rename to dlls/winegstreamer/audio_decoder.c index dfab7a8b0c0..771eae465fe 100644 --- a/dlls/winegstreamer/aac_decoder.c +++ b/dlls/winegstreamer/audio_decoder.c @@ -32,22 +32,12 @@ WINE_DEFAULT_DEBUG_CHANNEL(mfplat); WINE_DECLARE_DEBUG_CHANNEL(winediag); -static struct -{ - const GUID *const guid; - UINT32 payload_type; -} aac_decoder_input_types[] = -{ - {&MFAudioFormat_AAC, 0}, - {&MFAudioFormat_RAW_AAC, -1}, - {&MFAudioFormat_AAC, 1}, - {&MFAudioFormat_AAC, 3}, - {&MFAudioFormat_ADTS, -1}, -}; -static const GUID *const aac_decoder_output_types[] = +#define NEXT_WAVEFORMATEXTENSIBLE(format) (WAVEFORMATEXTENSIBLE *)((BYTE *)(&(format)->Format + 1) + (format)->Format.cbSize) + +static WAVEFORMATEXTENSIBLE const audio_decoder_output_types[] = { - &MFAudioFormat_PCM, - &MFAudioFormat_Float, + {.Format = {.wFormatTag = WAVE_FORMAT_IEEE_FLOAT, .wBitsPerSample = 32, .nSamplesPerSec = 48000, .nChannels = 2}}, + {.Format = {.wFormatTag = WAVE_FORMAT_PCM, .wBitsPerSample = 16, .nSamplesPerSec = 48000, .nChannels = 2}}, }; static const UINT32 default_channel_mask[7] = @@ -61,10 +51,14 @@ static const UINT32 default_channel_mask[7] = KSAUDIO_SPEAKER_5POINT1, }; -struct aac_decoder +struct audio_decoder { IMFTransform IMFTransform_iface; LONG refcount; + + UINT input_type_count; + WAVEFORMATEXTENSIBLE *input_types; + IMFMediaType *input_type; IMFMediaType *output_type; @@ -72,12 +66,12 @@ struct aac_decoder struct wg_sample_queue *wg_sample_queue; }; -static struct aac_decoder *impl_from_IMFTransform(IMFTransform *iface) +static struct audio_decoder *impl_from_IMFTransform(IMFTransform *iface) { - return CONTAINING_RECORD(iface, struct aac_decoder, IMFTransform_iface); + return CONTAINING_RECORD(iface, struct audio_decoder, IMFTransform_iface); } -static HRESULT try_create_wg_transform(struct aac_decoder *decoder) +static HRESULT try_create_wg_transform(struct audio_decoder *decoder) { struct wg_format input_format, output_format; struct wg_transform_attrs attrs = {0}; @@ -102,7 +96,7 @@ static HRESULT try_create_wg_transform(struct aac_decoder *decoder) static HRESULT WINAPI transform_QueryInterface(IMFTransform *iface, REFIID iid, void **out) { - struct aac_decoder *decoder = impl_from_IMFTransform(iface); + struct audio_decoder *decoder = impl_from_IMFTransform(iface); TRACE("iface %p, iid %s, out %p.\n", iface, debugstr_guid(iid), out); @@ -121,7 +115,7 @@ static HRESULT WINAPI transform_QueryInterface(IMFTransform *iface, REFIID iid, static ULONG WINAPI transform_AddRef(IMFTransform *iface) { - struct aac_decoder *decoder = impl_from_IMFTransform(iface); + struct audio_decoder *decoder = impl_from_IMFTransform(iface); ULONG refcount = InterlockedIncrement(&decoder->refcount); TRACE("iface %p increasing refcount to %lu.\n", decoder, refcount); return refcount; @@ -129,7 +123,7 @@ static ULONG WINAPI transform_AddRef(IMFTransform *iface) static ULONG WINAPI transform_Release(IMFTransform *iface) { - struct aac_decoder *decoder = impl_from_IMFTransform(iface); + struct audio_decoder *decoder = impl_from_IMFTransform(iface); ULONG refcount = InterlockedDecrement(&decoder->refcount); TRACE("iface %p decreasing refcount to %lu.\n", decoder, refcount); @@ -234,141 +228,77 @@ static HRESULT WINAPI transform_AddInputStreams(IMFTransform *iface, DWORD strea static HRESULT WINAPI transform_GetInputAvailableType(IMFTransform *iface, DWORD id, DWORD index, IMFMediaType **type) { - IMFMediaType *media_type; - const GUID *subtype; - HRESULT hr; + struct audio_decoder *decoder = impl_from_IMFTransform(iface); + const WAVEFORMATEXTENSIBLE *format = decoder->input_types; + UINT count = decoder->input_type_count; TRACE("iface %p, id %#lx, index %#lx, type %p.\n", iface, id, index, type); + *type = NULL; if (id) return MF_E_INVALIDSTREAMNUMBER; - - *type = NULL; - if (index >= ARRAY_SIZE(aac_decoder_input_types)) - return MF_E_NO_MORE_TYPES; - subtype = aac_decoder_input_types[index].guid; - - if (FAILED(hr = MFCreateMediaType(&media_type))) - return hr; - - if (FAILED(hr = IMFMediaType_SetGUID(media_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Audio))) - goto done; - if (FAILED(hr = IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, subtype))) - goto done; - - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_BITS_PER_SAMPLE, 32))) - goto done; - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_NUM_CHANNELS, 6))) - goto done; - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_BLOCK_ALIGNMENT, 24))) - goto done; - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, 48000))) - goto done; - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_AVG_BYTES_PER_SECOND, 1152000))) - goto done; - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_PREFER_WAVEFORMATEX, 1))) - goto done; - if (IsEqualGUID(subtype, &MFAudioFormat_AAC)) - { - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AAC_AUDIO_PROFILE_LEVEL_INDICATION, 0))) - goto done; - if (aac_decoder_input_types[index].payload_type != -1 - && FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AAC_PAYLOAD_TYPE, - aac_decoder_input_types[index].payload_type))) - goto done; - } - -done: - if (SUCCEEDED(hr)) - IMFMediaType_AddRef((*type = media_type)); - - IMFMediaType_Release(media_type); - return hr; + for (format = decoder->input_types; index > 0 && count > 0; index--, count--) + format = NEXT_WAVEFORMATEXTENSIBLE(format); + return count ? MFCreateAudioMediaType(&format->Format, (IMFAudioMediaType **)type) : MF_E_NO_MORE_TYPES; } static HRESULT WINAPI transform_GetOutputAvailableType(IMFTransform *iface, DWORD id, DWORD index, IMFMediaType **type) { - UINT32 channel_count, sample_size, sample_rate, block_alignment; - struct aac_decoder *decoder = impl_from_IMFTransform(iface); + struct audio_decoder *decoder = impl_from_IMFTransform(iface); + UINT32 channel_count, sample_rate; + WAVEFORMATEXTENSIBLE wfx = {{0}}; IMFMediaType *media_type; - const GUID *output_type; HRESULT hr; TRACE("iface %p, id %#lx, index %#lx, type %p.\n", iface, id, index, type); + *type = NULL; + if (id) return MF_E_INVALIDSTREAMNUMBER; if (!decoder->input_type) return MF_E_TRANSFORM_TYPE_NOT_SET; - *type = NULL; + wfx = audio_decoder_output_types[index % ARRAY_SIZE(audio_decoder_output_types)]; if (FAILED(hr = IMFMediaType_GetUINT32(decoder->input_type, &MF_MT_AUDIO_NUM_CHANNELS, &channel_count)) || !channel_count) - channel_count = 2; + channel_count = wfx.Format.nChannels; + if (FAILED(hr = IMFMediaType_GetUINT32(decoder->input_type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, &sample_rate))) + sample_rate = wfx.Format.nSamplesPerSec; if (channel_count >= ARRAY_SIZE(default_channel_mask)) return MF_E_INVALIDMEDIATYPE; - if (channel_count > 2 && index >= ARRAY_SIZE(aac_decoder_output_types)) + if (channel_count > 2 && index >= ARRAY_SIZE(audio_decoder_output_types)) { /* If there are more than two channels in the input type GetOutputAvailableType additionally lists * types with 2 channels. */ - index -= ARRAY_SIZE(aac_decoder_output_types); + index -= ARRAY_SIZE(audio_decoder_output_types); channel_count = 2; } - if (index >= ARRAY_SIZE(aac_decoder_output_types)) + if (index >= ARRAY_SIZE(audio_decoder_output_types)) return MF_E_NO_MORE_TYPES; - index = ARRAY_SIZE(aac_decoder_output_types) - index - 1; - output_type = aac_decoder_output_types[index]; - if (FAILED(hr = MFCreateMediaType(&media_type))) - return hr; + wfx.Format.nChannels = channel_count; + wfx.Format.nSamplesPerSec = sample_rate; + wfx.Format.nBlockAlign = wfx.Format.wBitsPerSample * wfx.Format.nChannels / 8; + wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign; - if (FAILED(hr = IMFMediaType_SetGUID(media_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Audio))) - goto done; - if (FAILED(hr = IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, output_type))) - goto done; - - if (IsEqualGUID(output_type, &MFAudioFormat_Float)) - sample_size = 32; - else if (IsEqualGUID(output_type, &MFAudioFormat_PCM)) - sample_size = 16; - else + if (wfx.Format.nChannels >= 3) { - FIXME("Subtype %s not implemented!\n", debugstr_guid(output_type)); - hr = E_NOTIMPL; - goto done; + wfx.SubFormat = MFAudioFormat_Base; + wfx.SubFormat.Data1 = wfx.Format.wFormatTag; + wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wfx.dwChannelMask = default_channel_mask[wfx.Format.nChannels]; } - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_BITS_PER_SAMPLE, sample_size))) - goto done; - - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_NUM_CHANNELS, channel_count))) - goto done; - - if (FAILED(hr = IMFMediaType_GetUINT32(decoder->input_type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, &sample_rate))) - goto done; - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, sample_rate))) - goto done; - - block_alignment = sample_size * channel_count / 8; - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_BLOCK_ALIGNMENT, block_alignment))) - goto done; - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_AVG_BYTES_PER_SECOND, sample_rate * block_alignment))) - goto done; - - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_ALL_SAMPLES_INDEPENDENT, 1))) - goto done; + if (FAILED(hr = MFCreateAudioMediaType(&wfx.Format, (IMFAudioMediaType **)&media_type))) + return hr; if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_FIXED_SIZE_SAMPLES, 1))) goto done; - if (channel_count < 3 && FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_PREFER_WAVEFORMATEX, 1))) - goto done; - if (channel_count >= 3 && FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_CHANNEL_MASK, - default_channel_mask[channel_count]))) - goto done; done: if (SUCCEEDED(hr)) @@ -378,42 +308,41 @@ static HRESULT WINAPI transform_GetOutputAvailableType(IMFTransform *iface, DWOR return hr; } +static BOOL matches_format(const WAVEFORMATEXTENSIBLE *a, const WAVEFORMATEXTENSIBLE *b) +{ + if (a->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && b->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) + return IsEqualGUID(&a->SubFormat, &b->SubFormat); + if (a->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) + return a->SubFormat.Data1 == b->Format.wFormatTag; + if (b->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) + return b->SubFormat.Data1 == a->Format.wFormatTag; + return a->Format.wFormatTag == b->Format.wFormatTag; +} + static HRESULT WINAPI transform_SetInputType(IMFTransform *iface, DWORD id, IMFMediaType *type, DWORD flags) { - struct aac_decoder *decoder = impl_from_IMFTransform(iface); - MF_ATTRIBUTE_TYPE item_type; - UINT32 channel_count; - GUID major, subtype; + struct audio_decoder *decoder = impl_from_IMFTransform(iface); + UINT32 size, count = decoder->input_type_count; + WAVEFORMATEXTENSIBLE *format, wfx; HRESULT hr; - ULONG i; TRACE("iface %p, id %#lx, type %p, flags %#lx.\n", iface, id, type, flags); if (id) return MF_E_INVALIDSTREAMNUMBER; - if (FAILED(hr = IMFMediaType_GetGUID(type, &MF_MT_MAJOR_TYPE, &major))) - return E_INVALIDARG; - - if (!IsEqualGUID(&major, &MFMediaType_Audio) - || FAILED(hr = IMFMediaType_GetGUID(type, &MF_MT_SUBTYPE, &subtype))) - return MF_E_INVALIDMEDIATYPE; - - for (i = 0; i < ARRAY_SIZE(aac_decoder_input_types); ++i) - if (IsEqualGUID(&subtype, aac_decoder_input_types[i].guid)) - break; - if (i == ARRAY_SIZE(aac_decoder_input_types)) - return MF_E_INVALIDMEDIATYPE; + if (FAILED(hr = MFCreateWaveFormatExFromMFMediaType(type, (WAVEFORMATEX **)&format, &size, + MFWaveFormatExConvertFlag_ForceExtensible))) + return hr; + wfx = *format; + CoTaskMemFree(format); - if (SUCCEEDED(IMFMediaType_GetUINT32(type, &MF_MT_AUDIO_NUM_CHANNELS, &channel_count)) - && channel_count >= ARRAY_SIZE(default_channel_mask)) + for (format = decoder->input_types; count > 0 && !matches_format(format, &wfx); count--) + format = NEXT_WAVEFORMATEXTENSIBLE(format); + if (!count) return MF_E_INVALIDMEDIATYPE; - if (FAILED(IMFMediaType_GetItemType(type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, &item_type)) - || item_type != MF_ATTRIBUTE_UINT32) - return MF_E_INVALIDMEDIATYPE; - if (FAILED(IMFMediaType_GetItemType(type, &MF_MT_USER_DATA, &item_type)) - || item_type != MF_ATTRIBUTE_BLOB) + if (wfx.Format.nChannels >= ARRAY_SIZE(default_channel_mask) || !wfx.Format.nSamplesPerSec || !wfx.Format.cbSize) return MF_E_INVALIDMEDIATYPE; if (flags & MFT_SET_TYPE_TEST_ONLY) return S_OK; @@ -432,9 +361,9 @@ static HRESULT WINAPI transform_SetInputType(IMFTransform *iface, DWORD id, IMFM static HRESULT WINAPI transform_SetOutputType(IMFTransform *iface, DWORD id, IMFMediaType *type, DWORD flags) { - struct aac_decoder *decoder = impl_from_IMFTransform(iface); - MF_ATTRIBUTE_TYPE item_type; - GUID major, subtype; + struct audio_decoder *decoder = impl_from_IMFTransform(iface); + WAVEFORMATEXTENSIBLE *format, wfx; + UINT32 size; HRESULT hr; ULONG i; @@ -445,27 +374,19 @@ static HRESULT WINAPI transform_SetOutputType(IMFTransform *iface, DWORD id, IMF if (!decoder->input_type) return MF_E_TRANSFORM_TYPE_NOT_SET; - if (FAILED(hr = IMFMediaType_GetGUID(type, &MF_MT_MAJOR_TYPE, &major)) - || FAILED(hr = IMFMediaType_GetGUID(type, &MF_MT_SUBTYPE, &subtype))) + if (FAILED(hr = MFCreateWaveFormatExFromMFMediaType(type, (WAVEFORMATEX **)&format, &size, + MFWaveFormatExConvertFlag_ForceExtensible))) return hr; + wfx = *format; + CoTaskMemFree(format); - if (!IsEqualGUID(&major, &MFMediaType_Audio)) - return MF_E_INVALIDMEDIATYPE; - - for (i = 0; i < ARRAY_SIZE(aac_decoder_output_types); ++i) - if (IsEqualGUID(&subtype, aac_decoder_output_types[i])) + for (i = 0; i < ARRAY_SIZE(audio_decoder_output_types); ++i) + if (matches_format(&audio_decoder_output_types[i], &wfx)) break; - if (i == ARRAY_SIZE(aac_decoder_output_types)) + if (i == ARRAY_SIZE(audio_decoder_output_types)) return MF_E_INVALIDMEDIATYPE; - if (FAILED(IMFMediaType_GetItemType(type, &MF_MT_AUDIO_BITS_PER_SAMPLE, &item_type)) - || item_type != MF_ATTRIBUTE_UINT32) - return MF_E_INVALIDMEDIATYPE; - if (FAILED(IMFMediaType_GetItemType(type, &MF_MT_AUDIO_NUM_CHANNELS, &item_type)) - || item_type != MF_ATTRIBUTE_UINT32) - return MF_E_INVALIDMEDIATYPE; - if (FAILED(IMFMediaType_GetItemType(type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, &item_type)) - || item_type != MF_ATTRIBUTE_UINT32) + if (!wfx.Format.wBitsPerSample || !wfx.Format.nChannels || !wfx.Format.nSamplesPerSec) return MF_E_INVALIDMEDIATYPE; if (flags & MFT_SET_TYPE_TEST_ONLY) return S_OK; @@ -489,7 +410,7 @@ static HRESULT WINAPI transform_SetOutputType(IMFTransform *iface, DWORD id, IMF static HRESULT WINAPI transform_GetInputCurrentType(IMFTransform *iface, DWORD id, IMFMediaType **out) { - struct aac_decoder *decoder = impl_from_IMFTransform(iface); + struct audio_decoder *decoder = impl_from_IMFTransform(iface); IMFMediaType *type; HRESULT hr; @@ -511,7 +432,7 @@ static HRESULT WINAPI transform_GetInputCurrentType(IMFTransform *iface, DWORD i static HRESULT WINAPI transform_GetOutputCurrentType(IMFTransform *iface, DWORD id, IMFMediaType **out) { - struct aac_decoder *decoder = impl_from_IMFTransform(iface); + struct audio_decoder *decoder = impl_from_IMFTransform(iface); IMFMediaType *type; HRESULT hr; @@ -533,7 +454,7 @@ static HRESULT WINAPI transform_GetOutputCurrentType(IMFTransform *iface, DWORD static HRESULT WINAPI transform_GetInputStatus(IMFTransform *iface, DWORD id, DWORD *flags) { - struct aac_decoder *decoder = impl_from_IMFTransform(iface); + struct audio_decoder *decoder = impl_from_IMFTransform(iface); bool accepts_input; TRACE("iface %p, id %#lx, flags %p.\n", iface, id, flags); @@ -574,7 +495,7 @@ static HRESULT WINAPI transform_ProcessMessage(IMFTransform *iface, MFT_MESSAGE_ static HRESULT WINAPI transform_ProcessInput(IMFTransform *iface, DWORD id, IMFSample *sample, DWORD flags) { - struct aac_decoder *decoder = impl_from_IMFTransform(iface); + struct audio_decoder *decoder = impl_from_IMFTransform(iface); TRACE("iface %p, id %#lx, sample %p, flags %#lx.\n", iface, id, sample, flags); @@ -587,7 +508,7 @@ static HRESULT WINAPI transform_ProcessInput(IMFTransform *iface, DWORD id, IMFS static HRESULT WINAPI transform_ProcessOutput(IMFTransform *iface, DWORD flags, DWORD count, MFT_OUTPUT_DATA_BUFFER *samples, DWORD *status) { - struct aac_decoder *decoder = impl_from_IMFTransform(iface); + struct audio_decoder *decoder = impl_from_IMFTransform(iface); MFT_OUTPUT_STREAM_INFO info; HRESULT hr; @@ -645,6 +566,22 @@ static const IMFTransformVtbl transform_vtbl = transform_ProcessOutput, }; +static HEAACWAVEINFO aac_decoder_input_types[] = +{ +#define MAKE_HEAACWAVEINFO(format, payload) \ + {.wfx = {.wFormatTag = format, .nChannels = 6, .nSamplesPerSec = 48000, .nAvgBytesPerSec = 1152000, \ + .nBlockAlign = 24, .wBitsPerSample = 32, .cbSize = sizeof(HEAACWAVEINFO) - sizeof(WAVEFORMATEX)}, \ + .wPayloadType = payload} + + MAKE_HEAACWAVEINFO(WAVE_FORMAT_MPEG_HEAAC, 0), + MAKE_HEAACWAVEINFO(WAVE_FORMAT_RAW_AAC1, 0), + MAKE_HEAACWAVEINFO(WAVE_FORMAT_MPEG_HEAAC, 1), + MAKE_HEAACWAVEINFO(WAVE_FORMAT_MPEG_HEAAC, 3), + MAKE_HEAACWAVEINFO(WAVE_FORMAT_MPEG_ADTS_AAC, 0), + +#undef MAKE_HEAACWAVEINFO +}; + HRESULT aac_decoder_create(REFIID riid, void **ret) { static const struct wg_format output_format = @@ -661,7 +598,7 @@ HRESULT aac_decoder_create(REFIID riid, void **ret) static const struct wg_format input_format = {.major_type = WG_MAJOR_TYPE_AUDIO_MPEG4}; struct wg_transform_attrs attrs = {0}; wg_transform_t transform; - struct aac_decoder *decoder; + struct audio_decoder *decoder; HRESULT hr; TRACE("riid %s, ret %p.\n", debugstr_guid(riid), ret); @@ -675,6 +612,11 @@ HRESULT aac_decoder_create(REFIID riid, void **ret) if (!(decoder = calloc(1, sizeof(*decoder)))) return E_OUTOFMEMORY; + decoder->IMFTransform_iface.lpVtbl = &transform_vtbl; + decoder->refcount = 1; + + decoder->input_types = (WAVEFORMATEXTENSIBLE *)aac_decoder_input_types; + decoder->input_type_count = ARRAY_SIZE(aac_decoder_input_types); if (FAILED(hr = wg_sample_queue_create(&decoder->wg_sample_queue))) { @@ -682,9 +624,44 @@ HRESULT aac_decoder_create(REFIID riid, void **ret) return hr; } + *ret = &decoder->IMFTransform_iface; + TRACE("Created decoder %p\n", *ret); + return S_OK; +} + +static WAVEFORMATEXTENSIBLE audio_decoder_input_types[] = +{ +#define MAKE_WAVEFORMATEXTENSIBLE(format) \ + {.Format = {.wFormatTag = WAVE_FORMAT_EXTENSIBLE, .nChannels = 6, .nSamplesPerSec = 48000, .nAvgBytesPerSec = 1152000, \ + .nBlockAlign = 24, .wBitsPerSample = 32, .cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)}, \ + .SubFormat = {format,0x0000,0x0010,{0x80,0x00,0x00,0xaa,0x00,0x38,0x9b,0x71}}} + + MAKE_WAVEFORMATEXTENSIBLE(MAKEFOURCC('G','S','T','a')), + +#undef MAKE_WAVEFORMATEXTENSIBLE +}; + +HRESULT audio_decoder_create(REFIID riid, void **ret) +{ + struct audio_decoder *decoder; + HRESULT hr; + + TRACE("riid %s, ret %p.\n", debugstr_guid(riid), ret); + + if (!(decoder = calloc(1, sizeof(*decoder)))) + return E_OUTOFMEMORY; decoder->IMFTransform_iface.lpVtbl = &transform_vtbl; decoder->refcount = 1; + decoder->input_types = audio_decoder_input_types; + decoder->input_type_count = ARRAY_SIZE(audio_decoder_input_types); + + if (FAILED(hr = wg_sample_queue_create(&decoder->wg_sample_queue))) + { + free(decoder); + return hr; + } + *ret = &decoder->IMFTransform_iface; TRACE("Created decoder %p\n", *ret); return S_OK; diff --git a/dlls/winegstreamer/gst_private.h b/dlls/winegstreamer/gst_private.h index 8bc50d6cd99..bce9b5d2fe4 100644 --- a/dlls/winegstreamer/gst_private.h +++ b/dlls/winegstreamer/gst_private.h @@ -103,6 +103,22 @@ char *wg_parser_stream_get_tag(wg_parser_stream_t stream, enum wg_parser_tag tag void wg_parser_stream_seek(wg_parser_stream_t stream, double rate, uint64_t start_pos, uint64_t stop_pos, DWORD start_flags, DWORD stop_flags); +HRESULT wg_source_create(const WCHAR *url, uint64_t file_size, + const void *data, uint32_t size, WCHAR mime_type[256], + wg_source_t *out); +void wg_source_destroy(wg_source_t source); +HRESULT wg_source_get_stream_count(wg_source_t source, uint32_t *stream_count); +HRESULT wg_source_get_duration(wg_source_t source, uint64_t *duration); +HRESULT wg_source_set_position(wg_source_t source, uint64_t time); +HRESULT wg_source_get_position(wg_source_t source, uint64_t *read_offset); +HRESULT wg_source_push_data(wg_source_t source, const void *data, uint32_t size); +HRESULT wg_source_read_data(wg_source_t source, UINT32 index, IMFSample **out); +bool wg_source_get_stream_format(wg_source_t source, UINT32 index, + struct wg_format *format); +char *wg_source_get_stream_tag(wg_source_t source, UINT32 index, + wg_parser_tag tag); +void wg_source_set_stream_flags(wg_source_t source, UINT32 index, BOOL select); + wg_transform_t wg_transform_create(const struct wg_format *input_format, const struct wg_format *output_format, const struct wg_transform_attrs *attrs); void wg_transform_destroy(wg_transform_t transform); @@ -166,11 +182,14 @@ HRESULT wg_transform_read_quartz(wg_transform_t transform, struct wg_sample *sam HRESULT wg_transform_read_dmo(wg_transform_t transform, DMO_OUTPUT_DATA_BUFFER *buffer); HRESULT gstreamer_byte_stream_handler_create(REFIID riid, void **obj); +HRESULT gstreamer_byte_stream_handler_2_create(REFIID riid, void **obj); unsigned int wg_format_get_stride(const struct wg_format *format); bool wg_video_format_is_rgb(enum wg_video_format format); +HRESULT audio_decoder_create(REFIID riid, void **ret); +HRESULT video_decoder_create(REFIID riid, void **ret); HRESULT aac_decoder_create(REFIID riid, void **ret); HRESULT h264_decoder_create(REFIID riid, void **ret); HRESULT video_processor_create(REFIID riid, void **ret); diff --git a/dlls/winegstreamer/h264_decoder.c b/dlls/winegstreamer/h264_decoder.c deleted file mode 100644 index 5a098c6e467..00000000000 --- a/dlls/winegstreamer/h264_decoder.c +++ /dev/null @@ -1,929 +0,0 @@ -/* H264 Decoder Transform - * - * Copyright 2022 RĂ©mi Bernon for CodeWeavers - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA - */ - -#include "gst_private.h" - -#include "mfapi.h" -#include "mferror.h" -#include "mfobjects.h" -#include "mftransform.h" - -#include "wine/debug.h" - -#include "initguid.h" - -#include "codecapi.h" - -WINE_DEFAULT_DEBUG_CHANNEL(mfplat); -WINE_DECLARE_DEBUG_CHANNEL(winediag); - -#define ALIGN_SIZE(size, alignment) (((size) + (alignment)) & ~((alignment))) - -static const GUID *const h264_decoder_input_types[] = -{ - &MFVideoFormat_H264, - &MFVideoFormat_H264_ES, -}; -static const GUID *const h264_decoder_output_types[] = -{ - &MFVideoFormat_NV12, - &MFVideoFormat_YV12, - &MFVideoFormat_IYUV, - &MFVideoFormat_I420, - &MFVideoFormat_YUY2, -}; - -struct h264_decoder -{ - IMFTransform IMFTransform_iface; - LONG refcount; - - IMFAttributes *attributes; - IMFAttributes *output_attributes; - - UINT64 sample_time; - IMFMediaType *input_type; - MFT_INPUT_STREAM_INFO input_info; - IMFMediaType *output_type; - MFT_OUTPUT_STREAM_INFO output_info; - IMFMediaType *stream_type; - - wg_transform_t wg_transform; - struct wg_sample_queue *wg_sample_queue; - - IMFVideoSampleAllocatorEx *allocator; - BOOL allocator_initialized; - IMFTransform *copier; - IMFMediaBuffer *temp_buffer; -}; - -static struct h264_decoder *impl_from_IMFTransform(IMFTransform *iface) -{ - return CONTAINING_RECORD(iface, struct h264_decoder, IMFTransform_iface); -} - -static HRESULT try_create_wg_transform(struct h264_decoder *decoder) -{ - /* Call of Duty: Black Ops 3 doesn't care about the ProcessInput/ProcessOutput - * return values, it calls them in a specific order and expects the decoder - * transform to be able to queue its input buffers. We need to use a buffer list - * to match its expectations. - */ - struct wg_transform_attrs attrs = - { - .output_plane_align = 15, - .input_queue_length = 15, - }; - struct wg_format input_format; - struct wg_format output_format; - UINT32 low_latency; - - if (decoder->wg_transform) - wg_transform_destroy(decoder->wg_transform); - decoder->wg_transform = 0; - - mf_media_type_to_wg_format(decoder->input_type, &input_format); - if (input_format.major_type == WG_MAJOR_TYPE_UNKNOWN) - return MF_E_INVALIDMEDIATYPE; - - mf_media_type_to_wg_format(decoder->output_type, &output_format); - if (output_format.major_type == WG_MAJOR_TYPE_UNKNOWN) - return MF_E_INVALIDMEDIATYPE; - - /* Don't force any specific size, H264 streams already have the metadata for it - * and will generate a MF_E_TRANSFORM_STREAM_CHANGE result later. - */ - output_format.u.video.width = 0; - output_format.u.video.height = 0; - output_format.u.video.fps_d = 0; - output_format.u.video.fps_n = 0; - - if (SUCCEEDED(IMFAttributes_GetUINT32(decoder->attributes, &MF_LOW_LATENCY, &low_latency))) - attrs.low_latency = !!low_latency; - - { - const char *sgi; - if ((sgi = getenv("SteamGameId")) && (!strcmp(sgi, "2009100"))) - attrs.low_latency = FALSE; - } - - if (!(decoder->wg_transform = wg_transform_create(&input_format, &output_format, &attrs))) - return E_FAIL; - - return S_OK; -} - -static HRESULT fill_output_media_type(struct h264_decoder *decoder, IMFMediaType *media_type) -{ - IMFMediaType *default_type = decoder->output_type; - UINT32 value, width, height; - MFVideoArea aperture; - UINT64 ratio; - GUID subtype; - HRESULT hr; - - if (FAILED(hr = IMFMediaType_GetGUID(media_type, &MF_MT_SUBTYPE, &subtype))) - return hr; - - if (FAILED(hr = IMFMediaType_GetUINT64(media_type, &MF_MT_FRAME_SIZE, &ratio))) - { - if (FAILED(IMFMediaType_GetUINT64(decoder->stream_type, &MF_MT_FRAME_SIZE, &ratio))) - ratio = (UINT64)1920 << 32 | 1080; - if (FAILED(hr = IMFMediaType_SetUINT64(media_type, &MF_MT_FRAME_SIZE, ratio))) - return hr; - } - width = ratio >> 32; - height = ratio; - - if (FAILED(hr = IMFMediaType_GetItem(media_type, &MF_MT_FRAME_RATE, NULL))) - { - if (FAILED(IMFMediaType_GetUINT64(decoder->stream_type, &MF_MT_FRAME_RATE, &ratio))) - ratio = (UINT64)30000 << 32 | 1001; - if (FAILED(hr = IMFMediaType_SetUINT64(media_type, &MF_MT_FRAME_RATE, ratio))) - return hr; - } - - if (FAILED(hr = IMFMediaType_GetItem(media_type, &MF_MT_PIXEL_ASPECT_RATIO, NULL))) - { - if (FAILED(IMFMediaType_GetUINT64(decoder->stream_type, &MF_MT_PIXEL_ASPECT_RATIO, &ratio))) - ratio = (UINT64)1 << 32 | 1; - if (FAILED(hr = IMFMediaType_SetUINT64(media_type, &MF_MT_PIXEL_ASPECT_RATIO, ratio))) - return hr; - } - - if (FAILED(hr = IMFMediaType_GetItem(media_type, &MF_MT_SAMPLE_SIZE, NULL))) - { - if (FAILED(hr = MFCalculateImageSize(&subtype, width, height, &value))) - return hr; - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_SAMPLE_SIZE, value))) - return hr; - } - - if (FAILED(hr = IMFMediaType_GetItem(media_type, &MF_MT_DEFAULT_STRIDE, NULL))) - { - if (FAILED(hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, width, (LONG *)&value))) - return hr; - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_DEFAULT_STRIDE, value))) - return hr; - } - - if (FAILED(hr = IMFMediaType_GetItem(media_type, &MF_MT_INTERLACE_MODE, NULL))) - { - if (!default_type || FAILED(hr = IMFMediaType_GetUINT32(default_type, &MF_MT_INTERLACE_MODE, &value))) - value = MFVideoInterlace_MixedInterlaceOrProgressive; - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_INTERLACE_MODE, value))) - return hr; - } - - if (FAILED(hr = IMFMediaType_GetItem(media_type, &MF_MT_ALL_SAMPLES_INDEPENDENT, NULL))) - { - if (!default_type || FAILED(hr = IMFMediaType_GetUINT32(default_type, &MF_MT_ALL_SAMPLES_INDEPENDENT, &value))) - value = 1; - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_ALL_SAMPLES_INDEPENDENT, value))) - return hr; - } - - if (FAILED(hr = IMFMediaType_GetItem(media_type, &MF_MT_VIDEO_ROTATION, NULL))) - { - if (!default_type || FAILED(hr = IMFMediaType_GetUINT32(default_type, &MF_MT_VIDEO_ROTATION, &value))) - value = 0; - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_VIDEO_ROTATION, value))) - return hr; - } - - if (FAILED(hr = IMFMediaType_GetItem(media_type, &MF_MT_FIXED_SIZE_SAMPLES, NULL))) - { - if (!default_type || FAILED(hr = IMFMediaType_GetUINT32(default_type, &MF_MT_FIXED_SIZE_SAMPLES, &value))) - value = 1; - if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_FIXED_SIZE_SAMPLES, value))) - return hr; - } - - if (FAILED(hr = IMFMediaType_GetItem(media_type, &MF_MT_MINIMUM_DISPLAY_APERTURE, NULL)) - && SUCCEEDED(hr = IMFMediaType_GetBlob(decoder->stream_type, &MF_MT_MINIMUM_DISPLAY_APERTURE, - (BYTE *)&aperture, sizeof(aperture), &value))) - { - if (FAILED(hr = IMFMediaType_SetBlob(media_type, &MF_MT_MINIMUM_DISPLAY_APERTURE, - (BYTE *)&aperture, sizeof(aperture)))) - return hr; - } - - return S_OK; -} - -static HRESULT init_allocator(struct h264_decoder *decoder) -{ - HRESULT hr; - - if (decoder->allocator_initialized) - return S_OK; - - if (FAILED(hr = IMFTransform_SetInputType(decoder->copier, 0, decoder->output_type, 0))) - return hr; - if (FAILED(hr = IMFTransform_SetOutputType(decoder->copier, 0, decoder->output_type, 0))) - return hr; - - if (FAILED(hr = IMFVideoSampleAllocatorEx_InitializeSampleAllocatorEx(decoder->allocator, 10, 10, - decoder->attributes, decoder->output_type))) - return hr; - decoder->allocator_initialized = TRUE; - return S_OK; -} - -static void uninit_allocator(struct h264_decoder *decoder) -{ - IMFVideoSampleAllocatorEx_UninitializeSampleAllocator(decoder->allocator); - decoder->allocator_initialized = FALSE; -} - -static HRESULT WINAPI transform_QueryInterface(IMFTransform *iface, REFIID iid, void **out) -{ - struct h264_decoder *decoder = impl_from_IMFTransform(iface); - - TRACE("iface %p, iid %s, out %p.\n", iface, debugstr_guid(iid), out); - - if (IsEqualGUID(iid, &IID_IUnknown) || - IsEqualGUID(iid, &IID_IMFTransform)) - *out = &decoder->IMFTransform_iface; - else - { - *out = NULL; - WARN("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); - return E_NOINTERFACE; - } - - IUnknown_AddRef((IUnknown *)*out); - return S_OK; -} - -static ULONG WINAPI transform_AddRef(IMFTransform *iface) -{ - struct h264_decoder *decoder = impl_from_IMFTransform(iface); - ULONG refcount = InterlockedIncrement(&decoder->refcount); - - TRACE("iface %p increasing refcount to %lu.\n", decoder, refcount); - - return refcount; -} - -static ULONG WINAPI transform_Release(IMFTransform *iface) -{ - struct h264_decoder *decoder = impl_from_IMFTransform(iface); - ULONG refcount = InterlockedDecrement(&decoder->refcount); - - TRACE("iface %p decreasing refcount to %lu.\n", decoder, refcount); - - if (!refcount) - { - IMFTransform_Release(decoder->copier); - IMFVideoSampleAllocatorEx_Release(decoder->allocator); - if (decoder->temp_buffer) - IMFMediaBuffer_Release(decoder->temp_buffer); - if (decoder->wg_transform) - wg_transform_destroy(decoder->wg_transform); - if (decoder->input_type) - IMFMediaType_Release(decoder->input_type); - if (decoder->output_type) - IMFMediaType_Release(decoder->output_type); - if (decoder->output_attributes) - IMFAttributes_Release(decoder->output_attributes); - if (decoder->attributes) - IMFAttributes_Release(decoder->attributes); - wg_sample_queue_destroy(decoder->wg_sample_queue); - free(decoder); - } - - return refcount; -} - -static HRESULT WINAPI transform_GetStreamLimits(IMFTransform *iface, DWORD *input_minimum, - DWORD *input_maximum, DWORD *output_minimum, DWORD *output_maximum) -{ - TRACE("iface %p, input_minimum %p, input_maximum %p, output_minimum %p, output_maximum %p.\n", - iface, input_minimum, input_maximum, output_minimum, output_maximum); - *input_minimum = *input_maximum = *output_minimum = *output_maximum = 1; - return S_OK; -} - -static HRESULT WINAPI transform_GetStreamCount(IMFTransform *iface, DWORD *inputs, DWORD *outputs) -{ - TRACE("iface %p, inputs %p, outputs %p.\n", iface, inputs, outputs); - *inputs = *outputs = 1; - return S_OK; -} - -static HRESULT WINAPI transform_GetStreamIDs(IMFTransform *iface, DWORD input_size, DWORD *inputs, - DWORD output_size, DWORD *outputs) -{ - TRACE("iface %p, input_size %lu, inputs %p, output_size %lu, outputs %p.\n", iface, - input_size, inputs, output_size, outputs); - return E_NOTIMPL; -} - -static HRESULT WINAPI transform_GetInputStreamInfo(IMFTransform *iface, DWORD id, MFT_INPUT_STREAM_INFO *info) -{ - struct h264_decoder *decoder = impl_from_IMFTransform(iface); - - TRACE("iface %p, id %#lx, info %p.\n", iface, id, info); - - *info = decoder->input_info; - return S_OK; -} - -static HRESULT WINAPI transform_GetOutputStreamInfo(IMFTransform *iface, DWORD id, MFT_OUTPUT_STREAM_INFO *info) -{ - struct h264_decoder *decoder = impl_from_IMFTransform(iface); - - TRACE("iface %p, id %#lx, info %p.\n", iface, id, info); - - *info = decoder->output_info; - return S_OK; -} - -static HRESULT WINAPI transform_GetAttributes(IMFTransform *iface, IMFAttributes **attributes) -{ - struct h264_decoder *decoder = impl_from_IMFTransform(iface); - - FIXME("iface %p, attributes %p semi-stub!\n", iface, attributes); - - if (!attributes) - return E_POINTER; - - IMFAttributes_AddRef((*attributes = decoder->attributes)); - return S_OK; -} - -static HRESULT WINAPI transform_GetInputStreamAttributes(IMFTransform *iface, DWORD id, IMFAttributes **attributes) -{ - TRACE("iface %p, id %#lx, attributes %p.\n", iface, id, attributes); - return E_NOTIMPL; -} - -static HRESULT WINAPI transform_GetOutputStreamAttributes(IMFTransform *iface, DWORD id, IMFAttributes **attributes) -{ - struct h264_decoder *decoder = impl_from_IMFTransform(iface); - - FIXME("iface %p, id %#lx, attributes %p semi-stub!\n", iface, id, attributes); - - if (!attributes) - return E_POINTER; - if (id) - return MF_E_INVALIDSTREAMNUMBER; - - IMFAttributes_AddRef((*attributes = decoder->output_attributes)); - return S_OK; -} - -static HRESULT WINAPI transform_DeleteInputStream(IMFTransform *iface, DWORD id) -{ - TRACE("iface %p, id %#lx.\n", iface, id); - return E_NOTIMPL; -} - -static HRESULT WINAPI transform_AddInputStreams(IMFTransform *iface, DWORD streams, DWORD *ids) -{ - TRACE("iface %p, streams %lu, ids %p.\n", iface, streams, ids); - return E_NOTIMPL; -} - -static HRESULT WINAPI transform_GetInputAvailableType(IMFTransform *iface, DWORD id, DWORD index, - IMFMediaType **type) -{ - IMFMediaType *media_type; - const GUID *subtype; - HRESULT hr; - - TRACE("iface %p, id %#lx, index %#lx, type %p.\n", iface, id, index, type); - - *type = NULL; - - if (index >= ARRAY_SIZE(h264_decoder_input_types)) - return MF_E_NO_MORE_TYPES; - subtype = h264_decoder_input_types[index]; - - if (FAILED(hr = MFCreateMediaType(&media_type))) - return hr; - - if (SUCCEEDED(hr = IMFMediaType_SetGUID(media_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Video)) && - SUCCEEDED(hr = IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, subtype))) - IMFMediaType_AddRef((*type = media_type)); - - IMFMediaType_Release(media_type); - return hr; -} - -static HRESULT WINAPI transform_GetOutputAvailableType(IMFTransform *iface, DWORD id, - DWORD index, IMFMediaType **type) -{ - struct h264_decoder *decoder = impl_from_IMFTransform(iface); - IMFMediaType *media_type; - const GUID *output_type; - HRESULT hr; - - TRACE("iface %p, id %#lx, index %#lx, type %p.\n", iface, id, index, type); - - if (!decoder->input_type) - return MF_E_TRANSFORM_TYPE_NOT_SET; - - *type = NULL; - - if (index >= ARRAY_SIZE(h264_decoder_output_types)) - return MF_E_NO_MORE_TYPES; - output_type = h264_decoder_output_types[index]; - - if (FAILED(hr = MFCreateMediaType(&media_type))) - return hr; - - if (FAILED(hr = IMFMediaType_SetGUID(media_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Video))) - goto done; - if (FAILED(hr = IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, output_type))) - goto done; - - hr = fill_output_media_type(decoder, media_type); - -done: - if (SUCCEEDED(hr)) - IMFMediaType_AddRef((*type = media_type)); - - IMFMediaType_Release(media_type); - return hr; -} - -static HRESULT WINAPI transform_SetInputType(IMFTransform *iface, DWORD id, IMFMediaType *type, DWORD flags) -{ - struct h264_decoder *decoder = impl_from_IMFTransform(iface); - GUID major, subtype; - UINT64 frame_size; - HRESULT hr; - ULONG i; - - TRACE("iface %p, id %#lx, type %p, flags %#lx.\n", iface, id, type, flags); - - if (FAILED(hr = IMFMediaType_GetGUID(type, &MF_MT_MAJOR_TYPE, &major)) || - FAILED(hr = IMFMediaType_GetGUID(type, &MF_MT_SUBTYPE, &subtype))) - return E_INVALIDARG; - - if (!IsEqualGUID(&major, &MFMediaType_Video)) - return MF_E_INVALIDMEDIATYPE; - - for (i = 0; i < ARRAY_SIZE(h264_decoder_input_types); ++i) - if (IsEqualGUID(&subtype, h264_decoder_input_types[i])) - break; - if (i == ARRAY_SIZE(h264_decoder_input_types)) - return MF_E_INVALIDMEDIATYPE; - if (flags & MFT_SET_TYPE_TEST_ONLY) - return S_OK; - - if (decoder->output_type) - { - IMFMediaType_Release(decoder->output_type); - decoder->output_type = NULL; - } - - if (decoder->input_type) - IMFMediaType_Release(decoder->input_type); - IMFMediaType_AddRef((decoder->input_type = type)); - - if (SUCCEEDED(IMFMediaType_GetUINT64(type, &MF_MT_FRAME_SIZE, &frame_size))) - { - if (FAILED(hr = IMFMediaType_SetUINT64(decoder->stream_type, &MF_MT_FRAME_SIZE, frame_size))) - WARN("Failed to update stream type frame size, hr %#lx\n", hr); - decoder->output_info.cbSize = (frame_size >> 32) * (UINT32)frame_size * 2; - } - - return S_OK; -} - -static HRESULT WINAPI transform_SetOutputType(IMFTransform *iface, DWORD id, IMFMediaType *type, DWORD flags) -{ - struct h264_decoder *decoder = impl_from_IMFTransform(iface); - UINT64 frame_size, stream_frame_size; - GUID major, subtype; - HRESULT hr; - ULONG i; - - TRACE("iface %p, id %#lx, type %p, flags %#lx.\n", iface, id, type, flags); - - if (!decoder->input_type) - return MF_E_TRANSFORM_TYPE_NOT_SET; - - if (FAILED(hr = IMFMediaType_GetGUID(type, &MF_MT_MAJOR_TYPE, &major)) || - FAILED(hr = IMFMediaType_GetGUID(type, &MF_MT_SUBTYPE, &subtype))) - return hr; - - if (!IsEqualGUID(&major, &MFMediaType_Video)) - return MF_E_INVALIDMEDIATYPE; - - for (i = 0; i < ARRAY_SIZE(h264_decoder_output_types); ++i) - if (IsEqualGUID(&subtype, h264_decoder_output_types[i])) - break; - if (i == ARRAY_SIZE(h264_decoder_output_types)) - return MF_E_INVALIDMEDIATYPE; - - if (FAILED(hr = IMFMediaType_GetUINT64(type, &MF_MT_FRAME_SIZE, &frame_size))) - return MF_E_INVALIDMEDIATYPE; - if (SUCCEEDED(IMFMediaType_GetUINT64(decoder->stream_type, &MF_MT_FRAME_SIZE, &stream_frame_size)) - && frame_size != stream_frame_size) - return MF_E_INVALIDMEDIATYPE; - if (flags & MFT_SET_TYPE_TEST_ONLY) - return S_OK; - - if (decoder->output_type) - IMFMediaType_Release(decoder->output_type); - IMFMediaType_AddRef((decoder->output_type = type)); - - if (decoder->wg_transform) - { - struct wg_format output_format; - mf_media_type_to_wg_format(decoder->output_type, &output_format); - - /* Don't force any specific size, H264 streams already have the metadata for it - * and will generate a MF_E_TRANSFORM_STREAM_CHANGE result later. - */ - output_format.u.video.width = 0; - output_format.u.video.height = 0; - output_format.u.video.fps_d = 0; - output_format.u.video.fps_n = 0; - - if (output_format.major_type == WG_MAJOR_TYPE_UNKNOWN - || !wg_transform_set_output_format(decoder->wg_transform, &output_format)) - { - IMFMediaType_Release(decoder->output_type); - decoder->output_type = NULL; - return MF_E_INVALIDMEDIATYPE; - } - } - else if (FAILED(hr = try_create_wg_transform(decoder))) - { - IMFMediaType_Release(decoder->output_type); - decoder->output_type = NULL; - } - - return hr; -} - -static HRESULT WINAPI transform_GetInputCurrentType(IMFTransform *iface, DWORD id, IMFMediaType **type) -{ - FIXME("iface %p, id %#lx, type %p stub!\n", iface, id, type); - return E_NOTIMPL; -} - -static HRESULT WINAPI transform_GetOutputCurrentType(IMFTransform *iface, DWORD id, IMFMediaType **type) -{ - struct h264_decoder *decoder = impl_from_IMFTransform(iface); - HRESULT hr; - - FIXME("iface %p, id %#lx, type %p stub!\n", iface, id, type); - - if (!decoder->output_type) - return MF_E_TRANSFORM_TYPE_NOT_SET; - - if (FAILED(hr = MFCreateMediaType(type))) - return hr; - - return IMFMediaType_CopyAllItems(decoder->output_type, (IMFAttributes *)*type); -} - -static HRESULT WINAPI transform_GetInputStatus(IMFTransform *iface, DWORD id, DWORD *flags) -{ - struct h264_decoder *decoder = impl_from_IMFTransform(iface); - - TRACE("iface %p, id %#lx, flags %p.\n", iface, id, flags); - - if (!decoder->wg_transform) - return MF_E_TRANSFORM_TYPE_NOT_SET; - - *flags = MFT_INPUT_STATUS_ACCEPT_DATA; - return S_OK; -} - -static HRESULT WINAPI transform_GetOutputStatus(IMFTransform *iface, DWORD *flags) -{ - FIXME("iface %p, flags %p stub!\n", iface, flags); - return E_NOTIMPL; -} - -static HRESULT WINAPI transform_SetOutputBounds(IMFTransform *iface, LONGLONG lower, LONGLONG upper) -{ - TRACE("iface %p, lower %I64d, upper %I64d.\n", iface, lower, upper); - return E_NOTIMPL; -} - -static HRESULT WINAPI transform_ProcessEvent(IMFTransform *iface, DWORD id, IMFMediaEvent *event) -{ - FIXME("iface %p, id %#lx, event %p stub!\n", iface, id, event); - return E_NOTIMPL; -} - -static HRESULT WINAPI transform_ProcessMessage(IMFTransform *iface, MFT_MESSAGE_TYPE message, ULONG_PTR param) -{ - struct h264_decoder *decoder = impl_from_IMFTransform(iface); - HRESULT hr; - - TRACE("iface %p, message %#x, param %Ix.\n", iface, message, param); - - switch (message) - { - case MFT_MESSAGE_SET_D3D_MANAGER: - if (FAILED(hr = IMFVideoSampleAllocatorEx_SetDirectXManager(decoder->allocator, (IUnknown *)param))) - return hr; - - uninit_allocator(decoder); - if (param) - decoder->output_info.dwFlags |= MFT_OUTPUT_STREAM_PROVIDES_SAMPLES; - else - decoder->output_info.dwFlags &= ~MFT_OUTPUT_STREAM_PROVIDES_SAMPLES; - return S_OK; - - case MFT_MESSAGE_COMMAND_DRAIN: - return wg_transform_drain(decoder->wg_transform); - - case MFT_MESSAGE_COMMAND_FLUSH: - return wg_transform_flush(decoder->wg_transform); - - default: - FIXME("Ignoring message %#x.\n", message); - return S_OK; - } -} - -static HRESULT WINAPI transform_ProcessInput(IMFTransform *iface, DWORD id, IMFSample *sample, DWORD flags) -{ - struct h264_decoder *decoder = impl_from_IMFTransform(iface); - - TRACE("iface %p, id %#lx, sample %p, flags %#lx.\n", iface, id, sample, flags); - - if (!decoder->wg_transform) - return MF_E_TRANSFORM_TYPE_NOT_SET; - - return wg_transform_push_mf(decoder->wg_transform, sample, decoder->wg_sample_queue); -} - -static HRESULT output_sample(struct h264_decoder *decoder, IMFSample **out, IMFSample *src_sample) -{ - MFT_OUTPUT_DATA_BUFFER output[1]; - IMFSample *sample; - DWORD status; - HRESULT hr; - - if (FAILED(hr = init_allocator(decoder))) - { - ERR("Failed to initialize allocator, hr %#lx.\n", hr); - return hr; - } - if (FAILED(hr = IMFVideoSampleAllocatorEx_AllocateSample(decoder->allocator, &sample))) - return hr; - - if (FAILED(hr = IMFTransform_ProcessInput(decoder->copier, 0, src_sample, 0))) - { - IMFSample_Release(sample); - return hr; - } - output[0].pSample = sample; - if (FAILED(hr = IMFTransform_ProcessOutput(decoder->copier, 0, 1, output, &status))) - { - IMFSample_Release(sample); - return hr; - } - *out = sample; - return S_OK; -} - -static HRESULT handle_stream_type_change(struct h264_decoder *decoder, const struct wg_format *format) -{ - UINT64 frame_size, frame_rate; - HRESULT hr; - - if (decoder->stream_type) - IMFMediaType_Release(decoder->stream_type); - if (!(decoder->stream_type = mf_media_type_from_wg_format(format))) - return E_OUTOFMEMORY; - - if (SUCCEEDED(IMFMediaType_GetUINT64(decoder->output_type, &MF_MT_FRAME_RATE, &frame_rate)) - && FAILED(hr = IMFMediaType_SetUINT64(decoder->stream_type, &MF_MT_FRAME_RATE, frame_rate))) - WARN("Failed to update stream type frame size, hr %#lx\n", hr); - - if (FAILED(hr = IMFMediaType_GetUINT64(decoder->stream_type, &MF_MT_FRAME_SIZE, &frame_size))) - return hr; - decoder->output_info.cbSize = (frame_size >> 32) * (UINT32)frame_size * 2; - uninit_allocator(decoder); - - return MF_E_TRANSFORM_STREAM_CHANGE; -} - -static HRESULT WINAPI transform_ProcessOutput(IMFTransform *iface, DWORD flags, DWORD count, - MFT_OUTPUT_DATA_BUFFER *samples, DWORD *status) -{ - struct h264_decoder *decoder = impl_from_IMFTransform(iface); - struct wg_format wg_format; - UINT32 sample_size; - LONGLONG duration; - IMFSample *sample; - UINT64 frame_size, frame_rate; - GUID subtype; - DWORD size; - HRESULT hr; - - TRACE("iface %p, flags %#lx, count %lu, samples %p, status %p.\n", iface, flags, count, samples, status); - - if (count != 1) - return E_INVALIDARG; - - if (!decoder->wg_transform) - return MF_E_TRANSFORM_TYPE_NOT_SET; - - *status = samples->dwStatus = 0; - if (!(sample = samples->pSample) && !(decoder->output_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES)) - return E_INVALIDARG; - - if (FAILED(hr = IMFMediaType_GetGUID(decoder->output_type, &MF_MT_SUBTYPE, &subtype))) - return hr; - if (FAILED(hr = IMFMediaType_GetUINT64(decoder->output_type, &MF_MT_FRAME_SIZE, &frame_size))) - return hr; - if (FAILED(hr = MFCalculateImageSize(&subtype, frame_size >> 32, (UINT32)frame_size, &sample_size))) - return hr; - - if (decoder->output_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) - { - if (decoder->temp_buffer) - { - if (FAILED(IMFMediaBuffer_GetMaxLength(decoder->temp_buffer, &size)) || size < sample_size) - { - IMFMediaBuffer_Release(decoder->temp_buffer); - decoder->temp_buffer = NULL; - } - } - if (!decoder->temp_buffer && FAILED(hr = MFCreateMemoryBuffer(sample_size, &decoder->temp_buffer))) - return hr; - if (FAILED(hr = MFCreateSample(&sample))) - return hr; - if (FAILED(hr = IMFSample_AddBuffer(sample, decoder->temp_buffer))) - { - IMFSample_Release(sample); - return hr; - } - } - - if (SUCCEEDED(hr = wg_transform_read_mf(decoder->wg_transform, sample, - sample_size, &wg_format, &samples->dwStatus))) - { - wg_sample_queue_flush(decoder->wg_sample_queue, false); - - if (FAILED(IMFMediaType_GetUINT64(decoder->input_type, &MF_MT_FRAME_RATE, &frame_rate))) - frame_rate = (UINT64)30000 << 32 | 1001; - - duration = (UINT64)10000000 * (UINT32)frame_rate / (frame_rate >> 32); - if (FAILED(IMFSample_SetSampleTime(sample, decoder->sample_time))) - WARN("Failed to set sample time\n"); - if (FAILED(IMFSample_SetSampleDuration(sample, duration))) - WARN("Failed to set sample duration\n"); - decoder->sample_time += duration; - } - - if (hr == MF_E_TRANSFORM_STREAM_CHANGE) - { - samples[0].dwStatus |= MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE; - *status |= MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE; - hr = handle_stream_type_change(decoder, &wg_format); - } - - if (decoder->output_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) - { - if (hr == S_OK && FAILED(hr = output_sample(decoder, &samples->pSample, sample))) - ERR("Failed to output sample, hr %#lx.\n", hr); - IMFSample_Release(sample); - } - - return hr; -} - -static const IMFTransformVtbl transform_vtbl = -{ - transform_QueryInterface, - transform_AddRef, - transform_Release, - transform_GetStreamLimits, - transform_GetStreamCount, - transform_GetStreamIDs, - transform_GetInputStreamInfo, - transform_GetOutputStreamInfo, - transform_GetAttributes, - transform_GetInputStreamAttributes, - transform_GetOutputStreamAttributes, - transform_DeleteInputStream, - transform_AddInputStreams, - transform_GetInputAvailableType, - transform_GetOutputAvailableType, - transform_SetInputType, - transform_SetOutputType, - transform_GetInputCurrentType, - transform_GetOutputCurrentType, - transform_GetInputStatus, - transform_GetOutputStatus, - transform_SetOutputBounds, - transform_ProcessEvent, - transform_ProcessMessage, - transform_ProcessInput, - transform_ProcessOutput, -}; - -HRESULT h264_decoder_create(REFIID riid, void **ret) -{ - static const struct wg_format output_format = - { - .major_type = WG_MAJOR_TYPE_VIDEO, - .u.video = - { - .format = WG_VIDEO_FORMAT_I420, - .width = 1920, - .height = 1080, - }, - }; - static const struct wg_format input_format = {.major_type = WG_MAJOR_TYPE_VIDEO_H264}; - struct wg_transform_attrs attrs = {0}; - wg_transform_t transform; - struct h264_decoder *decoder; - HRESULT hr; - - TRACE("riid %s, ret %p.\n", debugstr_guid(riid), ret); - - if (!(transform = wg_transform_create(&input_format, &output_format, &attrs))) - { - ERR_(winediag)("GStreamer doesn't support H.264 decoding, please install appropriate plugins\n"); - return E_FAIL; - } - wg_transform_destroy(transform); - - if (!(decoder = calloc(1, sizeof(*decoder)))) - return E_OUTOFMEMORY; - - decoder->IMFTransform_iface.lpVtbl = &transform_vtbl; - decoder->refcount = 1; - - decoder->input_info.dwFlags = MFT_INPUT_STREAM_WHOLE_SAMPLES | MFT_INPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER - | MFT_INPUT_STREAM_FIXED_SAMPLE_SIZE; - decoder->input_info.cbSize = 0x1000; - decoder->output_info.dwFlags = MFT_OUTPUT_STREAM_WHOLE_SAMPLES | MFT_OUTPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER - | MFT_OUTPUT_STREAM_FIXED_SAMPLE_SIZE; - decoder->output_info.cbSize = 1920 * 1088 * 2; - - if (FAILED(hr = MFCreateMediaType(&decoder->stream_type))) - goto failed; - if (FAILED(hr = MFCreateAttributes(&decoder->attributes, 16))) - goto failed; - if (FAILED(hr = IMFAttributes_SetUINT32(decoder->attributes, &MF_LOW_LATENCY, 0))) - goto failed; - if (FAILED(hr = IMFAttributes_SetUINT32(decoder->attributes, &MF_SA_D3D11_AWARE, TRUE))) - goto failed; - if (FAILED(hr = IMFAttributes_SetUINT32(decoder->attributes, &AVDecVideoAcceleration_H264, TRUE))) - goto failed; - - { - const char *sgi; - if ((sgi = getenv("SteamGameId")) && (!strcmp(sgi, "2009100"))) - IMFAttributes_SetUINT32(decoder->attributes, &MF_SA_D3D11_AWARE, FALSE); - } - - if (FAILED(hr = MFCreateAttributes(&decoder->output_attributes, 0))) - goto failed; - if (FAILED(hr = wg_sample_queue_create(&decoder->wg_sample_queue))) - goto failed; - if (FAILED(hr = MFCreateVideoSampleAllocatorEx(&IID_IMFVideoSampleAllocatorEx, (void **)&decoder->allocator))) - goto failed; - if (FAILED(hr = MFCreateSampleCopierMFT(&decoder->copier))) - goto failed; - - *ret = &decoder->IMFTransform_iface; - TRACE("Created decoder %p\n", *ret); - return S_OK; - -failed: - if (decoder->allocator) - IMFVideoSampleAllocatorEx_Release(decoder->allocator); - if (decoder->wg_sample_queue) - wg_sample_queue_destroy(decoder->wg_sample_queue); - if (decoder->output_attributes) - IMFAttributes_Release(decoder->output_attributes); - if (decoder->attributes) - IMFAttributes_Release(decoder->attributes); - if (decoder->stream_type) - IMFMediaType_Release(decoder->stream_type); - free(decoder); - return hr; -} diff --git a/dlls/winegstreamer/main.c b/dlls/winegstreamer/main.c index bc6111abe2a..992f6b3ae98 100644 --- a/dlls/winegstreamer/main.c +++ b/dlls/winegstreamer/main.c @@ -33,6 +33,8 @@ #include "wmcodecdsp.h" WINE_DEFAULT_DEBUG_CHANNEL(quartz); +WINE_DECLARE_DEBUG_CHANNEL(mfplat); +WINE_DECLARE_DEBUG_CHANNEL(wmvcore); DEFINE_GUID(GUID_NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); DEFINE_GUID(MEDIASUBTYPE_VC1S,MAKEFOURCC('V','C','1','S'),0x0000,0x0010,0x80,0x00,0x00,0xaa,0x00,0x38,0x9b,0x71); @@ -340,6 +342,196 @@ void wg_parser_stream_seek(wg_parser_stream_t stream, double rate, WINE_UNIX_CALL(unix_wg_parser_stream_seek, ¶ms); } +HRESULT wg_source_create(const WCHAR *url, uint64_t file_size, + const void *data, uint32_t size, WCHAR mime_type[256], + wg_source_t *out) +{ + struct wg_source_create_params params = + { + .file_size = file_size, + .data = data, .size = size, + }; + UINT len = url ? WideCharToMultiByte(CP_ACP, 0, url, -1, NULL, 0, NULL, NULL) : 0; + char *tmp = url ? malloc(len) : NULL; + NTSTATUS status; + + TRACE("url %s, file_size %#I64x, data %p, size %#x, mime_type %p\n", debugstr_w(url), + file_size, data, size, mime_type); + + if ((params.url = tmp)) + WideCharToMultiByte(CP_ACP, 0, url, -1, tmp, len, NULL, NULL); + + if ((status = WINE_UNIX_CALL(unix_wg_source_create, ¶ms))) + WARN("wg_source_create returned status %#lx\n", status); + else + { + TRACE("Returning source %#I64x.\n", params.source); + MultiByteToWideChar(CP_ACP, 0, params.mime_type, -1, mime_type, 256); + *out = params.source; + } + + free(tmp); + return HRESULT_FROM_NT(status); +} + +void wg_source_destroy(wg_source_t source) +{ + TRACE("source %#I64x.\n", source); + + WINE_UNIX_CALL(unix_wg_source_destroy, &source); +} + +HRESULT wg_source_get_stream_count(wg_source_t source, uint32_t *stream_count) +{ + struct wg_source_get_stream_count_params params = + { + .source = source, + }; + NTSTATUS status; + + TRACE("source %#I64x, stream_count %p\n", source, stream_count); + + if ((status = WINE_UNIX_CALL(unix_wg_source_get_stream_count, ¶ms)) + && status != STATUS_PENDING) + { + WARN("wg_source_get_stream_count returned status %#lx\n", status); + return HRESULT_FROM_NT(status); + } + + *stream_count = params.stream_count; + TRACE("source %#I64x, stream_count %u\n", source, *stream_count); + return S_OK; +} + +HRESULT wg_source_get_duration(wg_source_t source, uint64_t *duration) +{ + struct wg_source_get_duration_params params = + { + .source = source, + }; + NTSTATUS status; + + TRACE("source %#I64x, duration %p\n", source, duration); + + if ((status = WINE_UNIX_CALL(unix_wg_source_get_duration, ¶ms)) + && status != STATUS_PENDING) + { + WARN("wg_source_get_duration returned status %#lx\n", status); + return HRESULT_FROM_NT(status); + } + + *duration = params.duration; + TRACE("source %#I64x, duration %s\n", source, debugstr_time(*duration)); + return S_OK; +} + +HRESULT wg_source_get_position(wg_source_t source, uint64_t *read_offset) +{ + struct wg_source_get_position_params params = + { + .source = source, + }; + NTSTATUS status; + + TRACE("source %#I64x, read_offset %p\n", source, read_offset); + + if ((status = WINE_UNIX_CALL(unix_wg_source_get_position, ¶ms)) + && status != STATUS_PENDING) + { + WARN("wg_source_get_position returned status %#lx\n", status); + return HRESULT_FROM_NT(status); + } + + *read_offset = params.read_offset; + TRACE("source %#I64x, read_offset %#I64x\n", source, *read_offset); + return S_OK; +} + +HRESULT wg_source_set_position(wg_source_t source, uint64_t time) +{ + struct wg_source_set_position_params params = {.source = source, .time = time}; + + TRACE("source %#I64x, time %s\n", source, debugstr_time(time)); + + return HRESULT_FROM_NT(WINE_UNIX_CALL(unix_wg_source_set_position, ¶ms)); +} + +HRESULT wg_source_push_data(wg_source_t source, const void *data, uint32_t size) +{ + struct wg_source_push_data_params params = + { + .source = source, + .data = data, + .size = size, + }; + + TRACE("source %#I64x, data %p, size %#x\n", source, data, size); + + return HRESULT_FROM_NT(WINE_UNIX_CALL(unix_wg_source_push_data, ¶ms)); +} + +bool wg_source_get_stream_format(wg_source_t source, UINT32 index, + struct wg_format *format) +{ + struct wg_source_get_stream_format_params params = + { + .source = source, + .index = index, + }; + + TRACE("source %#I64x, index %u, format %p\n", source, + index, format); + + if (WINE_UNIX_CALL(unix_wg_source_get_stream_format, ¶ms)) + return false; + + *format = params.format; + return true; +} + +char *wg_source_get_stream_tag(wg_source_t source, UINT32 index, wg_parser_tag tag) +{ + struct wg_source_get_stream_tag_params params = + { + .source = source, + .index = index, + .tag = tag, + }; + char *buffer; + + TRACE("source %#I64x, index %u, tag %#I64x\n", source, index, tag); + + if (WINE_UNIX_CALL(unix_wg_source_get_stream_tag, ¶ms) != STATUS_BUFFER_TOO_SMALL) + return NULL; + if (!(buffer = malloc(params.size))) + { + ERR("No memory.\n"); + return NULL; + } + params.buffer = buffer; + if (WINE_UNIX_CALL(unix_wg_source_get_stream_tag, ¶ms)) + { + ERR("wg_source_get_stream_tag failed unexpectedly.\n"); + free(buffer); + return NULL; + } + return buffer; +} + +void wg_source_set_stream_flags(wg_source_t source, UINT32 index, BOOL select) +{ + struct wg_source_set_stream_flags_params params = + { + .source = source, + .index = index, + .select = select, + }; + + TRACE("source %#I64x, index %u, select %u\n", source, index, select); + + WINE_UNIX_CALL(unix_wg_source_set_stream_flags, ¶ms); +} + wg_transform_t wg_transform_create(const struct wg_format *input_format, const struct wg_format *output_format, const struct wg_transform_attrs *attrs) { @@ -815,9 +1007,15 @@ HRESULT WINAPI DllGetClassObject(REFCLSID clsid, REFIID iid, void **out) static BOOL CALLBACK init_gstreamer_proc(INIT_ONCE *once, void *param, void **ctx) { + struct wg_init_gstreamer_params params = + { + .trace_on = TRACE_ON(mfplat) || TRACE_ON(quartz) || TRACE_ON(wmvcore), + .warn_on = WARN_ON(mfplat) || WARN_ON(quartz) || WARN_ON(wmvcore), + .err_on = ERR_ON(mfplat) || ERR_ON(quartz) || ERR_ON(wmvcore), + }; HINSTANCE handle; - if (WINE_UNIX_CALL(unix_wg_init_gstreamer, NULL)) + if (WINE_UNIX_CALL(unix_wg_init_gstreamer, ¶ms)) return FALSE; /* Unloading glib is a bad idea.. it installs atexit handlers, diff --git a/dlls/winegstreamer/media-converter/audioconv.c b/dlls/winegstreamer/media-converter/audioconv.c new file mode 100644 index 00000000000..71612cbfddd --- /dev/null +++ b/dlls/winegstreamer/media-converter/audioconv.c @@ -0,0 +1,1102 @@ +/* + * Copyright 2024 Ziqing Hui for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/* Algorithm + * --------- + * + * The application feeds encoded audio into XAudio2 in chunks. Since we don't have access to all + * chunks in a stream on initialization (as we do with the video converter), we continuously hash + * the stream as it is sent to us. Each "chunk" is identified as the hash of the entire stream up + * to that chunk. + * + * Since chunks are small (~2 kilobytes), this leads to a significant possibility of two different + * streams having identical intro chunks (imagine two streams that start with several seconds of + * silence). This means we need a tree of chunks. Suppose two incoming streams with chunks that + * hash as shown (i.e. identical intro chunks that diverge later): + * + * Stream 1: [AA BB CC DD] + * + * Stream 2: [AA BB YY ZZ] + * + * We record a tree and the transcoder will walk it depth-first in order to reconstruct each unique + * stream: + * + * AA => aa.ptna + * AA+BB => bb.ptna + * AA+BB+CC => cc.ptna + * AA+BB+CC+DD => dd.ptna + * AA+BB+YY => yy.ptna + * AA+BB+YY+ZZ => zz.ptna + * + * Upon playback, we chain each transcoded stream chunk together as the packets come in: + * + * AA -> start stream with aa.ptna + * BB -> play bb.ptna + * CC -> play cc.ptna + * DD -> play dd.ptna + * + * or: + * + * AA -> start stream with aa.ptna + * BB -> play bb.ptna + * YY -> play yy.ptna + * ZZ -> play zz.ptna + * + * or: + * + * AA -> start stream with aa.ptna + * NN -> not recognized, instead play blank.ptna and mark this stream as needs-transcoding + * OO -> play blank.ptna + * PP -> play blank.ptna + * When the Stream is destroyed, we'll record AA+NN+OO+PP into the needs-transcode database + * for the transcoder to convert later. + * + * + * Physical Format + * --------------- + * + * All stored values are little-endian. + * + * Transcoded audio is stored in the "transcoded" Fossilize database under the + * AUDIO_CONV_FOZ_TAG_PTNADATA tag. Each chunk is stored in one entry with as many of the following + * "Proton Audio" (ptna) packets as are required to store the entire transcoded chunk: + * + * uint32_t packet_header: Information about the upcoming packet, see bitmask: + * MSB [FFFF PPPP PPPP PPPP PPPP LLLL LLLL LLLL] LSB + * L: Number of _bytes_ in this packet following this header. + * P: Number of _samples_ at the end of this packet which are padding and should be skipped. + * F: Flag bits: + * 0x1: This packet is an Opus header + * 0x2, 0x4, 0x8: Reserved for future use. + * + * If the Opus header flag is set: + * Following packet is an Opus identification header, as defined in RFC 7845 "Ogg + * Encapsulation for the Opus Audio Codec" Section 5.1. + * + * + * If the header flag is not set: + * Following packet is raw Opus data to be sent to an Opus decoder. + * + * + * If we encounter a stream which needs transcoding, we record the buffers and metadata in + * a Fossilize database. The database has three tag types: + * + * AUDIO_CONV_FOZ_TAG_STREAM: This identifies each unique stream of buffers. For example: + * [hash(AA+BB+CC+DD)] -> [AA, BB, CC, DD] + * [hash(AA+BB+XX+YY)] -> [AA, BB, XX, YY] + * + * AUDIO_CONV_FOZ_TAG_AUDIODATA: This contans the actual encoded audio data. For example: + * [AA] -> [AA's buffer data] + * [BB] -> [BB's buffer data] + * + * AUDIO_CONV_FOZ_TAG_CODECINFO: This contans the codec data required to decode the buffer. Only + * the "head" of each stream is recorded. For example: + * [AA] -> [ + * uint32_t wmaversion (from WAVEFORMATEX.wFormatTag) + * uint32_t bitrate (from WAVEFORMATEX.nAvgBytesPerSec) + * uint32_t channels (WAVEFORMATEX.nChannels) + * uint32_t rate (WAVEFORMATEX.nSamplesPerSec) + * uint32_t block_align (WAVEFORMATEX.nBlockAlign) + * uint32_t depth (WAVEFORMATEX.wBitsPerSample) + * char[remainder of entry] codec_data (codec data which follows WAVEFORMATEX) + * ] + * + */ + +#if 0 +#pragma makedep unix +#endif + +#include "media-converter.h" + +#include +#include +#include +#include + +GST_DEBUG_CATEGORY_EXTERN(media_converter_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT media_converter_debug + +#define AUDIO_CONV_ENCODED_LENGTH_MASK 0x00000fff /* 4kB fits in here. */ +#define AUDIO_CONV_PADDING_LENGTH_MASK 0x0ffff000 /* 120ms of samples at 48kHz fits in here. */ +#define AUDIO_CONV_PADDING_LENGTH_SHIFT 12 +#define AUDIO_CONV_FLAG_MASK 0xf0000000 +#define AUDIO_CONV_FLAG_HEADER 0x10000000 /* This chunk is the Opus header. */ +#define _AUDIO_CONV_FLAG_RESERVED1 0x20000000 /* Not yet used */ +#define _AUDIO_CONV_FLAG_RESERVED2 0x40000000 /* Not yet used */ +#define _AUDIO_CONV_FLAG_V2 0x80000000 /* Indicates a "version 2" header, process somehow differently (TBD). */ + +/* Properties of the "blank" audio file. */ +#define _BLANK_AUDIO_FILE_LENGTH_MS 10.0 +#define _BLANK_AUDIO_FILE_RATE 48000.0 + +#define AUDIO_CONV_FOZ_TAG_STREAM 0 +#define AUDIO_CONV_FOZ_TAG_CODECINFO 1 +#define AUDIO_CONV_FOZ_TAG_AUDIODATA 2 +#define AUDIO_CONV_FOZ_TAG_PTNADATA 3 +#define AUDIO_CONV_FOZ_NUM_TAGS 4 + +typedef enum +{ + NO_LOOP, + LOOPING, + LOOP_ENDED, + LOOP_ERROR, +} loop_state; + +struct buffer_entry +{ + struct payload_hash hash; + GstBuffer *buffer; +}; + +/* Followed by codec_data_size bytes codec data. */ +struct need_transcode_head +{ + size_t codec_data_size; + uint32_t wmaversion; + uint32_t bitrate; + uint32_t channels; + uint32_t rate; + uint32_t block_align; + uint32_t depth; +} __attribute__((packed)); + +/* Represents a Stream, a sequence of buffers. */ +struct stream_state +{ + struct murmur3_128_state hash_state; + struct payload_hash current_hash; + GList *buffers; /* Entry type: struct buffer_entry. */ + GList *loop_buffers; /* Entry type: struct buffer_entry. */ + struct need_transcode_head *codec_info; + bool needs_dump; +}; + +struct stream_state_serializer +{ + struct stream_state *state; + int index; +}; + +struct audio_conv_state +{ + bool sent_header; + struct need_transcode_head *codec_data; + struct murmur3_128_state hash_state; + struct murmur3_128_state loop_hash_state; + struct stream_state *stream_state; + struct fozdb *read_fozdb; +}; + +typedef struct +{ + GstElement element; + GstPad *sink_pad, *src_pad; + pthread_mutex_t state_mutex; + struct audio_conv_state *state; +} AudioConv; + +typedef struct +{ + GstElementClass class; +} AudioConvClass; + +G_DEFINE_TYPE(AudioConv, audio_conv, GST_TYPE_ELEMENT); +#define AUDIO_CONV_TYPE (audio_conv_get_type()) +#define AUDIO_CONV(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), AUDIO_CONV_TYPE, AudioConv)) +#define parent_class (audio_conv_parent_class) +GST_ELEMENT_REGISTER_DEFINE(protonaudioconverter, "protonaudioconverter", + GST_RANK_MARGINAL, AUDIO_CONV_TYPE); + +static GstStaticPadTemplate audio_conv_sink_template = GST_STATIC_PAD_TEMPLATE("sink", + GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-wma;")); + +static GstStaticPadTemplate audio_conv_src_template = GST_STATIC_PAD_TEMPLATE("src", + GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-opus;")); + +static struct dump_fozdb dump_fozdb = {PTHREAD_MUTEX_INITIALIZER, NULL, false}; + +static struct buffer_entry *buffer_entry_create(struct payload_hash *hash, GstBuffer *buffer) +{ + struct buffer_entry *entry = calloc(1, sizeof(*entry)); + entry->hash = *hash; + entry->buffer = gst_buffer_ref(buffer); + return entry; +} + +static void buffer_entry_release(void *arg) +{ + struct buffer_entry *entry = arg; + gst_buffer_unref(entry->buffer); + free(entry); +} + +static bool dumping_disabled(void) +{ + return option_enabled("MEDIACONV_AUDIO_DONT_DUMP"); +} + +static bool hash_data(const uint8_t *data, size_t size, struct murmur3_128_state *hash_state, struct payload_hash *hash) +{ + struct bytes_reader reader; + bytes_reader_init(&reader, data, size); + return murmur3_128_full(&reader, bytes_reader_read, hash_state, hash); +} + +static int dump_fozdb_open_audio(bool create) +{ + return dump_fozdb_open(&dump_fozdb, create, "MEDIACONV_AUDIO_DUMP_FILE", AUDIO_CONV_FOZ_NUM_TAGS); +} + +static void dump_fozdb_discard_transcoded(void) +{ + GList *chunks_to_discard = NULL, *chunks_to_keep = NULL, *chunks = NULL, *list_iter; + struct payload_hash chunk_id, *stream_id; + struct fozdb *read_fozdb; + char *read_fozdb_path; + GHashTableIter iter; + int ret; + + if (dump_fozdb.already_cleaned) + return; + dump_fozdb.already_cleaned = true; + + if (discarding_disabled()) + return; + if (!file_exists(getenv("MEDIACONV_AUDIO_DUMP_FILE"))) + return; + + if ((dump_fozdb_open_audio(false)) < 0) + return; + + if (!(read_fozdb_path = getenv("MEDIACONV_AUDIO_TRANSCODED_FILE"))) + { + GST_ERROR("Env MEDIACONV_AUDIO_TRANSCODED_FILE not set."); + return; + } + + if ((ret = fozdb_create(read_fozdb_path, O_RDONLY, true /* Read-only? */, AUDIO_CONV_FOZ_NUM_TAGS, &read_fozdb)) < 0) + { + GST_ERROR("Failed to create read fozdb, ret %d.", ret); + return; + } + + fozdb_iter_tag(dump_fozdb.fozdb, AUDIO_CONV_FOZ_TAG_STREAM, &iter); + while (g_hash_table_iter_next(&iter, (void *)&stream_id, NULL)) + { + uint32_t chunks_size, i; + size_t read_size; + + if (fozdb_entry_size(dump_fozdb.fozdb, AUDIO_CONV_FOZ_TAG_STREAM, stream_id, &chunks_size) == CONV_OK) + { + uint8_t *buffer = calloc(1, chunks_size); + if (fozdb_read_entry_data(dump_fozdb.fozdb, AUDIO_CONV_FOZ_TAG_STREAM, stream_id, + 0, buffer, chunks_size, &read_size, true) == CONV_OK) + { + GList *stream_chunks = NULL; + bool has_all = true; + + for (i = 0; i < read_size / sizeof(chunk_id); ++i) + { + payload_hash_from_bytes(&chunk_id, buffer + i * sizeof(chunk_id)); + if (!fozdb_has_entry(read_fozdb, AUDIO_CONV_FOZ_TAG_PTNADATA, &chunk_id)) + { + has_all = false; + break; + } + stream_chunks = g_list_append(stream_chunks, + entry_name_create(AUDIO_CONV_FOZ_TAG_AUDIODATA, &chunk_id)); + } + + for (list_iter = stream_chunks; list_iter; list_iter = list_iter->next) + { + struct entry_name *entry = list_iter->data; + if (has_all) + { + chunks_to_discard = g_list_append(chunks_to_discard, + entry_name_create(entry->tag, &entry->hash)); + chunks_to_discard = g_list_append(chunks_to_discard, + entry_name_create(AUDIO_CONV_FOZ_TAG_CODECINFO, &entry->hash)); + } + else + { + chunks_to_keep = g_list_append(chunks_to_keep, + entry_name_create(entry->tag, &entry->hash)); + chunks_to_keep = g_list_append(chunks_to_keep, + entry_name_create(AUDIO_CONV_FOZ_TAG_CODECINFO, &entry->hash)); + } + } + + if (has_all) + chunks_to_discard = g_list_append(chunks_to_discard, + entry_name_create(AUDIO_CONV_FOZ_TAG_STREAM, stream_id)); + + g_list_free_full(stream_chunks, free); + } + free(buffer); + } + } + + for (list_iter = chunks_to_discard; list_iter; list_iter = list_iter->next) + { + struct entry_name *entry = list_iter->data; + if (!g_list_find_custom(chunks_to_keep, entry, entry_name_compare)) + chunks = g_list_append(chunks, entry_name_create(entry->tag, &entry->hash)); + } + + if ((ret = fozdb_discard_entries(dump_fozdb.fozdb, chunks)) < 0) + { + GST_ERROR("Failed to discard entries, ret %d.", ret); + dump_fozdb_close(&dump_fozdb); + } + + g_list_free_full(chunks, free); + g_list_free_full(chunks_to_keep, free); + g_list_free_full(chunks_to_discard, free); +} + +static bool need_transcode_head_create_from_caps(GstCaps *caps, struct need_transcode_head **out) +{ + int wmaversion, bitrate, channels, rate, block_align, depth; + const GstStructure *structure = gst_caps_get_structure(caps, 0); + struct need_transcode_head *head; + const GValue *codec_data_value; + GstBuffer *codec_data_buffer; + gsize codec_data_size; + + GST_DEBUG("caps %"GST_PTR_FORMAT", out %p.", caps, out); + + if (!gst_structure_get_int(structure, "wmaversion", &wmaversion)) + { + GST_ERROR("Caps have no wmaversion field."); + return false; + } + if (!gst_structure_get_int(structure, "bitrate", &bitrate)) + { + GST_ERROR("Caps have no bitrate field."); + return false; + } + if (!gst_structure_get_int(structure, "channels", &channels)) + { + GST_ERROR("Caps have no channels field."); + return false; + } + if (!gst_structure_get_int(structure, "rate", &rate)) + { + GST_ERROR("Caps have no rate field."); + return false; + } + if (!gst_structure_get_int(structure, "block_align", &block_align)) + { + GST_ERROR("Caps have no block_align field."); + return false; + } + if (!gst_structure_get_int(structure, "depth", &depth)) + { + GST_ERROR("Caps have no depth field."); + return false; + } + if (!(codec_data_value = gst_structure_get_value(structure, "codec_data")) + || !(codec_data_buffer = gst_value_get_buffer(codec_data_value))) + { + GST_ERROR("Caps have no codec_data field."); + return false; + } + + codec_data_size = gst_buffer_get_size(codec_data_buffer); + head = calloc(1, sizeof(*head) + codec_data_size); + head->codec_data_size = codec_data_size; + head->wmaversion = wmaversion; + head->bitrate = bitrate; + head->channels = channels; + head->rate = rate; + head->block_align = block_align; + head->depth = depth; + gst_buffer_extract(codec_data_buffer, 0, head + 1, codec_data_size); + + *out = head; + return true; +} + +static struct need_transcode_head *need_transcode_head_dup(struct need_transcode_head *head) +{ + size_t size = sizeof(*head) + head->codec_data_size; + struct need_transcode_head *dup; + + dup = calloc(1, size); + memcpy(dup, head, size); + + return dup; +} + + +static void need_transcode_head_serialize(struct need_transcode_head *head, + uint8_t *buffer, size_t buffer_size, size_t *out_size) +{ + *out_size = sizeof(*head) - sizeof(head->codec_data_size) + head->codec_data_size; + + if (buffer_size < *out_size) + { + GST_ERROR("Buffer too small: buffer size %zu, out size %zu.", buffer_size, *out_size); + return; + } + memcpy(buffer, &head->wmaversion, *out_size); +} + +static void stream_state_serializer_init(struct stream_state_serializer *serializer, struct stream_state *state) +{ + serializer->state = state; + serializer->index = 0; +} + +static int stream_state_serializer_read(void *data_reader, uint8_t *buffer, size_t size, size_t *read_size) +{ + struct stream_state_serializer *serializer = data_reader; + struct buffer_entry *entry; + + if (!size) + { + *read_size = 0; + return CONV_OK; + } + + if (serializer->index >= g_list_length(serializer->state->buffers)) + return CONV_ERROR_DATA_END; + + entry = g_list_nth_data(serializer->state->buffers, serializer->index++); + memcpy(buffer, &entry->hash, sizeof(entry->hash)); + + *read_size = sizeof(entry->hash); + return CONV_OK; +} + +static struct stream_state *stream_state_create(void) +{ + struct stream_state *state; + state = calloc(1, sizeof(*state)); + murmur3_128_state_init(&state->hash_state, HASH_SEED); + return state; +} + +static void stream_state_release(struct stream_state *state) +{ + g_list_free_full(state->buffers, buffer_entry_release); + g_list_free_full(state->loop_buffers, buffer_entry_release); + if (state->codec_info) + free(state->codec_info); + free(state); +} + +static void stream_state_reset(struct stream_state *state) +{ + murmur3_128_state_reset(&state->hash_state); + memset(&state->current_hash, 0, sizeof(state->current_hash)); + g_list_free_full(g_steal_pointer(&state->buffers), buffer_entry_release); + g_list_free_full(g_steal_pointer(&state->loop_buffers), buffer_entry_release); + free(state->codec_info); + state->codec_info = NULL; + state->needs_dump = false; +} + +static loop_state stream_state_record_buffer(struct stream_state *state, struct payload_hash *buffer_hash, + struct payload_hash *loop_hash, GstBuffer *buffer, struct need_transcode_head *codec_info) +{ + if (!state->codec_info && codec_info) + state->codec_info = need_transcode_head_dup(codec_info); + + if (g_list_length(state->loop_buffers) < g_list_length(state->buffers)) + { + struct buffer_entry *entry = g_list_nth_data(state->buffers, g_list_length(state->loop_buffers)); + if (!memcmp(&entry->hash, loop_hash, sizeof(*loop_hash))) + { + state->loop_buffers = g_list_append(state->loop_buffers, + buffer_entry_create(buffer_hash /* Not loop_hash! */, buffer)); + if (g_list_length(state->loop_buffers) == g_list_length(state->buffers)) + { + /* Full loop, just drop them. */ + g_list_free_full(g_steal_pointer(&state->loop_buffers), buffer_entry_release); + return LOOP_ENDED; + } + + return LOOPING; + } + } + + /* Partial loop, track them and then continue */ + if (state->loop_buffers) + state->buffers = g_list_concat(state->buffers, g_steal_pointer(&state->loop_buffers)); + + state->buffers = g_list_append(state->buffers, buffer_entry_create(buffer_hash, buffer)); + + if (!hash_data((const uint8_t *)buffer_hash, sizeof(*buffer_hash), &state->hash_state, &state->current_hash)) + return LOOP_ERROR; + + return NO_LOOP; +} + +static bool stream_state_is_stream_subset(struct stream_state *state, struct fozdb *db, struct payload_hash *stream_id) +{ + uint64_t offset = 0; + GList *list_iter; + + for (list_iter = state->buffers; list_iter; list_iter = list_iter->next) + { + struct buffer_entry *entry = list_iter->data; + struct payload_hash buffer_id; + size_t read_size; + + if ((fozdb_read_entry_data(db, AUDIO_CONV_FOZ_TAG_STREAM, stream_id, + offset, (uint8_t *)&buffer_id, sizeof(buffer_id), &read_size, true)) < 0 + || read_size != sizeof(buffer_id)) + return false; + + if (memcmp(&buffer_id, &entry->hash, sizeof(buffer_id)) != 0) + return false; + + offset += sizeof(buffer_id); + } + + GST_LOG("Stream id %s is a subset of %s, so not recording stream.", + format_hash(&state->current_hash), format_hash(stream_id)); + + return true; +} + +static int stream_state_write_to_foz(struct stream_state *state) +{ + struct stream_state_serializer serializer; + struct buffer_entry *entry; + struct bytes_reader reader; + uint8_t buffer[1024]; + size_t header_size; + GList *list_iter; + bool found; + int ret; + + GST_DEBUG("state %p, current hash %s.", state, format_hash(&state->current_hash)); + + if (!state->needs_dump || !state->buffers) + return CONV_OK; + + pthread_mutex_lock(&dump_fozdb.mutex); + + if ((ret = dump_fozdb_open_audio(true)) < 0) + { + GST_ERROR("Failed to open audio dump fozdb, ret %d.", ret); + pthread_mutex_unlock(&dump_fozdb.mutex); + return ret; + } + + found = fozdb_has_entry(dump_fozdb.fozdb, AUDIO_CONV_FOZ_TAG_STREAM, &state->current_hash); + if (!found) + { + /* Are there any recorded streams of which this stream is a subset? */ + struct payload_hash *stream_id; + GHashTableIter stream_ids; + + fozdb_iter_tag(dump_fozdb.fozdb, AUDIO_CONV_FOZ_TAG_STREAM, &stream_ids); + while (g_hash_table_iter_next(&stream_ids, (void **)&stream_id, NULL)) + { + if (stream_state_is_stream_subset(state, dump_fozdb.fozdb, stream_id)) + { + found = true; + break; + } + } + } + + if (!found) + { + if (dumping_disabled()) + { + GST_LOG("Dumping disabled, so not recording stream id %s.", format_hash(&state->current_hash)); + } + else + { + GST_LOG("Recording stream id %s.", format_hash(&state->current_hash)); + + need_transcode_head_serialize(state->codec_info, buffer, sizeof(buffer), &header_size); + bytes_reader_init(&reader, buffer, header_size); + entry = state->buffers->data; + if ((ret = fozdb_write_entry(dump_fozdb.fozdb, AUDIO_CONV_FOZ_TAG_CODECINFO, &entry->hash, + &reader, bytes_reader_read, true)) < 0) + { + GST_ERROR("Unable to write stream header, ret %d.\n", ret); + pthread_mutex_unlock(&dump_fozdb.mutex); + return ret; + } + + stream_state_serializer_init(&serializer, state); + if ((ret = fozdb_write_entry(dump_fozdb.fozdb, AUDIO_CONV_FOZ_TAG_STREAM, &state->current_hash, + &serializer, stream_state_serializer_read, true)) < 0) + { + GST_ERROR("Unable to write stream, ret %d.\n", ret); + pthread_mutex_unlock(&dump_fozdb.mutex); + return ret; + } + + for (list_iter = state->buffers; list_iter; list_iter = list_iter->next) + { + struct gst_buffer_reader buffer_reader; + + entry = list_iter->data; + gst_buffer_reader_init(&buffer_reader, entry->buffer); + if ((ret = fozdb_write_entry(dump_fozdb.fozdb, AUDIO_CONV_FOZ_TAG_AUDIODATA, &entry->hash, + &buffer_reader, gst_buffer_reader_read, true)) < 0) + { + GST_ERROR("Unable to write audio data, ret %d.\n", ret); + pthread_mutex_unlock(&dump_fozdb.mutex); + return ret; + } + } + } + } + + pthread_mutex_unlock(&dump_fozdb.mutex); + return CONV_OK; +} + +static int audio_conv_state_create(struct audio_conv_state **out) +{ + struct audio_conv_state *state; + struct fozdb *fozdb = NULL; + char *read_fozdb_path; + int ret; + + if (!(read_fozdb_path = getenv("MEDIACONV_AUDIO_TRANSCODED_FILE"))) + { + GST_ERROR("Env MEDIACONV_AUDIO_TRANSCODED_FILE is not set!"); + return CONV_ERROR_ENV_NOT_SET; + } + + if ((ret = fozdb_create(read_fozdb_path, O_RDONLY, true /* Read-only? */, AUDIO_CONV_FOZ_NUM_TAGS, &fozdb)) < 0) + GST_ERROR("Failed to create fozdb from %s, ret %d.", read_fozdb_path, ret); + + state = calloc(1, sizeof(*state)); + murmur3_128_state_init(&state->hash_state, HASH_SEED); + murmur3_128_state_init(&state->loop_hash_state, HASH_SEED); + state->stream_state = stream_state_create(); + state->read_fozdb = fozdb; + + *out = state; + return CONV_OK; +} + +static void audio_conv_state_release(struct audio_conv_state *state) +{ + free(state->codec_data); + stream_state_release(state->stream_state); + if (state->read_fozdb) + fozdb_release(state->read_fozdb); + free(state); +} + +static void audio_conv_state_reset(struct audio_conv_state *state) +{ + if (stream_state_write_to_foz(state->stream_state) < 0) + GST_ERROR("Failed to write stream to dump fozdb."); + + stream_state_reset(state->stream_state); + murmur3_128_state_reset(&state->hash_state); + murmur3_128_state_reset(&state->loop_hash_state); +} + +/* Allocate a buffer on success, free it after usage. */ +static int audio_conv_state_open_transcode_file(struct audio_conv_state *state, GstBuffer *buffer, + uint8_t **out_data, size_t *out_size) +{ + struct payload_hash hash, loop_hash; + uint32_t transcoded_size; + const char *blank_audio; + bool try_loop, hash_ok; + GstMapInfo map_info; + uint64_t file_size; + loop_state loop; + uint8_t *data; + size_t size; + int fd; + + /* Hash buffer. */ + if (!gst_buffer_map(buffer, &map_info, GST_MAP_READ)) + return CONV_ERROR; + hash_ok = hash_data(map_info.data, map_info.size, &state->hash_state, &hash) + && hash_data(map_info.data, map_info.size, &state->loop_hash_state, &loop_hash); + gst_buffer_unmap(buffer, &map_info); + if (!hash_ok) + { + GST_ERROR("Failed to hash buffer."); + return CONV_ERROR; + } + + loop = stream_state_record_buffer(state->stream_state, &hash, &loop_hash, buffer, state->codec_data); + gst_buffer_unref(buffer); /* Buffer has been recorded, so unref it. */ + switch (loop) + { + case NO_LOOP: + murmur3_128_state_reset(&state->loop_hash_state); + try_loop = false; + break; + + case LOOP_ENDED: + murmur3_128_state_reset(&state->loop_hash_state); + case LOOPING: + try_loop = true; + break; + + case LOOP_ERROR: + default: + return CONV_ERROR; + } + + if (try_loop) + GST_INFO("Buffer hash: %s (Loop: %s).", format_hash(&hash), format_hash(&loop_hash)); + else + GST_INFO("Buffer hash: %s.", format_hash(&hash)); + + /* Try to read transcoded data. */ + if (state->read_fozdb) + { + if (fozdb_entry_size(state->read_fozdb, + AUDIO_CONV_FOZ_TAG_PTNADATA, &hash, &transcoded_size) == CONV_OK) + { + data = calloc(1, transcoded_size); + if (fozdb_read_entry_data(state->read_fozdb, AUDIO_CONV_FOZ_TAG_PTNADATA, &hash, 0, + data, transcoded_size, &size, false) == CONV_OK) + { + *out_data = data; + *out_size = size; + return CONV_OK; + } + free(data); + } + + if (try_loop && fozdb_entry_size(state->read_fozdb, + AUDIO_CONV_FOZ_TAG_PTNADATA, &loop_hash, &transcoded_size) == CONV_OK) + { + data = calloc(1, transcoded_size); + if (fozdb_read_entry_data(state->read_fozdb, AUDIO_CONV_FOZ_TAG_PTNADATA, &loop_hash, 0, + data, transcoded_size, &size, false) == CONV_OK) + { + *out_data = data; + *out_size = size; + return CONV_OK; + } + free(data); + } + } + + /* If we can't, return the blank file */ + state->stream_state->needs_dump = true; + if (!(blank_audio = getenv("MEDIACONV_BLANK_AUDIO_FILE"))) + { + GST_ERROR("Env MEDIACONV_BLANK_AUDIO_FILE not set."); + return CONV_ERROR_ENV_NOT_SET; + } + if (!open_file(blank_audio, O_RDONLY, &fd)) + return CONV_ERROR_OPEN_FAILED; + if (!get_file_size(fd, &file_size)) + { + close(fd); + return CONV_ERROR; + } + data = calloc(1, file_size); + if (!complete_read(fd, data, file_size)) + { + free(data); + close(fd); + return CONV_ERROR_READ_FAILED; + } + + create_placeholder_file("placeholder-audio-used"); + + *out_data = data; + *out_size = file_size; + + return CONV_OK; +} + +/* Call pthread_mutex_unlock() to unlock after usage. */ +static struct audio_conv_state *audio_conv_lock_state(AudioConv *conv) +{ + pthread_mutex_lock(&conv->state_mutex); + if (!conv->state) + pthread_mutex_unlock(&conv->state_mutex); + return conv->state; +} + +static GstStateChangeReturn audio_conv_change_state(GstElement *element, GstStateChange transition) +{ + AudioConv *conv = AUDIO_CONV(element); + struct audio_conv_state *state; + int ret; + + GST_DEBUG_OBJECT(element, "State transition %s.", gst_state_change_get_name(transition)); + + switch (transition) + { + case GST_STATE_CHANGE_NULL_TO_READY: + /* Do runtime setup. */ + + /* Open fozdb here; this is the right place to fail + * and opening may be expensive. */ + pthread_mutex_lock(&dump_fozdb.mutex); + dump_fozdb_discard_transcoded(); + ret = dump_fozdb_open_audio(true); + pthread_mutex_unlock(&dump_fozdb.mutex); + if (ret < 0) + { + GST_ERROR("Failed to open dump fozdb, ret %d.", ret); + return GST_STATE_CHANGE_FAILURE; + } + + /* Create audio conv state. */ + if ((ret = audio_conv_state_create(&state)) < 0) + { + GST_ERROR("Failed to create audio conv state, ret %d.", ret); + return GST_STATE_CHANGE_FAILURE; + } + pthread_mutex_lock(&conv->state_mutex); + assert(!conv->state); + conv->state = state; + pthread_mutex_unlock(&conv->state_mutex); + break; + + case GST_STATE_CHANGE_READY_TO_NULL: + /* Do runtime teardown. */ + pthread_mutex_lock(&conv->state_mutex); + state = conv->state; + conv->state = NULL; + pthread_mutex_unlock(&conv->state_mutex); + + if (state && (ret = stream_state_write_to_foz(state->stream_state)) < 0) + GST_WARNING("Error writing out stream data, ret %d.", ret); + audio_conv_state_release(state); + break; + + default: + break; + } + + return GST_ELEMENT_CLASS(parent_class)->change_state(element, transition); + + /* XXX on ReadyToNull, sodium drops state _again_ here... why? */ +} + +static gboolean audio_conv_sink_event(GstPad *pad, GstObject *parent, GstEvent *event) +{ + AudioConv *conv = AUDIO_CONV(parent); + struct audio_conv_state *state; + GstCaps *caps; + bool ret; + + GST_DEBUG_OBJECT(pad, "Got sink event %"GST_PTR_FORMAT".", event); + + switch (event->type) + { + case GST_EVENT_CAPS: + if ((state = audio_conv_lock_state(conv))) + { + gst_event_parse_caps(event, &caps); + if (!need_transcode_head_create_from_caps(caps, &state->codec_data)) + { + GST_ERROR("Invalid WMA caps!"); + pthread_mutex_unlock(&conv->state_mutex); + return false; + } + pthread_mutex_unlock(&conv->state_mutex); + } + + caps = gst_caps_from_string("audio/x-opus, channel-mapping-family=0"); + ret = push_event(conv->src_pad, gst_event_new_caps(caps)); + gst_caps_unref(caps); + return ret; + + case GST_EVENT_FLUSH_STOP: + if ((state = audio_conv_lock_state(conv))) + { + audio_conv_state_reset(state); + pthread_mutex_unlock(&conv->state_mutex); + } + return gst_pad_event_default(pad, parent, event); + + default: + return gst_pad_event_default(pad, parent, event); + } +} + +static GstFlowReturn audio_conv_chain(GstPad *pad, GstObject *parent, GstBuffer *buffer) +{ + size_t ptna_data_size, offset, encoded_len; + GstFlowReturn flow_ret = GST_FLOW_ERROR; + AudioConv *conv = AUDIO_CONV(parent); + struct audio_conv_state *state; + uint8_t *ptna_data = NULL; + int ret; + + GST_LOG_OBJECT(pad, "Handling buffer <%"GST_PTR_FORMAT">.", buffer); + + if (!(state = audio_conv_lock_state(conv))) + return flow_ret; + + if ((ret = audio_conv_state_open_transcode_file(state, buffer, &ptna_data, &ptna_data_size)) < 0) + { + GST_ERROR("Failed to read transcoded audio, ret %d. Things will go badly...", ret); + goto done; + } + + for (offset = 0; offset < ptna_data_size; offset += encoded_len) + { + uint32_t packet_header, flags, padding_len; + GstBuffer *new_buffer; + bool packet_is_header; + + if (offset + 4 >= ptna_data_size) + { + GST_WARNING( "Short read on ptna header?"); + break; + } + + packet_header = bytes_to_uint32(&ptna_data[offset]); + offset += 4; + + flags = packet_header & AUDIO_CONV_FLAG_MASK, + padding_len = (packet_header & AUDIO_CONV_PADDING_LENGTH_MASK) >> AUDIO_CONV_PADDING_LENGTH_SHIFT; + encoded_len = packet_header & AUDIO_CONV_ENCODED_LENGTH_MASK; + + if (offset + encoded_len > ptna_data_size) + { + GST_WARNING("Short read on ptna data?"); + break; + } + + packet_is_header = flags & AUDIO_CONV_FLAG_HEADER; + if (packet_is_header && state->sent_header) + continue; /* Only send one header. */ + + /* TODO: can we use a GstBuffer cache here? */ + new_buffer = gst_buffer_new_and_alloc(encoded_len); + if (!packet_is_header && padding_len > 0) + gst_buffer_add_audio_clipping_meta(new_buffer, GST_FORMAT_DEFAULT, 0, padding_len); + gst_buffer_fill(new_buffer, 0, ptna_data + offset, encoded_len); + + GST_LOG("Pushing one packet of len %zu.", encoded_len); + if ((flow_ret = gst_pad_push(conv->src_pad, new_buffer)) < 0) + { + GST_ERROR("Failed to push buffer <%"GST_PTR_FORMAT"> to src pad %"GST_PTR_FORMAT, + new_buffer, conv->src_pad); + goto done; + } + + if (packet_is_header) + state->sent_header = true; + } + + flow_ret = GST_FLOW_OK; + +done: + if (ptna_data) + free(ptna_data); + pthread_mutex_unlock(&conv->state_mutex); + return flow_ret; +} + +static gboolean audio_conv_src_query(GstPad *pad, GstObject *parent, GstQuery *query) +{ + AudioConv *conv = AUDIO_CONV(parent); + GstSchedulingFlags flags; + gint min, max, align; + GstQuery *peer_query; + + GST_DEBUG_OBJECT(pad, "Got query %"GST_PTR_FORMAT".", query); + + switch (query->type) + { + case GST_QUERY_SCHEDULING: + peer_query = gst_query_new_scheduling(); + if (!gst_pad_peer_query(conv->sink_pad, peer_query)) + { + gst_query_unref(peer_query); + return false; + } + gst_query_parse_scheduling(peer_query, &flags, &min, &max, &align); + gst_query_unref(peer_query); + gst_query_set_scheduling(query, flags, min, max, align); + return true; + + default: + return gst_pad_query_default(pad, parent, query); + } + +} + +static gboolean audio_conv_active_mode(GstPad *pad, GstObject *parent, GstPadMode mode, gboolean active) +{ + AudioConv *conv = AUDIO_CONV(parent); + return gst_pad_activate_mode(conv->sink_pad, mode, active); +} + +static void audio_conv_finalize(GObject *object) +{ + AudioConv *conv = AUDIO_CONV(object); + + pthread_mutex_destroy(&conv->state_mutex); + if (conv->state) + audio_conv_state_release(conv->state); + + G_OBJECT_CLASS(parent_class)->finalize(object); +} + +static void audio_conv_class_init(AudioConvClass *klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + gst_element_class_set_metadata(element_class, + "Proton audio converter", + "Codec/Demuxer", + "Converts audio for Proton", + "Andrew Eikum , Ziqing Hui "); + + element_class->change_state = audio_conv_change_state; + object_class->finalize = audio_conv_finalize; + + gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&audio_conv_sink_template)); + gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&audio_conv_src_template)); +} + +static void audio_conv_init(AudioConv *conv) +{ + GstElement *element = GST_ELEMENT(conv); + + conv->sink_pad = gst_pad_new_from_static_template(&audio_conv_sink_template, "sink"); + gst_pad_set_event_function(conv->sink_pad, GST_DEBUG_FUNCPTR(audio_conv_sink_event)); + gst_pad_set_chain_function(conv->sink_pad, GST_DEBUG_FUNCPTR(audio_conv_chain)); + gst_element_add_pad(element, conv->sink_pad); + + conv->src_pad = gst_pad_new_from_static_template(&audio_conv_src_template, "src"); + gst_pad_set_query_function(conv->src_pad, GST_DEBUG_FUNCPTR(audio_conv_src_query)); + gst_pad_set_activatemode_function(conv->src_pad, GST_DEBUG_FUNCPTR(audio_conv_active_mode)); + gst_element_add_pad(element, conv->src_pad); + + pthread_mutex_init(&conv->state_mutex, NULL); + conv->state = NULL; +} diff --git a/dlls/winegstreamer/media-converter/audioconvbin.c b/dlls/winegstreamer/media-converter/audioconvbin.c new file mode 100644 index 00000000000..9857a0ff507 --- /dev/null +++ b/dlls/winegstreamer/media-converter/audioconvbin.c @@ -0,0 +1,148 @@ +/* + * Copyright 2024 Ziqing Hui for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +#include "media-converter.h" + +GST_DEBUG_CATEGORY_EXTERN(media_converter_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT media_converter_debug + +typedef struct +{ + GstBin bin; + GstElement *audio_conv, *opus_dec, *caps_setter; + GstPad *sink_pad, *src_pad; /* Ghost pads. */ +} AudioConvBin; + +typedef struct +{ + GstBinClass class; +} AudioConvBinClass; + +G_DEFINE_TYPE(AudioConvBin, audio_conv_bin, GST_TYPE_BIN); +#define AUDIO_CONV_BIN_TYPE (audio_conv_bin_get_type()) +#define AUDIO_CONV_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), AUDIO_CONV_BIN_TYPE, AudioConvBin)) +GST_ELEMENT_REGISTER_DEFINE(protonaudioconverterbin, "protonaudioconverterbin", + GST_RANK_MARGINAL + 1, AUDIO_CONV_BIN_TYPE); + +static GstStaticPadTemplate audio_conv_bin_sink_template = GST_STATIC_PAD_TEMPLATE("sink", + GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-wma;")); + +static GstStaticPadTemplate audio_conv_bin_src_template = GST_STATIC_PAD_TEMPLATE("src", + GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS("audio/x-raw, format=S16LE;")); + +static void link_elements(GstElement *src_element, GstElement *sink_element) +{ + if (!gst_element_link(src_element, sink_element)) + GST_ERROR("Failed to link src element %"GST_PTR_FORMAT" to sink element %"GST_PTR_FORMAT".", + src_element, sink_element); +} + +static gboolean audio_conv_bin_sink_event(GstPad *pad, GstObject *parent, GstEvent *event) +{ + AudioConvBin * bin = AUDIO_CONV_BIN(parent); + GstCaps *caps, *rate_caps; + GstStructure *structure; + GstPad *audio_conv_sink; + gint override_rate; + gboolean ret; + + GST_DEBUG_OBJECT(pad, "Got sink event %"GST_PTR_FORMAT".", event); + + switch (event->type) + { + case GST_EVENT_CAPS: + gst_event_parse_caps(event, &caps); + if ((structure = gst_caps_get_structure(caps, 0)) + && gst_structure_get_int(structure, "rate", &override_rate)) + { + rate_caps = gst_caps_new_simple("audio/x-raw", "rate", G_TYPE_INT, override_rate, NULL); + g_object_set(bin->caps_setter, "caps", rate_caps, NULL); + } + else + { + GST_WARNING("Event has no rate."); + } + + /* Forward on to the real pad. */ + audio_conv_sink = gst_element_get_static_pad(bin->audio_conv, "sink"); + ret = gst_pad_send_event(audio_conv_sink, event); + gst_object_unref(audio_conv_sink); + return ret; + + default: + return gst_pad_event_default(pad, parent, event); + } +} + +static void audio_conv_bin_class_init(AudioConvBinClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + + gst_element_class_set_metadata(element_class, + "Proton audio converter with rate fixup", + "Codec/Decoder/Audio", + "Converts audio for Proton, fixing up samplerates", + "Andrew Eikum , Ziqing Hui "); + + gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&audio_conv_bin_sink_template)); + gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&audio_conv_bin_src_template)); +} + +static void audio_conv_bin_init(AudioConvBin *bin) +{ + GstElement *element = GST_ELEMENT(bin); + GstPad *sink, *src; + + bin->sink_pad = gst_ghost_pad_new_no_target_from_template("sink", + gst_element_get_pad_template(element, "sink")); + bin->src_pad = gst_ghost_pad_new_no_target_from_template("src", + gst_element_get_pad_template(element, "src")); + gst_pad_set_event_function(bin->sink_pad, GST_DEBUG_FUNCPTR(audio_conv_bin_sink_event)); + + bin->audio_conv = create_element("protonaudioconverter", "protonmediaconverter"); + bin->opus_dec = create_element("opusdec", "base"); + bin->caps_setter = create_element("capssetter", "good"); + + gst_bin_add(GST_BIN(bin), bin->audio_conv); + gst_bin_add(GST_BIN(bin), bin->opus_dec); + gst_bin_add(GST_BIN(bin), bin->caps_setter); + + link_elements(bin->audio_conv, bin->opus_dec); + link_elements(bin->opus_dec, bin->caps_setter); + + sink = gst_element_get_static_pad(bin->audio_conv, "sink"); + src = gst_element_get_static_pad(bin->caps_setter, "src"); + gst_ghost_pad_set_target(GST_GHOST_PAD(bin->sink_pad), sink); + gst_ghost_pad_set_target(GST_GHOST_PAD(bin->src_pad), src); + gst_object_unref(src); + gst_object_unref(sink); + + gst_element_add_pad(element, bin->sink_pad); + gst_element_add_pad(element, bin->src_pad); + + GST_INFO("Initialized AudioConvBin %"GST_PTR_FORMAT": audio_conv %"GST_PTR_FORMAT", opus_dec %"GST_PTR_FORMAT", " + "caps_setter %"GST_PTR_FORMAT", sink_pad %"GST_PTR_FORMAT", src_pad %"GST_PTR_FORMAT".", + bin, bin->audio_conv, bin->opus_dec, bin->caps_setter, bin->sink_pad, bin->src_pad); +} diff --git a/dlls/winegstreamer/media-converter/fossilize.c b/dlls/winegstreamer/media-converter/fossilize.c new file mode 100644 index 00000000000..057825ad5b5 --- /dev/null +++ b/dlls/winegstreamer/media-converter/fossilize.c @@ -0,0 +1,722 @@ +/* + * Copyright 2024 Ziqing Hui for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Based on "Fossilize," which is + * Copyright (c) 2018-2019 Hans-Kristian Arntzen + * https://github.com/ValveSoftware/Fossilize/ + */ + +/* This is a read/write implementation of the Fossilize database format. + * + * https://github.com/ValveSoftware/Fossilize/ + * + * That C++ implementation is specific to Vulkan, while this one tries to be generic to store any + * type of data. + */ + +#if 0 +#pragma makedep unix +#endif + +#include "media-converter.h" + +/* Fossilize StreamArchive database format version 6: + * + * The file consists of a header, followed by an unlimited series of "entries". + * + * All multi-byte entities are little-endian. + * + * The file header is as follows: + * + * Field Type Description + * ----- ---- ----------- + * magic_number uint8_t[12] Constant value: "\x81""FOSSILIZEDB" + * version uint32_t StreamArchive version: 6 + * + * + * Each entry follows this format: + * + * Field Type Description + * ----- ---- ----------- + * name unsigned char[40] Application-defined entry identifier, stored in hexadecimal big-endian + * ASCII. Usually N-char tag followed by (40 - N)-char hash. + * size uint32_t Size of the payload as stored in this file. + * flags uint32_t Flags for this entry (e.g. compression). See below. + * crc32 uint32_t CRC32 of the payload as stored in this file. + * full_size uint32_t Size of this payload after decompression. + * payload uint8_t[stored_size] Entry data. + * + * The flags field may contain: + * 0x1: No compression. + * 0x2: Deflate compression. + */ + +#define FOZDB_MIN_COMPAT_VERSION 5 +#define FOZDB_VERSION 6 + +#define FOZDB_COMPRESSION_NONE 1 +#define FOZDB_COMPRESSION_DEFLATE 2 + +#define ENTRY_NAME_SIZE 40 + +#define BUFFER_COPY_BYTES (8 * 1024 * 1024) /* Tuneable. */ + +static const uint8_t FOZDB_MAGIC[] = {0x81, 'F', 'O', 'S', 'S', 'I', 'L', 'I', 'Z', 'E', 'D', 'B'}; + +struct file_header +{ + uint8_t magic[12]; + uint8_t unused1; + uint8_t unused2; + uint8_t unused3; + uint8_t version; +} __attribute__((packed)); + +struct payload_header +{ + uint32_t size; + uint32_t compression; + uint32_t crc; + uint32_t full_size; +} __attribute__((packed)); + +struct payload_entry +{ + struct payload_hash hash; + struct payload_header header; + uint64_t offset; +}; + +static guint hash_func(gconstpointer key) +{ + const struct payload_hash *payload_hash = key; + + return payload_hash->hash[0] + ^ payload_hash->hash[1] + ^ payload_hash->hash[2] + ^ payload_hash->hash[3]; +} + +static gboolean hash_equal(gconstpointer a, gconstpointer b) +{ + return memcmp(a, b, sizeof(struct payload_hash)) == 0; +} + +static bool tag_from_ascii_bytes(uint32_t *tag, const uint8_t *ascii_bytes) +{ + char str[sizeof(*tag) * 2 + 1] = {}; + + memcpy(str, ascii_bytes, sizeof(*tag) * 2); + + *tag = strtoul(str, NULL, 16); + if (errno != 0) + { + GST_ERROR("Failed to convert string \"%s\" to tag. %s.", str, strerror(errno)); + return false; + } + + return true; +} + +static bool hash_from_ascii_bytes(struct payload_hash *hash, const uint8_t *ascii_bytes) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(hash->hash); ++i) + { + uint32_t *hash_part = &hash->hash[ARRAY_SIZE(hash->hash) - 1 - i]; + char str[sizeof(hash_part) * 2 + 1] = {}; + + memcpy(str, ascii_bytes + sizeof(*hash_part) * 2 * i, sizeof(*hash_part) * 2); + + *hash_part = strtoul(str, NULL, 16); + + if (errno != 0) + { + GST_ERROR("Failed to convert string \"%s\" to hash part %u. %s.", str, 4 - i, strerror(errno)); + return false; + } + } + + return true; +} + +static void tag_to_ascii_bytes(uint32_t tag, uint8_t *ascii_bytes) +{ + char buffer[sizeof(tag) * 2 + 1]; + sprintf(buffer, "%08x", tag); + memcpy(ascii_bytes, buffer, sizeof(tag) * 2); +} + +static void hash_to_ascii_bytes(const struct payload_hash *hash, uint8_t *ascii_bytes) +{ + char buffer[sizeof(*hash) * 2 + 1]; + sprintf(buffer, "%08x%08x%08x%08x", hash->hash[3], hash->hash[2], hash->hash[1], hash->hash[0]); + memcpy(ascii_bytes, buffer, sizeof(*hash) * 2); +} + +static void payload_header_from_bytes(struct payload_header *header, const uint8_t *bytes) +{ + header->size = bytes_to_uint32(bytes); + header->compression = bytes_to_uint32(bytes + 4); + header->crc = bytes_to_uint32(bytes + 8); + header->full_size = bytes_to_uint32(bytes + 12); +} + +static int fozdb_read_file_header(struct fozdb *db) +{ + struct file_header header; + + if (!complete_read(db->file, &header, sizeof(header))) + { + GST_ERROR("Failed to read file header."); + return CONV_ERROR_READ_FAILED; + } + if (memcmp(&header.magic, FOZDB_MAGIC, sizeof(FOZDB_MAGIC)) != 0) + { + GST_ERROR("Bad magic."); + return CONV_ERROR_CORRUPT_DATABASE; + } + if (header.version < FOZDB_MIN_COMPAT_VERSION || header.version > FOZDB_VERSION) + { + GST_ERROR("Incompatible version %u.", header.version); + return CONV_ERROR_CORRUPT_DATABASE; + } + + return CONV_OK; +} + +static int fozdb_read_entry_tag_hash_header(struct fozdb *db, + uint32_t *out_tag, struct payload_hash *out_hash, struct payload_header *out_header) +{ + uint8_t entry_name_and_header[ENTRY_NAME_SIZE + sizeof(struct payload_header)]; + struct payload_hash hash; + uint32_t tag; + + if (!complete_read(db->file, entry_name_and_header, sizeof(entry_name_and_header))) + { + GST_ERROR("Failed to read entry name and header."); + return CONV_ERROR_READ_FAILED; + } + + if (!tag_from_ascii_bytes(&tag, entry_name_and_header) + || !hash_from_ascii_bytes(&hash, entry_name_and_header + sizeof(tag) * 2)) + return CONV_ERROR_CORRUPT_DATABASE; + + payload_header_from_bytes(out_header, entry_name_and_header + ENTRY_NAME_SIZE); + + *out_tag = tag; + *out_hash = hash; + return CONV_OK; +} + +static bool fozdb_seek_to_next_entry(struct fozdb *db, struct payload_header *header, bool *truncated) +{ + uint64_t file_size = 0, data_offset = lseek(db->file, 0, SEEK_CUR); + + if (truncated) + *truncated = false; + + get_file_size(db->file, &file_size); + + if (lseek(db->file, header->size, SEEK_CUR) < 0) + { + GST_ERROR("Failed to seek to next entry. %s. " + "Entry data offset %#"PRIx64", size %#x, file size %#"PRIx64".", + strerror(errno), data_offset, header->size, file_size); + return false; + } + + if (file_size && data_offset + header->size > file_size) + { + /* Truncated chunk is not fatal. */ + GST_WARNING("Entry data larger than file, truncating database here. " + "Entry data offset %#"PRIx64", size %#x, file size %#"PRIx64".", + data_offset, header->size, file_size); + if (truncated) + *truncated = true; + } + + return true; +} + +static bool fozdb_write_entry_name(struct fozdb *db, uint32_t tag, struct payload_hash *hash) +{ + uint8_t entry_name[ENTRY_NAME_SIZE]; + + tag_to_ascii_bytes(tag, entry_name); + hash_to_ascii_bytes(hash, entry_name + sizeof(tag) * 2); + + if (!complete_write(db->file, entry_name, sizeof(entry_name))) + { + GST_ERROR("Failed to write entry name."); + return false; + } + + return true; +} + +/* Copy an entry to write_pos. */ +static int fozdb_copy_entry(struct fozdb *db, + struct entry_name *name, struct payload_header *header, uint64_t entry_data_offset) +{ + uint64_t read_offset, entry_end = entry_data_offset + header->size; + ssize_t read_size; + uint8_t *buffer; + + if (lseek(db->file, db->write_pos, SEEK_SET) < 0) + { + GST_ERROR("Failed to seek file to write_pos."); + return CONV_ERROR_SEEK_FAILED; + } + + /* Write entry name. */ + if (!fozdb_write_entry_name(db, name->tag, &name->hash)) + return CONV_ERROR_WRITE_FAILED; + db->write_pos += ENTRY_NAME_SIZE; + + /* Write entry header. */ + if (!complete_write(db->file, header, sizeof(*header))) + { + GST_ERROR("Failed to write entry header."); + return CONV_ERROR_WRITE_FAILED; + } + db->write_pos += sizeof(*header); + + /* Copy entry data. */ + buffer = calloc(1, BUFFER_COPY_BYTES); + for (read_offset = entry_data_offset; read_offset < entry_end; read_offset += read_size) + { + size_t to_read = min(entry_end - read_offset, BUFFER_COPY_BYTES); + + /* Read data from entry. */ + if (lseek(db->file, read_offset, SEEK_SET) < 0) + { + GST_ERROR("Failed to seek to read offset. %s.", strerror(errno)); + free(buffer); + return CONV_ERROR_SEEK_FAILED; + } + if ((read_size = read(db->file, buffer, to_read)) < 0) + { + GST_ERROR("Failed to read entry data. %s.", strerror(errno)); + free(buffer); + return CONV_ERROR_READ_FAILED; + } + if (read_size == 0) + break; + + /* Write data to write_pos. */ + if (lseek(db->file, db->write_pos, SEEK_SET) < 0) + { + GST_ERROR("Failed to seek to write_pos. %s.", strerror(errno)); + free(buffer); + return CONV_ERROR_SEEK_FAILED; + } + if (!complete_write(db->file, buffer, read_size)) + { + GST_ERROR("Failed to write entry data to write_pos."); + free(buffer); + return CONV_ERROR_WRITE_FAILED; + } + db->write_pos += read_size; + } + free(buffer); + + if (lseek(db->file, read_offset, SEEK_SET) < 0) + { + GST_ERROR("Failed to seek to read offset. %s.", strerror(errno)); + return CONV_ERROR_SEEK_FAILED; + } + + return CONV_OK; +} + +int fozdb_create(const char *file_name, int open_flags, bool read_only, uint32_t num_tags, struct fozdb **out) +{ + struct fozdb *db; + size_t i; + int ret; + + GST_DEBUG("file_name %s, open_flags %d, read_only %d, num_tags %u, out %p.", + file_name, open_flags, read_only, num_tags, out); + + db = calloc(1, sizeof(*db)); + + if (!open_file(file_name, open_flags, &db->file)) + { + free(db); + return CONV_ERROR_OPEN_FAILED; + } + + db->file_name = file_name; + db->num_tags = num_tags; + db->read_only = read_only; + + /* Create entry hash tables. */ + db->seen_blobs = calloc(num_tags, sizeof(*db->seen_blobs)); + for (i = 0; i < num_tags; ++i) + db->seen_blobs[i] = g_hash_table_new_full(hash_func, hash_equal, NULL, free); + + /* Load entries. */ + if ((ret = fozdb_prepare(db)) < 0) + { + GST_ERROR("Failed to prepare fozdb, ret %d.", ret); + fozdb_release(db); + return ret; + } + + GST_INFO("Created fozdb %p from %s.", db, file_name); + + *out = db; + return CONV_OK; +} + +void fozdb_release(struct fozdb *db) +{ + int i; + + GST_DEBUG("db %p.", db); + + for (i = 0; i < db->num_tags; ++i) + g_hash_table_destroy(db->seen_blobs[i]); + free(db->seen_blobs); + close(db->file); + free(db); +} + +int fozdb_prepare(struct fozdb *db) +{ + uint64_t file_size; + int ret; + + GST_DEBUG("db %p, file_name %s, read_only %d, num_tags %u.", + db, db->file_name, db->read_only, db->num_tags); + + db->write_pos = lseek(db->file, 0, SEEK_SET); + if (!get_file_size(db->file, &file_size)) + return CONV_ERROR; + + /* New file, write foz header. */ + if (!file_size) + { + struct file_header file_header; + + memcpy(file_header.magic, FOZDB_MAGIC, sizeof(FOZDB_MAGIC)); + file_header.unused1 = 0; + file_header.unused2 = 0; + file_header.unused3 = 0; + file_header.version = FOZDB_VERSION; + + if (!complete_write(db->file, &file_header, sizeof(file_header))) + { + GST_ERROR("Failed to write file header."); + return CONV_ERROR_WRITE_FAILED; + } + db->write_pos = sizeof(file_header); + + return CONV_OK; + } + + /* Read file header. */ + if ((ret = fozdb_read_file_header(db)) < 0) + return ret; + db->write_pos = lseek(db->file, 0, SEEK_CUR); + + /* Read entries to seen_blobs. */ + while (db->write_pos < file_size) + { + struct payload_entry entry, *table_entry; + uint32_t tag; + + /* Read an entry. */ + if ((ret = fozdb_read_entry_tag_hash_header(db, &tag, &entry.hash, &entry.header) < 0)) + return ret; + entry.offset = lseek(db->file, 0, SEEK_CUR); + + if (!fozdb_seek_to_next_entry(db, &entry.header, NULL)) + return CONV_ERROR_SEEK_FAILED; + db->write_pos = lseek(db->file, 0, SEEK_CUR); + + GST_INFO("Got entry: tag %u, hash %s, offset %#"PRIx64", size %#x, crc %#x.", + tag, format_hash(&entry.hash), entry.offset, entry.header.size, entry.header.crc); + + /* Insert entry to hash table. */ + if (tag >= db->num_tags) + { + GST_WARNING("Invalid tag %u.", tag); + + /* Ignore unknown tags for read-only DBs. */ + if (db->read_only) + continue; + else + return CONV_ERROR_INVALID_TAG; + } + table_entry = calloc(1, sizeof(*table_entry)); + *table_entry = entry; + g_hash_table_insert(db->seen_blobs[tag], &table_entry->hash, table_entry); + } + + return CONV_OK; +} + +bool fozdb_has_entry(struct fozdb *db, uint32_t tag, struct payload_hash *hash) +{ + if (tag >= db->num_tags) + return false; + return g_hash_table_contains(db->seen_blobs[tag], hash); +} + +int fozdb_entry_size(struct fozdb *db, uint32_t tag, struct payload_hash *hash, uint32_t *size) +{ + struct payload_entry *entry; + + if (tag >= db->num_tags) + return CONV_ERROR_INVALID_TAG; + if (!(entry = g_hash_table_lookup(db->seen_blobs[tag], hash))) + return CONV_ERROR_ENTRY_NOT_FOUND; + + *size = entry->header.full_size; + + return CONV_OK; +} + +void fozdb_iter_tag(struct fozdb *db, uint32_t tag, GHashTableIter *iter) +{ + if (tag > db->num_tags) + { + GST_ERROR("Invalid tag %u.", tag); + return; + } + g_hash_table_iter_init(iter, db->seen_blobs[tag]); +} + +int fozdb_read_entry_data(struct fozdb *db, uint32_t tag, struct payload_hash *hash, + uint64_t offset, uint8_t *buffer, size_t size, size_t *read_size, bool with_crc) +{ + struct payload_entry *entry; + size_t to_copy; + + GST_DEBUG("db %p, file_name %s, tag %u, hash %s, offset %#"PRIx64", buffer %p, size %zu, read_size %p, with_crc %d.", + db, db->file_name, tag, format_hash(hash), offset, buffer, size, read_size, with_crc); + + if (tag >= db->num_tags) + return CONV_ERROR_INVALID_TAG; + if (!(entry = g_hash_table_lookup(db->seen_blobs[tag], hash))) + return CONV_ERROR_ENTRY_NOT_FOUND; + + if (entry->header.compression != FOZDB_COMPRESSION_NONE) + return CONV_ERROR_NOT_IMPLEMENTED; + + if (offset >= entry->header.full_size) + return CONV_OK; + + if (lseek(db->file, entry->offset + offset, SEEK_SET) < 0) + return CONV_ERROR_SEEK_FAILED; + + to_copy = min(entry->header.full_size - offset, size); + if (!complete_read(db->file, buffer, to_copy)) + { + GST_ERROR("Failed to read entry data."); + return CONV_ERROR_READ_FAILED; + } + *read_size = to_copy; + + if (entry->header.crc != 0 && with_crc && entry->header.crc != crc32(0, buffer, to_copy)) + { + GST_ERROR("Wrong check sum."); + return CONV_ERROR_WRONG_CHECKSUM; + } + + return CONV_OK; +} + +int fozdb_write_entry(struct fozdb *db, uint32_t tag, struct payload_hash *hash, + void *data_src, data_read_callback read_callback, bool with_crc) +{ + struct payload_header header; + struct payload_entry *entry; + off_t header_offset; + uint32_t size = 0; + size_t read_size; + uint8_t *buffer; + uint64_t offset; + int ret; + + GST_DEBUG("db %p, file_name %s, tag %u, hash %s, data_src %p, read_callback %p, with_crc %d.", + db, db->file_name, tag, format_hash(hash), data_src, read_callback, with_crc); + + if (tag >= db->num_tags) + { + GST_ERROR("Invalid tag %u.", tag); + return CONV_ERROR_INVALID_TAG; + } + if (fozdb_has_entry(db, tag, hash)) + return CONV_OK; + + if (lseek(db->file, db->write_pos, SEEK_SET) < 0) + { + GST_ERROR("Failed to seek file to write_pos %#"PRIx64".", db->write_pos); + return CONV_ERROR_SEEK_FAILED; + } + + /* Write entry name. */ + if (!fozdb_write_entry_name(db, tag, hash)) + return CONV_ERROR_WRITE_FAILED; + + /* Write payload header first. */ + header_offset = lseek(db->file, 0, SEEK_CUR); + header.size = UINT32_MAX; /* Will be filled later. */ + header.compression = FOZDB_COMPRESSION_NONE; + header.crc = 0; /* Will be filled later. */ + header.full_size = UINT32_MAX; /* Will be filled later. */ + if (!complete_write(db->file, &header, sizeof(header))) + { + GST_ERROR("Failed to write entry header."); + return CONV_ERROR_WRITE_FAILED; + } + offset = lseek(db->file, 0, SEEK_CUR); + + /* Write data. */ + buffer = calloc(1, BUFFER_COPY_BYTES); + while ((ret = read_callback(data_src, buffer, BUFFER_COPY_BYTES, &read_size)) == CONV_OK) + { + if (size + read_size > UINT32_MAX) + { + GST_ERROR("Data too large. Fossilize format only supports 4 GB entries."); + free(buffer); + return CONV_ERROR; + } + + size += read_size; + if (!complete_write(db->file, buffer, read_size)) + { + GST_ERROR("Failed to write entry data."); + free(buffer); + return CONV_ERROR_WRITE_FAILED; + } + + if (with_crc) + header.crc = crc32(header.crc, buffer, read_size); + } + db->write_pos = lseek(db->file, 0, SEEK_CUR); + free(buffer); + if (ret != CONV_ERROR_DATA_END) + { + GST_ERROR("Failed to read data from data src, ret %d.", ret); + return ret; + } + + /* Seek back and fill in the size to header. */ + if (lseek(db->file, header_offset, SEEK_SET) < 0) + { + GST_ERROR("Failed to seek back to entry header. %s.", strerror(errno)); + return CONV_ERROR_SEEK_FAILED; + } + header.size = size; + header.full_size = size; + if (!complete_write(db->file, &header, sizeof(header))) + { + GST_ERROR("Failed to write entry header."); + return CONV_ERROR_WRITE_FAILED; + } + + /* Success. Record entry and exit. */ + entry = calloc(1, sizeof(*entry)); + entry->header = header; + entry->hash = *hash; + entry->offset = offset; + g_hash_table_insert(db->seen_blobs[tag], &entry->hash, entry); + + GST_INFO("Wrote entry: tag %u, hash %s, offset %#"PRIx64", size %#x, crc %#x.", + tag, format_hash(&entry->hash), entry->offset, entry->header.size, entry->header.crc); + + return CONV_OK; +} + +/* Rewrites the database, discarding entries listed. */ +int fozdb_discard_entries(struct fozdb *db, GList *to_discard_names) +{ + uint8_t entry_name_and_header[ENTRY_NAME_SIZE + sizeof(struct payload_header)]; + uint64_t file_size; + int i, ret; + + GST_DEBUG("db %p, file_name %s, to_discard_entries %p.", db, db->file_name, to_discard_names); + + /* Rewind the file and clear the entry tables. */ + if (lseek(db->file, 0, SEEK_SET) < 0) + { + GST_ERROR("Failed to seek to file start. %s.", strerror(errno)); + return CONV_ERROR_SEEK_FAILED; + } + for (i = 0; i < db->num_tags; ++i) + g_hash_table_remove_all(db->seen_blobs[i]); + + /* Read file header. */ + if ((ret = fozdb_read_file_header(db)) < 0) + return ret; + db->write_pos = lseek(db->file, 0, SEEK_CUR); + + /* Read each entry and see if it should be discarded. */ + if (!get_file_size(db->file, &file_size)) + return CONV_ERROR; + while (lseek(db->file, 0, SEEK_CUR) < file_size) + { + struct payload_header header; + uint64_t entry_data_offset; + struct entry_name name; + bool truncated; + + if ((ret = fozdb_read_entry_tag_hash_header(db, &name.tag, &name.hash, &header) < 0)) + return CONV_ERROR_READ_FAILED; + entry_data_offset = lseek(db->file, 0, SEEK_CUR); + + /* Check if entry should be discarded. */ + if (g_list_find_custom(to_discard_names, &name, entry_name_compare)) + { + if (!fozdb_seek_to_next_entry(db, &header, &truncated)) + return CONV_ERROR_SEEK_FAILED; + if (truncated) + break; + } + else + { + if (db->write_pos == entry_data_offset - sizeof(entry_name_and_header)) + { + /* If we haven't dropped any chunks, we can just skip it rather than rewrite it. */ + if (!fozdb_seek_to_next_entry(db, &header, &truncated)) + return CONV_ERROR_SEEK_FAILED; + if (truncated) + break; + db->write_pos = lseek(db->file, 0, SEEK_CUR); + } + else + { + /* We're offset, so we have to rewrite. */ + if ((ret = fozdb_copy_entry(db, &name, &header, entry_data_offset)) < 0) + return ret; + } + } + } + + if (ftruncate(db->file, db->write_pos) < 0) + { + GST_ERROR("Failed to truncate file. %s.", strerror(errno)); + return CONV_ERROR; + } + + return fozdb_prepare(db); +} diff --git a/dlls/winegstreamer/media-converter/lib.c b/dlls/winegstreamer/media-converter/lib.c new file mode 100644 index 00000000000..4f7884c1178 --- /dev/null +++ b/dlls/winegstreamer/media-converter/lib.c @@ -0,0 +1,336 @@ +/* + * Copyright 2024 Ziqing Hui for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#if 0 +#pragma makedep unix +#endif + +#include "media-converter.h" + +GST_ELEMENT_REGISTER_DECLARE(protonvideoconverter); +GST_ELEMENT_REGISTER_DECLARE(protonaudioconverter); +GST_ELEMENT_REGISTER_DECLARE(protonaudioconverterbin); +GST_ELEMENT_REGISTER_DECLARE(protondemuxer); + +GST_DEBUG_CATEGORY(media_converter_debug); + +static void get_dirname(const char *path, char *result) +{ + size_t i; + + for (i = strlen(path) - 1; i > 0; --i) + { + if (path[i] == '\\' || path[i] == '/') + break; + } + + memcpy(result, path, i); + result[i] = 0; +} + +static void path_concat(char *result, const char *a, const char *b) +{ + size_t a_len, b_offset; + + a_len = strlen(a); + b_offset = a_len; + memcpy(result, a, a_len); + + if (result[a_len - 1] != '/') + { + result[a_len] = '/'; + ++b_offset; + } + + strcpy(result + b_offset, b); +} + +static bool create_all_dir(const char *dir) +{ + char *ptr, buffer[4096] = {0}; + + strcpy(buffer, dir); + if (buffer[strlen(dir) - 1] != '/') + buffer[strlen(dir)] = '/'; + + ptr = strchr(buffer + 1, '/'); + while (ptr) + { + *ptr = '\0'; + + if (mkdir(buffer, 0777) < 0) + { + if (errno != EEXIST) + { + GST_ERROR("Failed to make directory %s. %s.", buffer, strerror(errno)); + return false; + } + } + *ptr = '/'; + + ptr = strchr(ptr + 1, '/'); + } + return true; +} + +static int create_file(const char *file_name) +{ + int fd, ret = CONV_OK; + char dir[4096]; + + get_dirname(file_name, dir); + if (access(dir, F_OK) < 0 && !create_all_dir(dir)) + return CONV_ERROR_PATH_NOT_FOUND; + + if ((fd = open(file_name, O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) < 0) + { + GST_WARNING("Failed to open file %s with O_CREAT. %s.", file_name, strerror(errno)); + return CONV_ERROR_OPEN_FAILED; + } + + if (futimens(fd, NULL) < 0) + { + GST_WARNING("Failed to set file %s timestamps. %s.", file_name, strerror(errno)); + ret = CONV_ERROR; + } + + close(fd); + + return ret; +} + +bool open_file(const char *file_name, int open_flags, int *out_fd) +{ + int fd; + + if ((fd = open(file_name, open_flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) < 0) + { + GST_WARNING("Failed to open %s with flags %d. %s.", file_name, open_flags, strerror(errno)); + return false; + } + + *out_fd = fd; + return true; +} + +bool get_file_size(int fd, uint64_t *file_size) +{ + struct stat file_info; + + if (fstat(fd, &file_info) < 0) + { + GST_WARNING("Failed to fstat fd %d. %s.", fd, strerror(errno)); + return false; + } + + *file_size = file_info.st_size; + return true; +} + +bool complete_read(int file, void *buffer, size_t size) +{ + size_t current_read = 0; + ssize_t read_size; + errno = 0; + + while (current_read < size) + { + read_size = read(file, ((char *)buffer) + current_read, size - current_read); + if(read_size <= 0) + { + if(errno != EINTR && errno != EAGAIN) + return false; + } + else + { + current_read += read_size; + } + } + + return current_read == size; +} + +bool complete_write(int file, const void *buffer, size_t size) +{ + size_t current_write = 0; + ssize_t write_size; + errno = 0; + + while (current_write < size) + { + write_size = write(file, (const char *)buffer + current_write, size - current_write); + if(write_size < 0) + { + if(errno != EINTR && errno != EAGAIN) + return false; + } + else + { + current_write += write_size; + } + } + + return current_write == size; +} + +uint32_t crc32(uint32_t crc, const uint8_t *ptr, size_t buf_len) +{ + static const uint32_t s_crc_table[256] = + { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, + 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, + 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, + 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, + 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, + 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, + 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, + 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, + 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, + 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, + 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, + 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, + 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, + 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, + 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, + 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, + 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, + 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, + 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, + 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, + 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, + 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, + 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, + 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, + 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, + }; + uint32_t crc32 = (uint32_t)crc ^ 0xffffffff; + const uint8_t *buffer = (const uint8_t *)ptr; + + while (buf_len >= 4) + { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ buffer[0]) & 0xff]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ buffer[1]) & 0xff]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ buffer[2]) & 0xff]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ buffer[3]) & 0xff]; + buffer += 4; + buf_len -= 4; + } + + while (buf_len) + { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ buffer[0]) & 0xff]; + ++buffer; + --buf_len; + } + + return ~crc32; +} + +int create_placeholder_file(const char *file_name) +{ + const char *shader_path; + char path[1024]; + int ret; + + if ((shader_path = getenv("STEAM_COMPAT_SHADER_PATH"))) + { + path_concat(path, shader_path, file_name); + if ((ret = create_file(path)) < 0) + GST_ERROR("Failed to create %s file, ret %d.", file_name, ret); + } + else + { + GST_ERROR("Env STEAM_COMPAT_SHADER_PATH not set."); + ret = CONV_ERROR_ENV_NOT_SET; + } + + return ret; +} + +int dump_fozdb_open(struct dump_fozdb *db, bool create, const char *file_path_env, int num_tags) +{ + char *dump_file_path; + + if (db->fozdb) + return CONV_OK; + + if (!(dump_file_path = getenv(file_path_env))) + { + GST_ERROR("Env %s not set.", file_path_env); + return CONV_ERROR_ENV_NOT_SET; + } + + if (create) + create_file(dump_file_path); + + return fozdb_create(dump_file_path, O_RDWR, false, num_tags, &db->fozdb); +} + +void dump_fozdb_close(struct dump_fozdb *db) +{ + if (db->fozdb) + { + fozdb_release(db->fozdb); + db->fozdb = NULL; + } +} + +bool media_converter_init(void) +{ + GST_DEBUG_CATEGORY_INIT(media_converter_debug, + "protonmediaconverter", GST_DEBUG_FG_YELLOW, "Proton media converter"); + + if (!GST_ELEMENT_REGISTER(protonvideoconverter, NULL)) + { + GST_ERROR("Failed to register protonvideoconverter."); + return false; + } + + if (!GST_ELEMENT_REGISTER(protonaudioconverter, NULL)) + { + GST_ERROR("Failed to register protonaudioconverter."); + return false; + } + + if (!GST_ELEMENT_REGISTER(protonaudioconverterbin, NULL)) + { + GST_ERROR("Failed to register protonaudioconverterbin."); + return false; + } + + if (!GST_ELEMENT_REGISTER(protondemuxer, NULL)) + { + GST_ERROR("Failed to register protondemuxer."); + return false; + } + + return true; +} diff --git a/dlls/winegstreamer/media-converter/media-converter.h b/dlls/winegstreamer/media-converter/media-converter.h new file mode 100644 index 00000000000..51b54147806 --- /dev/null +++ b/dlls/winegstreamer/media-converter/media-converter.h @@ -0,0 +1,287 @@ +/* + * Copyright 2024 Ziqing Hui for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef __MEDIA_CONVERTER_H__ +#define __MEDIA_CONVERTER_H__ + +#define _FILE_OFFSET_BITS 64 + +#include +#include +#include +#include + +#include "unix_private.h" + +GST_DEBUG_CATEGORY_EXTERN(media_converter_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT media_converter_debug + +typedef int (*data_read_callback)(void *data_reader, uint8_t *buffer, size_t size, size_t *read_size); + +/* Changing this will invalidate the cache. You MUST clear it. */ +#define HASH_SEED 0x4AA61F63 + +enum conv_ret +{ + CONV_OK = 0, + CONV_ERROR = -1, + CONV_ERROR_NOT_IMPLEMENTED = -2, + CONV_ERROR_INVALID_ARGUMENT = -3, + CONV_ERROR_OPEN_FAILED = -4, + CONV_ERROR_READ_FAILED = -5, + CONV_ERROR_WRITE_FAILED = -6, + CONV_ERROR_SEEK_FAILED = -7, + CONV_ERROR_CORRUPT_DATABASE = -8, + CONV_ERROR_WRONG_CHECKSUM = -9, + CONV_ERROR_ENTRY_NOT_FOUND = -10, + CONV_ERROR_ENV_NOT_SET = -11, + CONV_ERROR_PATH_NOT_FOUND = -12, + CONV_ERROR_INVALID_TAG = -13, + CONV_ERROR_DATA_END = -14, +}; + +struct murmur3_x64_128_state +{ + uint32_t seed; + uint64_t h1; + uint64_t h2; + size_t processed; +}; + +struct murmur3_x86_128_state +{ + uint32_t seed; + uint32_t h1; + uint32_t h2; + uint32_t h3; + uint32_t h4; + size_t processed; +}; + +struct bytes_reader +{ + const uint8_t *data; + size_t size; + size_t offset; +}; + +struct gst_buffer_reader +{ + GstBuffer *buffer; /* No ref here, no need to unref. */ + size_t offset; +}; + +struct payload_hash +{ + uint32_t hash[4]; +}; + +struct entry_name +{ + uint32_t tag; + struct payload_hash hash; +}; + +struct dump_fozdb +{ + pthread_mutex_t mutex; + struct fozdb *fozdb; + bool already_cleaned; +}; + +struct fozdb +{ + const char *file_name; + int file; + bool read_only; + uint64_t write_pos; + GHashTable **seen_blobs; + uint32_t num_tags; +}; + +/* lib.c. */ +extern bool open_file(const char *file_name, int open_flags, int *out_fd); +extern bool get_file_size(int fd, uint64_t *file_size); +extern bool complete_read(int file, void *buffer, size_t size); +extern bool complete_write(int file, const void *buffer, size_t size); +extern uint32_t crc32(uint32_t crc, const uint8_t *ptr, size_t buf_len); +extern int create_placeholder_file(const char *file_name); +extern int dump_fozdb_open(struct dump_fozdb *db, bool create, const char *file_path_env, int num_tags); +extern void dump_fozdb_close(struct dump_fozdb *db); + +/* murmur3.c. */ +extern void murmur3_x64_128_state_init(struct murmur3_x64_128_state *state, uint32_t seed); +extern void murmur3_x64_128_state_reset(struct murmur3_x64_128_state *state); +extern bool murmur3_x64_128_full(void *data_src, data_read_callback read_callback, + struct murmur3_x64_128_state* state, void *out); +extern bool murmur3_x64_128(void *data_src, data_read_callback read_callback, uint32_t seed, void *out); +extern void murmur3_x86_128_state_init(struct murmur3_x86_128_state *state, uint32_t seed); +extern void murmur3_x86_128_state_reset(struct murmur3_x86_128_state *state); +extern bool murmur3_x86_128_full(void *data_src, data_read_callback read_callback, + struct murmur3_x86_128_state* state, void *out); +extern bool murmur3_x86_128(void *data_src, data_read_callback read_callback, uint32_t seed, void *out); +#ifdef __x86_64__ +#define murmur3_128_state murmur3_x64_128_state +#define murmur3_128_state_init murmur3_x64_128_state_init +#define murmur3_128_state_reset murmur3_x64_128_state_reset +#define murmur3_128_full murmur3_x64_128_full +#define murmur3_128 murmur3_x64_128 +#elif defined(__i386__) +#define murmur3_128_state murmur3_x86_128_state +#define murmur3_128_state_init murmur3_x86_128_state_init +#define murmur3_128_state_reset murmur3_x86_128_state_reset +#define murmur3_128_full murmur3_x86_128_full +#define murmur3_128 murmur3_x86_128 +#endif /* __x86_64__ */ + +/* fossilize.c. */ +extern int fozdb_create(const char *file_name, int open_flags, bool read_only, uint32_t num_tags, struct fozdb **out); +extern void fozdb_release(struct fozdb *db); +extern int fozdb_prepare(struct fozdb *db); +extern bool fozdb_has_entry(struct fozdb *db, uint32_t tag, struct payload_hash *hash); +extern int fozdb_entry_size(struct fozdb *db, uint32_t tag, struct payload_hash *hash, uint32_t *size); +extern void fozdb_iter_tag(struct fozdb *db, uint32_t tag, GHashTableIter *iter); +extern int fozdb_read_entry_data(struct fozdb *db, uint32_t tag, struct payload_hash *hash, + uint64_t offset, uint8_t *buffer, size_t size, size_t *read_size, bool with_crc); +extern int fozdb_write_entry(struct fozdb *db, uint32_t tag, struct payload_hash *hash, + void *data_src, data_read_callback read_callback, bool with_crc); +extern int fozdb_discard_entries(struct fozdb *db, GList *to_discard_entries); + +static inline bool option_enabled(const char *env) +{ + const char *env_var; + + if (!(env_var = getenv(env))) + return false; + + return strcmp(env_var, "0") != 0; +} + +static inline bool discarding_disabled(void) +{ + return option_enabled("MEDIACONV_DONT_DISCARD"); +} + +static inline const char *format_hash(struct payload_hash *hash) +{ + int hash_str_size = 2 + sizeof(*hash) * 2 + 1; + static char buffer[1024] = {}; + static int offset = 0; + char *ret; + + if (offset + hash_str_size > sizeof(buffer)) + offset = 0; + + ret = buffer + offset; + sprintf(ret, "0x%08x%08x%08x%08x", hash->hash[3], hash->hash[2], hash->hash[1], hash->hash[0]); + offset += hash_str_size; + + return ret; +} + +static inline void bytes_reader_init(struct bytes_reader *reader, const uint8_t *data, size_t size) +{ + reader->data = data; + reader->size = size; + reader->offset = 0; +} + +static inline int bytes_reader_read(void *data_reader, uint8_t *buffer, size_t size, size_t *read_size) +{ + struct bytes_reader *reader = data_reader; + size_t data_size, to_copy; + + if (!size) + { + *read_size = 0; + return CONV_OK; + } + + if (!(data_size = reader->size - reader->offset)) + return CONV_ERROR_DATA_END; + + to_copy = min(data_size, size); + memcpy(buffer, reader->data + reader->offset, to_copy); + reader->offset += to_copy; + + *read_size = to_copy; + return CONV_OK; +} + +static inline void gst_buffer_reader_init(struct gst_buffer_reader *reader, GstBuffer *buffer) +{ + reader->buffer = buffer; /* No ref here, so no need to unref. */ + reader->offset = 0; +} + +static inline int gst_buffer_reader_read(void *data_reader, uint8_t *buffer, size_t size, size_t *read_size) +{ + struct gst_buffer_reader *reader = data_reader; + + if (!size) + { + *read_size = 0; + return CONV_OK; + } + + *read_size = gst_buffer_extract(reader->buffer, reader->offset, buffer, size); + reader->offset += *read_size; + if (!*read_size) + return CONV_ERROR_DATA_END; + + return CONV_OK; +} + +static inline bool file_exists(const char *file_path) +{ + if (!file_path) + return false; + return access(file_path, F_OK) == 0; +} + +static inline struct entry_name *entry_name_create(uint32_t tag, struct payload_hash *hash) +{ + struct entry_name *entry = calloc(1, sizeof(*entry)); + entry->tag = tag; + entry->hash = *hash; + return entry; +} + +static inline gint entry_name_compare(const void *a, const void *b) +{ + return memcmp(a, b, sizeof(struct entry_name)); +} + +static inline uint32_t bytes_to_uint32(const uint8_t *bytes) +{ + return ((uint32_t)bytes[0] << 0) + | ((uint32_t)bytes[1] << 8) + | ((uint32_t)bytes[2] << 16) + | ((uint32_t)bytes[3] << 24); +} + +static inline void payload_hash_from_bytes(struct payload_hash *hash, uint8_t *bytes) +{ + hash->hash[0] = bytes_to_uint32(bytes + 0); + hash->hash[1] = bytes_to_uint32(bytes + 4); + hash->hash[2] = bytes_to_uint32(bytes + 8); + hash->hash[3] = bytes_to_uint32(bytes + 12); +} + +#endif /* __MEDIA_CONVERTER_H__ */ diff --git a/dlls/winegstreamer/media-converter/murmur3.c b/dlls/winegstreamer/media-converter/murmur3.c new file mode 100644 index 00000000000..014345e61d5 --- /dev/null +++ b/dlls/winegstreamer/media-converter/murmur3.c @@ -0,0 +1,371 @@ +/* + * Copyright 2024 Ziqing Hui for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +#include "media-converter.h" + +static uint64_t rotl64(uint64_t value, uint8_t shift) +{ + return (value << shift) | (value >> (64 - shift)); +} + +static uint32_t rotl32(uint32_t value, uint8_t shift) +{ + return (value << shift) | (value >> (32 - shift)); +} + +static uint64_t fmix64(uint64_t h) +{ + const uint64_t c1 = 0xff51afd7ed558ccd; + const uint64_t c2 = 0xc4ceb9fe1a85ec53; + const uint32_t r = 33; + + h ^= h >> r; + h *= c1; + h ^= h >> r; + h *= c2; + h ^= h >> r; + + return h; +} + +static uint32_t fmix32 (uint32_t h) +{ + const uint32_t c1 = 0x85ebca6b; + const uint32_t c2 = 0xc2b2ae35; + const uint32_t r1 = 16; + const uint32_t r2 = 13; + + h ^= h >> r1; + h *= c1; + h ^= h >> r2; + h *= c2; + h ^= h >> r1; + + return h; +} + +void murmur3_x64_128_state_init(struct murmur3_x64_128_state *state, uint32_t seed) +{ + state->seed = seed; + state->h1 = seed; + state->h2 = seed; + state->processed = 0; +} + +void murmur3_x64_128_state_reset(struct murmur3_x64_128_state *state) +{ + state->h1 = state->seed; + state->h2 = state->seed; + state->processed = 0; +} + +bool murmur3_x64_128_full(void *data_reader, data_read_callback read_callback, + struct murmur3_x64_128_state* state, void *out) +{ + const uint64_t c1 = 0x87c37b91114253d5, c2 = 0x4cf5ad432745937f, c3 = 0x52dce729, c4 = 0x38495ab5, m = 5; + size_t read_size, processed = state->processed; + const uint32_t r1 = 27, r2 = 31, r3 = 33; + uint64_t h1 = state->h1, h2 = state->h2; + uint8_t buffer[16] = {0}; + uint64_t k1, k2; + int ret; + + while ((ret = read_callback(data_reader, buffer, sizeof(buffer), &read_size)) == CONV_OK) + { + processed += read_size; + + if (read_size == 16) + { + k1 = *(uint64_t *)&buffer[0]; + k2 = *(uint64_t *)&buffer[8]; + + k1 *= c1; + k1 = rotl64(k1, r2); + k1 *= c2; + h1 ^= k1; + h1 = rotl64(h1, r1); + h1 += h2; + h1 = h1 * m + c3; + + k2 *= c2; + k2 = rotl64(k2, r3); + k2 *= c1; + h2 ^= k2; + h2 = rotl64(h2, r2); + h2 += h1; + h2 = h2 * m + c4; + } + else + { + k1 = 0; + k2 = 0; + + switch (read_size) + { + case 15: + k2 ^= ((uint64_t)buffer[14]) << 48; + case 14: + k2 ^= ((uint64_t)buffer[13]) << 40; + case 13: + k2 ^= ((uint64_t)buffer[12]) << 32; + case 12: + k2 ^= ((uint64_t)buffer[11]) << 24; + case 11: + k2 ^= ((uint64_t)buffer[10]) << 16; + case 10: + k2 ^= ((uint64_t)buffer[9]) << 8; + case 9: + k2 ^= ((uint64_t)buffer[8]) << 0; + k2 *= c2; + k2 = rotl64(k2, r3); + k2 *= c1; + h2 ^= k2; + case 8: + k1 ^= ((uint64_t)buffer[7]) << 56; + case 7: + k1 ^= ((uint64_t)buffer[6]) << 48; + case 6: + k1 ^= ((uint64_t)buffer[5]) << 40; + case 5: + k1 ^= ((uint64_t)buffer[4]) << 32; + case 4: + k1 ^= ((uint64_t)buffer[3]) << 24; + case 3: + k1 ^= ((uint64_t)buffer[2]) << 16; + case 2: + k1 ^= ((uint64_t)buffer[1]) << 8; + case 1: + k1 ^= ((uint64_t)buffer[0]) << 0; + k1 *= c1; + k1 = rotl64(k1, r2); + k1 *= c2; + h1 ^= k1; + } + } + } + + if (ret != CONV_ERROR_DATA_END) + return false; + + state->processed = processed; + state->h1 = h1; + state->h2 = h2; + + h1 ^= (uint64_t)processed; + h2 ^= (uint64_t)processed; + h1 += h2; + h2 += h1; + h1 = fmix64(h1); + h2 = fmix64(h2); + h1 += h2; + h2 += h1; + + ((uint64_t *)out)[0] = h1; + ((uint64_t *)out)[1] = h2; + + return true; +} + +bool murmur3_x64_128(void *data_src, data_read_callback read_callback, uint32_t seed, void *out) +{ + struct murmur3_x64_128_state state; + murmur3_x64_128_state_init(&state, seed); + return murmur3_x64_128_full(data_src, read_callback, &state, out); +} + +void murmur3_x86_128_state_init(struct murmur3_x86_128_state *state, uint32_t seed) +{ + state->seed = seed; + state->h1 = seed; + state->h2 = seed; + state->h3 = seed; + state->h4 = seed; + state->processed = 0; +} + +void murmur3_x86_128_state_reset(struct murmur3_x86_128_state *state) +{ + state->h1 = state->seed; + state->h2 = state->seed; + state->h3 = state->seed; + state->h4 = state->seed; + state->processed = 0; +} + +bool murmur3_x86_128_full(void *data_reader, data_read_callback read_callback, + struct murmur3_x86_128_state *state, void *out) +{ + const uint32_t c1 = 0x239b961b, c2 = 0xab0e9789, c3 = 0x38b34ae5, c4 = 0xa1e38b93; + const uint32_t c5 = 0x561ccd1b, c6 = 0x0bcaa747, c7 = 0x96cd1c35, c8 = 0x32ac3b17; + uint32_t h1 = state->h1, h2 = state->h2, h3 = state->h3, h4 = state->h4; + size_t read_size, processed = state->processed; + unsigned char buffer[16] = {0}; + uint64_t k1, k2, k3, k4; + const uint32_t m = 5; + int ret; + + while ((ret = read_callback(data_reader, buffer, sizeof(buffer), &read_size)) == CONV_OK) + { + processed += read_size; + + if (read_size == 16) + { + k1 = *(uint32_t*)&buffer[0]; + k2 = *(uint32_t*)&buffer[4]; + k3 = *(uint32_t*)&buffer[8]; + k4 = *(uint32_t*)&buffer[12]; + + k1 *= c1; + k1 = rotl32(k1, 15); + k1 *= c2; + h1 ^= k1; + h1 = rotl32(h1, 19); + h1 += h2; + h1 = h1 * m + c5; + + k2 *= c2; + k2 = rotl32(k2, 16); + k2 *= c3; + h2 ^= k2; + h2 = rotl32(h2, 17); + h2 += h3; + h2 = h2 * m + c6; + + k3 *= c3; + k3 = rotl32(k3, 17); + k3 *= c4; + h3 ^= k3; + h3 = rotl32(h3, 15); + h3 += h4; + h3 = h3 * m + c7; + + k4 *= c4; + k4 = rotl32(k4, 18); + k4 *= c1; + h4 ^= k4; + h4 = rotl32(h4, 13); + h4 += h1; + h4 = h4 * m + c8; + } + else + { + k1 = 0; + k2 = 0; + k3 = 0; + k4 = 0; + + switch (read_size) + { + case 15: + k4 ^= buffer[14] << 16; + case 14: + k4 ^= buffer[13] << 8; + case 13: + k4 ^= buffer[12] << 0; + k4 *= c4; + k4 = rotl32(k4,18); + k4 *= c1; + h4 ^= k4; + case 12: + k3 ^= buffer[11] << 24; + case 11: + k3 ^= buffer[10] << 16; + case 10: + k3 ^= buffer[9] << 8; + case 9: + k3 ^= buffer[8] << 0; + k3 *= c3; + k3 = rotl32(k3, 17); + k3 *= c4; + h3 ^= k3; + case 8: + k2 ^= buffer[7] << 24; + case 7: + k2 ^= buffer[6] << 16; + case 6: + k2 ^= buffer[5] << 8; + case 5: + k2 ^= buffer[4] << 0; + k2 *= c2; + k2 = rotl32(k2, 16); + k2 *= c3; + h2 ^= k2; + case 4: + k1 ^= buffer[3] << 24; + case 3: + k1 ^= buffer[2] << 16; + case 2: + k1 ^= buffer[1] << 8; + case 1: + k1 ^= buffer[0] << 0; + k1 *= c1; + k1 = rotl32(k1, 15); + k1 *= c2; + h1 ^= k1; + } + } + } + + if (ret != CONV_ERROR_DATA_END) + return false; + + state->processed = processed; + state->h1 = h1; + state->h2 = h2; + state->h3 = h3; + state->h4 = h4; + + h1 ^= processed; + h2 ^= processed; + h3 ^= processed; + h4 ^= processed; + h1 += h2; + h1 += h3; + h1 += h4; + h2 += h1; + h3 += h1; + h4 += h1; + h1 = fmix32(h1); + h2 = fmix32(h2); + h3 = fmix32(h3); + h4 = fmix32(h4); + h1 += h2; + h1 += h3; + h1 += h4; + h2 += h1; + h3 += h1; + h4 += h1; + + ((uint32_t*)out)[0] = h1; + ((uint32_t*)out)[1] = h2; + ((uint32_t*)out)[2] = h3; + ((uint32_t*)out)[3] = h4; + + return true; +} + +bool murmur3_x86_128(void *data_src, data_read_callback read_callback, uint32_t seed, void *out) +{ + struct murmur3_x86_128_state state; + murmur3_x86_128_state_init(&state, seed); + return murmur3_x86_128_full(data_src, read_callback, &state, out); +} diff --git a/dlls/winegstreamer/media-converter/protondemuxer.c b/dlls/winegstreamer/media-converter/protondemuxer.c new file mode 100644 index 00000000000..124d105ca46 --- /dev/null +++ b/dlls/winegstreamer/media-converter/protondemuxer.c @@ -0,0 +1,250 @@ +/* + * Copyright 2024 Remi Bernon for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +#include "media-converter.h" + +GST_DEBUG_CATEGORY_EXTERN(media_converter_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT media_converter_debug + +typedef struct +{ + GstBin bin; + GstElement *video_conv, *demuxer; + GstPad *sink_pad; /* Ghost pad. */ + GstPad *inner_sink, *inner_src; +} ProtonDemuxer; + +typedef struct +{ + GstBinClass class; +} ProtonDemuxerClass; + +G_DEFINE_TYPE(ProtonDemuxer, proton_demuxer, GST_TYPE_BIN); +#define PROTON_DEMUXER_TYPE (proton_demuxer_get_type()) +#define PROTON_DEMUXER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PROTON_DEMUXER_TYPE, ProtonDemuxer)) +GST_ELEMENT_REGISTER_DEFINE(protondemuxer, "protondemuxer", GST_RANK_MARGINAL, PROTON_DEMUXER_TYPE); + +static GstStaticPadTemplate proton_demuxer_sink_template = GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, + GST_PAD_ALWAYS, GST_STATIC_CAPS("video/x-ms-asf; video/x-msvideo; video/mpeg; video/quicktime;")); +static GstStaticPadTemplate proton_demuxer_src_template = GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, + GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); + +static gboolean proton_demuxer_sink_event(GstPad *pad, GstObject *parent, GstEvent *event) +{ + GST_DEBUG_OBJECT(pad, "Got sink event %"GST_PTR_FORMAT".", event); + return gst_pad_event_default(pad, parent, event); +} + +static void proton_demuxer_pad_added(GstElement *element, GstPad *pad, gpointer user) +{ + ProtonDemuxer *bin = PROTON_DEMUXER(user); + GstPad *ghost_src, *src_pad; + GstElement *decoder = NULL; + GstEvent *event; + GstCaps *caps; + + GST_DEBUG_OBJECT(element, "Got inner pad added %"GST_PTR_FORMAT".", pad); + + if ((caps = gst_pad_get_current_caps(pad))) + { + const char *mime_type = gst_structure_get_name(gst_caps_get_structure(caps, 0)); + GST_DEBUG_OBJECT(element, "Got inner pad caps %"GST_PTR_FORMAT".", caps); + + if (!strcmp(mime_type, "audio/x-vorbis")) + decoder = create_element("vorbisdec", "base"); + else if (!strcmp(mime_type, "audio/x-opus")) + decoder = create_element("opusdec", "base"); + + gst_caps_unref(caps); + } + + if (!decoder) + ghost_src = gst_ghost_pad_new(GST_PAD_NAME(pad), pad); + else + { + gst_bin_add(GST_BIN(bin), decoder); + link_src_to_element(pad, decoder); + + src_pad = gst_element_get_static_pad(decoder, "src"); + ghost_src = gst_ghost_pad_new(GST_PAD_NAME(src_pad), src_pad); + gst_object_unref(src_pad); + + gst_element_sync_state_with_parent(decoder); + } + + if ((event = gst_pad_get_sticky_event(pad, GST_EVENT_STREAM_START, 0))) + { + gst_pad_store_sticky_event(ghost_src, event); + gst_event_unref(event); + } + + gst_pad_set_active(ghost_src, true); + gst_element_add_pad(GST_ELEMENT(&bin->bin), ghost_src); +} + +static void proton_demuxer_no_more_pads(GstElement *element, gpointer user) +{ + ProtonDemuxer *bin = PROTON_DEMUXER(user); + GST_DEBUG_OBJECT(element, "Got inner no-more-pads."); + gst_element_no_more_pads(GST_ELEMENT(&bin->bin)); +} + +static GstFlowReturn proton_demuxer_inner_sink_chain(GstPad *pad, GstObject *parent, GstBuffer *buffer) +{ + ProtonDemuxer *bin = PROTON_DEMUXER(gst_pad_get_element_private(pad)); + GST_DEBUG_OBJECT(pad, "Got inner sink buffer %"GST_PTR_FORMAT".", buffer); + return gst_pad_push(bin->inner_src, buffer); +} + +static gboolean proton_demuxer_inner_sink_event_caps(ProtonDemuxer *bin, GstEvent *event) +{ + GstCaps *src_caps, *any_caps; + GstEvent *stream_start; + GstPad *src; + + gst_event_parse_caps(event, &src_caps); + + if (!bin->demuxer) + { + if (!(any_caps = gst_caps_new_any())) + return false; + if (!(bin->demuxer = find_element(GST_ELEMENT_FACTORY_TYPE_DECODABLE, src_caps, any_caps))) + { + gst_caps_unref(any_caps); + return false; + } + gst_caps_unref(any_caps); + g_signal_connect(bin->demuxer, "pad-added", G_CALLBACK(proton_demuxer_pad_added), bin); + g_signal_connect(bin->demuxer, "no-more-pads", G_CALLBACK(proton_demuxer_no_more_pads), bin); + + if ((src = gst_element_get_static_pad(bin->demuxer, "src"))) + { + GstPad *ghost_src = gst_ghost_pad_new_no_target_from_template(GST_PAD_NAME(src), + gst_element_get_pad_template(GST_ELEMENT(&bin->bin), "src")); + gst_ghost_pad_set_target(GST_GHOST_PAD(ghost_src), src); + gst_element_add_pad(GST_ELEMENT(&bin->bin), ghost_src); + gst_object_unref(src); + + gst_element_no_more_pads(GST_ELEMENT(&bin->bin)); + } + + gst_bin_add(GST_BIN(bin), bin->demuxer); + link_src_to_element(bin->inner_src, bin->demuxer); + gst_pad_set_active(bin->inner_src, true); + + if ((stream_start = gst_pad_get_sticky_event(bin->inner_sink, GST_EVENT_STREAM_START, 0))) + push_event(bin->inner_src, stream_start); + + gst_element_sync_state_with_parent(bin->demuxer); + } + + return gst_pad_push_event(bin->inner_src, event); +} + +static gboolean proton_demuxer_inner_sink_event(GstPad *pad, GstObject *parent, GstEvent *event) +{ + ProtonDemuxer *bin = PROTON_DEMUXER(gst_pad_get_element_private(pad)); + + GST_DEBUG_OBJECT(pad, "Got inner sink event %"GST_PTR_FORMAT".", event); + + if (event->type == GST_EVENT_CAPS) + return proton_demuxer_inner_sink_event_caps(bin, event); + if (!bin->demuxer) + return gst_pad_event_default(pad, parent, event); + if (event->type == GST_EVENT_STREAM_START) + { + GstEvent *stream_start; + if ((stream_start = gst_pad_get_sticky_event(bin->inner_src, GST_EVENT_STREAM_START, 0))) + push_event(bin->inner_src, stream_start); + return gst_pad_event_default(pad, parent, event); + } + + return gst_pad_push_event(bin->inner_src, event); +} + +static gboolean proton_demuxer_inner_src_query(GstPad *pad, GstObject *parent, GstQuery *query) +{ + ProtonDemuxer *bin = PROTON_DEMUXER(gst_pad_get_element_private(pad)); + + GST_DEBUG_OBJECT(pad, "Got inner src query %"GST_PTR_FORMAT".", query); + + if (!bin->demuxer) + return gst_pad_query_default(pad, parent, query); + return gst_pad_peer_query(bin->inner_sink, query); +} + +static gboolean proton_demuxer_inner_src_event(GstPad *pad, GstObject *parent, GstEvent *event) +{ + ProtonDemuxer *bin = PROTON_DEMUXER(gst_pad_get_element_private(pad)); + GST_DEBUG_OBJECT(pad, "Got inner src event %"GST_PTR_FORMAT".", event); + return gst_pad_push_event(bin->inner_sink, event); +} + +static void proton_demuxer_class_init(ProtonDemuxerClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + + /* wg_parser autoplugging ordering relies on the element "Proton video converter" name */ + gst_element_class_set_metadata(element_class, "Proton video converter", "Codec/Demuxer", "Demuxes video for Proton", + "Andrew Eikum , Ziqing Hui "); + + gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&proton_demuxer_sink_template)); + gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&proton_demuxer_src_template)); +} + +static void proton_demuxer_init(ProtonDemuxer *bin) +{ + GstStaticPadTemplate inner_sink_template = GST_STATIC_PAD_TEMPLATE("inner-sink", + GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); + GstStaticPadTemplate inner_src_template = GST_STATIC_PAD_TEMPLATE("inner-src", + GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); + GstElement *element = GST_ELEMENT(bin); + GstPad *sink; + + bin->sink_pad = gst_ghost_pad_new_no_target_from_template("sink", gst_element_get_pad_template(element, "sink")); + gst_pad_set_event_function(bin->sink_pad, GST_DEBUG_FUNCPTR(proton_demuxer_sink_event)); + + bin->inner_sink = gst_pad_new_from_static_template(&inner_sink_template, "inner-sink"); + gst_pad_set_chain_function(bin->inner_sink, GST_DEBUG_FUNCPTR(proton_demuxer_inner_sink_chain)); + gst_pad_set_event_function(bin->inner_sink, GST_DEBUG_FUNCPTR(proton_demuxer_inner_sink_event)); + gst_pad_set_element_private(bin->inner_sink, bin); + + bin->inner_src = gst_pad_new_from_static_template(&inner_src_template, "inner-src"); + gst_pad_set_query_function(bin->inner_src, GST_DEBUG_FUNCPTR(proton_demuxer_inner_src_query)); + gst_pad_set_event_function(bin->inner_src, GST_DEBUG_FUNCPTR(proton_demuxer_inner_src_event)); + gst_pad_set_element_private(bin->inner_src, bin); + + bin->video_conv = create_element("protonvideoconverter", "protonmediaconverter"); + gst_bin_add(GST_BIN(bin), bin->video_conv); + link_element_to_sink(bin->video_conv, bin->inner_sink); + gst_pad_set_active(bin->inner_sink, true); + + sink = gst_element_get_static_pad(bin->video_conv, "sink"); + gst_ghost_pad_set_target(GST_GHOST_PAD(bin->sink_pad), sink); + gst_object_unref(sink); + + gst_element_add_pad(element, bin->sink_pad); + + GST_INFO("Initialized ProtonDemuxer %"GST_PTR_FORMAT": video_conv %"GST_PTR_FORMAT", demuxer %"GST_PTR_FORMAT", " + "sink_pad %"GST_PTR_FORMAT".", bin, bin->video_conv, bin->demuxer, bin->sink_pad); +} diff --git a/dlls/winegstreamer/media-converter/videoconv.c b/dlls/winegstreamer/media-converter/videoconv.c new file mode 100644 index 00000000000..0f916b86f66 --- /dev/null +++ b/dlls/winegstreamer/media-converter/videoconv.c @@ -0,0 +1,1245 @@ +/* + * Copyright 2024 Ziqing Hui for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/* Algorithm + * --------- + * + * Nicely, both Quartz and Media Foundation allow us random access to the entire data stream. So we + * can easily hash the entire incoming stream and substitute it with our Ogg Theora video. If there + * is a cache miss, then we dump the entire incoming stream. In case of a cache hit, we dump + * nothing. + * + * Incoming video data is stored in the video.foz Fossilize database. + * + * Transcoded video data is stored in the transcoded_video.foz Fossilize database. + * + * + * Hashing algorithm + * ----------------- + * + * We use murmur3 hash with the seed given below. We use the x32 variant for 32-bit programs, and + * the x64 variant for 64-bit programs. + * + * For speed when hashing, we specify a stride which will skip over chunks of the input. However, + * we will always hash the first "stride" number of bytes, to try to avoid collisions on smaller + * files with size between chunk and stride. + * + * For example, the 'H's below are hashed, the 'x's are skipped: + * + * int chunk = 4; + * int stride = chunk * 3; + * H = hashed, x = skipped + * [HHHH HHHH HHHH HHHH xxxx xxxx HHHH xxxx xxxx HHHH xxxx] < data stream + * ^^^^ ^^^^ ^^^^ stride prefix, hashed + * ^^^^ chunk + * ^^^^ ^^^^ ^^^^ stride + * ^^^^ chunk + * ^^^^ ^^^^ ^^^^ stride + * ^^^^ chunk + * ^^^^ ^^^^ stride + */ + +#if 0 +#pragma makedep unix +#endif + +#include "media-converter.h" +#include + +#include + +#define HASH_CHUNK_SIZE (8 * 1024 * 1024) /* 8 MB. */ +#define HASH_STRIDE (HASH_CHUNK_SIZE * 6) + +#define VIDEO_CONV_FOZ_TAG_VIDEODATA 0 +#define VIDEO_CONV_FOZ_TAG_OGVDATA 1 +#define VIDEO_CONV_FOZ_TAG_STREAM 2 +#define VIDEO_CONV_FOZ_TAG_MKVDATA 3 +#define VIDEO_CONV_FOZ_NUM_TAGS 4 + +#define DURATION_NONE (UINT64_MAX) + +struct pad_reader +{ + GstPad *pad; + size_t offset; + uint8_t *chunk; + size_t chunk_offset; + size_t chunk_end; + size_t stride; /* Set to SIZE_MAX to skip no bytes. */ +}; + +struct hashes_reader +{ + GList *current_hash; +}; + +enum video_conv_state_flags +{ + VIDEO_CONV_STREAM_STARTED = 1, + VIDEO_CONV_HAS_TRANSCODED = 2, + VIDEO_CONV_IS_DUMPING = 4, +}; + +struct video_conv_state +{ + struct payload_hash transcode_hash; + struct fozdb *read_fozdb; + int blank_file; + uint64_t upstream_duration; + uint64_t our_duration; + uint32_t transcoded_tag; + uint32_t state_flags; + uint64_t read_offset; + GList *chunk_hashes; +}; + +typedef struct +{ + GstElement element; + GstPad *sink_pad, *src_pad; + pthread_mutex_t state_mutex; + struct video_conv_state *state; + GstPadMode active_mode; + GstAdapter *adapter; +} VideoConv; + +typedef struct +{ + GstElementClass class; +} VideoConvClass; + +G_DEFINE_TYPE(VideoConv, video_conv, GST_TYPE_ELEMENT); +#define VIDEO_CONV_TYPE (video_conv_get_type()) +#define VIDEO_CONV(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), VIDEO_CONV_TYPE, VideoConv)) +#define parent_class (video_conv_parent_class) +GST_ELEMENT_REGISTER_DEFINE(protonvideoconverter, "protonvideoconverter", + GST_RANK_MARGINAL + 1, VIDEO_CONV_TYPE); + +static GstStaticPadTemplate video_conv_sink_template = GST_STATIC_PAD_TEMPLATE("sink", + GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("video/x-ms-asf; video/x-msvideo; video/mpeg; video/quicktime;")); + +static GstStaticPadTemplate video_conv_src_template = GST_STATIC_PAD_TEMPLATE("src", + GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS("video/x-matroska; application/ogg;")); + +static struct dump_fozdb dump_fozdb = {PTHREAD_MUTEX_INITIALIZER, NULL, false}; + +void hashes_reader_init(struct hashes_reader *reader, GList *hashes) +{ + reader->current_hash = hashes; +} + +static int hashes_reader_read(void *reader, uint8_t *buffer, size_t size, size_t *read_size) +{ + struct payload_hash *hash = (struct payload_hash *)buffer; + struct hashes_reader *hashes_reader = reader; + + if (!size) + { + *read_size = 0; + return CONV_OK; + } + + if (!hashes_reader->current_hash) + return CONV_ERROR_DATA_END; + + *hash = *(struct payload_hash *)(hashes_reader->current_hash->data); + hashes_reader->current_hash = hashes_reader->current_hash->next; + + *read_size = sizeof(*hash); + return CONV_OK; +} + +static int dump_fozdb_open_video(bool create) +{ + return dump_fozdb_open(&dump_fozdb, create, "MEDIACONV_VIDEO_DUMP_FILE", VIDEO_CONV_FOZ_NUM_TAGS); +} + +static void dump_fozdb_discard_transcoded(void) +{ + GList *to_discard_chunks = NULL; + struct payload_hash *stream_id; + struct fozdb *read_fozdb; + char *read_fozdb_path; + GHashTableIter iter; + int ret; + + if (dump_fozdb.already_cleaned) + return; + dump_fozdb.already_cleaned = true; + + if (discarding_disabled()) + return; + if (!file_exists(getenv("MEDIACONV_VIDEO_DUMP_FILE"))) + return; + + if (dump_fozdb_open_video(false) < 0) + return; + + if (!(read_fozdb_path = getenv("MEDIACONV_VIDEO_TRANSCODED_FILE"))) + { + GST_ERROR("Env MEDIACONV_VIDEO_TRANSCODED_FILE not set."); + return; + } + + if ((ret = fozdb_create(read_fozdb_path, O_RDONLY, true /* Read-only? */, VIDEO_CONV_FOZ_NUM_TAGS, &read_fozdb)) < 0) + { + GST_ERROR("Failed to create read fozdb, ret %d.", ret); + return; + } + + fozdb_iter_tag(dump_fozdb.fozdb, VIDEO_CONV_FOZ_TAG_STREAM, &iter); + while (g_hash_table_iter_next(&iter, (void **)&stream_id, NULL)) + { + struct payload_hash chunk_id; + uint32_t chunks_size, i; + size_t read_size; + + if (fozdb_has_entry(read_fozdb, VIDEO_CONV_FOZ_TAG_OGVDATA, stream_id)) + { + if (fozdb_entry_size(dump_fozdb.fozdb, VIDEO_CONV_FOZ_TAG_STREAM, stream_id, &chunks_size) == CONV_OK) + { + uint8_t *buffer = calloc(1, chunks_size); + if (fozdb_read_entry_data(dump_fozdb.fozdb, VIDEO_CONV_FOZ_TAG_STREAM, stream_id, + 0, buffer, chunks_size, &read_size, true) == CONV_OK) + { + for (i = 0; i < read_size / sizeof(chunk_id); ++i) + { + payload_hash_from_bytes(&chunk_id, buffer + i * sizeof(chunk_id)); + to_discard_chunks = g_list_append(to_discard_chunks, + entry_name_create(VIDEO_CONV_FOZ_TAG_VIDEODATA, &chunk_id)); + } + } + free(buffer); + } + + to_discard_chunks = g_list_append(to_discard_chunks, + entry_name_create(VIDEO_CONV_FOZ_TAG_STREAM, stream_id)); + } + } + + if ((ret = fozdb_discard_entries(dump_fozdb.fozdb, to_discard_chunks)) < 0) + { + GST_ERROR("Failed to discard entries, ret %d.", ret); + dump_fozdb_close(&dump_fozdb); + } + + g_list_free_full(to_discard_chunks, free); +} + +struct pad_reader *pad_reader_create_with_stride(GstPad *pad, size_t stride) +{ + struct pad_reader *pad_reader; + + pad_reader = calloc(1, sizeof(*pad_reader)); + pad_reader->chunk = calloc(HASH_CHUNK_SIZE, sizeof(*pad_reader->chunk)); + pad_reader->stride = stride; + gst_object_ref((pad_reader->pad = pad)); + + return pad_reader; +} + +struct pad_reader *pad_reader_create(GstPad *pad) +{ + return pad_reader_create_with_stride(pad, SIZE_MAX); +} + +void pad_reader_release(struct pad_reader *reader) +{ + gst_object_unref(reader->pad); + free(reader->chunk); + free(reader); +} + +int pad_reader_read(void *data_src, uint8_t *buffer, size_t size, size_t *read_size) +{ + struct pad_reader *reader = data_src; + GstBuffer *gst_buffer = NULL; + GstFlowReturn gst_ret; + size_t to_copy; + + if (!size) + { + *read_size = 0; + return CONV_OK; + } + + if (reader->chunk_offset >= reader->chunk_end) + { + reader->chunk_offset = 0; + reader->chunk_end = 0; + + if ((gst_ret = gst_pad_pull_range(reader->pad, + reader->offset, HASH_CHUNK_SIZE, &gst_buffer)) == GST_FLOW_OK) + { + gsize buffer_size = gst_buffer_get_size(gst_buffer); + + if (reader->offset + buffer_size < reader->stride) + { + to_copy = buffer_size; + reader->offset += to_copy; + } + else if (reader->offset < reader->stride) + { + to_copy = reader->stride - reader->offset; + reader->offset = reader->stride; + } + else + { + to_copy = buffer_size; + reader->offset += reader->stride; + } + + if (size >= to_copy) /* Copy directly into out buffer and return. */ + { + *read_size = gst_buffer_extract(gst_buffer, 0, buffer, to_copy); + gst_buffer_unref(gst_buffer); + return CONV_OK; + } + else + { + reader->chunk_end = gst_buffer_extract(gst_buffer, 0, reader->chunk, to_copy); + gst_buffer_unref(gst_buffer); + } + } + else if (gst_ret == GST_FLOW_EOS) + { + return CONV_ERROR_DATA_END; + } + else + { + GST_WARNING("Failed to pull data from %"GST_PTR_FORMAT", reason %s.", + reader->pad, gst_flow_get_name(gst_ret)); + return CONV_ERROR; + } + } + + /* Copy chunk data to output buffer. */ + to_copy = min(reader->chunk_end - reader->chunk_offset, size); + memcpy(buffer, reader->chunk + reader->chunk_offset, to_copy); + reader->chunk_offset += to_copy; + + *read_size = to_copy; + return CONV_OK; +} + +static int video_conv_state_create(struct video_conv_state **out) +{ + struct video_conv_state *state; + struct fozdb *fozdb = NULL; + char *read_fozdb_path, *blank_video; + uint64_t blank_file_size; + int ret, fd; + + if (!(read_fozdb_path = getenv("MEDIACONV_VIDEO_TRANSCODED_FILE"))) + { + GST_ERROR("MEDIACONV_VIDEO_TRANSCODED_FILE is not set."); + return CONV_ERROR_ENV_NOT_SET; + } + if (!(blank_video = getenv("MEDIACONV_BLANK_VIDEO_FILE"))) + { + GST_ERROR("Env MEDIACONV_BLANK_VIDEO_FILE not set."); + return CONV_ERROR_ENV_NOT_SET; + } + + if (!open_file(blank_video, O_RDONLY, &fd)) + return CONV_ERROR_OPEN_FAILED; + if (!get_file_size(fd, &blank_file_size)) + { + close(fd); + return CONV_ERROR_OPEN_FAILED; + } + + if ((ret = fozdb_create(read_fozdb_path, O_RDONLY, true /* Read-only? */, VIDEO_CONV_FOZ_NUM_TAGS, &fozdb)) < 0) + GST_ERROR("Failed to create read fozdb from %s, ret %d.", read_fozdb_path, ret); + + state = calloc(1, sizeof(*state)); + state->read_fozdb = fozdb; + state->blank_file = fd; + state->upstream_duration = DURATION_NONE; + state->our_duration = blank_file_size; + state->transcoded_tag = VIDEO_CONV_FOZ_TAG_MKVDATA; + + *out = state; + return CONV_OK; +} + +static void video_conv_state_release(struct video_conv_state *state) +{ + if (state->read_fozdb) + fozdb_release(state->read_fozdb); + close(state->blank_file); + free(state); +} + +/* Return true if the file is transcoded, false if not. */ +bool video_conv_state_begin_transcode(struct video_conv_state *state, struct payload_hash *hash) +{ + GST_DEBUG("state %p, hash %s.", state, format_hash(hash)); + + state->transcode_hash = *hash; + + if (state->read_fozdb) + { + uint32_t entry_size; + + if (fozdb_entry_size(state->read_fozdb, VIDEO_CONV_FOZ_TAG_MKVDATA, hash, &entry_size) == CONV_OK) + { + GST_DEBUG("Found an MKV video for hash %s.", format_hash(hash)); + state->our_duration = entry_size; + state->transcoded_tag = VIDEO_CONV_FOZ_TAG_MKVDATA; + state->state_flags |= VIDEO_CONV_HAS_TRANSCODED; + return true; + } + + if (fozdb_entry_size(state->read_fozdb, VIDEO_CONV_FOZ_TAG_OGVDATA, hash, &entry_size) == CONV_OK) + { + GST_DEBUG("Found an OGV video for hash %s.", format_hash(hash)); + state->our_duration = entry_size; + state->transcoded_tag = VIDEO_CONV_FOZ_TAG_OGVDATA; + state->state_flags |= VIDEO_CONV_HAS_TRANSCODED; + return true; + } + } + + GST_INFO("No transcoded video for %s. Substituting a blank video.", format_hash(hash)); + state->state_flags &= ~VIDEO_CONV_HAS_TRANSCODED; + + create_placeholder_file("placeholder-video-used"); + + return false; +} + +int video_conv_state_fill_buffer(struct video_conv_state *state, uint64_t offset, + uint8_t *buffer, size_t size, size_t *fill_size) +{ + size_t to_copy; + bool read_ok; + int ret; + + if (state->state_flags & VIDEO_CONV_HAS_TRANSCODED) + { + if ((ret = fozdb_read_entry_data(state->read_fozdb, state->transcoded_tag, &state->transcode_hash, + offset, buffer, size, fill_size, false)) < 0) + GST_ERROR("Failed to read entry data, ret %d.", ret); + return ret; + } + else /* Fill blank video data to buffer. */ + { + offset = offset % state->our_duration; + to_copy = min(state->our_duration - offset, size); + + /* Copy data. */ + if (lseek(state->blank_file, offset, SEEK_SET) < 0) + { + GST_ERROR("Failed to seek to %#"PRIx64". %s.", offset, strerror(errno)); + return CONV_ERROR; + } + if (!(read_ok = complete_read(state->blank_file, buffer, to_copy))) + { + GST_ERROR("Failed to read blank video data."); + return CONV_ERROR_READ_FAILED; + } + *fill_size = to_copy; + return CONV_OK; + } +} + +/* Call pthread_mutex_unlock() to unlock after usage. */ +static struct video_conv_state *video_conv_lock_state(VideoConv *conv) +{ + pthread_mutex_lock(&conv->state_mutex); + if (!conv->state) + pthread_mutex_unlock(&conv->state_mutex); + return conv->state; +} + +static GstStateChangeReturn video_conv_change_state(GstElement *element, GstStateChange transition) +{ + VideoConv *conv = VIDEO_CONV(element); + struct video_conv_state *state; + int ret; + + GST_INFO_OBJECT(element, "State transition: %s.", gst_state_change_get_name(transition)); + + switch (transition) + { + case GST_STATE_CHANGE_NULL_TO_READY: + /* Do runtime setup. */ + if ((ret = video_conv_state_create(&state)) < 0) + { + GST_ERROR("Failed to create video conv state, ret %d.", ret); + return GST_STATE_CHANGE_FAILURE; + } + pthread_mutex_lock(&conv->state_mutex); + assert(!conv->state); + conv->state = state; + pthread_mutex_unlock(&conv->state_mutex); + break; + + case GST_STATE_CHANGE_READY_TO_NULL: + /* Do runtime teardown. */ + pthread_mutex_lock(&conv->state_mutex); + video_conv_state_release(conv->state); + conv->state = NULL; + pthread_mutex_unlock(&conv->state_mutex); + break; + + default: + break; + } + + return GST_ELEMENT_CLASS(parent_class)->change_state(element, transition); + + /* XXX on ReadyToNull, sodium drops state _again_ here... why? */ +} + +static uint64_t video_conv_duration_ours_to_upstream(VideoConv *conv, uint64_t pos) +{ + struct video_conv_state *state = conv->state; + + if (state->upstream_duration != DURATION_NONE && state->our_duration != DURATION_NONE) + return gst_util_uint64_scale_round(pos, state->upstream_duration, state->our_duration); + else + return DURATION_NONE; +} + +static void video_conv_query_upstream_duration(VideoConv *conv) +{ + gint64 duration; + + if (gst_pad_peer_query_duration(conv->sink_pad, GST_FORMAT_BYTES, &duration)) + conv->state->upstream_duration = duration; + else + GST_ERROR_OBJECT(conv, "Failed to query upstream duration."); +} + +static bool video_conv_get_upstream_range(VideoConv *conv, uint64_t offset, uint32_t requested_size, + uint64_t *upstream_offset, uint64_t *upstream_requested_size) +{ + struct video_conv_state *state; + + if (!(state = video_conv_lock_state(conv))) + return false; + + if (state->upstream_duration == DURATION_NONE) + video_conv_query_upstream_duration(conv); + + *upstream_offset = video_conv_duration_ours_to_upstream(conv, offset); + *upstream_requested_size = video_conv_duration_ours_to_upstream(conv, requested_size); + + pthread_mutex_unlock(&conv->state_mutex); + + return true; +} + +static uint64_t video_conv_duration_ours_to_downstream(VideoConv *conv, uint64_t pos) +{ + struct video_conv_state *state = conv->state; + + if (state->upstream_duration != DURATION_NONE && state->our_duration != DURATION_NONE) + return gst_util_uint64_scale_round(pos, state->our_duration, state->upstream_duration); + else + return DURATION_NONE; +} + +static bool video_conv_get_downstream_range(VideoConv *conv, uint64_t offset, uint32_t end, + uint64_t *downstream_offset, uint64_t *downstream_end) +{ + struct video_conv_state *state; + + if (!(state = video_conv_lock_state(conv))) + return false; + + if (state->upstream_duration == DURATION_NONE) + video_conv_query_upstream_duration(conv); + + *downstream_offset = video_conv_duration_ours_to_downstream(conv, offset); + *downstream_end = video_conv_duration_ours_to_downstream(conv, end); + + pthread_mutex_unlock(&conv->state_mutex); + + return true; +} + +static bool video_conv_hash_upstream_data(VideoConv *conv, struct payload_hash *hash) +{ + bool ret = false; + + memset(hash, 0, sizeof(*hash)); + + if (conv->active_mode == GST_PAD_MODE_PUSH && gst_adapter_available(conv->adapter) > 0) + { + struct bytes_reader bytes_reader; + gsize read_size = gst_adapter_available(conv->adapter); + const void *buffer = gst_adapter_map(conv->adapter, read_size); + bytes_reader_init(&bytes_reader, buffer, read_size); + ret = murmur3_128(&bytes_reader, bytes_reader_read, HASH_SEED, hash); + gst_adapter_unmap(conv->adapter); + gst_adapter_clear(conv->adapter); + } + else if (gst_pad_activate_mode(conv->sink_pad, GST_PAD_MODE_PULL, true)) + { + struct pad_reader *reader = pad_reader_create_with_stride(conv->sink_pad, HASH_STRIDE); + ret = murmur3_128(reader, pad_reader_read, HASH_SEED, hash); + pad_reader_release(reader); + conv->active_mode = GST_PAD_MODE_PULL; + } + + return ret; +} + +static int video_conv_dump_upstream_chunk(VideoConv *conv, const void *buffer, size_t read_size, + GList **chunk_hashes) +{ + struct bytes_reader bytes_reader; + struct payload_hash *chunk_hash; + + bytes_reader_init(&bytes_reader, buffer, read_size); + chunk_hash = calloc(1, sizeof(*chunk_hash)); + murmur3_128(&bytes_reader, bytes_reader_read, HASH_SEED, chunk_hash); + *chunk_hashes = g_list_append(*chunk_hashes, chunk_hash); + + bytes_reader_init(&bytes_reader, buffer, read_size); + return fozdb_write_entry(dump_fozdb.fozdb, VIDEO_CONV_FOZ_TAG_VIDEODATA, chunk_hash, + &bytes_reader, bytes_reader_read, true); +} + +static int video_conv_dump_upstream_data(VideoConv *conv, struct payload_hash *hash) +{ + struct hashes_reader chunk_hashes_reader; + struct pad_reader *pad_reader = NULL; + GList *chunk_hashes = NULL; + uint8_t *buffer = NULL; + size_t read_size; + int ret; + + GST_DEBUG("Dumping upstream data, hash %s.", format_hash(hash)); + + if ((ret = dump_fozdb_open_video(true)) < 0) + { + GST_ERROR("Failed to open video dump fozdb, ret %d.", ret); + goto done; + } + + if (conv->active_mode == GST_PAD_MODE_PUSH) + { + conv->state->state_flags |= VIDEO_CONV_IS_DUMPING; + return 0; + } + + buffer = calloc(1, HASH_CHUNK_SIZE); + pad_reader = pad_reader_create(conv->sink_pad); + while ((ret = pad_reader_read(pad_reader, buffer, HASH_CHUNK_SIZE, &read_size)) == CONV_OK) + { + if ((ret = video_conv_dump_upstream_chunk(conv, buffer, read_size, &chunk_hashes)) < 0) + { + GST_ERROR("Error writing video data to fozdb, ret %d.", ret); + goto done; + } + } + + if (ret != CONV_ERROR_DATA_END) + { + GST_ERROR("Failed to read data from pad reader, ret %d.", ret); + goto done; + } + + hashes_reader_init(&chunk_hashes_reader, chunk_hashes); + if ((ret = fozdb_write_entry(dump_fozdb.fozdb, VIDEO_CONV_FOZ_TAG_STREAM, hash, + &chunk_hashes_reader, hashes_reader_read, true)) < 0) + GST_ERROR("Error writing stream data to fozdb, ret %d.", ret); + +done: + if (chunk_hashes) + g_list_free_full(chunk_hashes, free); + if (pad_reader) + pad_reader_release(pad_reader); + if (buffer) + free(buffer); + return ret; +} + +static void video_conv_init_transcode(VideoConv *conv) +{ + struct video_conv_state *state = conv->state; + struct payload_hash hash; + int ret; + + if (state->state_flags & VIDEO_CONV_HAS_TRANSCODED) + return; + + pthread_mutex_lock(&dump_fozdb.mutex); + + dump_fozdb_discard_transcoded(); + + if (video_conv_hash_upstream_data(conv, &hash)) + { + GST_INFO("Got upstream data hash: %s.", format_hash(&hash)); + if (!video_conv_state_begin_transcode(state, &hash) + && (ret = video_conv_dump_upstream_data(conv, &hash)) < 0) + GST_ERROR("Failed to dump upstream data, ret %d.", ret); + } + else + { + GST_WARNING("Failed to hash upstream data."); + } + + if (!(state->state_flags & VIDEO_CONV_IS_DUMPING)) + pthread_mutex_unlock(&dump_fozdb.mutex); +} + +static uint32_t video_conv_get_state_flags(VideoConv *conv) +{ + struct video_conv_state *state; + uint32_t state_flags; + + if (!(state = video_conv_lock_state(conv))) + { + GST_ERROR("VideoConv not yet in READY state?"); + return 0; + } + state_flags = state->state_flags; + pthread_mutex_unlock(&conv->state_mutex); + + return state_flags; +} + +static gboolean video_conv_push_stream_start(VideoConv *conv, struct payload_hash *hash) +{ + struct video_conv_state *state; + + push_event(conv->src_pad, gst_event_new_stream_start(format_hash(hash))); + + if (!(state = video_conv_lock_state(conv))) + { + GST_ERROR("VideoConv not yet in READY state?"); + return false; + } + state->state_flags |= VIDEO_CONV_STREAM_STARTED; + pthread_mutex_unlock(&conv->state_mutex); + + return true; +} + +static gboolean video_conv_push_caps(VideoConv *conv, uint32_t transcode_tag) +{ + GstCaps *caps; + gboolean ret; + + if (transcode_tag == VIDEO_CONV_FOZ_TAG_MKVDATA) + caps = gst_caps_from_string("video/x-matroska"); + else if (transcode_tag == VIDEO_CONV_FOZ_TAG_OGVDATA) + caps = gst_caps_from_string("application/ogg"); + else + return false; + + ret = push_event(conv->src_pad, gst_event_new_caps(caps)); + gst_caps_unref(caps); + return ret; +} + +static gboolean video_conv_sink_event_caps(VideoConv *conv, GstEvent *event) +{ + struct video_conv_state *state; + uint32_t transcode_tag; + + gst_event_unref(event); + + /* push_event, below, can also grab state and cause a deadlock, so make sure it's + * unlocked before calling */ + if (!(state = video_conv_lock_state(conv))) + { + GST_ERROR("VideoConv not yet in READY state?"); + return false; + } + + video_conv_init_transcode(conv); + transcode_tag = state->transcoded_tag; + + pthread_mutex_unlock(&conv->state_mutex); + + return video_conv_push_caps(conv, transcode_tag); +} + +static gboolean video_conv_sink_event_eos(VideoConv *conv, GstEvent *event) +{ + struct video_conv_state *state; + struct payload_hash hash; + uint32_t transcode_tag; + uint32_t state_flags; + int ret; + + gst_event_unref(event); + + if (!(state = video_conv_lock_state(conv))) + return false; + + if (state->state_flags & VIDEO_CONV_IS_DUMPING) + { + struct hashes_reader chunk_hashes_reader; + gsize read_bytes; + + if ((read_bytes = gst_adapter_available(conv->adapter))) + { + const void *buffer = gst_adapter_map(conv->adapter, read_bytes); + + if ((ret = video_conv_dump_upstream_chunk(conv, buffer, read_bytes, &state->chunk_hashes)) < 0) + GST_ERROR("Error writing stream data to fozdb, ret %d.", ret); + + gst_adapter_unmap(conv->adapter); + gst_adapter_clear(conv->adapter); + } + + hashes_reader_init(&chunk_hashes_reader, state->chunk_hashes); + if ((ret = fozdb_write_entry(dump_fozdb.fozdb, VIDEO_CONV_FOZ_TAG_STREAM, &state->transcode_hash, + &chunk_hashes_reader, hashes_reader_read, true)) < 0) + GST_ERROR("Error writing stream data to fozdb, ret %d.", ret); + + if (state->chunk_hashes) + g_list_free_full(state->chunk_hashes, free); + + pthread_mutex_unlock(&dump_fozdb.mutex); + state->state_flags &= ~VIDEO_CONV_IS_DUMPING; + pthread_mutex_unlock(&conv->state_mutex); + + return gst_pad_push_event(conv->src_pad, gst_event_new_eos()); + } + + video_conv_init_transcode(conv); + hash = state->transcode_hash; + state_flags = state->state_flags; + transcode_tag = state->transcoded_tag; + + pthread_mutex_unlock(&conv->state_mutex); + + if (!(state_flags & VIDEO_CONV_STREAM_STARTED)) + { + /* rewind and start a new stream for dumping or playback */ + if (!push_event(conv->sink_pad, gst_event_new_seek(1.0, GST_FORMAT_BYTES, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, -1))) + return false; + if (!video_conv_push_stream_start(conv, &hash)) + return false; + if (!video_conv_push_caps(conv, transcode_tag)) + return false; + + /* return false to cancel upstream pads EOS event handling and avoid setting EOS flag */ + return false; + } + + return gst_pad_push_event(conv->src_pad, gst_event_new_eos()); +} + +static gboolean video_conv_sink_event(GstPad *pad, GstObject *parent, GstEvent *event) +{ + VideoConv *conv = VIDEO_CONV(parent); + + GST_DEBUG_OBJECT(pad, "Got event %"GST_PTR_FORMAT".", event); + + if (event->type == GST_EVENT_CAPS) + return video_conv_sink_event_caps(conv, event); + if (event->type == GST_EVENT_EOS) + return video_conv_sink_event_eos(conv, event); + + if (video_conv_get_state_flags(conv) & VIDEO_CONV_STREAM_STARTED) + return gst_pad_event_default(pad, parent, event); + + gst_event_unref(event); + return true; +} + +static gboolean video_conv_src_event_seek(VideoConv *conv, GstEvent *event) +{ + guint seqnum = gst_event_get_seqnum(event); + guint64 upstream_offset, upstream_size; + GstSeekType offset_type, stop_type; + struct video_conv_state *state; + gint64 offset, stop; + GstSeekFlags flags; + GstFormat format; + gdouble rate; + + gst_event_parse_seek(event, &rate, &format, &flags, &offset_type, &offset, &stop_type, &stop); + gst_event_unref(event); + if (format != GST_FORMAT_BYTES) + return false; + + GST_TRACE("conv %p, rate %f, format %s, flags %#x, offset_type %u, cur %#" G_GINT64_MODIFIER "x, " + "stop_type %u, stop %#" G_GINT64_MODIFIER "x.", conv, rate, gst_format_get_name(format), + flags, offset_type, offset, stop_type, stop); + + if (!(state = video_conv_lock_state(conv))) + return GST_FLOW_ERROR; + if (state->state_flags & VIDEO_CONV_IS_DUMPING) + { + pthread_mutex_unlock(&conv->state_mutex); + return true; + } + pthread_mutex_unlock(&conv->state_mutex); + + if (!video_conv_get_upstream_range(conv, offset, HASH_CHUNK_SIZE, &upstream_offset, &upstream_size)) + return false; + + if ((event = gst_event_new_seek(1.0, GST_FORMAT_BYTES, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, upstream_offset, GST_SEEK_TYPE_NONE, -1))) + gst_event_set_seqnum(event, seqnum); + return push_event(conv->sink_pad, event); +} + +static gboolean video_conv_src_event(GstPad *pad, GstObject *parent, GstEvent *event) +{ + VideoConv *conv = VIDEO_CONV(parent); + + GST_DEBUG_OBJECT(pad, "Got event %"GST_PTR_FORMAT".", event); + + if (event->type == GST_EVENT_SEEK) + return video_conv_src_event_seek(conv, event); + + if (video_conv_get_state_flags(conv) & VIDEO_CONV_STREAM_STARTED) + return gst_pad_event_default(pad, parent, event); + + gst_event_unref(event); + return true; +} + +static void video_conv_dump_buffered_chunks(VideoConv *conv, gsize bytes_available) +{ + struct video_conv_state *state = conv->state; + int ret; + + while (bytes_available >= HASH_CHUNK_SIZE) + { + const void *buffer = gst_adapter_map(conv->adapter, HASH_CHUNK_SIZE); + if ((ret = video_conv_dump_upstream_chunk(conv, buffer, HASH_CHUNK_SIZE, &state->chunk_hashes)) < 0) + { + pthread_mutex_unlock(&dump_fozdb.mutex); + state->state_flags &= ~VIDEO_CONV_IS_DUMPING; + break; + } + + gst_adapter_unmap(conv->adapter); + gst_adapter_flush(conv->adapter, HASH_CHUNK_SIZE); + bytes_available -= HASH_CHUNK_SIZE; + } +} + +static GstFlowReturn video_conv_sink_chain(GstPad *pad, GstObject *parent, GstBuffer *buffer) +{ + gsize buffer_size = gst_buffer_get_size(buffer), buffer_offset = GST_BUFFER_OFFSET(buffer); + uint64_t downstream_offset, downstream_end, seek_offset = -1; + VideoConv *conv = VIDEO_CONV(parent); + struct video_conv_state *state; + GstBuffer *transcoded = NULL; + uint32_t state_flags; + int ret = 0; + + GST_DEBUG_OBJECT(pad, "Got buffer %"GST_PTR_FORMAT".", buffer); + + if (!(state = video_conv_lock_state(conv))) + return GST_FLOW_ERROR; + state_flags = state->state_flags; + + if ((state_flags & VIDEO_CONV_IS_DUMPING) || !(state_flags & VIDEO_CONV_STREAM_STARTED)) + { + gsize bytes_available = gst_adapter_available(conv->adapter) + buffer_size; + gst_adapter_push(conv->adapter, buffer); + + if (state_flags & VIDEO_CONV_IS_DUMPING) + video_conv_dump_buffered_chunks(conv, bytes_available); + else if (!(bytes_available % HASH_CHUNK_SIZE) && bytes_available >= HASH_STRIDE) + { + state->read_offset += HASH_STRIDE; + seek_offset = state->read_offset; + } + } + + pthread_mutex_unlock(&conv->state_mutex); + + if (ret < 0) + { + GST_ERROR("Failed to dump or read transcoded buffer, error %d.", ret); + if (transcoded) + gst_buffer_unref(transcoded); + return GST_FLOW_ERROR; + } + + if (seek_offset != -1 && !push_event(pad, gst_event_new_seek(1.0, GST_FORMAT_BYTES, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, seek_offset, GST_SEEK_TYPE_NONE, -1))) + return GST_FLOW_ERROR; + if (!(state_flags & VIDEO_CONV_STREAM_STARTED)) + return GST_FLOW_OK; + + if (!video_conv_get_downstream_range(conv, buffer_offset, buffer_offset + buffer_size, + &downstream_offset, &downstream_end)) + return GST_FLOW_ERROR; + + if (downstream_end == downstream_offset) + return GST_FLOW_OK; + if (!(transcoded = gst_buffer_new_and_alloc(downstream_end - downstream_offset))) + return GST_FLOW_ERROR; + else + { + GstBufferMapInfo map; + size_t fill_size = 0; + + if (gst_buffer_map(transcoded, &map, GST_MAP_READWRITE)) + { + ret = video_conv_state_fill_buffer(state, downstream_offset, map.data, map.size, &fill_size); + gst_buffer_unmap(transcoded, &map); + gst_buffer_set_size(transcoded, fill_size); + } + + if (gst_buffer_get_size(transcoded)) + { + GST_BUFFER_OFFSET(transcoded) = downstream_offset; + return gst_pad_push(conv->src_pad, transcoded); + } + + gst_buffer_unref(transcoded); + } + + return GST_FLOW_OK; +} + +static GstFlowReturn video_conv_src_get_range(GstPad *pad, GstObject *parent, + guint64 offset, guint request_size, GstBuffer **buffer) +{ + GstBuffer *my_buffer, *upstream_buffer = NULL, *new_buffer = NULL; + uint64_t upstream_offset, upstream_request_size; + GstFlowReturn flow_ret = GST_FLOW_ERROR; + VideoConv *conv = VIDEO_CONV(parent); + struct video_conv_state *state; + size_t fill_size; + GstMapInfo map; + int ret; + + if (!video_conv_get_upstream_range(conv, offset, request_size, &upstream_offset, &upstream_request_size)) + return flow_ret; + + if (!(state = video_conv_lock_state(conv))) + return flow_ret; + + /* Read and ignore upstream bytes. */ + if ((flow_ret = gst_pad_pull_range(conv->sink_pad, upstream_offset, upstream_request_size, &upstream_buffer)) < 0) + { + GST_ERROR("Failed to pull upstream data from %"GST_PTR_FORMAT", offset %#"PRIx64", size %#"PRIx64", reason %s.", + conv->sink_pad, upstream_offset, upstream_request_size, gst_flow_get_name(flow_ret)); + goto done; + } + gst_buffer_unref(upstream_buffer); + + /* Allocate and map buffer. */ + my_buffer = *buffer; + if (!my_buffer) + { + /* XXX: can we use a buffer cache here? */ + if (!(new_buffer = gst_buffer_new_and_alloc(request_size))) + { + GST_ERROR("Failed to allocate buffer of %u bytes.", request_size); + goto done; + } + my_buffer = new_buffer; + } + if (!gst_buffer_map(my_buffer, &map, GST_MAP_READWRITE)) + { + GST_ERROR("Failed to map buffer <%"GST_PTR_FORMAT">.", my_buffer); + goto done; + } + + /* Fill buffer. */ + ret = video_conv_state_fill_buffer(state, offset, map.data, map.size, &fill_size); + gst_buffer_unmap(my_buffer, &map); + if (ret < 0) + { + GST_ERROR("Failed to fill buffer, ret %d.", ret); + goto done; + } + + if (fill_size > 0 || !gst_buffer_get_size(my_buffer)) + { + gst_buffer_set_size(my_buffer, fill_size); + *buffer = my_buffer; + flow_ret = GST_FLOW_OK; + } + else + { + flow_ret = GST_FLOW_EOS; + } + +done: + if (flow_ret < 0 && new_buffer) + gst_buffer_unref(new_buffer); + pthread_mutex_unlock(&conv->state_mutex); + return flow_ret; +} + +static gboolean video_conv_src_query(GstPad *pad, GstObject *parent, GstQuery *query) +{ + VideoConv *conv = VIDEO_CONV(parent); + struct video_conv_state *state; + GstSchedulingFlags flags; + gint min, max, align; + GstQuery *peer_query; + uint64_t duration; + GstFormat format; + + GST_DEBUG_OBJECT(pad, "Got query %"GST_PTR_FORMAT".", query); + + switch (query->type) + { + case GST_QUERY_SCHEDULING: + peer_query = gst_query_new_scheduling(); + if (!gst_pad_peer_query(conv->sink_pad, peer_query)) + { + GST_ERROR_OBJECT(conv->sink_pad, "Failed to query scheduling from peer."); + gst_query_unref(peer_query); + return false; + } + gst_query_parse_scheduling(peer_query, &flags, &min, &max, &align); + gst_query_unref(peer_query); + + gst_query_set_scheduling(query, flags, min, max, align); + if (conv->active_mode != GST_PAD_MODE_NONE) + gst_query_add_scheduling_mode(query, conv->active_mode); + else + { + gst_query_add_scheduling_mode(query, GST_PAD_MODE_PULL); + gst_query_add_scheduling_mode(query, GST_PAD_MODE_PUSH); + } + + return true; + + case GST_QUERY_DURATION: + gst_query_parse_duration(query, &format, NULL); + if (format != GST_FORMAT_BYTES) + { + GST_WARNING("Duration query format is not GST_FORMAT_BYTES."); + return false; + } + + if (!(state = video_conv_lock_state(conv))) + return false; + if (state->upstream_duration == DURATION_NONE) + video_conv_query_upstream_duration(conv); + duration = state->our_duration; + pthread_mutex_unlock(&conv->state_mutex); + + if (duration == DURATION_NONE) + return false; + gst_query_set_duration(query, GST_FORMAT_BYTES, duration); + return true; + + default: + return gst_pad_query_default(pad, parent, query); + } +} + +static gboolean video_conv_src_active_mode(GstPad *pad, GstObject *parent, GstPadMode mode, gboolean active) +{ + VideoConv *conv = VIDEO_CONV(parent); + struct video_conv_state *state; + struct payload_hash hash; + uint32_t state_flags; + + GST_DEBUG_OBJECT(pad, "mode %s, active %d.", gst_pad_mode_get_name(mode), active); + + if (!gst_pad_activate_mode(conv->sink_pad, mode, active)) + { + GST_ERROR_OBJECT(conv->sink_pad, "Failed to active sink pad: mode %s, active %d.", + gst_pad_mode_get_name(mode), active); + return false; + } + + conv->active_mode = mode; + if (mode != GST_PAD_MODE_PULL) + return true; + + if (!(state = video_conv_lock_state(conv))) + { + GST_ERROR("VideoConv not yet in READY state?"); + return false; + } + + video_conv_init_transcode(conv); + hash = state->transcode_hash; + state_flags = state->state_flags; + + /* push_event, below, can also grab state and cause a deadlock, so make sure it's + * unlocked before calling */ + pthread_mutex_unlock(&conv->state_mutex); + + if (active && !(state_flags & VIDEO_CONV_STREAM_STARTED) && (state_flags & VIDEO_CONV_HAS_TRANSCODED)) + return video_conv_push_stream_start(conv, &hash); + return true; +} + + +static void video_conv_finalize(GObject *object) +{ + VideoConv *conv = VIDEO_CONV(object); + + gst_object_unref(conv->adapter); + pthread_mutex_destroy(&conv->state_mutex); + if (conv->state) + video_conv_state_release(conv->state); + + G_OBJECT_CLASS(parent_class)->finalize(object); +} + +static void video_conv_class_init(VideoConvClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + gst_element_class_set_metadata(element_class, + "Proton video converter", + "Codec/Demuxer", + "Converts video for Proton", + "Andrew Eikum , Ziqing Hui "); + + element_class->change_state = video_conv_change_state; + object_class->finalize = video_conv_finalize; + + gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&video_conv_sink_template)); + gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&video_conv_src_template)); +} + +static void video_conv_init(VideoConv *conv) +{ + GstElement *element = GST_ELEMENT(conv); + + conv->sink_pad = gst_pad_new_from_static_template(&video_conv_sink_template, "sink"); + gst_pad_set_event_function(conv->sink_pad, GST_DEBUG_FUNCPTR(video_conv_sink_event)); + gst_pad_set_chain_function(conv->sink_pad, GST_DEBUG_FUNCPTR(video_conv_sink_chain)); + gst_element_add_pad(element, conv->sink_pad); + + conv->src_pad = gst_pad_new_from_static_template(&video_conv_src_template, "src"); + gst_pad_set_event_function(conv->src_pad, GST_DEBUG_FUNCPTR(video_conv_src_event)); + gst_pad_set_getrange_function(conv->src_pad, GST_DEBUG_FUNCPTR(video_conv_src_get_range)); + gst_pad_set_query_function(conv->src_pad, GST_DEBUG_FUNCPTR(video_conv_src_query)); + gst_pad_set_activatemode_function(conv->src_pad, GST_DEBUG_FUNCPTR(video_conv_src_active_mode)); + gst_element_add_pad(element, conv->src_pad); + + pthread_mutex_init(&conv->state_mutex, NULL); + conv->state = NULL; + conv->adapter = gst_adapter_new(); + conv->active_mode = GST_PAD_MODE_NONE; +} diff --git a/dlls/winegstreamer/mfplat.c b/dlls/winegstreamer/mfplat.c index aae1b63de5d..0c2e37abe8a 100644 --- a/dlls/winegstreamer/mfplat.c +++ b/dlls/winegstreamer/mfplat.c @@ -17,6 +17,8 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ +#include "ntstatus.h" +#define WIN32_NO_STATUS #include "gst_private.h" #include "ks.h" @@ -25,6 +27,8 @@ #include "initguid.h" #include "d3d9types.h" #include "mfapi.h" +#include "mmreg.h" +#include "mferror.h" #include "wine/debug.h" #include "wine/list.h" @@ -41,6 +45,8 @@ DEFINE_MEDIATYPE_GUID(MFAudioFormat_RAW_AAC,WAVE_FORMAT_RAW_AAC1); DEFINE_MEDIATYPE_GUID(MFAudioFormat_XMAudio2, 0x0166); DEFINE_MEDIATYPE_GUID(MFVideoFormat_VC1S,MAKEFOURCC('V','C','1','S')); DEFINE_MEDIATYPE_GUID(MFVideoFormat_IV50,MAKEFOURCC('I','V','5','0')); +DEFINE_MEDIATYPE_GUID(MFAudioFormat_GStreamer,MAKEFOURCC('G','S','T','a')); +DEFINE_MEDIATYPE_GUID(MFVideoFormat_GStreamer,MAKEFOURCC('G','S','T','v')); extern GUID MEDIASUBTYPE_VC1S; @@ -121,6 +127,9 @@ static const IClassFactoryVtbl class_factory_vtbl = }; static const GUID CLSID_GStreamerByteStreamHandler = {0x317df618, 0x5e5a, 0x468a, {0x9f, 0x15, 0xd8, 0x27, 0xa9, 0xa0, 0x81, 0x62}}; +static const GUID CLSID_GStreamerByteStreamHandler2 = {0x317df619, 0x5e5a, 0x468a, {0x9f, 0x15, 0xd8, 0x27, 0xa9, 0xa0, 0x81, 0x62}}; +static const GUID CLSID_GStreamerAudioDecoder = {0x480b1517, 0xc8e9, 0x4eae, {0xb0, 0x06, 0xe6, 0x30, 0x07, 0x18, 0xd8, 0x5d}}; +static const GUID CLSID_GStreamerVideoDecoder = {0x480b1518, 0xc8e9, 0x4eae, {0xb0, 0x06, 0xe6, 0x30, 0x07, 0x18, 0xd8, 0x5d}}; static const GUID CLSID_GStreamerSchemePlugin = {0x587eeb6a,0x7336,0x4ebd,{0xa4,0xf2,0x91,0xc9,0x48,0xde,0x62,0x2c}}; @@ -131,8 +140,11 @@ static const struct class_object } class_objects[] = { + { &CLSID_GStreamerAudioDecoder, &audio_decoder_create }, + { &CLSID_GStreamerVideoDecoder, &video_decoder_create }, { &CLSID_VideoProcessorMFT, &video_processor_create }, { &CLSID_GStreamerByteStreamHandler, &gstreamer_byte_stream_handler_create }, + { &CLSID_GStreamerByteStreamHandler2, &gstreamer_byte_stream_handler_2_create }, { &CLSID_MSAACDecMFT, &aac_decoder_create }, { &CLSID_MSH264DecoderMFT, &h264_decoder_create }, { &CLSID_GStreamerSchemePlugin, &gstreamer_scheme_handler_create }, @@ -331,6 +343,34 @@ HRESULT mfplat_DllRegisterServer(void) {MFMediaType_Video, MFVideoFormat_NV11}, }; + MFT_REGISTER_TYPE_INFO audio_decoder_input_types[] = + { + {MFMediaType_Audio, MFAudioFormat_GStreamer}, + }; + MFT_REGISTER_TYPE_INFO audio_decoder_output_types[] = + { + {MFMediaType_Audio, MFAudioFormat_Float}, + {MFMediaType_Audio, MFAudioFormat_PCM}, + }; + + MFT_REGISTER_TYPE_INFO video_decoder_input_types[] = + { + {MFMediaType_Video, MFVideoFormat_GStreamer}, + {MFMediaType_Video, MFVideoFormat_IV50}, + }; + MFT_REGISTER_TYPE_INFO video_decoder_output_types[] = + { + {MFMediaType_Video, MFVideoFormat_YV12}, + {MFMediaType_Video, MFVideoFormat_YUY2}, + {MFMediaType_Video, MFVideoFormat_NV11}, + {MFMediaType_Video, MFVideoFormat_NV12}, + {MFMediaType_Video, MFVideoFormat_RGB32}, + {MFMediaType_Video, MFVideoFormat_RGB24}, + {MFMediaType_Video, MFVideoFormat_RGB565}, + {MFMediaType_Video, MFVideoFormat_RGB555}, + {MFMediaType_Video, MFVideoFormat_RGB8}, + }; + struct mft { GUID clsid; @@ -404,6 +444,39 @@ HRESULT mfplat_DllRegisterServer(void) ARRAY_SIZE(resampler_types), resampler_types, }, + { + CLSID_CFrameRateConvertDmo, + MFT_CATEGORY_VIDEO_EFFECT, + L"Frame Rate Converter", + MFT_ENUM_FLAG_SYNCMFT, + /* FIXME: check the actual media types */ + ARRAY_SIZE(color_convert_input_types), + color_convert_input_types, + ARRAY_SIZE(color_convert_output_types), + color_convert_output_types, + }, + { + CLSID_CResizerDMO, + MFT_CATEGORY_VIDEO_EFFECT, + L"Resizer MFT", + MFT_ENUM_FLAG_SYNCMFT, + /* FIXME: check the actual media types */ + ARRAY_SIZE(color_convert_input_types), + color_convert_input_types, + ARRAY_SIZE(color_convert_output_types), + color_convert_output_types, + }, + { + CLSID_CColorControlDmo, + MFT_CATEGORY_VIDEO_EFFECT, + L"Color Control", + MFT_ENUM_FLAG_SYNCMFT, + /* FIXME: check the actual media types */ + ARRAY_SIZE(color_convert_input_types), + color_convert_input_types, + ARRAY_SIZE(color_convert_output_types), + color_convert_output_types, + }, { CLSID_CColorConvertDMO, MFT_CATEGORY_VIDEO_EFFECT, @@ -414,6 +487,39 @@ HRESULT mfplat_DllRegisterServer(void) ARRAY_SIZE(color_convert_output_types), color_convert_output_types, }, + { + CLSID_GStreamerAudioDecoder, + MFT_CATEGORY_AUDIO_DECODER, + L"Wine Audio Decoder MFT", + MFT_ENUM_FLAG_SYNCMFT, + ARRAY_SIZE(audio_decoder_input_types), + audio_decoder_input_types, + ARRAY_SIZE(audio_decoder_output_types), + audio_decoder_output_types, + }, + { + CLSID_GStreamerVideoDecoder, + MFT_CATEGORY_VIDEO_DECODER, + L"Wine Video Decoder MFT", + MFT_ENUM_FLAG_SYNCMFT, + ARRAY_SIZE(video_decoder_input_types), + video_decoder_input_types, + ARRAY_SIZE(video_decoder_output_types), + video_decoder_output_types, + }, + { + /* HACK: Register the video processor as a decoder too as + * the media source currently always decodes. + */ + CLSID_VideoProcessorMFT, + MFT_CATEGORY_VIDEO_DECODER, + L"Null Decoder", + MFT_ENUM_FLAG_SYNCMFT, + ARRAY_SIZE(video_processor_input_types), + video_processor_input_types, + ARRAY_SIZE(video_processor_output_types), + video_processor_output_types, + }, }; unsigned int i; @@ -563,6 +669,74 @@ static IMFMediaType *mf_media_type_from_wg_format_video(const struct wg_format * return NULL; } +static IMFMediaType *mf_media_type_from_wg_format_audio_encoded(const struct wg_format *format) +{ + IMFMediaType *type; + UINT32 value; + HRESULT hr; + + if (FAILED(MFCreateMediaType(&type))) + return NULL; + if (FAILED(hr = IMFMediaType_SetGUID(type, &MF_MT_MAJOR_TYPE, &MFMediaType_Audio))) + goto done; + if (FAILED(hr = IMFMediaType_SetGUID(type, &MF_MT_SUBTYPE, &MFAudioFormat_GStreamer))) + goto done; + + value = format->u.audio_encoded.rate; + if (value && FAILED(hr = IMFMediaType_SetUINT32(type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, value))) + goto done; + value = format->u.audio_encoded.channels; + if (value && FAILED(hr = IMFMediaType_SetUINT32(type, &MF_MT_AUDIO_NUM_CHANNELS, value))) + goto done; + if (FAILED(hr = IMFMediaType_SetBlob(type, &MF_MT_USER_DATA, (BYTE *)format->u.audio_encoded.caps, + strlen(format->u.audio_encoded.caps) + 1))) + goto done; + +done: + if (FAILED(hr)) + { + IMFMediaType_Release(type); + return NULL; + } + return type; +} + +static IMFMediaType *mf_media_type_from_wg_format_video_encoded(const struct wg_format *format) +{ + UINT64 frame_rate, frame_size; + IMFMediaType *type; + HRESULT hr; + + if (FAILED(MFCreateMediaType(&type))) + return NULL; + if (FAILED(hr = IMFMediaType_SetGUID(type, &MF_MT_MAJOR_TYPE, &MFMediaType_Video))) + goto done; + if (FAILED(hr = IMFMediaType_SetGUID(type, &MF_MT_SUBTYPE, &MFVideoFormat_GStreamer))) + goto done; + if (FAILED(hr = IMFMediaType_SetUINT32(type, &MF_MT_VIDEO_ROTATION, MFVideoRotationFormat_0))) + goto done; + if (FAILED(hr = IMFMediaType_SetUINT32(type, &MF_MT_COMPRESSED, TRUE))) + goto done; + + frame_size = (UINT64)format->u.video_encoded.width << 32 | format->u.video_encoded.height; + if (FAILED(hr = IMFMediaType_SetUINT64(type, &MF_MT_FRAME_SIZE, frame_size))) + goto done; + frame_rate = (UINT64)format->u.video_encoded.fps_n << 32 | format->u.video_encoded.fps_d; + if (FAILED(hr = IMFMediaType_SetUINT64(type, &MF_MT_FRAME_RATE, frame_rate))) + goto done; + if (FAILED(hr = IMFMediaType_SetBlob(type, &MF_MT_USER_DATA, (BYTE *)format->u.video_encoded.caps, + strlen(format->u.video_encoded.caps) + 1))) + goto done; + +done: + if (FAILED(hr)) + { + IMFMediaType_Release(type); + return NULL; + } + return type; +} + IMFMediaType *mf_media_type_from_wg_format(const struct wg_format *format) { switch (format->major_type) @@ -582,9 +756,13 @@ IMFMediaType *mf_media_type_from_wg_format(const struct wg_format *format) case WG_MAJOR_TYPE_AUDIO: return mf_media_type_from_wg_format_audio(format); + case WG_MAJOR_TYPE_AUDIO_ENCODED: + return mf_media_type_from_wg_format_audio_encoded(format); case WG_MAJOR_TYPE_VIDEO: return mf_media_type_from_wg_format_video(format); + case WG_MAJOR_TYPE_VIDEO_ENCODED: + return mf_media_type_from_wg_format_video_encoded(format); } assert(0); @@ -617,6 +795,10 @@ static void mf_media_type_to_wg_format_audio(IMFMediaType *type, const GUID *sub channel_mask = KSAUDIO_SPEAKER_MONO; else if (channels == 2) channel_mask = KSAUDIO_SPEAKER_STEREO; + else if (channels == 6) + channel_mask = KSAUDIO_SPEAKER_5POINT1; + else if (channels == 8) + channel_mask = KSAUDIO_SPEAKER_7POINT1; else { FIXME("Channel mask is not set.\n"); @@ -642,29 +824,14 @@ static void mf_media_type_to_wg_format_audio(IMFMediaType *type, const GUID *sub static void mf_media_type_to_wg_format_audio_mpeg4(IMFMediaType *type, const GUID *subtype, struct wg_format *format) { - /* Audio specific config is stored at after HEAACWAVEINFO in MF_MT_USER_DATA - * https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-heaacwaveformat - */ - typedef struct - { - WORD wPayloadType; - WORD wAudioProfileLevelIndication; - WORD wStructType; - WORD wReserved1; - DWORD dwReserved2; - } HEAACWAVEINFO; - typedef struct - { - HEAACWAVEINFO wfInfo; - BYTE pbAudioSpecificConfig[1]; - } HEAACWAVEFORMAT; - - BYTE buffer[64]; - HEAACWAVEFORMAT *user_data = (HEAACWAVEFORMAT *)buffer; + BYTE buffer[sizeof(HEAACWAVEFORMAT) + 64]; + HEAACWAVEFORMAT *wfx = (HEAACWAVEFORMAT *)buffer; UINT32 codec_data_size; BOOL raw_aac; - if (FAILED(IMFMediaType_GetBlob(type, &MF_MT_USER_DATA, buffer, sizeof(buffer), &codec_data_size))) + wfx->wfInfo.wfx.cbSize = sizeof(buffer) - sizeof(wfx->wfInfo.wfx); + if (FAILED(IMFMediaType_GetBlob(type, &MF_MT_USER_DATA, (BYTE *)(&wfx->wfInfo.wfx + 1), + wfx->wfInfo.wfx.cbSize, &codec_data_size))) { FIXME("Codec data is not set.\n"); return; @@ -672,16 +839,16 @@ static void mf_media_type_to_wg_format_audio_mpeg4(IMFMediaType *type, const GUI raw_aac = IsEqualGUID(subtype, &MFAudioFormat_RAW_AAC); if (!raw_aac) - codec_data_size -= min(codec_data_size, offsetof(HEAACWAVEFORMAT, pbAudioSpecificConfig)); + codec_data_size -= min(codec_data_size, sizeof(HEAACWAVEINFO) - sizeof(WAVEFORMATEX)); if (codec_data_size > sizeof(format->u.audio_mpeg4.codec_data)) { FIXME("Codec data needs %u bytes.\n", codec_data_size); return; } if (raw_aac) - memcpy(format->u.audio_mpeg4.codec_data, buffer, codec_data_size); + memcpy(format->u.audio_mpeg4.codec_data, (BYTE *)(&wfx->wfInfo.wfx + 1), codec_data_size); else - memcpy(format->u.audio_mpeg4.codec_data, user_data->pbAudioSpecificConfig, codec_data_size); + memcpy(format->u.audio_mpeg4.codec_data, wfx->pbAudioSpecificConfig, codec_data_size); format->major_type = WG_MAJOR_TYPE_AUDIO_MPEG4; @@ -691,6 +858,29 @@ static void mf_media_type_to_wg_format_audio_mpeg4(IMFMediaType *type, const GUI format->u.audio_mpeg4.codec_data_len = codec_data_size; } +static void mf_media_type_to_wg_format_audio_encoded(IMFMediaType *type, struct wg_format *format) +{ + UINT32 caps_len; + BYTE *caps; + HRESULT hr; + + memset(format, 0, sizeof(*format)); + format->major_type = WG_MAJOR_TYPE_AUDIO_ENCODED; + + if (FAILED(hr = IMFMediaType_GetUINT32(type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, &format->u.audio_encoded.rate))) + WARN("Failed to get MF_MT_AUDIO_SAMPLES_PER_SECOND for type %p, hr %#lx.\n", type, hr); + if (FAILED(hr = IMFMediaType_GetUINT32(type, &MF_MT_AUDIO_NUM_CHANNELS, &format->u.audio_encoded.channels))) + WARN("Failed to get MF_MT_AUDIO_NUM_CHANNELS for type %p, hr %#lx.\n", type, hr); + + if (FAILED(hr = IMFMediaType_GetAllocatedBlob(type, &MF_MT_USER_DATA, &caps, &caps_len))) + WARN("Failed to get MF_MT_USER_DATA for type %p, hr %#lx.\n", type, hr); + else + { + strcpy(format->u.audio_encoded.caps, (char *)caps); + CoTaskMemFree(caps); + } +} + static enum wg_video_format mf_video_format_to_wg(const GUID *subtype) { unsigned int i; @@ -929,6 +1119,41 @@ static void mf_media_type_to_wg_format_wmv(IMFMediaType *type, const GUID *subty format->u.video_wmv.format = WG_WMV_VIDEO_FORMAT_UNKNOWN; } +static void mf_media_type_to_wg_format_video_encoded(IMFMediaType *type, struct wg_format *format) +{ + UINT64 frame_rate, frame_size; + UINT32 caps_len; + HRESULT hr; + BYTE *caps; + + memset(format, 0, sizeof(*format)); + format->major_type = WG_MAJOR_TYPE_VIDEO_ENCODED; + + if (FAILED(hr = IMFMediaType_GetUINT64(type, &MF_MT_FRAME_SIZE, &frame_size))) + WARN("Failed to get MF_MT_FRAME_SIZE for type %p, hr %#lx.\n", type, hr); + else + { + format->u.video_encoded.width = frame_size >> 32; + format->u.video_encoded.height = (UINT32)frame_size; + } + + if (FAILED(IMFMediaType_GetUINT64(type, &MF_MT_FRAME_RATE, &frame_rate)) && (UINT32)frame_rate) + WARN("Failed to get MF_MT_FRAME_RATE for type %p, hr %#lx.\n", type, hr); + else + { + format->u.video_encoded.fps_n = frame_rate >> 32; + format->u.video_encoded.fps_d = (UINT32)frame_rate; + } + + if (FAILED(hr = IMFMediaType_GetAllocatedBlob(type, &MF_MT_USER_DATA, &caps, &caps_len))) + WARN("Failed to get MF_MT_USER_DATA for type %p, hr %#lx.\n", type, hr); + else + { + strcpy(format->u.video_encoded.caps, (char *)caps); + CoTaskMemFree(caps); + } +} + void mf_media_type_to_wg_format(IMFMediaType *type, struct wg_format *format) { GUID major_type, subtype; @@ -956,6 +1181,8 @@ void mf_media_type_to_wg_format(IMFMediaType *type, struct wg_format *format) mf_media_type_to_wg_format_audio_wma(type, &subtype, format); else if (IsEqualGUID(&subtype, &MFAudioFormat_AAC) || IsEqualGUID(&subtype, &MFAudioFormat_RAW_AAC)) mf_media_type_to_wg_format_audio_mpeg4(type, &subtype, format); + else if (IsEqualGUID(&subtype, &MFAudioFormat_GStreamer)) + mf_media_type_to_wg_format_audio_encoded(type, format); else mf_media_type_to_wg_format_audio(type, &subtype, format); } @@ -975,6 +1202,8 @@ void mf_media_type_to_wg_format(IMFMediaType *type, struct wg_format *format) || IsEqualGUID(&subtype, &MFVideoFormat_WVC1) || IsEqualGUID(&subtype, &MEDIASUBTYPE_VC1S)) mf_media_type_to_wg_format_wmv(type, &subtype, format); + else if (IsEqualGUID(&subtype, &MFVideoFormat_GStreamer)) + mf_media_type_to_wg_format_video_encoded(type, format); else mf_media_type_to_wg_format_video(type, &subtype, format); } diff --git a/dlls/winegstreamer/new_media_source.c b/dlls/winegstreamer/new_media_source.c new file mode 100644 index 00000000000..efaf6a3e087 --- /dev/null +++ b/dlls/winegstreamer/new_media_source.c @@ -0,0 +1,2116 @@ +/* GStreamer Media Source + * + * Copyright 2020 Derek Lesho + * Copyright 2020 Zebediah Figura for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "gst_private.h" + +#include "mfapi.h" +#include "mferror.h" + +#include "wine/list.h" + +WINE_DEFAULT_DEBUG_CHANNEL(mfplat); + +#define SOURCE_BUFFER_SIZE (32 * 1024) + +struct object_context +{ + IUnknown IUnknown_iface; + LONG refcount; + + IMFAsyncResult *result; + IMFByteStream *stream; + UINT64 file_size; + WCHAR *url; + + BYTE *buffer; + wg_source_t wg_source; + WCHAR mime_type[256]; + UINT32 stream_count; +}; + +static struct object_context *impl_from_IUnknown(IUnknown *iface) +{ + return CONTAINING_RECORD(iface, struct object_context, IUnknown_iface); +} + +static HRESULT WINAPI object_context_QueryInterface(IUnknown *iface, REFIID riid, void **obj) +{ + TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); + + if (IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IUnknown_AddRef(iface); + return S_OK; + } + + WARN("Unsupported %s.\n", debugstr_guid(riid)); + *obj = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI object_context_AddRef(IUnknown *iface) +{ + struct object_context *context = impl_from_IUnknown(iface); + ULONG refcount = InterlockedIncrement(&context->refcount); + + TRACE("%p, refcount %lu.\n", iface, refcount); + + return refcount; +} + +static ULONG WINAPI object_context_Release(IUnknown *iface) +{ + struct object_context *context = impl_from_IUnknown(iface); + ULONG refcount = InterlockedDecrement(&context->refcount); + + TRACE("%p, refcount %lu.\n", iface, refcount); + + if (!refcount) + { + if (context->wg_source) + wg_source_destroy(context->wg_source); + IMFAsyncResult_Release(context->result); + IMFByteStream_Release(context->stream); + free(context->buffer); + free(context->url); + free(context); + } + + return refcount; +} + +static const IUnknownVtbl object_context_vtbl = +{ + object_context_QueryInterface, + object_context_AddRef, + object_context_Release, +}; + +static WCHAR *byte_stream_get_url(IMFByteStream *stream, const WCHAR *url) +{ + IMFAttributes *attributes; + WCHAR buffer[MAX_PATH]; + UINT32 size; + HRESULT hr; + + if (SUCCEEDED(hr = IMFByteStream_QueryInterface(stream, &IID_IMFAttributes, (void **)&attributes))) + { + if (FAILED(hr = IMFAttributes_GetString(attributes, &MF_BYTESTREAM_ORIGIN_NAME, + buffer, ARRAY_SIZE(buffer), &size))) + WARN("Failed to get MF_BYTESTREAM_ORIGIN_NAME got size %#x, hr %#lx\n", size, hr); + else + url = buffer; + IMFAttributes_Release(attributes); + } + + return url ? wcsdup(url) : NULL; +} + +static HRESULT object_context_create(DWORD flags, IMFByteStream *stream, const WCHAR *url, + QWORD file_size, IMFAsyncResult *result, IUnknown **out, BYTE **out_buf) +{ + WCHAR *tmp_url = byte_stream_get_url(stream, url); + struct object_context *context; + + if (!(context = calloc(1, sizeof(*context))) + || !(context->buffer = malloc(SOURCE_BUFFER_SIZE))) + { + free(tmp_url); + free(context); + return E_OUTOFMEMORY; + } + + context->IUnknown_iface.lpVtbl = &object_context_vtbl; + context->refcount = 1; + context->stream = stream; + IMFByteStream_AddRef(context->stream); + context->file_size = file_size; + context->url = tmp_url; + context->result = result; + IMFAsyncResult_AddRef(context->result); + + *out = &context->IUnknown_iface; + *out_buf = context->buffer; + return S_OK; +} + +struct media_source_fallback_callback +{ + IMFAsyncCallback IMFAsyncCallback_iface; + IMFAsyncResult *result; + HANDLE event; +}; + +static HRESULT WINAPI media_source_fallback_callback_QueryInterface(IMFAsyncCallback *iface, REFIID riid, void **obj) +{ + if (IsEqualIID(riid, &IID_IMFAsyncCallback) || + IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IMFAsyncCallback_AddRef(iface); + return S_OK; + } + + WARN("Unsupported %s.\n", debugstr_guid(riid)); + *obj = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI media_source_fallback_callback_AddRef(IMFAsyncCallback *iface) +{ + return 2; +} + +static ULONG WINAPI media_source_fallback_callback_Release(IMFAsyncCallback *iface) +{ + return 1; +} + +static HRESULT WINAPI media_source_fallback_callback_GetParameters(IMFAsyncCallback *iface, DWORD *flags, DWORD *queue) +{ + return E_NOTIMPL; +} + +static HRESULT WINAPI media_source_fallback_callback_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) +{ + struct media_source_fallback_callback *impl = CONTAINING_RECORD(iface, struct media_source_fallback_callback, IMFAsyncCallback_iface); + + IMFAsyncResult_AddRef((impl->result = result)); + SetEvent(impl->event); + + return S_OK; +} + +static const IMFAsyncCallbackVtbl media_source_fallback_callback_vtbl = +{ + media_source_fallback_callback_QueryInterface, + media_source_fallback_callback_AddRef, + media_source_fallback_callback_Release, + media_source_fallback_callback_GetParameters, + media_source_fallback_callback_Invoke, +}; + +static HRESULT create_media_source_fallback(struct object_context *context, IUnknown **object) +{ + static const GUID CLSID_GStreamerByteStreamHandler = {0x317df618, 0x5e5a, 0x468a, {0x9f, 0x15, 0xd8, 0x27, 0xa9, 0xa0, 0x81, 0x62}}; + struct media_source_fallback_callback callback = {{&media_source_fallback_callback_vtbl}}; + IMFByteStreamHandler *handler; + MF_OBJECT_TYPE type; + HRESULT hr; + + if (!(callback.event = CreateEventW(NULL, FALSE, FALSE, NULL))) + return HRESULT_FROM_WIN32(GetLastError()); + + if (FAILED(hr = CoCreateInstance(&CLSID_GStreamerByteStreamHandler, NULL, CLSCTX_INPROC_SERVER, + &IID_IMFByteStreamHandler, (void **)&handler))) + { + CloseHandle(callback.event); + return hr; + } + + if (SUCCEEDED(hr = IMFByteStreamHandler_BeginCreateObject(handler, context->stream, NULL, MF_RESOLUTION_MEDIASOURCE, + NULL, NULL, &callback.IMFAsyncCallback_iface, NULL))) + { + WaitForSingleObject(callback.event, INFINITE); + hr = IMFByteStreamHandler_EndCreateObject(handler, callback.result, &type, object); + IMFAsyncResult_Release(callback.result); + } + + IMFByteStreamHandler_Release(handler); + CloseHandle(callback.event); + return hr; +} + +struct media_stream +{ + IMFMediaStream IMFMediaStream_iface; + LONG ref; + + IMFMediaSource *media_source; + IMFMediaEventQueue *event_queue; + IMFStreamDescriptor *descriptor; + + IUnknown **token_queue; + LONG token_queue_count; + LONG token_queue_cap; + + BOOL active; + BOOL eos; +}; + +enum source_async_op +{ + SOURCE_ASYNC_START, + SOURCE_ASYNC_PAUSE, + SOURCE_ASYNC_STOP, + SOURCE_ASYNC_REQUEST_SAMPLE, +}; + +struct source_async_command +{ + IUnknown IUnknown_iface; + LONG refcount; + enum source_async_op op; + union + { + struct + { + IMFPresentationDescriptor *descriptor; + GUID format; + PROPVARIANT position; + } start; + struct + { + struct media_stream *stream; + IUnknown *token; + } request_sample; + } u; +}; + +struct media_source +{ + IMFMediaSource IMFMediaSource_iface; + IMFGetService IMFGetService_iface; + IMFRateSupport IMFRateSupport_iface; + IMFRateControl IMFRateControl_iface; + IMFAsyncCallback async_commands_callback; + LONG ref; + DWORD async_commands_queue; + IMFMediaEventQueue *event_queue; + IMFByteStream *byte_stream; + + CRITICAL_SECTION cs; + + wg_source_t wg_source; + WCHAR mime_type[256]; + UINT64 file_size; + UINT64 duration; + BYTE *read_buffer; + + IMFStreamDescriptor **descriptors; + struct media_stream **streams; + ULONG stream_count; + UINT *stream_map; + + enum + { + SOURCE_OPENING, + SOURCE_STOPPED, + SOURCE_PAUSED, + SOURCE_RUNNING, + SOURCE_SHUTDOWN, + } state; + float rate; +}; + +static inline struct media_stream *impl_from_IMFMediaStream(IMFMediaStream *iface) +{ + return CONTAINING_RECORD(iface, struct media_stream, IMFMediaStream_iface); +} + +static inline struct media_source *impl_from_IMFMediaSource(IMFMediaSource *iface) +{ + return CONTAINING_RECORD(iface, struct media_source, IMFMediaSource_iface); +} + +static inline struct media_source *impl_from_IMFGetService(IMFGetService *iface) +{ + return CONTAINING_RECORD(iface, struct media_source, IMFGetService_iface); +} + +static inline struct media_source *impl_from_IMFRateSupport(IMFRateSupport *iface) +{ + return CONTAINING_RECORD(iface, struct media_source, IMFRateSupport_iface); +} + +static inline struct media_source *impl_from_IMFRateControl(IMFRateControl *iface) +{ + return CONTAINING_RECORD(iface, struct media_source, IMFRateControl_iface); +} + +static inline struct media_source *impl_from_async_commands_callback_IMFAsyncCallback(IMFAsyncCallback *iface) +{ + return CONTAINING_RECORD(iface, struct media_source, async_commands_callback); +} + +static inline struct source_async_command *impl_from_async_command_IUnknown(IUnknown *iface) +{ + return CONTAINING_RECORD(iface, struct source_async_command, IUnknown_iface); +} + +static HRESULT WINAPI source_async_command_QueryInterface(IUnknown *iface, REFIID riid, void **obj) +{ + if (IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IUnknown_AddRef(iface); + return S_OK; + } + + WARN("Unsupported interface %s.\n", debugstr_guid(riid)); + *obj = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI source_async_command_AddRef(IUnknown *iface) +{ + struct source_async_command *command = impl_from_async_command_IUnknown(iface); + return InterlockedIncrement(&command->refcount); +} + +static ULONG WINAPI source_async_command_Release(IUnknown *iface) +{ + struct source_async_command *command = impl_from_async_command_IUnknown(iface); + ULONG refcount = InterlockedDecrement(&command->refcount); + + if (!refcount) + { + if (command->op == SOURCE_ASYNC_START) + { + IMFPresentationDescriptor_Release(command->u.start.descriptor); + PropVariantClear(&command->u.start.position); + } + else if (command->op == SOURCE_ASYNC_REQUEST_SAMPLE) + { + if (command->u.request_sample.token) + IUnknown_Release(command->u.request_sample.token); + } + free(command); + } + + return refcount; +} + +static const IUnknownVtbl source_async_command_vtbl = +{ + source_async_command_QueryInterface, + source_async_command_AddRef, + source_async_command_Release, +}; + +static HRESULT source_create_async_op(enum source_async_op op, IUnknown **out) +{ + struct source_async_command *command; + + if (!(command = calloc(1, sizeof(*command)))) + return E_OUTOFMEMORY; + + command->IUnknown_iface.lpVtbl = &source_async_command_vtbl; + command->refcount = 1; + command->op = op; + + *out = &command->IUnknown_iface; + return S_OK; +} + +static HRESULT WINAPI callback_QueryInterface(IMFAsyncCallback *iface, REFIID riid, void **obj) +{ + TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); + + if (IsEqualIID(riid, &IID_IMFAsyncCallback) || + IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IMFAsyncCallback_AddRef(iface); + return S_OK; + } + + WARN("Unsupported %s.\n", debugstr_guid(riid)); + *obj = NULL; + return E_NOINTERFACE; +} + +static HRESULT WINAPI callback_GetParameters(IMFAsyncCallback *iface, + DWORD *flags, DWORD *queue) +{ + return E_NOTIMPL; +} + +static ULONG WINAPI source_async_commands_callback_AddRef(IMFAsyncCallback *iface) +{ + struct media_source *source = impl_from_async_commands_callback_IMFAsyncCallback(iface); + return IMFMediaSource_AddRef(&source->IMFMediaSource_iface); +} + +static ULONG WINAPI source_async_commands_callback_Release(IMFAsyncCallback *iface) +{ + struct media_source *source = impl_from_async_commands_callback_IMFAsyncCallback(iface); + return IMFMediaSource_Release(&source->IMFMediaSource_iface); +} + +static HRESULT stream_descriptor_get_media_type(IMFStreamDescriptor *descriptor, IMFMediaType **media_type) +{ + IMFMediaTypeHandler *handler; + HRESULT hr; + + if (FAILED(hr = IMFStreamDescriptor_GetMediaTypeHandler(descriptor, &handler))) + return hr; + hr = IMFMediaTypeHandler_GetCurrentMediaType(handler, media_type); + IMFMediaTypeHandler_Release(handler); + + return hr; +} + +static HRESULT stream_descriptor_get_major_type(IMFStreamDescriptor *descriptor, GUID *major) +{ + IMFMediaTypeHandler *handler; + HRESULT hr; + + if (FAILED(hr = IMFStreamDescriptor_GetMediaTypeHandler(descriptor, &handler))) + return hr; + hr = IMFMediaTypeHandler_GetMajorType(handler, major); + IMFMediaTypeHandler_Release(handler); + + return hr; +} + +static HRESULT wg_format_from_stream_descriptor(IMFStreamDescriptor *descriptor, struct wg_format *format) +{ + IMFMediaType *media_type; + HRESULT hr; + + if (FAILED(hr = stream_descriptor_get_media_type(descriptor, &media_type))) + return hr; + mf_media_type_to_wg_format(media_type, format); + IMFMediaType_Release(media_type); + + return hr; +} + +static HRESULT stream_descriptor_set_tag(IMFStreamDescriptor *descriptor, + wg_source_t source, UINT index, const GUID *attr, enum wg_parser_tag tag) +{ + WCHAR *strW; + HRESULT hr; + DWORD len; + char *str; + + if (!(str = wg_source_get_stream_tag(source, index, tag)) + || !(len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0))) + hr = S_OK; + else if (!(strW = malloc(len * sizeof(*strW)))) + hr = E_OUTOFMEMORY; + else + { + if (MultiByteToWideChar(CP_UTF8, 0, str, -1, strW, len)) + hr = IMFStreamDescriptor_SetString(descriptor, attr, strW); + else + hr = E_FAIL; + free(strW); + } + + free(str); + return hr; +} + +static HRESULT stream_descriptor_create(UINT32 id, struct wg_format *format, IMFStreamDescriptor **out) +{ + IMFStreamDescriptor *descriptor; + IMFMediaTypeHandler *handler; + IMFMediaType *type; + HRESULT hr; + + if (!(type = mf_media_type_from_wg_format(format))) + return MF_E_INVALIDMEDIATYPE; + if (FAILED(hr = MFCreateStreamDescriptor(id, 1, &type, &descriptor))) + goto done; + + if (FAILED(hr = IMFStreamDescriptor_GetMediaTypeHandler(descriptor, &handler))) + IMFStreamDescriptor_Release(descriptor); + else + { + hr = IMFMediaTypeHandler_SetCurrentMediaType(handler, type); + IMFMediaTypeHandler_Release(handler); + } + +done: + IMFMediaType_Release(type); + *out = SUCCEEDED(hr) ? descriptor : NULL; + return hr; +} + +static BOOL enqueue_token(struct media_stream *stream, IUnknown *token) +{ + if (stream->token_queue_count == stream->token_queue_cap) + { + IUnknown **buf; + stream->token_queue_cap = stream->token_queue_cap * 2 + 1; + buf = realloc(stream->token_queue, stream->token_queue_cap * sizeof(*buf)); + if (buf) + stream->token_queue = buf; + else + { + stream->token_queue_cap = stream->token_queue_count; + return FALSE; + } + } + stream->token_queue[stream->token_queue_count++] = token; + return TRUE; +} + +static void flush_token_queue(struct media_stream *stream, BOOL send) +{ + struct media_source *source = impl_from_IMFMediaSource(stream->media_source); + LONG i; + + for (i = 0; i < stream->token_queue_count; i++) + { + if (send) + { + IUnknown *op; + HRESULT hr; + + if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_REQUEST_SAMPLE, &op))) + { + struct source_async_command *command = impl_from_async_command_IUnknown(op); + command->u.request_sample.stream = stream; + command->u.request_sample.token = stream->token_queue[i]; + + hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, op); + IUnknown_Release(op); + } + if (FAILED(hr)) + WARN("Could not enqueue sample request, hr %#lx\n", hr); + } + else if (stream->token_queue[i]) + IUnknown_Release(stream->token_queue[i]); + } + free(stream->token_queue); + stream->token_queue = NULL; + stream->token_queue_count = 0; + stream->token_queue_cap = 0; +} + +static HRESULT media_stream_start(struct media_stream *stream, BOOL active, BOOL seeking, const PROPVARIANT *position) +{ + struct media_source *source = impl_from_IMFMediaSource(stream->media_source); + struct wg_format format; + HRESULT hr; + + TRACE("source %p, stream %p\n", source, stream); + + if (FAILED(hr = wg_format_from_stream_descriptor(stream->descriptor, &format))) + WARN("Failed to get wg_format from stream descriptor, hr %#lx\n", hr); + + if (FAILED(hr = IMFMediaEventQueue_QueueEventParamUnk(source->event_queue, active ? MEUpdatedStream : MENewStream, + &GUID_NULL, S_OK, (IUnknown *)&stream->IMFMediaStream_iface))) + WARN("Failed to send source stream event, hr %#lx\n", hr); + return IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, seeking ? MEStreamSeeked : MEStreamStarted, + &GUID_NULL, S_OK, position); +} + +static HRESULT media_source_start(struct media_source *source, IMFPresentationDescriptor *descriptor, + GUID *format, PROPVARIANT *position) +{ + BOOL starting = source->state == SOURCE_STOPPED, seek_message = !starting && position->vt != VT_EMPTY; + IMFStreamDescriptor **descriptors; + DWORD i, count; + HRESULT hr; + + TRACE("source %p, descriptor %p, format %s, position %s\n", source, descriptor, + debugstr_guid(format), wine_dbgstr_variant((VARIANT *)position)); + + if (source->state == SOURCE_SHUTDOWN) + return MF_E_SHUTDOWN; + + /* seek to beginning on stop->play */ + if (source->state == SOURCE_STOPPED && position->vt == VT_EMPTY) + { + position->vt = VT_I8; + position->hVal.QuadPart = 0; + } + + if (!(descriptors = calloc(source->stream_count, sizeof(*descriptors)))) + return E_OUTOFMEMORY; + + if (FAILED(hr = IMFPresentationDescriptor_GetStreamDescriptorCount(descriptor, &count))) + WARN("Failed to get presentation descriptor stream count, hr %#lx\n", hr); + + for (i = 0; i < count; i++) + { + IMFStreamDescriptor *stream_descriptor; + BOOL selected; + DWORD id; + + if (FAILED(hr = IMFPresentationDescriptor_GetStreamDescriptorByIndex(descriptor, i, + &selected, &stream_descriptor))) + WARN("Failed to get presentation stream descriptor, hr %#lx\n", hr); + else + { + if (FAILED(hr = IMFStreamDescriptor_GetStreamIdentifier(stream_descriptor, &id))) + WARN("Failed to get stream descriptor id, hr %#lx\n", hr); + else if (id > source->stream_count) + WARN("Invalid stream descriptor id %lu, hr %#lx\n", id, hr); + else if (selected) + IMFStreamDescriptor_AddRef((descriptors[id - 1] = stream_descriptor)); + + IMFStreamDescriptor_Release(stream_descriptor); + } + } + + for (i = 0; i < source->stream_count; i++) + { + struct media_stream *stream = source->streams[i]; + BOOL was_active = !starting && stream->active; + + if (position->vt != VT_EMPTY) + stream->eos = FALSE; + + if (!(stream->active = !!descriptors[i])) + wg_source_set_stream_flags(source->wg_source, source->stream_map[i], FALSE); + else + { + if (FAILED(hr = media_stream_start(stream, was_active, seek_message, position))) + WARN("Failed to start media stream, hr %#lx\n", hr); + else + wg_source_set_stream_flags(source->wg_source, source->stream_map[i], TRUE); + IMFStreamDescriptor_Release(descriptors[i]); + } + } + + free(descriptors); + + source->state = SOURCE_RUNNING; + if (position->vt == VT_I8) + wg_source_set_position(source->wg_source, position->hVal.QuadPart); + + for (i = 0; i < source->stream_count; i++) + flush_token_queue(source->streams[i], position->vt == VT_EMPTY); + + return IMFMediaEventQueue_QueueEventParamVar(source->event_queue, + seek_message ? MESourceSeeked : MESourceStarted, &GUID_NULL, S_OK, position); +} + +static HRESULT media_source_pause(struct media_source *source) +{ + unsigned int i; + HRESULT hr; + + TRACE("source %p\n", source); + + if (source->state == SOURCE_SHUTDOWN) + return MF_E_SHUTDOWN; + + for (i = 0; i < source->stream_count; i++) + { + struct media_stream *stream = source->streams[i]; + if (stream->active && FAILED(hr = IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, MEStreamPaused, + &GUID_NULL, S_OK, NULL))) + WARN("Failed to queue MEStreamPaused event, hr %#lx\n", hr); + } + + source->state = SOURCE_PAUSED; + return IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MESourcePaused, &GUID_NULL, S_OK, NULL); +} + +static HRESULT media_source_stop(struct media_source *source) +{ + unsigned int i; + HRESULT hr; + + TRACE("source %p\n", source); + + if (source->state == SOURCE_SHUTDOWN) + return MF_E_SHUTDOWN; + + for (i = 0; i < source->stream_count; i++) + { + struct media_stream *stream = source->streams[i]; + if (stream->active && FAILED(hr = IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, MEStreamStopped, + &GUID_NULL, S_OK, NULL))) + WARN("Failed to queue MEStreamStopped event, hr %#lx\n", hr); + } + + source->state = SOURCE_STOPPED; + + for (i = 0; i < source->stream_count; i++) + flush_token_queue(source->streams[i], FALSE); + + return IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MESourceStopped, &GUID_NULL, S_OK, NULL); +} + +static HRESULT media_stream_send_eos(struct media_source *source, struct media_stream *stream) +{ + PROPVARIANT empty = {.vt = VT_EMPTY}; + HRESULT hr; + UINT i; + + TRACE("source %p, stream %p\n", source, stream); + + stream->eos = TRUE; + if (FAILED(hr = IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, MEEndOfStream, &GUID_NULL, S_OK, &empty))) + WARN("Failed to queue MEEndOfStream event, hr %#lx\n", hr); + + for (i = 0; i < source->stream_count; i++) + { + struct media_stream *stream = source->streams[i]; + if (stream->active && !stream->eos) + return S_OK; + } + + if (FAILED(hr = IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MEEndOfPresentation, &GUID_NULL, S_OK, &empty))) + WARN("Failed to queue MEEndOfPresentation event, hr %#lx\n", hr); + return S_OK; +} + +static HRESULT wait_on_sample(struct media_stream *stream, IUnknown *token) +{ + struct media_source *source = impl_from_IMFMediaSource(stream->media_source); + DWORD id, read_size; + UINT64 read_offset; + IMFSample *sample; + HRESULT hr; + + TRACE("%p, %p\n", stream, token); + + if (FAILED(hr = IMFStreamDescriptor_GetStreamIdentifier(stream->descriptor, &id))) + return hr; + id = source->stream_map[id - 1]; + + while (SUCCEEDED(hr) && (hr = wg_source_read_data(source->wg_source, id, &sample)) == E_PENDING) + { + if (FAILED(hr = wg_source_get_position(source->wg_source, &read_offset))) + break; + if (FAILED(hr = IMFByteStream_SetCurrentPosition(source->byte_stream, read_offset))) + WARN("Failed to seek stream to %#I64x, hr %#lx\n", read_offset, hr); + else if (FAILED(hr = IMFByteStream_Read(source->byte_stream, source->read_buffer, SOURCE_BUFFER_SIZE, &read_size))) + WARN("Failed to read %#lx bytes from stream, hr %#lx\n", read_size, hr); + else if (FAILED(hr = wg_source_push_data(source->wg_source, source->read_buffer, read_size))) + WARN("Failed to push %#lx bytes to source, hr %#lx\n", read_size, hr); + } + + if (FAILED(hr)) + WARN("Failed to read stream %lu data, hr %#lx\n", id, hr); + else + { + if (!token || SUCCEEDED(hr = IMFSample_SetUnknown(sample, &MFSampleExtension_Token, token))) + hr = IMFMediaEventQueue_QueueEventParamUnk(stream->event_queue, MEMediaSample, + &GUID_NULL, S_OK, (IUnknown *)sample); + IMFSample_Release(sample); + return hr; + } + + return media_stream_send_eos(source, stream); +} + +static HRESULT WINAPI source_async_commands_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) +{ + struct media_source *source = impl_from_async_commands_callback_IMFAsyncCallback(iface); + struct source_async_command *command; + IUnknown *state; + HRESULT hr; + + if (FAILED(hr = IMFAsyncResult_GetState(result, &state))) + return hr; + + EnterCriticalSection(&source->cs); + + command = impl_from_async_command_IUnknown(state); + switch (command->op) + { + case SOURCE_ASYNC_START: + { + IMFPresentationDescriptor *descriptor = command->u.start.descriptor; + GUID format = command->u.start.format; + PROPVARIANT position = command->u.start.position; + + if (FAILED(hr = media_source_start(source, descriptor, &format, &position))) + WARN("Failed to start source %p, hr %#lx\n", source, hr); + break; + } + case SOURCE_ASYNC_PAUSE: + if (FAILED(hr = media_source_pause(source))) + WARN("Failed to pause source %p, hr %#lx\n", source, hr); + break; + case SOURCE_ASYNC_STOP: + if (FAILED(hr = media_source_stop(source))) + WARN("Failed to stop source %p, hr %#lx\n", source, hr); + break; + case SOURCE_ASYNC_REQUEST_SAMPLE: + if (source->state == SOURCE_PAUSED) + enqueue_token(command->u.request_sample.stream, command->u.request_sample.token); + else if (source->state == SOURCE_RUNNING) + { + if (FAILED(hr = wait_on_sample(command->u.request_sample.stream, command->u.request_sample.token))) + WARN("Failed to request sample, hr %#lx\n", hr); + } + break; + } + + LeaveCriticalSection(&source->cs); + + IUnknown_Release(state); + + return S_OK; +} + +static const IMFAsyncCallbackVtbl source_async_commands_callback_vtbl = +{ + callback_QueryInterface, + source_async_commands_callback_AddRef, + source_async_commands_callback_Release, + callback_GetParameters, + source_async_commands_Invoke, +}; + +static HRESULT WINAPI media_stream_QueryInterface(IMFMediaStream *iface, REFIID riid, void **out) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), out); + + if (IsEqualIID(riid, &IID_IMFMediaStream) || + IsEqualIID(riid, &IID_IMFMediaEventGenerator) || + IsEqualIID(riid, &IID_IUnknown)) + { + *out = &stream->IMFMediaStream_iface; + } + else + { + FIXME("(%s, %p)\n", debugstr_guid(riid), out); + *out = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown*)*out); + return S_OK; +} + +static ULONG WINAPI media_stream_AddRef(IMFMediaStream *iface) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + ULONG ref = InterlockedIncrement(&stream->ref); + + TRACE("%p, refcount %lu.\n", iface, ref); + + return ref; +} + +static ULONG WINAPI media_stream_Release(IMFMediaStream *iface) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + ULONG ref = InterlockedDecrement(&stream->ref); + + TRACE("%p, refcount %lu.\n", iface, ref); + + if (!ref) + { + IMFMediaSource_Release(stream->media_source); + IMFStreamDescriptor_Release(stream->descriptor); + IMFMediaEventQueue_Release(stream->event_queue); + flush_token_queue(stream, FALSE); + free(stream); + } + + return ref; +} + +static HRESULT WINAPI media_stream_GetEvent(IMFMediaStream *iface, DWORD flags, IMFMediaEvent **event) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + TRACE("%p, %#lx, %p.\n", iface, flags, event); + + return IMFMediaEventQueue_GetEvent(stream->event_queue, flags, event); +} + +static HRESULT WINAPI media_stream_BeginGetEvent(IMFMediaStream *iface, IMFAsyncCallback *callback, IUnknown *state) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + TRACE("%p, %p, %p.\n", iface, callback, state); + + return IMFMediaEventQueue_BeginGetEvent(stream->event_queue, callback, state); +} + +static HRESULT WINAPI media_stream_EndGetEvent(IMFMediaStream *iface, IMFAsyncResult *result, IMFMediaEvent **event) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + TRACE("%p, %p, %p.\n", stream, result, event); + + return IMFMediaEventQueue_EndGetEvent(stream->event_queue, result, event); +} + +static HRESULT WINAPI media_stream_QueueEvent(IMFMediaStream *iface, MediaEventType event_type, REFGUID ext_type, + HRESULT hr, const PROPVARIANT *value) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + TRACE("%p, %lu, %s, %#lx, %p.\n", iface, event_type, debugstr_guid(ext_type), hr, value); + + return IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, event_type, ext_type, hr, value); +} + +static HRESULT WINAPI media_stream_GetMediaSource(IMFMediaStream *iface, IMFMediaSource **out) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + struct media_source *source = impl_from_IMFMediaSource(stream->media_source); + HRESULT hr = S_OK; + + TRACE("%p, %p.\n", iface, out); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + hr = MF_E_SHUTDOWN; + else + { + IMFMediaSource_AddRef(&source->IMFMediaSource_iface); + *out = &source->IMFMediaSource_iface; + } + + LeaveCriticalSection(&source->cs); + + return hr; +} + +static HRESULT WINAPI media_stream_GetStreamDescriptor(IMFMediaStream* iface, IMFStreamDescriptor **descriptor) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + struct media_source *source = impl_from_IMFMediaSource(stream->media_source); + HRESULT hr = S_OK; + + TRACE("%p, %p.\n", iface, descriptor); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + hr = MF_E_SHUTDOWN; + else + { + IMFStreamDescriptor_AddRef(stream->descriptor); + *descriptor = stream->descriptor; + } + + LeaveCriticalSection(&source->cs); + + return hr; +} + +static HRESULT WINAPI media_stream_RequestSample(IMFMediaStream *iface, IUnknown *token) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + struct media_source *source = impl_from_IMFMediaSource(stream->media_source); + IUnknown *op; + HRESULT hr; + + TRACE("%p, %p.\n", iface, token); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + hr = MF_E_SHUTDOWN; + else if (!stream->active) + hr = MF_E_MEDIA_SOURCE_WRONGSTATE; + else if (stream->eos) + hr = MF_E_END_OF_STREAM; + else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_REQUEST_SAMPLE, &op))) + { + struct source_async_command *command = impl_from_async_command_IUnknown(op); + command->u.request_sample.stream = stream; + if (token) + IUnknown_AddRef(token); + command->u.request_sample.token = token; + + hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, op); + IUnknown_Release(op); + } + + LeaveCriticalSection(&source->cs); + + return hr; +} + +static const IMFMediaStreamVtbl media_stream_vtbl = +{ + media_stream_QueryInterface, + media_stream_AddRef, + media_stream_Release, + media_stream_GetEvent, + media_stream_BeginGetEvent, + media_stream_EndGetEvent, + media_stream_QueueEvent, + media_stream_GetMediaSource, + media_stream_GetStreamDescriptor, + media_stream_RequestSample +}; + +static HRESULT media_stream_create(IMFMediaSource *source, IMFStreamDescriptor *descriptor, struct media_stream **out) +{ + struct media_stream *object; + HRESULT hr; + + TRACE("source %p, descriptor %p.\n", source, descriptor); + + if (!(object = calloc(1, sizeof(*object)))) + return E_OUTOFMEMORY; + + object->IMFMediaStream_iface.lpVtbl = &media_stream_vtbl; + object->ref = 1; + + if (FAILED(hr = MFCreateEventQueue(&object->event_queue))) + { + free(object); + return hr; + } + + IMFMediaSource_AddRef(source); + object->media_source = source; + IMFStreamDescriptor_AddRef(descriptor); + object->descriptor = descriptor; + + TRACE("Created stream object %p.\n", object); + + *out = object; + return S_OK; +} + +static HRESULT WINAPI media_source_get_service_QueryInterface(IMFGetService *iface, REFIID riid, void **obj) +{ + struct media_source *source = impl_from_IMFGetService(iface); + return IMFMediaSource_QueryInterface(&source->IMFMediaSource_iface, riid, obj); +} + +static ULONG WINAPI media_source_get_service_AddRef(IMFGetService *iface) +{ + struct media_source *source = impl_from_IMFGetService(iface); + return IMFMediaSource_AddRef(&source->IMFMediaSource_iface); +} + +static ULONG WINAPI media_source_get_service_Release(IMFGetService *iface) +{ + struct media_source *source = impl_from_IMFGetService(iface); + return IMFMediaSource_Release(&source->IMFMediaSource_iface); +} + +static HRESULT WINAPI media_source_get_service_GetService(IMFGetService *iface, REFGUID service, REFIID riid, void **obj) +{ + struct media_source *source = impl_from_IMFGetService(iface); + + TRACE("%p, %s, %s, %p.\n", iface, debugstr_guid(service), debugstr_guid(riid), obj); + + *obj = NULL; + + if (IsEqualGUID(service, &MF_RATE_CONTROL_SERVICE)) + { + if (IsEqualIID(riid, &IID_IMFRateSupport)) + { + *obj = &source->IMFRateSupport_iface; + } + else if (IsEqualIID(riid, &IID_IMFRateControl)) + { + *obj = &source->IMFRateControl_iface; + } + } + else + FIXME("Unsupported service %s.\n", debugstr_guid(service)); + + if (*obj) + IUnknown_AddRef((IUnknown *)*obj); + + return *obj ? S_OK : E_NOINTERFACE; +} + +static const IMFGetServiceVtbl media_source_get_service_vtbl = +{ + media_source_get_service_QueryInterface, + media_source_get_service_AddRef, + media_source_get_service_Release, + media_source_get_service_GetService, +}; + +static HRESULT WINAPI media_source_rate_support_QueryInterface(IMFRateSupport *iface, REFIID riid, void **obj) +{ + struct media_source *source = impl_from_IMFRateSupport(iface); + return IMFMediaSource_QueryInterface(&source->IMFMediaSource_iface, riid, obj); +} + +static ULONG WINAPI media_source_rate_support_AddRef(IMFRateSupport *iface) +{ + struct media_source *source = impl_from_IMFRateSupport(iface); + return IMFMediaSource_AddRef(&source->IMFMediaSource_iface); +} + +static ULONG WINAPI media_source_rate_support_Release(IMFRateSupport *iface) +{ + struct media_source *source = impl_from_IMFRateSupport(iface); + return IMFMediaSource_Release(&source->IMFMediaSource_iface); +} + +static HRESULT WINAPI media_source_rate_support_GetSlowestRate(IMFRateSupport *iface, MFRATE_DIRECTION direction, BOOL thin, float *rate) +{ + TRACE("%p, %d, %d, %p.\n", iface, direction, thin, rate); + + *rate = 0.0f; + + return S_OK; +} + +static HRESULT WINAPI media_source_rate_support_GetFastestRate(IMFRateSupport *iface, MFRATE_DIRECTION direction, BOOL thin, float *rate) +{ + TRACE("%p, %d, %d, %p.\n", iface, direction, thin, rate); + + *rate = direction == MFRATE_FORWARD ? 1e6f : -1e6f; + + return S_OK; +} + +static HRESULT WINAPI media_source_rate_support_IsRateSupported(IMFRateSupport *iface, BOOL thin, float rate, + float *nearest_rate) +{ + TRACE("%p, %d, %f, %p.\n", iface, thin, rate, nearest_rate); + + if (nearest_rate) + *nearest_rate = rate; + + return rate >= -1e6f && rate <= 1e6f ? S_OK : MF_E_UNSUPPORTED_RATE; +} + +static const IMFRateSupportVtbl media_source_rate_support_vtbl = +{ + media_source_rate_support_QueryInterface, + media_source_rate_support_AddRef, + media_source_rate_support_Release, + media_source_rate_support_GetSlowestRate, + media_source_rate_support_GetFastestRate, + media_source_rate_support_IsRateSupported, +}; + +static HRESULT WINAPI media_source_rate_control_QueryInterface(IMFRateControl *iface, REFIID riid, void **obj) +{ + struct media_source *source = impl_from_IMFRateControl(iface); + return IMFMediaSource_QueryInterface(&source->IMFMediaSource_iface, riid, obj); +} + +static ULONG WINAPI media_source_rate_control_AddRef(IMFRateControl *iface) +{ + struct media_source *source = impl_from_IMFRateControl(iface); + return IMFMediaSource_AddRef(&source->IMFMediaSource_iface); +} + +static ULONG WINAPI media_source_rate_control_Release(IMFRateControl *iface) +{ + struct media_source *source = impl_from_IMFRateControl(iface); + return IMFMediaSource_Release(&source->IMFMediaSource_iface); +} + +static HRESULT WINAPI media_source_rate_control_SetRate(IMFRateControl *iface, BOOL thin, float rate) +{ + struct media_source *source = impl_from_IMFRateControl(iface); + HRESULT hr; + + FIXME("%p, %d, %f.\n", iface, thin, rate); + + if (rate < 0.0f) + return MF_E_REVERSE_UNSUPPORTED; + + if (thin) + return MF_E_THINNING_UNSUPPORTED; + + if (FAILED(hr = IMFRateSupport_IsRateSupported(&source->IMFRateSupport_iface, thin, rate, NULL))) + return hr; + + EnterCriticalSection(&source->cs); + source->rate = rate; + LeaveCriticalSection(&source->cs); + + return IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MESourceRateChanged, &GUID_NULL, S_OK, NULL); +} + +static HRESULT WINAPI media_source_rate_control_GetRate(IMFRateControl *iface, BOOL *thin, float *rate) +{ + struct media_source *source = impl_from_IMFRateControl(iface); + + TRACE("%p, %p, %p.\n", iface, thin, rate); + + if (thin) + *thin = FALSE; + + EnterCriticalSection(&source->cs); + *rate = source->rate; + LeaveCriticalSection(&source->cs); + + return S_OK; +} + +static const IMFRateControlVtbl media_source_rate_control_vtbl = +{ + media_source_rate_control_QueryInterface, + media_source_rate_control_AddRef, + media_source_rate_control_Release, + media_source_rate_control_SetRate, + media_source_rate_control_GetRate, +}; + +static HRESULT WINAPI media_source_QueryInterface(IMFMediaSource *iface, REFIID riid, void **out) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + + TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), out); + + if (IsEqualIID(riid, &IID_IMFMediaSource) || + IsEqualIID(riid, &IID_IMFMediaEventGenerator) || + IsEqualIID(riid, &IID_IUnknown)) + { + *out = &source->IMFMediaSource_iface; + } + else if (IsEqualIID(riid, &IID_IMFGetService)) + { + *out = &source->IMFGetService_iface; + } + else + { + FIXME("%s, %p.\n", debugstr_guid(riid), out); + *out = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown*)*out); + return S_OK; +} + +static ULONG WINAPI media_source_AddRef(IMFMediaSource *iface) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + ULONG ref = InterlockedIncrement(&source->ref); + + TRACE("%p, refcount %lu.\n", iface, ref); + + return ref; +} + +static ULONG WINAPI media_source_Release(IMFMediaSource *iface) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + ULONG ref = InterlockedDecrement(&source->ref); + + TRACE("%p, refcount %lu.\n", iface, ref); + + if (!ref) + { + IMFMediaSource_Shutdown(iface); + IMFMediaEventQueue_Release(source->event_queue); + IMFByteStream_Release(source->byte_stream); + wg_source_destroy(source->wg_source); + free(source->read_buffer); + source->cs.DebugInfo->Spare[0] = 0; + DeleteCriticalSection(&source->cs); + free(source); + } + + return ref; +} + +static HRESULT WINAPI media_source_GetEvent(IMFMediaSource *iface, DWORD flags, IMFMediaEvent **event) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + + TRACE("%p, %#lx, %p.\n", iface, flags, event); + + return IMFMediaEventQueue_GetEvent(source->event_queue, flags, event); +} + +static HRESULT WINAPI media_source_BeginGetEvent(IMFMediaSource *iface, IMFAsyncCallback *callback, IUnknown *state) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + + TRACE("%p, %p, %p.\n", iface, callback, state); + + return IMFMediaEventQueue_BeginGetEvent(source->event_queue, callback, state); +} + +static HRESULT WINAPI media_source_EndGetEvent(IMFMediaSource *iface, IMFAsyncResult *result, IMFMediaEvent **event) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + + TRACE("%p, %p, %p.\n", iface, result, event); + + return IMFMediaEventQueue_EndGetEvent(source->event_queue, result, event); +} + +static HRESULT WINAPI media_source_QueueEvent(IMFMediaSource *iface, MediaEventType event_type, REFGUID ext_type, + HRESULT hr, const PROPVARIANT *value) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + + TRACE("%p, %lu, %s, %#lx, %p.\n", iface, event_type, debugstr_guid(ext_type), hr, value); + + return IMFMediaEventQueue_QueueEventParamVar(source->event_queue, event_type, ext_type, hr, value); +} + +static HRESULT WINAPI media_source_GetCharacteristics(IMFMediaSource *iface, DWORD *characteristics) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + HRESULT hr = S_OK; + + TRACE("%p, %p.\n", iface, characteristics); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + hr = MF_E_SHUTDOWN; + else + *characteristics = MFMEDIASOURCE_CAN_SEEK | MFMEDIASOURCE_CAN_PAUSE; + + LeaveCriticalSection(&source->cs); + + return hr; +} + +static HRESULT WINAPI media_source_CreatePresentationDescriptor(IMFMediaSource *iface, IMFPresentationDescriptor **descriptor) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + HRESULT hr; + UINT i; + + TRACE("%p, %p.\n", iface, descriptor); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + hr = MF_E_SHUTDOWN; + else if (SUCCEEDED(hr = MFCreatePresentationDescriptor(source->stream_count, source->descriptors, descriptor))) + { + if (FAILED(hr = IMFPresentationDescriptor_SetString(*descriptor, &MF_PD_MIME_TYPE, source->mime_type))) + WARN("Failed to set presentation descriptor MF_PD_MIME_TYPE, hr %#lx\n", hr); + if (FAILED(hr = IMFPresentationDescriptor_SetUINT64(*descriptor, &MF_PD_TOTAL_FILE_SIZE, source->file_size))) + WARN("Failed to set presentation descriptor MF_PD_TOTAL_FILE_SIZE, hr %#lx\n", hr); + if (FAILED(hr = IMFPresentationDescriptor_SetUINT64(*descriptor, &MF_PD_DURATION, source->duration))) + WARN("Failed to set presentation descriptor MF_PD_DURATION, hr %#lx\n", hr); + + for (i = 0; i < source->stream_count; ++i) + { + if (!source->streams[i]->active) + continue; + if (FAILED(hr = IMFPresentationDescriptor_SelectStream(*descriptor, i))) + WARN("Failed to select stream %u, hr %#lx\n", i, hr); + } + + hr = S_OK; + } + + LeaveCriticalSection(&source->cs); + + return hr; +} + +static HRESULT WINAPI media_source_Start(IMFMediaSource *iface, IMFPresentationDescriptor *descriptor, + const GUID *time_format, const PROPVARIANT *position) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + IUnknown *op; + HRESULT hr; + + TRACE("%p, %p, %p, %p.\n", iface, descriptor, time_format, position); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + hr = MF_E_SHUTDOWN; + else if (!(IsEqualIID(time_format, &GUID_NULL))) + hr = MF_E_UNSUPPORTED_TIME_FORMAT; + else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_START, &op))) + { + struct source_async_command *command = impl_from_async_command_IUnknown(op); + command->u.start.descriptor = descriptor; + IMFPresentationDescriptor_AddRef(descriptor); + command->u.start.format = *time_format; + PropVariantCopy(&command->u.start.position, position); + + hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, op); + IUnknown_Release(op); + } + + LeaveCriticalSection(&source->cs); + + return hr; +} + +static HRESULT WINAPI media_source_Stop(IMFMediaSource *iface) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + IUnknown *op; + HRESULT hr; + + TRACE("%p.\n", iface); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + hr = MF_E_SHUTDOWN; + else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_STOP, &op))) + { + hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, op); + IUnknown_Release(op); + } + + LeaveCriticalSection(&source->cs); + + return hr; +} + +static HRESULT WINAPI media_source_Pause(IMFMediaSource *iface) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + IUnknown *op; + HRESULT hr; + + TRACE("%p.\n", iface); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + hr = MF_E_SHUTDOWN; + else if (source->state != SOURCE_RUNNING) + hr = MF_E_INVALID_STATE_TRANSITION; + else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_PAUSE, &op))) + { + hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, op); + IUnknown_Release(op); + } + + LeaveCriticalSection(&source->cs); + + return S_OK; +} + +static HRESULT WINAPI media_source_Shutdown(IMFMediaSource *iface) +{ + struct media_source *source = impl_from_IMFMediaSource(iface); + + TRACE("%p.\n", iface); + + EnterCriticalSection(&source->cs); + + if (source->state == SOURCE_SHUTDOWN) + { + LeaveCriticalSection(&source->cs); + return MF_E_SHUTDOWN; + } + + source->state = SOURCE_SHUTDOWN; + + IMFMediaEventQueue_Shutdown(source->event_queue); + IMFByteStream_Close(source->byte_stream); + + while (source->stream_count--) + { + struct media_stream *stream = source->streams[source->stream_count]; + IMFStreamDescriptor_Release(source->descriptors[source->stream_count]); + IMFMediaEventQueue_Shutdown(stream->event_queue); + IMFMediaStream_Release(&stream->IMFMediaStream_iface); + } + free(source->stream_map); + free(source->descriptors); + free(source->streams); + + LeaveCriticalSection(&source->cs); + + MFUnlockWorkQueue(source->async_commands_queue); + + return S_OK; +} + +static const IMFMediaSourceVtbl IMFMediaSource_vtbl = +{ + media_source_QueryInterface, + media_source_AddRef, + media_source_Release, + media_source_GetEvent, + media_source_BeginGetEvent, + media_source_EndGetEvent, + media_source_QueueEvent, + media_source_GetCharacteristics, + media_source_CreatePresentationDescriptor, + media_source_Start, + media_source_Stop, + media_source_Pause, + media_source_Shutdown, +}; + +static void media_source_init_stream_map(struct media_source *source, UINT stream_count) +{ + struct wg_format format; + int i, n = 0; + + if (wcscmp(source->mime_type, L"video/mp4")) + { + for (i = stream_count - 1; i >= 0; i--) + { + TRACE("mapping stream %u to wg_source stream %u\n", i, i); + source->stream_map[i] = i; + } + return; + } + + for (i = stream_count - 1; i >= 0; i--) + { + wg_source_get_stream_format(source->wg_source, i, &format); + if (format.major_type == WG_MAJOR_TYPE_UNKNOWN) continue; + if (format.major_type >= WG_MAJOR_TYPE_VIDEO) continue; + TRACE("mapping stream %u to wg_source stream %u\n", n, i); + source->stream_map[n++] = i; + } + for (i = stream_count - 1; i >= 0; i--) + { + wg_source_get_stream_format(source->wg_source, i, &format); + if (format.major_type == WG_MAJOR_TYPE_UNKNOWN) continue; + if (format.major_type < WG_MAJOR_TYPE_VIDEO) continue; + TRACE("mapping stream %u to wg_source stream %u\n", n, i); + source->stream_map[n++] = i; + } + for (i = stream_count - 1; i >= 0; i--) + { + wg_source_get_stream_format(source->wg_source, i, &format); + if (format.major_type != WG_MAJOR_TYPE_UNKNOWN) continue; + TRACE("mapping stream %u to wg_source stream %u\n", n, i); + source->stream_map[n++] = i; + } +} + +static void media_source_init_descriptors(struct media_source *source) +{ + UINT i, last_audio = -1, last_video = -1, first_audio = -1, first_video = -1; + HRESULT hr; + + for (i = 0; i < source->stream_count; i++) + { + IMFStreamDescriptor *descriptor = source->descriptors[i]; + GUID major = GUID_NULL; + UINT exclude = -1; + + if (FAILED(hr = stream_descriptor_get_major_type(descriptor, &major))) + WARN("Failed to get major type from stream descriptor, hr %#lx\n", hr); + + if (IsEqualGUID(&major, &MFMediaType_Audio)) + { + if (first_audio == -1) + first_audio = i; + exclude = last_audio; + last_audio = i; + } + else if (IsEqualGUID(&major, &MFMediaType_Video)) + { + if (first_video == -1) + first_video = i; + exclude = last_video; + last_video = i; + } + + if (exclude != -1) + { + if (FAILED(IMFStreamDescriptor_SetUINT32(source->descriptors[exclude], &MF_SD_MUTUALLY_EXCLUSIVE, 1))) + WARN("Failed to set stream %u MF_SD_MUTUALLY_EXCLUSIVE\n", exclude); + else if (FAILED(IMFStreamDescriptor_SetUINT32(descriptor, &MF_SD_MUTUALLY_EXCLUSIVE, 1))) + WARN("Failed to set stream %u MF_SD_MUTUALLY_EXCLUSIVE\n", i); + } + + if (FAILED(hr = stream_descriptor_set_tag(descriptor, source->wg_source, source->stream_map[i], + &MF_SD_LANGUAGE, WG_PARSER_TAG_LANGUAGE))) + WARN("Failed to set stream descriptor language, hr %#lx\n", hr); + if (FAILED(hr = stream_descriptor_set_tag(descriptor, source->wg_source, source->stream_map[i], + &MF_SD_STREAM_NAME, WG_PARSER_TAG_NAME))) + WARN("Failed to set stream descriptor name, hr %#lx\n", hr); + } + + if (!wcscmp(source->mime_type, L"video/mp4")) + { + if (last_audio != -1) + source->streams[last_audio]->active = TRUE; + if (last_video != -1) + source->streams[last_video]->active = TRUE; + } + else + { + if (first_audio != -1) + source->streams[first_audio]->active = TRUE; + if (first_video != -1) + source->streams[first_video]->active = TRUE; + } +} + +static HRESULT media_source_create(struct object_context *context, IMFMediaSource **out) +{ + UINT32 stream_count = context->stream_count; + struct media_source *object; + unsigned int i; + HRESULT hr; + + if (!(object = calloc(1, sizeof(*object)))) + return E_OUTOFMEMORY; + + object->IMFMediaSource_iface.lpVtbl = &IMFMediaSource_vtbl; + object->IMFGetService_iface.lpVtbl = &media_source_get_service_vtbl; + object->IMFRateSupport_iface.lpVtbl = &media_source_rate_support_vtbl; + object->IMFRateControl_iface.lpVtbl = &media_source_rate_control_vtbl; + object->async_commands_callback.lpVtbl = &source_async_commands_callback_vtbl; + object->ref = 1; + object->byte_stream = context->stream; + IMFByteStream_AddRef(context->stream); + wcscpy(object->mime_type, context->mime_type); + object->file_size = context->file_size; + object->rate = 1.0f; + InitializeCriticalSection(&object->cs); + object->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": cs"); + + object->read_buffer = context->buffer; + context->buffer = NULL; + object->wg_source = context->wg_source; + context->wg_source = 0; + + if (FAILED(hr = MFCreateEventQueue(&object->event_queue))) + goto fail; + if (FAILED(hr = MFAllocateWorkQueue(&object->async_commands_queue))) + goto fail; + if (FAILED(hr = wg_source_get_duration(object->wg_source, &object->duration))) + goto fail; + object->state = SOURCE_OPENING; + + if (!(object->descriptors = calloc(stream_count, sizeof(*object->descriptors))) + || !(object->stream_map = calloc(stream_count, sizeof(*object->stream_map))) + || !(object->streams = calloc(stream_count, sizeof(*object->streams)))) + { + hr = E_OUTOFMEMORY; + goto fail; + } + + media_source_init_stream_map(object, stream_count); + + for (i = 0; i < stream_count; ++i) + { + IMFStreamDescriptor *descriptor; + struct media_stream *stream; + struct wg_format format; + + if (FAILED(hr = wg_source_get_stream_format(object->wg_source, object->stream_map[i], &format))) + goto fail; + if (FAILED(hr = stream_descriptor_create(i + 1, &format, &descriptor))) + goto fail; + if (FAILED(hr = media_stream_create(&object->IMFMediaSource_iface, descriptor, &stream))) + { + IMFStreamDescriptor_Release(descriptor); + goto fail; + } + + IMFStreamDescriptor_AddRef(descriptor); + object->descriptors[i] = descriptor; + object->streams[i] = stream; + object->stream_count++; + } + + media_source_init_descriptors(object); + object->state = SOURCE_STOPPED; + + *out = &object->IMFMediaSource_iface; + TRACE("Created IMFMediaSource %p\n", *out); + return S_OK; + +fail: + WARN("Failed to construct MFMediaSource, hr %#lx.\n", hr); + + while (object->streams && object->stream_count--) + { + struct media_stream *stream = object->streams[object->stream_count]; + IMFStreamDescriptor_Release(object->descriptors[object->stream_count]); + IMFMediaStream_Release(&stream->IMFMediaStream_iface); + } + free(object->stream_map); + free(object->descriptors); + free(object->streams); + + if (object->wg_source) + wg_source_destroy(object->wg_source); + if (object->async_commands_queue) + MFUnlockWorkQueue(object->async_commands_queue); + if (object->event_queue) + IMFMediaEventQueue_Release(object->event_queue); + IMFByteStream_Release(object->byte_stream); + free(object->read_buffer); + free(object); + + return hr; +} + +struct result_entry +{ + struct list entry; + IMFAsyncResult *result; + MF_OBJECT_TYPE type; + IUnknown *object; +}; + +static HRESULT result_entry_create(IMFAsyncResult *result, MF_OBJECT_TYPE type, + IUnknown *object, struct result_entry **out) +{ + struct result_entry *entry; + + if (!(entry = malloc(sizeof(*entry)))) + return E_OUTOFMEMORY; + + entry->result = result; + IMFAsyncResult_AddRef(entry->result); + entry->object = object; + IUnknown_AddRef(entry->object); + entry->type = type; + + *out = entry; + return S_OK; +} + +static void result_entry_destroy(struct result_entry *entry) +{ + IMFAsyncResult_Release(entry->result); + IUnknown_Release(entry->object); + free(entry); +} + +struct stream_handler +{ + IMFByteStreamHandler IMFByteStreamHandler_iface; + IMFAsyncCallback IMFAsyncCallback_iface; + LONG refcount; + struct list results; + CRITICAL_SECTION cs; +}; + +static struct result_entry *handler_find_result_entry(struct stream_handler *handler, IMFAsyncResult *result) +{ + struct result_entry *entry; + + EnterCriticalSection(&handler->cs); + LIST_FOR_EACH_ENTRY(entry, &handler->results, struct result_entry, entry) + { + if (result == entry->result) + { + list_remove(&entry->entry); + LeaveCriticalSection(&handler->cs); + return entry; + } + } + LeaveCriticalSection(&handler->cs); + + return NULL; +} + +static struct stream_handler *impl_from_IMFByteStreamHandler(IMFByteStreamHandler *iface) +{ + return CONTAINING_RECORD(iface, struct stream_handler, IMFByteStreamHandler_iface); +} + +static struct stream_handler *impl_from_IMFAsyncCallback(IMFAsyncCallback *iface) +{ + return CONTAINING_RECORD(iface, struct stream_handler, IMFAsyncCallback_iface); +} + +static HRESULT WINAPI stream_handler_QueryInterface(IMFByteStreamHandler *iface, REFIID riid, void **obj) +{ + TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); + + if (IsEqualIID(riid, &IID_IMFByteStreamHandler) || + IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IMFByteStreamHandler_AddRef(iface); + return S_OK; + } + + WARN("Unsupported %s.\n", debugstr_guid(riid)); + *obj = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI stream_handler_AddRef(IMFByteStreamHandler *iface) +{ + struct stream_handler *handler = impl_from_IMFByteStreamHandler(iface); + ULONG refcount = InterlockedIncrement(&handler->refcount); + + TRACE("%p, refcount %lu.\n", handler, refcount); + + return refcount; +} + +static ULONG WINAPI stream_handler_Release(IMFByteStreamHandler *iface) +{ + struct stream_handler *handler = impl_from_IMFByteStreamHandler(iface); + ULONG refcount = InterlockedDecrement(&handler->refcount); + struct result_entry *result, *next; + + TRACE("%p, refcount %lu.\n", iface, refcount); + + if (!refcount) + { + LIST_FOR_EACH_ENTRY_SAFE(result, next, &handler->results, struct result_entry, entry) + result_entry_destroy(result); + DeleteCriticalSection(&handler->cs); + free(handler); + } + + return refcount; +} + +static HRESULT WINAPI stream_handler_BeginCreateObject(IMFByteStreamHandler *iface, IMFByteStream *stream, const WCHAR *url, DWORD flags, + IPropertyStore *props, IUnknown **cancel_cookie, IMFAsyncCallback *callback, IUnknown *state) +{ + struct stream_handler *handler = impl_from_IMFByteStreamHandler(iface); + IMFAsyncResult *result; + IUnknown *context; + QWORD file_size, position; + BYTE *buffer; + HRESULT hr; + DWORD caps; + + TRACE("%p, %s, %#lx, %p, %p, %p, %p.\n", iface, debugstr_w(url), flags, props, cancel_cookie, callback, state); + + if (cancel_cookie) + *cancel_cookie = NULL; + + if (!stream) + return E_INVALIDARG; + if (flags != MF_RESOLUTION_MEDIASOURCE) + FIXME("Unimplemented flags %#lx\n", flags); + + if (FAILED(hr = IMFByteStream_GetCapabilities(stream, &caps))) + return hr; + if (!(caps & MFBYTESTREAM_IS_SEEKABLE)) + { + FIXME("Non-seekable bytestreams not supported.\n"); + return MF_E_BYTESTREAM_NOT_SEEKABLE; + } + if (FAILED(hr = IMFByteStream_GetLength(stream, &file_size))) + { + FIXME("Failed to get byte stream length, hr %#lx.\n", hr); + return hr; + } + + if (FAILED(hr = MFCreateAsyncResult(NULL, callback, state, &result))) + return hr; + if (FAILED(hr = object_context_create(flags, stream, url, file_size, result, &context, &buffer))) + goto done; + + if (FAILED(hr = IMFByteStream_GetCurrentPosition(stream, &position))) + WARN("Failed to get byte stream position, hr %#lx\n", hr); + else if (position != 0 && FAILED(hr = IMFByteStream_SetCurrentPosition(stream, 0))) + WARN("Failed to set byte stream position, hr %#lx\n", hr); + else if (FAILED(hr = IMFByteStream_BeginRead(stream, buffer, min(SOURCE_BUFFER_SIZE, file_size), + &handler->IMFAsyncCallback_iface, context))) + WARN("Failed to queue byte stream async read, hr %#lx\n", hr); + IUnknown_Release(context); + + if (SUCCEEDED(hr) && cancel_cookie) + { + *cancel_cookie = (IUnknown *)result; + IUnknown_AddRef(*cancel_cookie); + } + +done: + IMFAsyncResult_Release(result); + + return hr; +} + +static HRESULT WINAPI stream_handler_EndCreateObject(IMFByteStreamHandler *iface, IMFAsyncResult *result, + MF_OBJECT_TYPE *type, IUnknown **object) +{ + struct stream_handler *handler = impl_from_IMFByteStreamHandler(iface); + struct result_entry *entry; + HRESULT hr; + + TRACE("%p, %p, %p, %p.\n", iface, result, type, object); + + if (!(entry = handler_find_result_entry(handler, result))) + { + *type = MF_OBJECT_INVALID; + *object = NULL; + return MF_E_UNEXPECTED; + } + + hr = IMFAsyncResult_GetStatus(entry->result); + *type = entry->type; + *object = entry->object; + IUnknown_AddRef(*object); + result_entry_destroy(entry); + return hr; +} + +static HRESULT WINAPI stream_handler_CancelObjectCreation(IMFByteStreamHandler *iface, IUnknown *cookie) +{ + struct stream_handler *handler = impl_from_IMFByteStreamHandler(iface); + IMFAsyncResult *result = (IMFAsyncResult *)cookie; + struct result_entry *entry; + + TRACE("%p, %p.\n", iface, cookie); + + if (!(entry = handler_find_result_entry(handler, result))) + return MF_E_UNEXPECTED; + + result_entry_destroy(entry); + return S_OK; +} + +static HRESULT WINAPI stream_handler_GetMaxNumberOfBytesRequiredForResolution(IMFByteStreamHandler *iface, QWORD *bytes) +{ + FIXME("stub (%p %p)\n", iface, bytes); + return E_NOTIMPL; +} + +static const IMFByteStreamHandlerVtbl stream_handler_vtbl = +{ + stream_handler_QueryInterface, + stream_handler_AddRef, + stream_handler_Release, + stream_handler_BeginCreateObject, + stream_handler_EndCreateObject, + stream_handler_CancelObjectCreation, + stream_handler_GetMaxNumberOfBytesRequiredForResolution, +}; + +static HRESULT WINAPI stream_handler_callback_QueryInterface(IMFAsyncCallback *iface, REFIID riid, void **obj) +{ + if (IsEqualIID(riid, &IID_IMFAsyncCallback) || + IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IMFAsyncCallback_AddRef(iface); + return S_OK; + } + + WARN("Unsupported %s.\n", debugstr_guid(riid)); + *obj = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI stream_handler_callback_AddRef(IMFAsyncCallback *iface) +{ + struct stream_handler *handler = impl_from_IMFAsyncCallback(iface); + return IMFByteStreamHandler_AddRef(&handler->IMFByteStreamHandler_iface); +} + +static ULONG WINAPI stream_handler_callback_Release(IMFAsyncCallback *iface) +{ + struct stream_handler *handler = impl_from_IMFAsyncCallback(iface); + return IMFByteStreamHandler_Release(&handler->IMFByteStreamHandler_iface); +} + +static HRESULT WINAPI stream_handler_callback_GetParameters(IMFAsyncCallback *iface, DWORD *flags, DWORD *queue) +{ + return E_NOTIMPL; +} + +static HRESULT WINAPI stream_handler_callback_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) +{ + struct stream_handler *handler = impl_from_IMFAsyncCallback(iface); + IUnknown *object, *state = IMFAsyncResult_GetStateNoAddRef(result); + struct object_context *context; + struct result_entry *entry; + UINT64 read_offset; + DWORD size = 0; + HRESULT hr; + + if (!state || !(context = impl_from_IUnknown(state))) + return E_INVALIDARG; + + if (FAILED(hr = IMFByteStream_EndRead(context->stream, result, &size))) + WARN("Failed to complete stream read, hr %#lx\n", hr); + else if (!context->wg_source && FAILED(hr = wg_source_create(context->url, context->file_size, + context->buffer, size, context->mime_type, &context->wg_source))) + WARN("Failed to create wg_source, hr %#lx\n", hr); + else if (FAILED(hr = wg_source_push_data(context->wg_source, context->buffer, size))) + WARN("Failed to push wg_source data, hr %#lx\n", hr); + else if (FAILED(hr = wg_source_get_stream_count(context->wg_source, &context->stream_count))) + WARN("Failed to get wg_source status, hr %#lx\n", hr); + else if (!context->stream_count) + { + QWORD position, offset; + if (FAILED(hr = wg_source_get_position(context->wg_source, &read_offset))) + WARN("Failed to get wg_source position, hr %#lx\n", hr); + else if (FAILED(hr = IMFByteStream_GetCurrentPosition(context->stream, &position))) + WARN("Failed to get current byte stream position, hr %#lx\n", hr); + else if (position != (offset = min(read_offset, context->file_size)) + && FAILED(hr = IMFByteStream_SetCurrentPosition(context->stream, offset))) + WARN("Failed to set current byte stream position, hr %#lx\n", hr); + else + { + UINT32 read_size = min(SOURCE_BUFFER_SIZE, context->file_size - offset); + return IMFByteStream_BeginRead(context->stream, context->buffer, read_size, + &handler->IMFAsyncCallback_iface, state); + } + } + else if (FAILED(hr = media_source_create(context, (IMFMediaSource **)&object))) + WARN("Failed to create media source, hr %#lx\n", hr); + + if (FAILED(hr)) + { + FIXME("Falling back to old media source, hr %#lx\n", hr); + hr = create_media_source_fallback(context, (IUnknown **)&object); + } + + if (SUCCEEDED(hr)) + { + if (FAILED(hr = result_entry_create(context->result, MF_OBJECT_MEDIASOURCE, object, &entry))) + WARN("Failed to create handler result, hr %#lx\n", hr); + else + { + EnterCriticalSection(&handler->cs); + list_add_tail(&handler->results, &entry->entry); + LeaveCriticalSection(&handler->cs); + } + + IUnknown_Release(object); + } + + IMFAsyncResult_SetStatus(context->result, hr); + MFInvokeCallback(context->result); + + return S_OK; +} + +static const IMFAsyncCallbackVtbl stream_handler_callback_vtbl = +{ + stream_handler_callback_QueryInterface, + stream_handler_callback_AddRef, + stream_handler_callback_Release, + stream_handler_callback_GetParameters, + stream_handler_callback_Invoke, +}; + +HRESULT gstreamer_byte_stream_handler_2_create(REFIID riid, void **obj) +{ + struct stream_handler *handler; + HRESULT hr; + + TRACE("%s, %p.\n", debugstr_guid(riid), obj); + + if (!(handler = calloc(1, sizeof(*handler)))) + return E_OUTOFMEMORY; + + list_init(&handler->results); + InitializeCriticalSection(&handler->cs); + + handler->IMFByteStreamHandler_iface.lpVtbl = &stream_handler_vtbl; + handler->IMFAsyncCallback_iface.lpVtbl = &stream_handler_callback_vtbl; + handler->refcount = 1; + + hr = IMFByteStreamHandler_QueryInterface(&handler->IMFByteStreamHandler_iface, riid, obj); + IMFByteStreamHandler_Release(&handler->IMFByteStreamHandler_iface); + + return hr; +} diff --git a/dlls/winegstreamer/quartz_parser.c b/dlls/winegstreamer/quartz_parser.c index dcd29d1c9fd..569a7010ebb 100644 --- a/dlls/winegstreamer/quartz_parser.c +++ b/dlls/winegstreamer/quartz_parser.c @@ -467,8 +467,10 @@ unsigned int wg_format_get_max_size(const struct wg_format *format) return format->u.audio_wma.rate * format->u.audio_wma.channels * format->u.audio_wma.depth / 8; case WG_MAJOR_TYPE_AUDIO_MPEG4: + case WG_MAJOR_TYPE_AUDIO_ENCODED: case WG_MAJOR_TYPE_VIDEO_H264: case WG_MAJOR_TYPE_VIDEO_INDEO: + case WG_MAJOR_TYPE_VIDEO_ENCODED: FIXME("Format %u not implemented!\n", format->major_type); return 0; @@ -729,8 +731,10 @@ bool amt_from_wg_format(AM_MEDIA_TYPE *mt, const struct wg_format *format, bool switch (format->major_type) { case WG_MAJOR_TYPE_AUDIO_MPEG4: + case WG_MAJOR_TYPE_AUDIO_ENCODED: case WG_MAJOR_TYPE_VIDEO_H264: case WG_MAJOR_TYPE_VIDEO_INDEO: + case WG_MAJOR_TYPE_VIDEO_ENCODED: FIXME("Format %u not implemented!\n", format->major_type); /* fallthrough */ case WG_MAJOR_TYPE_UNKNOWN: diff --git a/dlls/winegstreamer/unix_private.h b/dlls/winegstreamer/unix_private.h index 55c850bc1a3..a165b8f1cfe 100644 --- a/dlls/winegstreamer/unix_private.h +++ b/dlls/winegstreamer/unix_private.h @@ -57,6 +57,20 @@ extern void wg_format_from_caps(struct wg_format *format, const GstCaps *caps); extern bool wg_format_compare(const struct wg_format *a, const struct wg_format *b); extern GstCaps *wg_format_to_caps(const struct wg_format *format); +/* wg_source.c */ + +extern NTSTATUS wg_source_create(void *args); +extern NTSTATUS wg_source_destroy(void *args); +extern NTSTATUS wg_source_get_stream_count(void *args); +extern NTSTATUS wg_source_get_duration(void *args); +extern NTSTATUS wg_source_get_position(void *args); +extern NTSTATUS wg_source_set_position(void *args); +extern NTSTATUS wg_source_push_data(void *args); +extern NTSTATUS wg_source_read_data(void *args); +extern NTSTATUS wg_source_get_stream_format(void *args); +extern NTSTATUS wg_source_get_stream_tag(void *args); +extern NTSTATUS wg_source_set_stream_flags(void *args); + /* wg_transform.c */ extern NTSTATUS wg_transform_create(void *args); @@ -79,6 +93,10 @@ extern NTSTATUS wg_muxer_push_sample(void *args); extern NTSTATUS wg_muxer_read_data(void *args); extern NTSTATUS wg_muxer_finalize(void *args); +/* wg_task_pool.c */ + +extern GstTaskPool *wg_task_pool_new(void); + /* wg_allocator.c */ static inline BYTE *wg_sample_data(struct wg_sample *sample) diff --git a/dlls/winegstreamer/unixlib.c b/dlls/winegstreamer/unixlib.c index 53c1328c0ab..881566339eb 100644 --- a/dlls/winegstreamer/unixlib.c +++ b/dlls/winegstreamer/unixlib.c @@ -46,6 +46,8 @@ GST_DEBUG_CATEGORY(wine); +extern bool media_converter_init(void); + GstGLDisplay *gl_display; GstStreamType stream_type_from_caps(GstCaps *caps) @@ -247,6 +249,7 @@ bool push_event(GstPad *pad, GstEvent *event) NTSTATUS wg_init_gstreamer(void *arg) { + struct wg_init_gstreamer_params *params = arg; static GstGLContext *gl_context; char arg0[] = "wine"; @@ -273,6 +276,19 @@ NTSTATUS wg_init_gstreamer(void *arg) setenv("GST_REGISTRY_1_0", gst_reg, 1); } + if (params->trace_on) + setenv("GST_DEBUG", "WINE:9,protonmediaconverter:9,4", FALSE); + if (params->warn_on) + setenv("GST_DEBUG", "WINE:3,protonmediaconverter:3,3", FALSE); + if (params->err_on) + setenv("GST_DEBUG", "WINE:1,protonmediaconverter:1,1", FALSE); + setenv("GST_DEBUG_NO_COLOR", "1", FALSE); + + /* GStreamer installs a temporary SEGV handler when it loads plugins + * to initialize its registry calling exit(-1) when any fault is caught. + * We need to make sure any signal reaches our signal handlers to catch + * and handle them, or eventually propagate the exceptions to the user. + */ gst_segtrap_set_enabled(false); if (!gst_init_check(&argc, &argv, &err)) @@ -309,5 +325,12 @@ NTSTATUS wg_init_gstreamer(void *arg) } } + if (!media_converter_init()) + { + GST_ERROR("Failed to init media converter."); + gst_object_unref(gl_display); + return STATUS_UNSUCCESSFUL; + } + return STATUS_SUCCESS; } diff --git a/dlls/winegstreamer/unixlib.h b/dlls/winegstreamer/unixlib.h index 2045f3b65ff..fe21a24f6f5 100644 --- a/dlls/winegstreamer/unixlib.h +++ b/dlls/winegstreamer/unixlib.h @@ -37,12 +37,14 @@ enum wg_major_type WG_MAJOR_TYPE_AUDIO_MPEG1, WG_MAJOR_TYPE_AUDIO_MPEG4, WG_MAJOR_TYPE_AUDIO_WMA, + WG_MAJOR_TYPE_AUDIO_ENCODED, WG_MAJOR_TYPE_VIDEO, WG_MAJOR_TYPE_VIDEO_CINEPAK, WG_MAJOR_TYPE_VIDEO_H264, WG_MAJOR_TYPE_VIDEO_WMV, WG_MAJOR_TYPE_VIDEO_INDEO, WG_MAJOR_TYPE_VIDEO_MPEG1, + WG_MAJOR_TYPE_VIDEO_ENCODED, }; typedef UINT32 wg_audio_format; @@ -128,6 +130,12 @@ struct wg_format unsigned char codec_data[64]; UINT8 is_xma; } audio_wma; + struct + { + uint32_t channels; + uint32_t rate; + char caps[512]; + } audio_encoded; struct { @@ -173,6 +181,12 @@ struct wg_format int32_t width, height; uint32_t fps_n, fps_d; } video_mpeg1; + struct + { + int32_t width, height; + uint32_t fps_n, fps_d; + char caps[512]; + } video_encoded; } u; }; @@ -218,9 +232,17 @@ enum wg_parser_type typedef UINT64 wg_parser_t; typedef UINT64 wg_parser_stream_t; +typedef UINT64 wg_source_t; typedef UINT64 wg_transform_t; typedef UINT64 wg_muxer_t; +struct wg_init_gstreamer_params +{ + UINT8 trace_on; + UINT8 warn_on; + UINT8 err_on; +}; + struct wg_parser_create_params { wg_parser_t parser; @@ -340,10 +362,82 @@ struct wg_parser_stream_seek_params DWORD start_flags, stop_flags; }; +struct wg_source_create_params +{ + const char *url; + UINT64 file_size; + const void *data; + UINT32 size; + char mime_type[256]; + wg_source_t source; +}; + +struct wg_source_get_stream_count_params +{ + wg_source_t source; + UINT32 stream_count; +}; + +struct wg_source_get_duration_params +{ + wg_source_t source; + UINT64 duration; +}; + +struct wg_source_get_position_params +{ + wg_source_t source; + UINT64 read_offset; +}; + +struct wg_source_set_position_params +{ + wg_source_t source; + UINT64 time; +}; + +struct wg_source_push_data_params +{ + wg_source_t source; + const void *data; + UINT32 size; +}; + +struct wg_source_read_data_params +{ + wg_source_t source; + UINT32 index; + struct wg_sample *sample; +}; + +struct wg_source_get_stream_format_params +{ + wg_source_t source; + UINT32 index; + struct wg_format format; +}; + +struct wg_source_get_stream_tag_params +{ + wg_source_t source; + UINT32 index; + wg_parser_tag tag; + UINT32 size; + char *buffer; +}; + +struct wg_source_set_stream_flags_params +{ + wg_source_t source; + UINT32 index; + UINT32 select; +}; + struct wg_transform_attrs { UINT32 output_plane_align; UINT32 input_queue_length; + BOOL allow_size_change; BOOL low_latency; }; @@ -449,6 +543,18 @@ enum unix_funcs unix_wg_parser_stream_get_tag, unix_wg_parser_stream_seek, + unix_wg_source_create, + unix_wg_source_destroy, + unix_wg_source_get_stream_count, + unix_wg_source_get_duration, + unix_wg_source_get_position, + unix_wg_source_set_position, + unix_wg_source_push_data, + unix_wg_source_read_data, + unix_wg_source_get_stream_format, + unix_wg_source_get_stream_tag, + unix_wg_source_set_stream_flags, + unix_wg_transform_create, unix_wg_transform_destroy, unix_wg_transform_set_output_format, diff --git a/dlls/winegstreamer/video_decoder.c b/dlls/winegstreamer/video_decoder.c index 2faab78faf2..44e5c4821f2 100644 --- a/dlls/winegstreamer/video_decoder.c +++ b/dlls/winegstreamer/video_decoder.c @@ -27,25 +27,25 @@ #include "wine/debug.h" -WINE_DEFAULT_DEBUG_CHANNEL(mfplat); +#include "initguid.h" + +#include "codecapi.h" -DEFINE_MEDIATYPE_GUID(MFVideoFormat_IV50, MAKEFOURCC('I','V','5','0')); +WINE_DEFAULT_DEBUG_CHANNEL(mfplat); +WINE_DECLARE_DEBUG_CHANNEL(winediag); -static const GUID *const input_types[] = +extern GUID MFVideoFormat_GStreamer; +static const GUID *const video_decoder_input_types[] = { - &MFVideoFormat_IV50, + &MFVideoFormat_GStreamer, }; -static const GUID *const output_types[] = +static const GUID *const video_decoder_output_types[] = { + &MFVideoFormat_NV12, &MFVideoFormat_YV12, + &MFVideoFormat_IYUV, + &MFVideoFormat_I420, &MFVideoFormat_YUY2, - &MFVideoFormat_NV11, - &MFVideoFormat_NV12, - &MFVideoFormat_RGB32, - &MFVideoFormat_RGB24, - &MFVideoFormat_RGB565, - &MFVideoFormat_RGB555, - &MFVideoFormat_RGB8, }; struct video_decoder @@ -53,12 +53,28 @@ struct video_decoder IMFTransform IMFTransform_iface; LONG refcount; + IMFAttributes *attributes; + IMFAttributes *output_attributes; + + UINT input_type_count; + const GUID *const *input_types; + UINT output_type_count; + const GUID *const *output_types; + + UINT64 sample_time; IMFMediaType *input_type; + MFT_INPUT_STREAM_INFO input_info; IMFMediaType *output_type; + MFT_OUTPUT_STREAM_INFO output_info; + IMFMediaType *stream_type; - struct wg_format wg_format; wg_transform_t wg_transform; struct wg_sample_queue *wg_sample_queue; + + IMFVideoSampleAllocatorEx *allocator; + BOOL allocator_initialized; + IMFTransform *copier; + IMFMediaBuffer *temp_buffer; }; static struct video_decoder *impl_from_IMFTransform(IMFTransform *iface) @@ -68,9 +84,20 @@ static struct video_decoder *impl_from_IMFTransform(IMFTransform *iface) static HRESULT try_create_wg_transform(struct video_decoder *decoder) { - struct wg_transform_attrs attrs = {0}; + /* Call of Duty: Black Ops 3 doesn't care about the ProcessInput/ProcessOutput + * return values, it calls them in a specific order and expects the decoder + * transform to be able to queue its input buffers. We need to use a buffer list + * to match its expectations. + */ + struct wg_transform_attrs attrs = + { + .output_plane_align = 15, + .input_queue_length = 15, + .allow_size_change = TRUE, + }; struct wg_format input_format; struct wg_format output_format; + UINT32 low_latency; if (decoder->wg_transform) wg_transform_destroy(decoder->wg_transform); @@ -84,8 +111,14 @@ static HRESULT try_create_wg_transform(struct video_decoder *decoder) if (output_format.major_type == WG_MAJOR_TYPE_UNKNOWN) return MF_E_INVALIDMEDIATYPE; - output_format.u.video.fps_d = 0; - output_format.u.video.fps_n = 0; + if (SUCCEEDED(IMFAttributes_GetUINT32(decoder->attributes, &MF_LOW_LATENCY, &low_latency))) + attrs.low_latency = !!low_latency; + + { + const char *sgi; + if ((sgi = getenv("SteamGameId")) && (!strcmp(sgi, "2009100") || !strcmp(sgi, "2555360") || !strcmp(sgi, "1630110"))) + attrs.low_latency = FALSE; + } if (!(decoder->wg_transform = wg_transform_create(&input_format, &output_format, &attrs))) { @@ -96,6 +129,105 @@ static HRESULT try_create_wg_transform(struct video_decoder *decoder) return S_OK; } +static HRESULT create_output_media_type(struct video_decoder *decoder, const GUID *subtype, + IMFMediaType **media_type) +{ + IMFMediaType *default_type = decoder->output_type; + IMFVideoMediaType *video_type; + UINT32 value, width, height; + MFVideoArea aperture; + UINT64 ratio; + HRESULT hr; + + if (FAILED(hr = MFCreateVideoMediaTypeFromSubtype(subtype, &video_type))) + return hr; + + if (FAILED(IMFMediaType_GetUINT64(decoder->stream_type, &MF_MT_FRAME_SIZE, &ratio))) + ratio = (UINT64)1920 << 32 | 1080; + if (FAILED(hr = IMFVideoMediaType_SetUINT64(video_type, &MF_MT_FRAME_SIZE, ratio))) + goto done; + width = ratio >> 32; + height = ratio; + + if (FAILED(IMFMediaType_GetUINT64(decoder->stream_type, &MF_MT_FRAME_RATE, &ratio))) + ratio = (UINT64)30000 << 32 | 1001; + if (FAILED(hr = IMFVideoMediaType_SetUINT64(video_type, &MF_MT_FRAME_RATE, ratio))) + goto done; + + if (FAILED(IMFMediaType_GetUINT64(decoder->stream_type, &MF_MT_PIXEL_ASPECT_RATIO, &ratio))) + ratio = (UINT64)1 << 32 | 1; + if (FAILED(hr = IMFVideoMediaType_SetUINT64(video_type, &MF_MT_PIXEL_ASPECT_RATIO, ratio))) + goto done; + + if (FAILED(hr = MFCalculateImageSize(subtype, width, height, &value))) + goto done; + if (FAILED(hr = IMFVideoMediaType_SetUINT32(video_type, &MF_MT_SAMPLE_SIZE, value))) + goto done; + + if (FAILED(hr = MFGetStrideForBitmapInfoHeader(subtype->Data1, width, (LONG *)&value))) + goto done; + if (FAILED(hr = IMFVideoMediaType_SetUINT32(video_type, &MF_MT_DEFAULT_STRIDE, value))) + goto done; + + if (!default_type || FAILED(IMFMediaType_GetUINT32(default_type, &MF_MT_INTERLACE_MODE, &value))) + value = MFVideoInterlace_MixedInterlaceOrProgressive; + if (FAILED(hr = IMFVideoMediaType_SetUINT32(video_type, &MF_MT_INTERLACE_MODE, value))) + goto done; + + if (!default_type || FAILED(IMFMediaType_GetUINT32(default_type, &MF_MT_ALL_SAMPLES_INDEPENDENT, &value))) + value = 1; + if (FAILED(hr = IMFVideoMediaType_SetUINT32(video_type, &MF_MT_ALL_SAMPLES_INDEPENDENT, value))) + goto done; + + if (!default_type || FAILED(IMFMediaType_GetUINT32(default_type, &MF_MT_VIDEO_ROTATION, &value))) + value = 0; + if (FAILED(hr = IMFVideoMediaType_SetUINT32(video_type, &MF_MT_VIDEO_ROTATION, value))) + goto done; + + if (!default_type || FAILED(IMFMediaType_GetUINT32(default_type, &MF_MT_FIXED_SIZE_SAMPLES, &value))) + value = 1; + if (FAILED(hr = IMFVideoMediaType_SetUINT32(video_type, &MF_MT_FIXED_SIZE_SAMPLES, value))) + goto done; + + if (SUCCEEDED(IMFMediaType_GetBlob(decoder->stream_type, &MF_MT_MINIMUM_DISPLAY_APERTURE, + (BYTE *)&aperture, sizeof(aperture), &value))) + { + if (FAILED(hr = IMFVideoMediaType_SetBlob(video_type, &MF_MT_MINIMUM_DISPLAY_APERTURE, + (BYTE *)&aperture, sizeof(aperture)))) + goto done; + } + + IMFMediaType_AddRef((*media_type = (IMFMediaType *)video_type)); +done: + IMFVideoMediaType_Release(video_type); + return hr; +} + +static HRESULT init_allocator(struct video_decoder *decoder) +{ + HRESULT hr; + + if (decoder->allocator_initialized) + return S_OK; + + if (FAILED(hr = IMFTransform_SetInputType(decoder->copier, 0, decoder->output_type, 0))) + return hr; + if (FAILED(hr = IMFTransform_SetOutputType(decoder->copier, 0, decoder->output_type, 0))) + return hr; + + if (FAILED(hr = IMFVideoSampleAllocatorEx_InitializeSampleAllocatorEx(decoder->allocator, 10, 10, + decoder->attributes, decoder->output_type))) + return hr; + decoder->allocator_initialized = TRUE; + return S_OK; +} + +static void uninit_allocator(struct video_decoder *decoder) +{ + IMFVideoSampleAllocatorEx_UninitializeSampleAllocator(decoder->allocator); + decoder->allocator_initialized = FALSE; +} + static HRESULT WINAPI transform_QueryInterface(IMFTransform *iface, REFIID iid, void **out) { struct video_decoder *decoder = impl_from_IMFTransform(iface); @@ -135,13 +267,20 @@ static ULONG WINAPI transform_Release(IMFTransform *iface) if (!refcount) { + IMFTransform_Release(decoder->copier); + IMFVideoSampleAllocatorEx_Release(decoder->allocator); + if (decoder->temp_buffer) + IMFMediaBuffer_Release(decoder->temp_buffer); if (decoder->wg_transform) wg_transform_destroy(decoder->wg_transform); if (decoder->input_type) IMFMediaType_Release(decoder->input_type); if (decoder->output_type) IMFMediaType_Release(decoder->output_type); - + if (decoder->output_attributes) + IMFAttributes_Release(decoder->output_attributes); + if (decoder->attributes) + IMFAttributes_Release(decoder->attributes); wg_sample_queue_destroy(decoder->wg_sample_queue); free(decoder); } @@ -152,79 +291,119 @@ static ULONG WINAPI transform_Release(IMFTransform *iface) static HRESULT WINAPI transform_GetStreamLimits(IMFTransform *iface, DWORD *input_minimum, DWORD *input_maximum, DWORD *output_minimum, DWORD *output_maximum) { - FIXME("iface %p, input_minimum %p, input_maximum %p, output_minimum %p, output_maximum %p.\n", + TRACE("iface %p, input_minimum %p, input_maximum %p, output_minimum %p, output_maximum %p.\n", iface, input_minimum, input_maximum, output_minimum, output_maximum); - return E_NOTIMPL; + *input_minimum = *input_maximum = *output_minimum = *output_maximum = 1; + return S_OK; } static HRESULT WINAPI transform_GetStreamCount(IMFTransform *iface, DWORD *inputs, DWORD *outputs) { - FIXME("iface %p, inputs %p, outputs %p.\n", iface, inputs, outputs); - return E_NOTIMPL; + TRACE("iface %p, inputs %p, outputs %p.\n", iface, inputs, outputs); + *inputs = *outputs = 1; + return S_OK; } static HRESULT WINAPI transform_GetStreamIDs(IMFTransform *iface, DWORD input_size, DWORD *inputs, DWORD output_size, DWORD *outputs) { - FIXME("iface %p, input_size %lu, inputs %p, output_size %lu, outputs %p.\n", - iface, input_size, inputs, output_size, outputs); + TRACE("iface %p, input_size %lu, inputs %p, output_size %lu, outputs %p.\n", iface, + input_size, inputs, output_size, outputs); return E_NOTIMPL; } static HRESULT WINAPI transform_GetInputStreamInfo(IMFTransform *iface, DWORD id, MFT_INPUT_STREAM_INFO *info) { - FIXME("iface %p, id %#lx, info %p.\n", iface, id, info); - return E_NOTIMPL; + struct video_decoder *decoder = impl_from_IMFTransform(iface); + + TRACE("iface %p, id %#lx, info %p.\n", iface, id, info); + + *info = decoder->input_info; + return S_OK; } static HRESULT WINAPI transform_GetOutputStreamInfo(IMFTransform *iface, DWORD id, MFT_OUTPUT_STREAM_INFO *info) { - FIXME("iface %p, id %#lx, info %p.\n", iface, id, info); - return E_NOTIMPL; + struct video_decoder *decoder = impl_from_IMFTransform(iface); + + TRACE("iface %p, id %#lx, info %p.\n", iface, id, info); + + *info = decoder->output_info; + return S_OK; } static HRESULT WINAPI transform_GetAttributes(IMFTransform *iface, IMFAttributes **attributes) { + struct video_decoder *decoder = impl_from_IMFTransform(iface); + FIXME("iface %p, attributes %p semi-stub!\n", iface, attributes); - return E_NOTIMPL; + + if (!attributes) + return E_POINTER; + + IMFAttributes_AddRef((*attributes = decoder->attributes)); + return S_OK; } static HRESULT WINAPI transform_GetInputStreamAttributes(IMFTransform *iface, DWORD id, IMFAttributes **attributes) { - FIXME("iface %p, id %#lx, attributes %p.\n", iface, id, attributes); + TRACE("iface %p, id %#lx, attributes %p.\n", iface, id, attributes); return E_NOTIMPL; } static HRESULT WINAPI transform_GetOutputStreamAttributes(IMFTransform *iface, DWORD id, IMFAttributes **attributes) { - FIXME("iface %p, id %#lx, attributes %p stub!\n", iface, id, attributes); - return E_NOTIMPL; + struct video_decoder *decoder = impl_from_IMFTransform(iface); + + FIXME("iface %p, id %#lx, attributes %p semi-stub!\n", iface, id, attributes); + + if (!attributes) + return E_POINTER; + if (id) + return MF_E_INVALIDSTREAMNUMBER; + + IMFAttributes_AddRef((*attributes = decoder->output_attributes)); + return S_OK; } static HRESULT WINAPI transform_DeleteInputStream(IMFTransform *iface, DWORD id) { - FIXME("iface %p, id %#lx.\n", iface, id); + TRACE("iface %p, id %#lx.\n", iface, id); return E_NOTIMPL; } static HRESULT WINAPI transform_AddInputStreams(IMFTransform *iface, DWORD streams, DWORD *ids) { - FIXME("iface %p, streams %lu, ids %p.\n", iface, streams, ids); + TRACE("iface %p, streams %lu, ids %p.\n", iface, streams, ids); return E_NOTIMPL; } static HRESULT WINAPI transform_GetInputAvailableType(IMFTransform *iface, DWORD id, DWORD index, IMFMediaType **type) { - FIXME("iface %p, id %#lx, index %#lx, type %p.\n", iface, id, index, type); - return E_NOTIMPL; + struct video_decoder *decoder = impl_from_IMFTransform(iface); + + TRACE("iface %p, id %#lx, index %#lx, type %p.\n", iface, id, index, type); + + *type = NULL; + if (index >= decoder->input_type_count) + return MF_E_NO_MORE_TYPES; + return MFCreateVideoMediaTypeFromSubtype(decoder->input_types[index], (IMFVideoMediaType **)type); } static HRESULT WINAPI transform_GetOutputAvailableType(IMFTransform *iface, DWORD id, DWORD index, IMFMediaType **type) { - FIXME("iface %p, id %#lx, index %#lx, type %p.\n", iface, id, index, type); - return E_NOTIMPL; + struct video_decoder *decoder = impl_from_IMFTransform(iface); + + TRACE("iface %p, id %#lx, index %#lx, type %p.\n", iface, id, index, type); + + *type = NULL; + if (!decoder->input_type) + return MF_E_TRANSFORM_TYPE_NOT_SET; + if (index >= decoder->output_type_count) + return MF_E_NO_MORE_TYPES; + return create_output_media_type(decoder, decoder->output_types[index], type); } static HRESULT WINAPI transform_SetInputType(IMFTransform *iface, DWORD id, IMFMediaType *type, DWORD flags) @@ -244,16 +423,11 @@ static HRESULT WINAPI transform_SetInputType(IMFTransform *iface, DWORD id, IMFM if (!IsEqualGUID(&major, &MFMediaType_Video)) return MF_E_INVALIDMEDIATYPE; - for (i = 0; i < ARRAY_SIZE(input_types); ++i) - if (IsEqualGUID(&subtype, input_types[i])) + for (i = 0; i < decoder->input_type_count; ++i) + if (IsEqualGUID(&subtype, decoder->input_types[i])) break; - if (i == ARRAY_SIZE(input_types)) + if (i == decoder->input_type_count) return MF_E_INVALIDMEDIATYPE; - - if (FAILED(hr = IMFMediaType_GetUINT64(type, &MF_MT_FRAME_SIZE, &frame_size)) || - (frame_size >> 32) == 0 || (UINT32)frame_size == 0) - return MF_E_INVALIDMEDIATYPE; - if (flags & MFT_SET_TYPE_TEST_ONLY) return S_OK; @@ -267,15 +441,21 @@ static HRESULT WINAPI transform_SetInputType(IMFTransform *iface, DWORD id, IMFM IMFMediaType_Release(decoder->input_type); IMFMediaType_AddRef((decoder->input_type = type)); + if (SUCCEEDED(IMFMediaType_GetUINT64(type, &MF_MT_FRAME_SIZE, &frame_size))) + { + if (FAILED(hr = IMFMediaType_SetUINT64(decoder->stream_type, &MF_MT_FRAME_SIZE, frame_size))) + WARN("Failed to update stream type frame size, hr %#lx\n", hr); + MFCalculateImageSize(decoder->output_types[0], frame_size >> 32, frame_size, (UINT32 *)&decoder->output_info.cbSize); + } + return S_OK; } static HRESULT WINAPI transform_SetOutputType(IMFTransform *iface, DWORD id, IMFMediaType *type, DWORD flags) { struct video_decoder *decoder = impl_from_IMFTransform(iface); + UINT64 frame_size, stream_frame_size; GUID major, subtype; - UINT64 frame_size; - struct wg_format output_format; HRESULT hr; ULONG i; @@ -291,15 +471,17 @@ static HRESULT WINAPI transform_SetOutputType(IMFTransform *iface, DWORD id, IMF if (!IsEqualGUID(&major, &MFMediaType_Video)) return MF_E_INVALIDMEDIATYPE; - for (i = 0; i < ARRAY_SIZE(output_types); ++i) - if (IsEqualGUID(&subtype, output_types[i])) + for (i = 0; i < decoder->output_type_count; ++i) + if (IsEqualGUID(&subtype, decoder->output_types[i])) break; - if (i == ARRAY_SIZE(output_types)) + if (i == decoder->output_type_count) return MF_E_INVALIDMEDIATYPE; if (FAILED(hr = IMFMediaType_GetUINT64(type, &MF_MT_FRAME_SIZE, &frame_size))) - return hr; - + return MF_E_INVALIDMEDIATYPE; + if (SUCCEEDED(IMFMediaType_GetUINT64(decoder->stream_type, &MF_MT_FRAME_SIZE, &stream_frame_size)) + && frame_size != stream_frame_size) + return MF_E_INVALIDMEDIATYPE; if (flags & MFT_SET_TYPE_TEST_ONLY) return S_OK; @@ -309,13 +491,11 @@ static HRESULT WINAPI transform_SetOutputType(IMFTransform *iface, DWORD id, IMF if (decoder->wg_transform) { + struct wg_format output_format; mf_media_type_to_wg_format(decoder->output_type, &output_format); - output_format.u.video.fps_d = 0; - output_format.u.video.fps_n = 0; - if (output_format.major_type == WG_MAJOR_TYPE_UNKNOWN - || !wg_transform_set_output_format(decoder->wg_transform, &output_format)) + || !wg_transform_set_output_format(decoder->wg_transform, &output_format)) { IMFMediaType_Release(decoder->output_type); decoder->output_type = NULL; @@ -326,31 +506,54 @@ static HRESULT WINAPI transform_SetOutputType(IMFTransform *iface, DWORD id, IMF { IMFMediaType_Release(decoder->output_type); decoder->output_type = NULL; - return hr; } - decoder->wg_format.u.video.width = frame_size >> 32; - decoder->wg_format.u.video.height = (UINT32)frame_size; - return hr; } static HRESULT WINAPI transform_GetInputCurrentType(IMFTransform *iface, DWORD id, IMFMediaType **type) { - FIXME("iface %p, id %#lx, type %p stub!\n", iface, id, type); - return E_NOTIMPL; + struct video_decoder *decoder = impl_from_IMFTransform(iface); + HRESULT hr; + + TRACE("iface %p, id %#lx, type %p\n", iface, id, type); + + if (!decoder->input_type) + return MF_E_TRANSFORM_TYPE_NOT_SET; + + if (FAILED(hr = MFCreateMediaType(type))) + return hr; + + return IMFMediaType_CopyAllItems(decoder->input_type, (IMFAttributes *)*type); } static HRESULT WINAPI transform_GetOutputCurrentType(IMFTransform *iface, DWORD id, IMFMediaType **type) { - FIXME("iface %p, id %#lx, type %p stub!\n", iface, id, type); - return E_NOTIMPL; + struct video_decoder *decoder = impl_from_IMFTransform(iface); + HRESULT hr; + + TRACE("iface %p, id %#lx, type %p\n", iface, id, type); + + if (!decoder->output_type) + return MF_E_TRANSFORM_TYPE_NOT_SET; + + if (FAILED(hr = MFCreateMediaType(type))) + return hr; + + return IMFMediaType_CopyAllItems(decoder->output_type, (IMFAttributes *)*type); } static HRESULT WINAPI transform_GetInputStatus(IMFTransform *iface, DWORD id, DWORD *flags) { - FIXME("iface %p, id %#lx, flags %p stub!\n", iface, id, flags); - return E_NOTIMPL; + struct video_decoder *decoder = impl_from_IMFTransform(iface); + + TRACE("iface %p, id %#lx, flags %p.\n", iface, id, flags); + + if (!decoder->wg_transform) + return MF_E_TRANSFORM_TYPE_NOT_SET; + + *flags = MFT_INPUT_STATUS_ACCEPT_DATA; + return S_OK; } static HRESULT WINAPI transform_GetOutputStatus(IMFTransform *iface, DWORD *flags) @@ -373,23 +576,105 @@ static HRESULT WINAPI transform_ProcessEvent(IMFTransform *iface, DWORD id, IMFM static HRESULT WINAPI transform_ProcessMessage(IMFTransform *iface, MFT_MESSAGE_TYPE message, ULONG_PTR param) { - FIXME("iface %p, message %#x, param %Ix stub!\n", iface, message, param); - return S_OK; + struct video_decoder *decoder = impl_from_IMFTransform(iface); + HRESULT hr; + + TRACE("iface %p, message %#x, param %Ix.\n", iface, message, param); + + switch (message) + { + case MFT_MESSAGE_SET_D3D_MANAGER: + if (FAILED(hr = IMFVideoSampleAllocatorEx_SetDirectXManager(decoder->allocator, (IUnknown *)param))) + return hr; + + uninit_allocator(decoder); + if (param) + decoder->output_info.dwFlags |= MFT_OUTPUT_STREAM_PROVIDES_SAMPLES; + else + decoder->output_info.dwFlags &= ~MFT_OUTPUT_STREAM_PROVIDES_SAMPLES; + return S_OK; + + case MFT_MESSAGE_COMMAND_DRAIN: + return wg_transform_drain(decoder->wg_transform); + + case MFT_MESSAGE_COMMAND_FLUSH: + return wg_transform_flush(decoder->wg_transform); + + case MFT_MESSAGE_NOTIFY_START_OF_STREAM: + decoder->sample_time = 0; + return S_OK; + + default: + FIXME("Ignoring message %#x.\n", message); + return S_OK; + } } static HRESULT WINAPI transform_ProcessInput(IMFTransform *iface, DWORD id, IMFSample *sample, DWORD flags) { struct video_decoder *decoder = impl_from_IMFTransform(iface); - HRESULT hr; TRACE("iface %p, id %#lx, sample %p, flags %#lx.\n", iface, id, sample, flags); if (!decoder->wg_transform) return MF_E_TRANSFORM_TYPE_NOT_SET; - hr = wg_transform_push_mf(decoder->wg_transform, sample, decoder->wg_sample_queue); + return wg_transform_push_mf(decoder->wg_transform, sample, decoder->wg_sample_queue); +} - return hr; +static HRESULT output_sample(struct video_decoder *decoder, IMFSample **out, IMFSample *src_sample) +{ + MFT_OUTPUT_DATA_BUFFER output[1]; + IMFSample *sample; + DWORD status; + HRESULT hr; + + if (FAILED(hr = init_allocator(decoder))) + { + ERR("Failed to initialize allocator, hr %#lx.\n", hr); + return hr; + } + if (FAILED(hr = IMFVideoSampleAllocatorEx_AllocateSample(decoder->allocator, &sample))) + return hr; + + if (FAILED(hr = IMFTransform_ProcessInput(decoder->copier, 0, src_sample, 0))) + { + IMFSample_Release(sample); + return hr; + } + output[0].pSample = sample; + if (FAILED(hr = IMFTransform_ProcessOutput(decoder->copier, 0, 1, output, &status))) + { + IMFSample_Release(sample); + return hr; + } + *out = sample; + return S_OK; +} + +static HRESULT handle_stream_type_change(struct video_decoder *decoder, const struct wg_format *format) +{ + UINT64 frame_size, frame_rate; + GUID subtype; + HRESULT hr; + + if (decoder->stream_type) + IMFMediaType_Release(decoder->stream_type); + if (!(decoder->stream_type = mf_media_type_from_wg_format(format))) + return E_OUTOFMEMORY; + + if (SUCCEEDED(IMFMediaType_GetUINT64(decoder->output_type, &MF_MT_FRAME_RATE, &frame_rate)) + && FAILED(hr = IMFMediaType_SetUINT64(decoder->stream_type, &MF_MT_FRAME_RATE, frame_rate))) + WARN("Failed to update stream type frame size, hr %#lx\n", hr); + + if (FAILED(hr = IMFMediaType_GetUINT64(decoder->stream_type, &MF_MT_FRAME_SIZE, &frame_size))) + return hr; + if (FAILED(hr = IMFMediaType_GetGUID(decoder->stream_type, &MF_MT_SUBTYPE, &subtype))) + return hr; + MFCalculateImageSize(&subtype, frame_size >> 32, frame_size, (UINT32 *)&decoder->output_info.cbSize); + uninit_allocator(decoder); + + return MF_E_TRANSFORM_STREAM_CHANGE; } static HRESULT WINAPI transform_ProcessOutput(IMFTransform *iface, DWORD flags, DWORD count, @@ -398,8 +683,11 @@ static HRESULT WINAPI transform_ProcessOutput(IMFTransform *iface, DWORD flags, struct video_decoder *decoder = impl_from_IMFTransform(iface); struct wg_format wg_format; UINT32 sample_size; - UINT64 frame_rate; + LONGLONG duration; + IMFSample *sample; + UINT64 frame_size, frame_rate; GUID subtype; + DWORD size; HRESULT hr; TRACE("iface %p, flags %#lx, count %lu, samples %p, status %p.\n", iface, flags, count, samples, status); @@ -411,36 +699,65 @@ static HRESULT WINAPI transform_ProcessOutput(IMFTransform *iface, DWORD flags, return MF_E_TRANSFORM_TYPE_NOT_SET; *status = samples->dwStatus = 0; - if (!samples->pSample) + if (!(sample = samples->pSample) && !(decoder->output_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES)) return E_INVALIDARG; if (FAILED(hr = IMFMediaType_GetGUID(decoder->output_type, &MF_MT_SUBTYPE, &subtype))) return hr; - if (FAILED(hr = MFCalculateImageSize(&subtype, decoder->wg_format.u.video.width, - decoder->wg_format.u.video.height, &sample_size))) + if (FAILED(hr = IMFMediaType_GetUINT64(decoder->output_type, &MF_MT_FRAME_SIZE, &frame_size))) + return hr; + if (FAILED(hr = MFCalculateImageSize(&subtype, frame_size >> 32, (UINT32)frame_size, &sample_size))) return hr; - if (SUCCEEDED(hr = wg_transform_read_mf(decoder->wg_transform, samples->pSample, - sample_size, &wg_format, &samples->dwStatus))) - wg_sample_queue_flush(decoder->wg_sample_queue, false); - - if (hr == MF_E_TRANSFORM_STREAM_CHANGE) + if (decoder->output_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) { - decoder->wg_format = wg_format; - - if (FAILED(hr = MFCalculateImageSize(&subtype, decoder->wg_format.u.video.width, - decoder->wg_format.u.video.height, &sample_size))) + if (decoder->temp_buffer) + { + if (FAILED(IMFMediaBuffer_GetMaxLength(decoder->temp_buffer, &size)) || size < sample_size) + { + IMFMediaBuffer_Release(decoder->temp_buffer); + decoder->temp_buffer = NULL; + } + } + if (!decoder->temp_buffer && FAILED(hr = MFCreateMemoryBuffer(sample_size, &decoder->temp_buffer))) return hr; - - /* keep the frame rate that was requested, GStreamer doesn't provide any */ - if (SUCCEEDED(IMFMediaType_GetUINT64(decoder->output_type, &MF_MT_FRAME_RATE, &frame_rate))) + if (FAILED(hr = MFCreateSample(&sample))) + return hr; + if (FAILED(hr = IMFSample_AddBuffer(sample, decoder->temp_buffer))) { - decoder->wg_format.u.video.fps_n = frame_rate >> 32; - decoder->wg_format.u.video.fps_d = (UINT32)frame_rate; + IMFSample_Release(sample); + return hr; } + } + if (SUCCEEDED(hr = wg_transform_read_mf(decoder->wg_transform, sample, + sample_size, &wg_format, &samples->dwStatus))) + { + wg_sample_queue_flush(decoder->wg_sample_queue, false); + + if (FAILED(IMFMediaType_GetUINT64(decoder->input_type, &MF_MT_FRAME_RATE, &frame_rate))) + frame_rate = (UINT64)30000 << 32 | 1001; + + duration = (UINT64)10000000 * (UINT32)frame_rate / (frame_rate >> 32); + if (FAILED(IMFSample_SetSampleTime(sample, decoder->sample_time))) + WARN("Failed to set sample time\n"); + if (FAILED(IMFSample_SetSampleDuration(sample, duration))) + WARN("Failed to set sample duration\n"); + decoder->sample_time += duration; + } + + if (hr == MF_E_TRANSFORM_STREAM_CHANGE) + { samples[0].dwStatus |= MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE; *status |= MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE; + hr = handle_stream_type_change(decoder, &wg_format); + } + + if (decoder->output_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) + { + if (hr == S_OK && FAILED(hr = output_sample(decoder, &samples->pSample, sample))) + ERR("Failed to output sample, hr %#lx.\n", hr); + IMFSample_Release(sample); } return hr; @@ -476,33 +793,158 @@ static const IMFTransformVtbl transform_vtbl = transform_ProcessOutput, }; -HRESULT WINAPI winegstreamer_create_video_decoder(IMFTransform **out) +static HRESULT video_decoder_create_with_types(const GUID *const *input_types, UINT input_type_count, + const GUID *const *output_types, UINT output_type_count, IMFTransform **ret) { struct video_decoder *decoder; HRESULT hr; - TRACE("out %p.\n", out); - - if (!init_gstreamer()) - return E_FAIL; - if (!(decoder = calloc(1, sizeof(*decoder)))) return E_OUTOFMEMORY; decoder->IMFTransform_iface.lpVtbl = &transform_vtbl; decoder->refcount = 1; - decoder->wg_format.u.video.fps_d = 1; - decoder->wg_format.u.video.fps_n = 1; + decoder->input_type_count = input_type_count; + decoder->input_types = input_types; + decoder->output_type_count = output_type_count; + decoder->output_types = output_types; + + decoder->input_info.dwFlags = MFT_INPUT_STREAM_WHOLE_SAMPLES | MFT_INPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER + | MFT_INPUT_STREAM_FIXED_SAMPLE_SIZE; + decoder->input_info.cbSize = 0x1000; + decoder->output_info.dwFlags = MFT_OUTPUT_STREAM_WHOLE_SAMPLES | MFT_OUTPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER + | MFT_OUTPUT_STREAM_FIXED_SAMPLE_SIZE; + decoder->output_info.cbSize = 1920 * 1088 * 2; + if (FAILED(hr = MFCreateMediaType(&decoder->stream_type))) + goto failed; + if (FAILED(hr = MFCreateAttributes(&decoder->attributes, 16))) + goto failed; + if (FAILED(hr = IMFAttributes_SetUINT32(decoder->attributes, &MF_LOW_LATENCY, 0))) + goto failed; + if (FAILED(hr = IMFAttributes_SetUINT32(decoder->attributes, &MF_SA_D3D11_AWARE, TRUE))) + goto failed; + if (FAILED(hr = IMFAttributes_SetUINT32(decoder->attributes, &AVDecVideoAcceleration_H264, TRUE))) + goto failed; + + { + const char *sgi; + if ((sgi = getenv("SteamGameId")) && ((!strcmp(sgi, "2073850")) || (!strcmp(sgi, "2009100")) || (!strcmp(sgi, "2555360")))) + IMFAttributes_SetUINT32(decoder->attributes, &MF_SA_D3D11_AWARE, FALSE); + } + + if (FAILED(hr = MFCreateAttributes(&decoder->output_attributes, 0))) + goto failed; if (FAILED(hr = wg_sample_queue_create(&decoder->wg_sample_queue))) goto failed; + if (FAILED(hr = MFCreateVideoSampleAllocatorEx(&IID_IMFVideoSampleAllocatorEx, (void **)&decoder->allocator))) + goto failed; + if (FAILED(hr = MFCreateSampleCopierMFT(&decoder->copier))) + goto failed; - *out = &decoder->IMFTransform_iface; - TRACE("created decoder %p.\n", *out); + *ret = &decoder->IMFTransform_iface; + TRACE("Created decoder %p\n", *ret); return S_OK; failed: + if (decoder->allocator) + IMFVideoSampleAllocatorEx_Release(decoder->allocator); + if (decoder->wg_sample_queue) + wg_sample_queue_destroy(decoder->wg_sample_queue); + if (decoder->output_attributes) + IMFAttributes_Release(decoder->output_attributes); + if (decoder->attributes) + IMFAttributes_Release(decoder->attributes); + if (decoder->stream_type) + IMFMediaType_Release(decoder->stream_type); free(decoder); return hr; } + +HRESULT video_decoder_create(REFIID riid, void **out) +{ + IMFTransform *iface; + HRESULT hr; + + TRACE("riid %s, out %p.\n", debugstr_guid(riid), out); + + if (FAILED(hr = video_decoder_create_with_types(video_decoder_input_types, ARRAY_SIZE(video_decoder_input_types), + video_decoder_output_types, ARRAY_SIZE(video_decoder_output_types), &iface))) + return hr; + + hr = IMFTransform_QueryInterface(iface, riid, out); + IMFTransform_Release(iface); + return hr; +} + +static const GUID *const h264_decoder_input_types[] = +{ + &MFVideoFormat_H264, + &MFVideoFormat_H264_ES, +}; + +HRESULT h264_decoder_create(REFIID riid, void **out) +{ + static const struct wg_format output_format = + { + .major_type = WG_MAJOR_TYPE_VIDEO, + .u.video = + { + .format = WG_VIDEO_FORMAT_I420, + .width = 1920, + .height = 1080, + }, + }; + static const struct wg_format input_format = {.major_type = WG_MAJOR_TYPE_VIDEO_H264}; + struct wg_transform_attrs attrs = {0}; + wg_transform_t transform; + IMFTransform *iface; + HRESULT hr; + + TRACE("riid %s, out %p.\n", debugstr_guid(riid), out); + + if (!(transform = wg_transform_create(&input_format, &output_format, &attrs))) + { + ERR_(winediag)("GStreamer doesn't support H.264 decoding, please install appropriate plugins\n"); + return E_FAIL; + } + wg_transform_destroy(transform); + + if (FAILED(hr = video_decoder_create_with_types(h264_decoder_input_types, ARRAY_SIZE(h264_decoder_input_types), + video_decoder_output_types, ARRAY_SIZE(video_decoder_output_types), &iface))) + return hr; + + hr = IMFTransform_QueryInterface(iface, riid, out); + IMFTransform_Release(iface); + return hr; +} + +extern GUID MFVideoFormat_IV50; +static const GUID *const iv50_decoder_input_types[] = +{ + &MFVideoFormat_IV50, +}; +static const GUID *const iv50_decoder_output_types[] = +{ + &MFVideoFormat_YV12, + &MFVideoFormat_YUY2, + &MFVideoFormat_NV11, + &MFVideoFormat_NV12, + &MFVideoFormat_RGB32, + &MFVideoFormat_RGB24, + &MFVideoFormat_RGB565, + &MFVideoFormat_RGB555, + &MFVideoFormat_RGB8, +}; + +HRESULT WINAPI winegstreamer_create_video_decoder(IMFTransform **out) +{ + TRACE("out %p.\n", out); + + if (!init_gstreamer()) + return E_FAIL; + + return video_decoder_create_with_types(iv50_decoder_input_types, ARRAY_SIZE(iv50_decoder_input_types), + iv50_decoder_output_types, ARRAY_SIZE(iv50_decoder_output_types), out); +} diff --git a/dlls/winegstreamer/video_processor.c b/dlls/winegstreamer/video_processor.c index 3075bf997d2..1cbb37dafc7 100644 --- a/dlls/winegstreamer/video_processor.c +++ b/dlls/winegstreamer/video_processor.c @@ -102,6 +102,13 @@ static HRESULT try_create_wg_transform(struct video_processor *impl) if (output_format.major_type == WG_MAJOR_TYPE_UNKNOWN) return MF_E_INVALIDMEDIATYPE; + /* prevent fps differences from failing to connect the elements */ + if (output_format.u.video.fps_d || output_format.u.video.fps_n) + { + input_format.u.video.fps_d = output_format.u.video.fps_d; + input_format.u.video.fps_n = output_format.u.video.fps_n; + } + if (!(impl->wg_transform = wg_transform_create(&input_format, &output_format, &attrs))) return E_FAIL; diff --git a/dlls/winegstreamer/wg_format.c b/dlls/winegstreamer/wg_format.c index 1c9b49a1e55..6a1e6ac15b4 100644 --- a/dlls/winegstreamer/wg_format.c +++ b/dlls/winegstreamer/wg_format.c @@ -267,6 +267,25 @@ static void wg_format_from_caps_audio_wma(struct wg_format *format, const GstCap gst_buffer_unmap(codec_data, &map); } +static void wg_format_from_caps_audio_encoded(struct wg_format *format, const GstCaps *caps, + const GstAudioInfo *info) +{ + gchar *str; + gint len; + + format->major_type = WG_MAJOR_TYPE_AUDIO_ENCODED; + format->u.audio_encoded.rate = info->rate; + format->u.audio_encoded.channels = info->channels; + + str = gst_caps_to_string(caps); + len = strlen(str) + 1; + if (len >= ARRAY_SIZE(format->u.audio_encoded.caps)) + GST_FIXME("wg_format.audio_encoded.caps buffer is too small, need %u bytes", len); + else + memcpy(format->u.audio_encoded.caps, str, len); + g_free(str); +} + static void wg_format_from_caps_video_cinepak(struct wg_format *format, const GstCaps *caps) { const GstStructure *structure = gst_caps_get_structure(caps, 0); @@ -392,47 +411,66 @@ static void wg_format_from_caps_video_mpeg1(struct wg_format *format, const GstC format->u.video_mpeg1.fps_d = fps_d; } +static void wg_format_from_caps_video_encoded(struct wg_format *format, const GstCaps *caps, + const GstVideoInfo *info) +{ + gchar *str; + gint len; + + format->major_type = WG_MAJOR_TYPE_VIDEO_ENCODED; + format->u.video_encoded.width = info->width; + format->u.video_encoded.height = info->height; + format->u.video_encoded.fps_n = info->fps_n; + format->u.video_encoded.fps_d = info->fps_d; + + str = gst_caps_to_string(caps); + len = strlen(str) + 1; + if (len >= ARRAY_SIZE(format->u.video_encoded.caps)) + GST_FIXME("wg_format.video_encoded.caps buffer is too small, need %u bytes", len); + else + memcpy(format->u.video_encoded.caps, str, len); + g_free(str); +} + void wg_format_from_caps(struct wg_format *format, const GstCaps *caps) { const GstStructure *structure = gst_caps_get_structure(caps, 0); const char *name = gst_structure_get_name(structure); + GstAudioInfo audio_info; + GstVideoInfo video_info; gboolean parsed; memset(format, 0, sizeof(*format)); - if (!strcmp(name, "audio/x-raw")) - { - GstAudioInfo info; - - if (gst_audio_info_from_caps(&info, caps)) - wg_format_from_audio_info(format, &info); - } - else if (!strcmp(name, "video/x-raw")) - { - GstVideoInfo info; - - if (gst_video_info_from_caps(&info, caps)) - wg_format_from_video_info(format, &info); - } - else if (!strcmp(name, "audio/mpeg") && gst_structure_get_boolean(structure, "parsed", &parsed) && parsed) - { - wg_format_from_caps_audio_mpeg1(format, caps); - } - else if (!strcmp(name, "audio/x-wma")) - { - wg_format_from_caps_audio_wma(format, caps); - } - else if (!strcmp(name, "video/x-cinepak")) - { - wg_format_from_caps_video_cinepak(format, caps); - } - else if (!strcmp(name, "video/x-wmv")) + if (g_str_has_prefix(name, "audio/") && gst_audio_info_from_caps(&audio_info, caps)) { - wg_format_from_caps_video_wmv(format, caps); + if (GST_AUDIO_INFO_FORMAT(&audio_info) != GST_AUDIO_FORMAT_ENCODED) + wg_format_from_audio_info(format, &audio_info); + else if (!strcmp(name, "audio/mpeg") && gst_structure_get_boolean(structure, "parsed", &parsed) && parsed) + wg_format_from_caps_audio_mpeg1(format, caps); + else if (!strcmp(name, "audio/x-wma")) + wg_format_from_caps_audio_wma(format, caps); + else + { + GST_FIXME("Using fallback for encoded audio caps %" GST_PTR_FORMAT ".", caps); + wg_format_from_caps_audio_encoded(format, caps, &audio_info); + } } - else if (!strcmp(name, "video/mpeg") && gst_structure_get_boolean(structure, "parsed", &parsed) && parsed) + else if (g_str_has_prefix(name, "video/") && gst_video_info_from_caps(&video_info, caps)) { - wg_format_from_caps_video_mpeg1(format, caps); + if (GST_VIDEO_INFO_FORMAT(&video_info) != GST_VIDEO_FORMAT_ENCODED) + wg_format_from_video_info(format, &video_info); + else if (!strcmp(name, "video/x-cinepak")) + wg_format_from_caps_video_cinepak(format, caps); + else if (!strcmp(name, "video/x-wmv")) + wg_format_from_caps_video_wmv(format, caps); + else if (!strcmp(name, "video/mpeg") && gst_structure_get_boolean(structure, "parsed", &parsed) && parsed) + wg_format_from_caps_video_mpeg1(format, caps); + else + { + GST_FIXME("Using fallback for encoded video caps %" GST_PTR_FORMAT ".", caps); + wg_format_from_caps_video_encoded(format, caps, &video_info); + } } else { @@ -615,6 +653,7 @@ static GstCaps *wg_format_to_caps_video(const struct wg_format *format) /* Remove fields which we don't specify but might have some default value */ gst_structure_remove_fields(structure, "colorimetry", "chroma-site", NULL); + gst_structure_remove_fields(structure, "pixel-aspect-ratio", NULL); } } return caps; @@ -882,6 +921,8 @@ GstCaps *wg_format_to_caps(const struct wg_format *format) return wg_format_to_caps_audio_mpeg4(format); case WG_MAJOR_TYPE_AUDIO_WMA: return wg_format_to_caps_audio_wma(format); + case WG_MAJOR_TYPE_AUDIO_ENCODED: + return gst_caps_from_string(format->u.audio_encoded.caps); case WG_MAJOR_TYPE_VIDEO: return wg_format_to_caps_video(format); case WG_MAJOR_TYPE_VIDEO_CINEPAK: @@ -894,6 +935,8 @@ GstCaps *wg_format_to_caps(const struct wg_format *format) return wg_format_to_caps_video_indeo(format); case WG_MAJOR_TYPE_VIDEO_MPEG1: return wg_format_to_caps_video_mpeg1(format); + case WG_MAJOR_TYPE_VIDEO_ENCODED: + return gst_caps_from_string(format->u.video_encoded.caps); } assert(0); return NULL; @@ -909,9 +952,11 @@ bool wg_format_compare(const struct wg_format *a, const struct wg_format *b) case WG_MAJOR_TYPE_AUDIO_MPEG1: case WG_MAJOR_TYPE_AUDIO_MPEG4: case WG_MAJOR_TYPE_AUDIO_WMA: + case WG_MAJOR_TYPE_AUDIO_ENCODED: case WG_MAJOR_TYPE_VIDEO_H264: case WG_MAJOR_TYPE_VIDEO_INDEO: case WG_MAJOR_TYPE_VIDEO_MPEG1: + case WG_MAJOR_TYPE_VIDEO_ENCODED: GST_FIXME("Format %u not implemented!", a->major_type); /* fallthrough */ case WG_MAJOR_TYPE_UNKNOWN: diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index 7326cbc27cb..6769783fb69 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -73,6 +73,7 @@ struct wg_parser GstElement *container, *decodebin; GstBus *bus; + GstTaskPool *task_pool; GstPad *my_src; guint64 file_size, start_offset, next_offset, stop_offset; @@ -147,7 +148,9 @@ static bool format_is_compressed(struct wg_format *format) { return format->major_type != WG_MAJOR_TYPE_UNKNOWN && format->major_type != WG_MAJOR_TYPE_VIDEO - && format->major_type != WG_MAJOR_TYPE_AUDIO; + && format->major_type != WG_MAJOR_TYPE_AUDIO + && format->major_type != WG_MAJOR_TYPE_VIDEO_ENCODED + && format->major_type != WG_MAJOR_TYPE_AUDIO_ENCODED; } static NTSTATUS wg_parser_get_stream_count(void *args) @@ -1611,6 +1614,24 @@ static GstBusSyncReply bus_handler_cb(GstBus *bus, GstMessage *msg, gpointer use } break; + case GST_MESSAGE_STREAM_STATUS: + { + GstStreamStatusType type; + GstElement *element; + const GValue *val; + GstTask *task; + + gst_message_parse_stream_status(msg, &type, &element); + val = gst_message_get_stream_status_object(msg); + GST_DEBUG("parser %p, message %s, type %u, value %p (%s).", parser, GST_MESSAGE_TYPE_NAME(msg), type, val, G_VALUE_TYPE_NAME(val)); + + if (G_VALUE_TYPE(val) == GST_TYPE_TASK && (task = g_value_get_object(val)) + && type == GST_STREAM_STATUS_TYPE_CREATE) + gst_task_set_pool(task, parser->task_pool); + + break; + } + default: break; } @@ -2009,6 +2030,7 @@ static NTSTATUS wg_parser_disconnect(void *args) parser->input_cache_chunks[i].data = NULL; } + gst_task_pool_cleanup(parser->task_pool); return S_OK; } @@ -2125,6 +2147,7 @@ static NTSTATUS wg_parser_create(void *args) struct wg_parser_create_params *params = args; struct wg_parser *parser; + GError *error; if (!(parser = calloc(1, sizeof(*parser)))) return E_OUTOFMEMORY; @@ -2138,6 +2161,12 @@ static NTSTATUS wg_parser_create(void *args) parser->use_opengl = FALSE; } } + if (!(parser->task_pool = wg_task_pool_new())) + { + free(parser); + return E_OUTOFMEMORY; + } + gst_task_pool_prepare(parser->task_pool, &error); pthread_mutex_init(&parser->mutex, NULL); pthread_cond_init(&parser->init_cond, NULL); @@ -2161,6 +2190,7 @@ static NTSTATUS wg_parser_destroy(void *args) gst_bus_set_sync_handler(parser->bus, NULL, NULL, NULL); gst_object_unref(parser->bus); } + gst_object_unref(parser->task_pool); if (parser->context) gst_context_unref(parser->context); @@ -2206,6 +2236,18 @@ const unixlib_entry_t __wine_unix_call_funcs[] = X(wg_parser_stream_get_tag), X(wg_parser_stream_seek), + X(wg_source_create), + X(wg_source_destroy), + X(wg_source_get_stream_count), + X(wg_source_get_duration), + X(wg_source_get_position), + X(wg_source_set_position), + X(wg_source_push_data), + X(wg_source_read_data), + X(wg_source_get_stream_format), + X(wg_source_get_stream_tag), + X(wg_source_set_stream_flags), + X(wg_transform_create), X(wg_transform_destroy), X(wg_transform_set_output_format), @@ -2287,11 +2329,13 @@ static NTSTATUS wow64_wg_parser_stream_enable(void *args) { wg_parser_stream_t stream; PTR32 format; + UINT32 flags; } *params32 = args; struct wg_parser_stream_enable_params params = { .stream = params32->stream, .format = ULongToPtr(params32->format), + .flags = params32->flags, }; return wg_parser_stream_enable(¶ms); @@ -2353,6 +2397,88 @@ static NTSTATUS wow64_wg_parser_stream_get_tag(void *args) return wg_parser_stream_get_tag(¶ms); } +NTSTATUS wow64_wg_source_create(void *args) +{ + struct + { + PTR32 url; + PTR32 data; + UINT32 size; + char mime_type[256]; + wg_source_t source; + } *params32 = args; + struct wg_source_create_params params = + { + .url = ULongToPtr(params32->url), + .data = ULongToPtr(params32->data), + .size = params32->size, + }; + NTSTATUS ret; + + ret = wg_source_create(¶ms); + strcpy(params32->mime_type, params.mime_type); + params32->source = params.source; + return ret; +} + +NTSTATUS wow64_wg_source_push_data(void *args) +{ + struct + { + wg_source_t source; + PTR32 data; + UINT32 size; + } *params32 = args; + struct wg_source_push_data_params params = + { + .source = params32->source, + .data = ULongToPtr(params32->data), + .size = params32->size, + }; + + return wg_source_push_data(¶ms); +} + +NTSTATUS wow64_wg_source_read_data(void *args) +{ + struct + { + wg_source_t source; + UINT32 index; + PTR32 sample; + } *params32 = args; + struct wg_source_read_data_params params = + { + .source = params32->source, + .index = params32->index, + .sample = ULongToPtr(params32->sample), + }; + + return wg_source_read_data(¶ms); +} + +NTSTATUS wow64_wg_source_get_stream_tag(void *args) +{ + struct + { + wg_source_t source; + UINT32 index; + wg_parser_tag tag; + UINT32 size; + PTR32 buffer; + } *params32 = args; + struct wg_source_get_stream_tag_params params = + { + .source = params32->source, + .index = params32->index, + .tag = params32->tag, + .size = params32->size, + .buffer = ULongToPtr(params32->buffer), + }; + + return wg_source_get_stream_tag(¶ms); +} + NTSTATUS wow64_wg_transform_create(void *args) { struct @@ -2539,6 +2665,16 @@ const unixlib_entry_t __wine_unix_call_wow64_funcs[] = X64(wg_parser_stream_get_tag), X(wg_parser_stream_seek), + X64(wg_source_create), + X(wg_source_destroy), + X(wg_source_get_stream_count), + X(wg_source_get_duration), + X(wg_source_get_position), + X64(wg_source_push_data), + X64(wg_source_read_data), + X(wg_source_get_stream_format), + X64(wg_source_get_stream_tag), + X64(wg_transform_create), X(wg_transform_destroy), X64(wg_transform_set_output_format), diff --git a/dlls/winegstreamer/wg_sample.c b/dlls/winegstreamer/wg_sample.c index 31281066957..513907d9d77 100644 --- a/dlls/winegstreamer/wg_sample.c +++ b/dlls/winegstreamer/wg_sample.c @@ -16,6 +16,8 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ +#include "ntstatus.h" +#define WIN32_NO_STATUS #include "gst_private.h" #include "wmcodecdsp.h" @@ -534,3 +536,70 @@ HRESULT wg_transform_read_dmo(wg_transform_t transform, DMO_OUTPUT_DATA_BUFFER * wg_sample_release(wg_sample); return hr; } + +HRESULT wg_source_read_data(wg_source_t source, UINT32 index, IMFSample **out) +{ + struct wg_sample wg_sample = {0}; + struct wg_source_read_data_params params = + { + .source = source, + .index = index, + .sample = &wg_sample, + }; + IMFMediaBuffer *buffer; + IMFSample *sample; + NTSTATUS status; + HRESULT hr; + + TRACE("source %#I64x, index %u, out %p\n", source, index, out); + + status = WINE_UNIX_CALL(unix_wg_source_read_data, ¶ms); + if (status == STATUS_PENDING) return E_PENDING; + if (status == STATUS_END_OF_FILE) return MF_E_END_OF_STREAM; + if (status != STATUS_BUFFER_TOO_SMALL) return HRESULT_FROM_NT(status); + + if (FAILED(hr = MFCreateSample(&sample))) + return hr; + if (SUCCEEDED(hr = MFCreateMemoryBuffer(wg_sample.max_size, &buffer))) + { + hr = IMFSample_AddBuffer(sample, buffer); + IMFMediaBuffer_Release(buffer); + } + + if (FAILED(hr) || FAILED(hr = wg_sample_create_mf(sample, ¶ms.sample))) + goto error; + + status = WINE_UNIX_CALL(unix_wg_source_read_data, ¶ms); + if (FAILED(hr = HRESULT_FROM_NT(status))) + { + wg_sample_release(params.sample); + goto error; + } + + if (params.sample->flags & WG_SAMPLE_FLAG_HAS_PTS) + IMFSample_SetSampleTime(sample, params.sample->pts); + if (params.sample->flags & WG_SAMPLE_FLAG_HAS_DURATION) + IMFSample_SetSampleDuration(sample, params.sample->duration); + if (params.sample->flags & WG_SAMPLE_FLAG_SYNC_POINT) + IMFSample_SetUINT32(sample, &MFSampleExtension_CleanPoint, 1); + if (params.sample->flags & WG_SAMPLE_FLAG_DISCONTINUITY) + IMFSample_SetUINT32(sample, &MFSampleExtension_Discontinuity, 1); + + if (SUCCEEDED(hr = IMFSample_ConvertToContiguousBuffer(sample, &buffer))) + { + hr = IMFMediaBuffer_SetCurrentLength(buffer, params.sample->size); + IMFMediaBuffer_Release(buffer); + } + wg_sample_release(params.sample); + + if (FAILED(hr)) + goto error; + + *out = sample; + TRACE("source %#I64x, index %u, out %p\n", source, index, *out); + return S_OK; + +error: + IMFSample_Release(sample); + return hr; +} diff --git a/dlls/winegstreamer/wg_source.c b/dlls/winegstreamer/wg_source.c new file mode 100644 index 00000000000..6e000e36e31 --- /dev/null +++ b/dlls/winegstreamer/wg_source.c @@ -0,0 +1,992 @@ +/* + * Copyright 2023 RĂ©mi Bernon for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "winternl.h" +#include "mferror.h" + +#include "unix_private.h" + +#define WG_SOURCE_MAX_STREAMS 32 + +struct source_stream +{ + GstPad *pad; + GstAtomicQueue *queue; + GstBuffer *buffer; + gboolean eos; +}; + +struct wg_source +{ + gchar *url; + GstPad *src_pad; + GstElement *container; + GstSegment segment; + bool valid_segment; + guint64 max_duration; + + guint stream_count; + struct source_stream streams[WG_SOURCE_MAX_STREAMS]; +}; + +static struct wg_source *get_source(wg_source_t source) +{ + return (struct wg_source *)(ULONG_PTR)source; +} + +static const char *media_type_from_caps(GstCaps *caps) +{ + GstStructure *structure; + if (!caps || !(structure = gst_caps_get_structure(caps, 0))) + return ""; + return gst_structure_get_name(structure); +} + +static GstCaps *detect_caps_from_data(const char *url, const void *data, guint size) +{ + const char *extension = url ? strrchr(url, '.') : NULL; + GstTypeFindProbability probability; + GstCaps *caps; + gchar *str; + + if (!(caps = gst_type_find_helper_for_data_with_extension(NULL, data, size, + extension ? extension + 1 : NULL, &probability))) + { + GST_ERROR("Failed to detect caps for url %s, data %p, size %u", url, data, size); + return NULL; + } + + str = gst_caps_to_string(caps); + if (probability > GST_TYPE_FIND_POSSIBLE) + GST_INFO("Detected caps %s with probability %u for url %s, data %p, size %u", + str, probability, url, data, size); + else + GST_FIXME("Detected caps %s with probability %u for url %s, data %p, size %u", + str, probability, url, data, size); + g_free(str); + + return caps; +} + +static GstPad *create_pad_with_caps(GstPadDirection direction, GstCaps *caps) +{ + GstCaps *pad_caps = caps ? gst_caps_ref(caps) : gst_caps_new_any(); + const char *name = direction == GST_PAD_SRC ? "src" : "sink"; + GstPadTemplate *template; + GstPad *pad; + + if (!pad_caps || !(template = gst_pad_template_new(name, direction, GST_PAD_ALWAYS, pad_caps))) + return NULL; + pad = gst_pad_new_from_template(template, "src"); + g_object_unref(template); + gst_caps_unref(pad_caps); + return pad; +} + +static GstBuffer *create_buffer_from_bytes(const void *data, guint size) +{ + GstBuffer *buffer; + + if (!(buffer = gst_buffer_new_and_alloc(size))) + GST_ERROR("Failed to allocate buffer for %#x bytes\n", size); + else + { + gst_buffer_fill(buffer, 0, data, size); + gst_buffer_set_size(buffer, size); + } + + return buffer; +} + +static GstStream *source_get_stream(struct wg_source *source, guint index) +{ + return index >= source->stream_count ? NULL : gst_pad_get_stream(source->streams[index].pad); +} + +static GstCaps *source_get_stream_caps(struct wg_source *source, guint index) +{ + GstStream *stream; + GstCaps *caps; + if (!(stream = source_get_stream(source, index))) + return NULL; + caps = gst_stream_get_caps(stream); + gst_object_unref(stream); + return caps; +} + +static GstTagList *source_get_stream_tags(struct wg_source *source, guint index) +{ + GstStream *stream; + GstTagList *tags; + if (!(stream = source_get_stream(source, index))) + return NULL; + tags = gst_stream_get_tags(stream); + gst_object_unref(stream); + return tags; +} + +static bool source_set_stream_flags(struct wg_source *source, guint index, GstStreamFlags flags) +{ + GstStream *stream; + if (!(stream = source_get_stream(source, index))) + return false; + gst_stream_set_stream_flags(stream, flags); + gst_object_unref(stream); + return true; +} + +static GstStreamFlags source_get_stream_flags(struct wg_source *source, guint index) +{ + GstStreamFlags flags; + GstStream *stream; + if (!(stream = source_get_stream(source, index))) + return 0; + flags = gst_stream_get_stream_flags(stream); + gst_object_unref(stream); + return flags; +} + +static NTSTATUS source_get_stream_buffer(struct wg_source *source, guint index, GstBuffer **buffer) +{ + GstBuffer **stream_buffer; + if (index >= source->stream_count) + return STATUS_INVALID_PARAMETER; + + stream_buffer = &source->streams[index].buffer; + if (!*stream_buffer && !(*stream_buffer = gst_atomic_queue_pop(source->streams[index].queue))) + return source->streams[index].eos ? STATUS_END_OF_FILE : STATUS_PENDING; + + *buffer = gst_buffer_ref(*stream_buffer); + return STATUS_SUCCESS; +} + +static gboolean src_event_seek(struct wg_source *source, GstEvent *event) +{ + guint32 i, seqnum = gst_event_get_seqnum(event); + GstSeekType cur_type, stop_type; + GstSeekFlags flags; + GstFormat format; + gint64 cur, stop; + gdouble rate; + + gst_event_parse_seek(event, &rate, &format, &flags, &cur_type, &cur, &stop_type, &stop); + gst_event_unref(event); + if (format != GST_FORMAT_BYTES) + return false; + + GST_TRACE("source %p, rate %f, format %s, flags %#x, cur_type %u, cur %#" G_GINT64_MODIFIER "x, " + "stop_type %u, stop %#" G_GINT64_MODIFIER "x.", source, rate, gst_format_get_name(format), + flags, cur_type, cur, stop_type, stop); + + if (flags & GST_SEEK_FLAG_FLUSH) + { + if (!(event = gst_event_new_flush_start())) + GST_ERROR("Failed to allocate flush_start event"); + else + { + gst_event_set_seqnum(event, seqnum); + if (!gst_pad_push_event(source->src_pad, event)) + GST_ERROR("Failed to push flush_start event"); + } + } + + source->segment.start = cur; + + for (i = 0; i < ARRAY_SIZE(source->streams); i++) + source->streams[i].eos = false; + + if (flags & GST_SEEK_FLAG_FLUSH) + { + if (!(event = gst_event_new_flush_stop(true))) + GST_ERROR("Failed to allocate flush_stop event"); + else + { + gst_event_set_seqnum(event, seqnum); + if (!gst_pad_push_event(source->src_pad, event)) + GST_ERROR("Failed to push flush_stop event"); + } + source->valid_segment = false; + } + + return true; +} + +static gboolean src_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) +{ + struct wg_source *source = gst_pad_get_element_private(pad); + + switch (GST_EVENT_TYPE(event)) + { + case GST_EVENT_SEEK: + return src_event_seek(source, event); + default: + return gst_pad_event_default(pad, parent, event); + } +} + +static gboolean src_query_duration(struct wg_source *source, GstQuery *query) +{ + GstFormat format; + + gst_query_parse_duration(query, &format, NULL); + GST_TRACE("source %p, format %s", source, gst_format_get_name(format)); + if (format != GST_FORMAT_BYTES) + return false; + + gst_query_set_duration(query, format, source->segment.stop); + return true; +} + +static gboolean src_query_scheduling(struct wg_source *source, GstQuery *query) +{ + GST_TRACE("source %p", source); + gst_query_set_scheduling(query, GST_SCHEDULING_FLAG_SEEKABLE, 1, -1, 0); + gst_query_add_scheduling_mode(query, GST_PAD_MODE_PUSH); + return true; +} + +static gboolean src_query_seeking(struct wg_source *source, GstQuery *query) +{ + GstFormat format; + + gst_query_parse_seeking(query, &format, NULL, NULL, NULL); + GST_TRACE("source %p, format %s", source, gst_format_get_name(format)); + if (format != GST_FORMAT_BYTES) + return false; + + gst_query_set_seeking(query, GST_FORMAT_BYTES, 1, 0, source->segment.stop); + return true; +} + +static gboolean src_query_uri(struct wg_source *source, GstQuery *query) +{ + gchar *uri; + + gst_query_parse_uri(query, &uri); + GST_TRACE("source %p, uri %s", source, uri); + gst_query_set_uri(query, source->url); + + return true; +} + +static gboolean src_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) +{ + struct wg_source *source = gst_pad_get_element_private(pad); + + switch (GST_QUERY_TYPE(query)) + { + case GST_QUERY_DURATION: + return src_query_duration(source, query); + case GST_QUERY_SCHEDULING: + return src_query_scheduling(source, query); + case GST_QUERY_SEEKING: + return src_query_seeking(source, query); + case GST_QUERY_URI: + if (!source->url) + return false; + return src_query_uri(source, query); + default: + return gst_pad_query_default(pad, parent, query); + } +} + +static GstFlowReturn sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *buffer) +{ + struct wg_source *source = gst_pad_get_element_private(pad); + guint index; + + GST_TRACE("source %p, pad %p, buffer %p.", source, pad, buffer); + + for (index = 0; index < source->stream_count; index++) + if (source->streams[index].pad == pad) + break; + + if (source_get_stream_flags(source, index) & GST_STREAM_FLAG_SELECT) + gst_atomic_queue_push(source->streams[index].queue, buffer); + else + gst_buffer_unref(buffer); + + return GST_FLOW_OK; +} + +static gboolean sink_event_caps(struct wg_source *source, GstPad *pad, GstEvent *event) +{ + GstStream *stream; + GstCaps *caps; + gchar *str; + + gst_event_parse_caps(event, &caps); + str = gst_caps_to_string(caps); + GST_TRACE("source %p, pad %p, caps %s", source, pad, str); + g_free(str); + + if ((stream = gst_pad_get_stream(pad))) + { + gst_stream_set_caps(stream, gst_caps_copy(caps)); + gst_stream_set_stream_type(stream, stream_type_from_caps(caps)); + gst_object_unref(stream); + } + + gst_event_unref(event); + return !!stream; +} + +static gboolean sink_event_tag(struct wg_source *source, GstPad *pad, GstEvent *event) +{ + GstTagList *new_tags; + GstStream *stream; + + gst_event_parse_tag(event, &new_tags); + GST_TRACE("source %p, pad %p, new_tags %p", source, pad, new_tags); + + if ((stream = gst_pad_get_stream(pad))) + { + GstTagList *old_tags = gst_stream_get_tags(stream); + if ((new_tags = gst_tag_list_merge(old_tags, new_tags, GST_TAG_MERGE_REPLACE))) + { + gst_stream_set_tags(stream, new_tags); + gst_tag_list_unref(new_tags); + } + if (old_tags) + gst_tag_list_unref(old_tags); + gst_object_unref(stream); + } + + gst_event_unref(event); + return stream && new_tags; +} + +static gboolean sink_event_stream_start(struct wg_source *source, GstPad *pad, GstEvent *event) +{ + guint group, flags; + GstStream *stream; + gint64 duration; + const gchar *id; + + gst_event_parse_stream_start(event, &id); + gst_event_parse_stream(event, &stream); + gst_event_parse_stream_flags(event, &flags); + if (!gst_event_parse_group_id(event, &group)) + group = -1; + + if (gst_pad_peer_query_duration(pad, GST_FORMAT_TIME, &duration) && GST_CLOCK_TIME_IS_VALID(duration)) + source->max_duration = max(source->max_duration, duration); + + GST_TRACE("source %p, pad %p, stream %p, id %s, flags %#x, group %d, duration %" GST_TIME_FORMAT, + source, pad, stream, id, flags, group, GST_TIME_ARGS(duration)); + + gst_event_unref(event); + return true; +} + +static gboolean sink_event_eos(struct wg_source *source, GstPad *pad, GstEvent *event) +{ + guint index; + + GST_TRACE("source %p, pad %p, event %p", source, pad, event); + + for (index = 0; index < source->stream_count; index++) + if (source->streams[index].pad == pad) + break; + + if (index < source->stream_count) + source->streams[index].eos = true; + + gst_event_unref(event); + return true; +} + +static gboolean sink_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) +{ + struct wg_source *source = gst_pad_get_element_private(pad); + + switch (GST_EVENT_TYPE(event)) + { + case GST_EVENT_CAPS: + return sink_event_caps(source, pad, event); + case GST_EVENT_TAG: + return sink_event_tag(source, pad, event); + case GST_EVENT_STREAM_START: + return sink_event_stream_start(source, pad, event); + case GST_EVENT_EOS: + return sink_event_eos(source, pad, event); + default: + return gst_pad_event_default(pad, parent, event); + } +} + +static GstEvent *create_stream_start_event(const char *stream_id) +{ + GstStream *stream; + GstEvent *event; + + if (!(stream = gst_stream_new(stream_id, NULL, GST_STREAM_TYPE_UNKNOWN, 0))) + return NULL; + gst_stream_set_stream_flags(stream, GST_STREAM_FLAG_SELECT); + if ((event = gst_event_new_stream_start(stream_id))) + { + gst_event_set_stream(event, stream); + gst_object_unref(stream); + } + + return event; +} + +static void pad_added_cb(GstElement *element, GstPad *pad, gpointer user) +{ + struct wg_source *source = user; + char stream_id[256]; + GstFlowReturn ret; + GstPad *sink_pad; + GstEvent *event; + guint index; + + GST_TRACE("source %p, element %p, pad %p.", source, element, pad); + if ((index = source->stream_count++) >= ARRAY_SIZE(source->streams)) + { + GST_FIXME("Not enough sink pads, need %u", source->stream_count); + return; + } + + sink_pad = source->streams[index].pad; + if (gst_pad_link(pad, sink_pad) < 0 || !gst_pad_set_active(sink_pad, true)) + GST_ERROR("Failed to link new pad to sink pad %p", sink_pad); + + snprintf(stream_id, ARRAY_SIZE(stream_id), "wg_source/%03u", index); + if (!(event = create_stream_start_event(stream_id))) + GST_ERROR("Failed to create stream event for sink pad %p", sink_pad); + else + { + if ((ret = gst_pad_store_sticky_event(pad, event)) < 0) + GST_ERROR("Failed to create pad %p stream, ret %d", sink_pad, ret); + if ((ret = gst_pad_store_sticky_event(sink_pad, event)) < 0) + GST_ERROR("Failed to create pad %p stream, ret %d", sink_pad, ret); + gst_event_unref(event); + } +} + +NTSTATUS wg_source_create(void *args) +{ + struct wg_source_create_params *params = args; + GstElement *first = NULL, *last = NULL, *element; + GstCaps *src_caps, *any_caps; + struct wg_source *source; + const gchar *media_type; + GstEvent *event; + GstPad *peer; + guint i; + + if (!(src_caps = detect_caps_from_data(params->url, params->data, params->size))) + return STATUS_UNSUCCESSFUL; + if (!(source = calloc(1, sizeof(*source)))) + { + gst_caps_unref(src_caps); + return STATUS_UNSUCCESSFUL; + } + source->url = params->url ? strdup(params->url) : NULL; + gst_segment_init(&source->segment, GST_FORMAT_BYTES); + source->segment.stop = params->file_size; + + media_type = gst_structure_get_name(gst_caps_get_structure(src_caps, 0)); + if (!strcmp(media_type, "video/quicktime")) + strcpy(params->mime_type, "video/mp4"); + else if (!strcmp(media_type, "video/x-msvideo")) + strcpy(params->mime_type, "video/avi"); + else + lstrcpynA(params->mime_type, media_type, ARRAY_SIZE(params->mime_type)); + + if (!(source->container = gst_bin_new("wg_source"))) + goto error; + GST_OBJECT_FLAG_SET(source->container, GST_BIN_FLAG_STREAMS_AWARE); + + if (!(source->src_pad = create_pad_with_caps(GST_PAD_SRC, src_caps))) + goto error; + gst_pad_set_element_private(source->src_pad, source); + gst_pad_set_query_function(source->src_pad, src_query_cb); + gst_pad_set_event_function(source->src_pad, src_event_cb); + + for (i = 0; i < ARRAY_SIZE(source->streams); i++) + { + if (!(source->streams[i].pad = create_pad_with_caps(GST_PAD_SINK, NULL))) + goto error; + if (!(source->streams[i].queue = gst_atomic_queue_new(1))) + goto error; + gst_pad_set_element_private(source->streams[i].pad, source); + gst_pad_set_chain_function(source->streams[i].pad, sink_chain_cb); + gst_pad_set_event_function(source->streams[i].pad, sink_event_cb); + } + + if (!(any_caps = gst_caps_new_any())) + goto error; + if (!(element = find_element(GST_ELEMENT_FACTORY_TYPE_DECODABLE, src_caps, any_caps)) + || !append_element(source->container, element, &first, &last)) + { + gst_caps_unref(any_caps); + goto error; + } + g_signal_connect(element, "pad-added", G_CALLBACK(pad_added_cb), source); + gst_caps_unref(any_caps); + + if (!link_src_to_element(source->src_pad, first)) + goto error; + if (!gst_pad_set_active(source->src_pad, true)) + goto error; + + /* try to link the first output pad, some demuxers only have static pads */ + if ((peer = gst_element_get_static_pad(last, "src"))) + { + GstPad *sink_pad = source->streams[0].pad; + if (gst_pad_link(peer, sink_pad) < 0 || !gst_pad_set_active(sink_pad, true)) + GST_ERROR("Failed to link static source pad %p", peer); + else + source->stream_count++; + gst_object_unref(peer); + } + + gst_element_set_state(source->container, GST_STATE_PAUSED); + if (!gst_element_get_state(source->container, NULL, NULL, -1)) + goto error; + + if (!(event = create_stream_start_event("wg_source")) + || !push_event(source->src_pad, event)) + goto error; + gst_caps_unref(src_caps); + + params->source = (wg_source_t)(ULONG_PTR)source; + GST_INFO("Created winegstreamer source %p.", source); + return STATUS_SUCCESS; + +error: + if (source->container) + { + gst_element_set_state(source->container, GST_STATE_NULL); + gst_object_unref(source->container); + } + for (i = 0; i < ARRAY_SIZE(source->streams); i++) + { + if (source->streams[i].buffer) + gst_buffer_unref(source->streams[i].buffer); + if (source->streams[i].queue) + gst_atomic_queue_unref(source->streams[i].queue); + if (source->streams[i].pad) + gst_object_unref(source->streams[i].pad); + } + if (source->src_pad) + gst_object_unref(source->src_pad); + free(source->url); + free(source); + + gst_caps_unref(src_caps); + + GST_ERROR("Failed to create winegstreamer source."); + return STATUS_UNSUCCESSFUL; +} + +NTSTATUS wg_source_destroy(void *args) +{ + struct wg_source *source = get_source(*(wg_source_t *)args); + guint i; + + GST_TRACE("source %p", source); + + gst_element_set_state(source->container, GST_STATE_NULL); + gst_object_unref(source->container); + for (i = 0; i < ARRAY_SIZE(source->streams); i++) + { + if (source->streams[i].buffer) + gst_buffer_unref(source->streams[i].buffer); + gst_atomic_queue_unref(source->streams[i].queue); + gst_object_unref(source->streams[i].pad); + } + gst_object_unref(source->src_pad); + free(source->url); + free(source); + + return STATUS_SUCCESS; +} + +NTSTATUS wg_source_get_stream_count(void *args) +{ + struct wg_source_get_stream_count_params *params = args; + struct wg_source *source = get_source(params->source); + UINT i, stream_count; + GstCaps *caps; + + GST_TRACE("source %p", source); + + for (i = 0, stream_count = source->stream_count; i < stream_count; i++) + { + if (!(caps = source_get_stream_caps(source, i))) + return STATUS_PENDING; + gst_caps_unref(caps); + } + + params->stream_count = stream_count; + return STATUS_SUCCESS; +} + +NTSTATUS wg_source_get_duration(void *args) +{ + struct wg_source_get_duration_params *params = args; + struct wg_source *source = get_source(params->source); + + GST_TRACE("source %p", source); + + params->duration = source->max_duration / 100; + return STATUS_SUCCESS; +} + +NTSTATUS wg_source_get_position(void *args) +{ + struct wg_source_get_position_params *params = args; + struct wg_source *source = get_source(params->source); + + GST_TRACE("source %p", source); + + params->read_offset = source->segment.start; + return STATUS_SUCCESS; +} + +NTSTATUS wg_source_set_position(void *args) +{ + struct wg_source_set_position_params *params = args; + struct wg_source *source = get_source(params->source); + guint64 time = params->time * 100; + GstEvent *event; + guint i; + + GST_TRACE("source %p", source); + + if (!(event = gst_event_new_seek(1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, time, GST_SEEK_TYPE_NONE, -1)) + || !gst_pad_push_event(source->streams[0].pad, event)) + GST_WARNING("Failed to seek source %p to %" G_GINT64_MODIFIER "x", source, time); + + for (i = 0; i < source->stream_count; i++) + { + GstBuffer *buffer; + + while (!source_get_stream_buffer(source, i, &buffer)) + { + gst_buffer_unref(source->streams[i].buffer); + source->streams[i].buffer = NULL; + gst_buffer_unref(buffer); + } + } + + return S_OK; +} + +NTSTATUS wg_source_push_data(void *args) +{ + struct wg_source_push_data_params *params = args; + struct wg_source *source = get_source(params->source); + GstFlowReturn ret = GST_FLOW_OK; + GstBuffer *buffer; + GstEvent *event; + + GST_TRACE("source %p, data %p, size %#x", source, params->data, params->size); + + if (!source->valid_segment) + { + if (!(event = gst_event_new_segment(&source->segment)) + || !gst_pad_push_event(source->src_pad, event)) + GST_ERROR("Failed to push new segment event"); + source->valid_segment = true; + } + + if (!params->size) + { + if (source->segment.start != source->segment.stop) + goto eos; + return STATUS_SUCCESS; + } + + if (!(buffer = create_buffer_from_bytes(params->data, params->size))) + { + GST_WARNING("Failed to allocate buffer for data"); + return STATUS_UNSUCCESSFUL; + } + + source->segment.start += params->size; + if ((ret = gst_pad_push(source->src_pad, buffer)) && ret != GST_FLOW_EOS) + { + GST_WARNING("Failed to push data buffer, ret %d", ret); + source->segment.start -= params->size; + return STATUS_UNSUCCESSFUL; + } + + if (source->segment.start != source->segment.stop) + return STATUS_SUCCESS; + +eos: + if (!(event = gst_event_new_eos()) + || !gst_pad_push_event(source->src_pad, event)) + GST_WARNING("Failed to push EOS event"); + source->segment.start = source->segment.stop; + + return STATUS_SUCCESS; +} + +NTSTATUS wg_source_read_data(void *args) +{ + struct wg_source_read_data_params *params = args; + struct wg_source *source = get_source(params->source); + struct wg_sample *sample = params->sample; + NTSTATUS status = STATUS_SUCCESS; + guint index = params->index; + GstMapInfo info = {0}; + GstBuffer *buffer; + + GST_TRACE("source %p, index %#x, sample %p", source, index, sample); + + if ((status = source_get_stream_buffer(source, index, &buffer))) + return status; + + if (!gst_buffer_map(buffer, &info, GST_MAP_READ)) + status = STATUS_UNSUCCESSFUL; + else + { + if (info.size > sample->max_size) + status = STATUS_BUFFER_TOO_SMALL; + else + { + memcpy(wg_sample_data(sample), info.data, info.size); + sample->size = info.size; + + if (GST_BUFFER_PTS_IS_VALID(buffer)) + { + sample->flags |= WG_SAMPLE_FLAG_HAS_PTS; + sample->pts = GST_BUFFER_PTS(buffer) / 100; + } + else if (GST_BUFFER_DTS_IS_VALID(buffer)) + { + sample->flags |= WG_SAMPLE_FLAG_HAS_PTS; + sample->pts = GST_BUFFER_DTS(buffer) / 100; + } + if (GST_BUFFER_DURATION_IS_VALID(buffer)) + { + GstClockTime duration = GST_BUFFER_DURATION(buffer) / 100; + sample->flags |= WG_SAMPLE_FLAG_HAS_DURATION; + sample->duration = duration; + } + if (!GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DELTA_UNIT)) + sample->flags |= WG_SAMPLE_FLAG_SYNC_POINT; + if (GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DISCONT)) + sample->flags |= WG_SAMPLE_FLAG_DISCONTINUITY; + + gst_buffer_unref(source->streams[index].buffer); + source->streams[index].buffer = NULL; + } + + sample->max_size = info.size; + gst_buffer_unmap(buffer, &info); + } + + gst_buffer_unref(buffer); + return status; +} + +NTSTATUS wg_source_get_stream_format(void *args) +{ + struct wg_source_get_stream_format_params *params = args; + struct wg_source *source = get_source(params->source); + guint index = params->index; + GstCaps *caps; + + GST_TRACE("source %p, index %u", source, index); + + if (!(caps = source_get_stream_caps(source, index))) + return STATUS_UNSUCCESSFUL; + wg_format_from_caps(¶ms->format, caps); + + gst_caps_unref(caps); + return STATUS_SUCCESS; +} + +static gchar *stream_lang_from_tags(GstTagList *tags, bool is_quicktime) +{ + gchar *value; + + if (!gst_tag_list_get_string(tags, GST_TAG_LANGUAGE_CODE, &value) || !value) + return NULL; + + if (is_quicktime) + { + /* For QuickTime media, we convert the language tags to ISO 639-1. */ + const gchar *lang_code_iso_639_1 = gst_tag_get_language_code_iso_639_1(value); + gchar *tmp = lang_code_iso_639_1 ? g_strdup(lang_code_iso_639_1) : NULL; + g_free(value); + value = tmp; + } + + return value; +} + +static gchar *stream_name_from_tags(GstTagList *tags) +{ + /* Extract stream name from Quick Time demuxer private tag where it puts unrecognized chunks. */ + guint i, tag_count = gst_tag_list_get_tag_size(tags, "private-qt-tag"); + gchar *value = NULL; + + for (i = 0; !value && i < tag_count; ++i) + { + const gchar *name; + const GValue *val; + GstSample *sample; + GstBuffer *buf; + gsize size; + + if (!(val = gst_tag_list_get_value_index(tags, "private-qt-tag", i))) + continue; + if (!GST_VALUE_HOLDS_SAMPLE(val) || !(sample = gst_value_get_sample(val))) + continue; + name = gst_structure_get_name(gst_sample_get_info(sample)); + if (!name || strcmp(name, "application/x-gst-qt-name-tag")) + continue; + if (!(buf = gst_sample_get_buffer(sample))) + continue; + if ((size = gst_buffer_get_size(buf)) < 8) + continue; + size -= 8; + if (!(value = g_malloc(size + 1))) + return NULL; + if (gst_buffer_extract(buf, 8, value, size) != size) + { + g_free(value); + value = NULL; + continue; + } + value[size] = 0; + } + + return value; +} + +NTSTATUS wg_source_get_stream_tag(void *args) +{ + struct wg_source_get_stream_tag_params *params = args; + struct wg_source *source = get_source(params->source); + enum wg_parser_tag tag = params->tag; + guint index = params->index; + GstTagList *tags; + NTSTATUS status; + uint32_t len; + gchar *value; + + GST_TRACE("source %p, index %u, tag %u", source, index, tag); + + if (params->tag >= WG_PARSER_TAG_COUNT) + return STATUS_INVALID_PARAMETER; + if (!(tags = source_get_stream_tags(source, index))) + return STATUS_UNSUCCESSFUL; + + switch (tag) + { + case WG_PARSER_TAG_LANGUAGE: + { + bool is_quicktime = false; + GstCaps *caps; + + if ((caps = gst_pad_get_current_caps(source->src_pad))) + { + is_quicktime = !strcmp(media_type_from_caps(caps), "video/quicktime"); + gst_caps_unref(caps); + } + + value = stream_lang_from_tags(tags, is_quicktime); + break; + } + case WG_PARSER_TAG_NAME: + value = stream_name_from_tags(tags); + break; + default: + GST_FIXME("Unsupported stream tag %u", tag); + value = NULL; + break; + } + + if (!value) + goto error; + + if ((len = strlen(value) + 1) > params->size) + { + params->size = len; + status = STATUS_BUFFER_TOO_SMALL; + } + else + { + memcpy(params->buffer, value, len); + status = STATUS_SUCCESS; + } + + gst_tag_list_unref(tags); + g_free(value); + return status; + +error: + gst_tag_list_unref(tags); + return STATUS_NOT_FOUND; +} + +NTSTATUS wg_source_set_stream_flags(void *args) +{ + struct wg_source_set_stream_flags_params *params = args; + struct wg_source *source = get_source(params->source); + BOOL select = params->select; + guint index = params->index; + GstStreamFlags flags; + + GST_TRACE("source %p, index %u, select %u", source, index, select); + + flags = select ? GST_STREAM_FLAG_SELECT : GST_STREAM_FLAG_UNSELECT; + if (!source_set_stream_flags(source, index, flags)) + return STATUS_UNSUCCESSFUL; + + if (!select) + { + GstBuffer *buffer; + + while (!source_get_stream_buffer(source, index, &buffer)) + { + gst_buffer_unref(source->streams[index].buffer); + source->streams[index].buffer = NULL; + gst_buffer_unref(buffer); + } + } + + return STATUS_SUCCESS; +} diff --git a/dlls/winegstreamer/wg_task_pool.c b/dlls/winegstreamer/wg_task_pool.c new file mode 100644 index 00000000000..ec7da286d5f --- /dev/null +++ b/dlls/winegstreamer/wg_task_pool.c @@ -0,0 +1,98 @@ +/* + * GStreamer task pool + * + * Copyright 2023 RĂ©mi Bernon for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include +#include + +#include + +#include "unix_private.h" + +typedef struct +{ + GstTaskPool parent; +} WgTaskPool; + +typedef struct +{ + GstTaskPoolClass parent_class; +} WgTaskPoolClass; + +G_DEFINE_TYPE(WgTaskPool, wg_task_pool, GST_TYPE_TASK_POOL); + +static void wg_task_pool_prepare(GstTaskPool *pool, GError **error) +{ + GST_LOG("pool %p, error %p", pool, error); +} + +static void wg_task_pool_cleanup(GstTaskPool *pool) +{ + GST_LOG("pool %p", pool); +} + +static gpointer wg_task_pool_push(GstTaskPool *pool, GstTaskPoolFunction func, gpointer data, GError **error) +{ + pthread_t *tid; + gint res; + + GST_LOG("pool %p, func %p, data %p, error %p", pool, func, data, error); + + if (!(tid = malloc(sizeof(*tid))) || !(res = pthread_create(tid, NULL, (void *)func, data))) + return tid; + + g_set_error(error, G_THREAD_ERROR, G_THREAD_ERROR_AGAIN, "Error creating thread: %s", g_strerror(res)); + free(tid); + + return NULL; +} + +static void wg_task_pool_join(GstTaskPool *pool, gpointer id) +{ + pthread_t *tid = id; + + GST_LOG("pool %p, id %p", pool, id); + + pthread_join(*tid, NULL); + free(tid); +} + +static void wg_task_pool_class_init(WgTaskPoolClass *klass) +{ + GstTaskPoolClass *parent_class = (GstTaskPoolClass *)klass; + parent_class->prepare = wg_task_pool_prepare; + parent_class->cleanup = wg_task_pool_cleanup; + parent_class->push = wg_task_pool_push; + parent_class->join = wg_task_pool_join; +} + +static void wg_task_pool_init(WgTaskPool *pool) +{ +} + +GstTaskPool *wg_task_pool_new(void) +{ + return g_object_new(wg_task_pool_get_type(), NULL); +} diff --git a/dlls/winegstreamer/wg_transform.c b/dlls/winegstreamer/wg_transform.c index 1f6bdb103d6..3a17ac9b401 100644 --- a/dlls/winegstreamer/wg_transform.c +++ b/dlls/winegstreamer/wg_transform.c @@ -87,7 +87,7 @@ static GstFlowReturn transform_sink_chain_cb(GstPad *pad, GstObject *parent, Gst struct wg_transform *transform = gst_pad_get_element_private(pad); GstSample *sample; - GST_LOG("transform %p, buffer %p.", transform, buffer); + GST_LOG("transform %p, %"GST_PTR_FORMAT, transform, buffer); if (!(sample = gst_sample_new(buffer, transform->output_caps, NULL, NULL))) { @@ -107,7 +107,7 @@ static GstFlowReturn transform_sink_chain_cb(GstPad *pad, GstObject *parent, Gst static gboolean transform_src_query_latency(struct wg_transform *transform, GstQuery *query) { - GST_LOG("transform %p, query %p", transform, query); + GST_LOG("transform %p, %"GST_PTR_FORMAT, transform, query); gst_query_set_latency(query, transform->attrs.low_latency, 0, 0); return true; } @@ -121,132 +121,177 @@ static gboolean transform_src_query_cb(GstPad *pad, GstObject *parent, GstQuery case GST_QUERY_LATENCY: return transform_src_query_latency(transform, query); default: + GST_TRACE("transform %p, ignoring %"GST_PTR_FORMAT, transform, query); return gst_pad_query_default(pad, parent, query); } } -static gboolean transform_sink_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) +static gboolean transform_sink_query_allocation(struct wg_transform *transform, GstQuery *query) { - struct wg_transform *transform = gst_pad_get_element_private(pad); + gsize plane_align = transform->attrs.output_plane_align; + GstStructure *config, *params; + GstVideoAlignment align; + gboolean needs_pool; + GstBufferPool *pool; + GstVideoInfo info; + GstCaps *caps; - GST_LOG("transform %p, type \"%s\".", transform, gst_query_type_get_name(query->type)); + GST_LOG("transform %p, %"GST_PTR_FORMAT, transform, query); - switch (query->type) - { - case GST_QUERY_ALLOCATION: - { - gsize plane_align = transform->attrs.output_plane_align; - GstStructure *config, *params; - GstVideoAlignment align; - gboolean needs_pool; - GstBufferPool *pool; - GstVideoInfo info; - GstCaps *caps; + gst_query_parse_allocation(query, &caps, &needs_pool); + if (stream_type_from_caps(caps) != GST_STREAM_TYPE_VIDEO || !needs_pool) + return false; - gst_query_parse_allocation(query, &caps, &needs_pool); - if (stream_type_from_caps(caps) != GST_STREAM_TYPE_VIDEO || !needs_pool) - break; + if (!gst_video_info_from_caps(&info, caps) + || !(pool = gst_video_buffer_pool_new())) + return false; - if (!gst_video_info_from_caps(&info, caps) - || !(pool = gst_video_buffer_pool_new())) - break; + align_video_info_planes(plane_align, &info, &align); - align_video_info_planes(plane_align, &info, &align); + if ((params = gst_structure_new("video-meta", + "padding-top", G_TYPE_UINT, align.padding_top, + "padding-bottom", G_TYPE_UINT, align.padding_bottom, + "padding-left", G_TYPE_UINT, align.padding_left, + "padding-right", G_TYPE_UINT, align.padding_right, + NULL))) + { + gst_query_add_allocation_meta(query, GST_VIDEO_META_API_TYPE, params); + gst_structure_free(params); + } - if ((params = gst_structure_new("video-meta", - "padding-top", G_TYPE_UINT, align.padding_top, - "padding-bottom", G_TYPE_UINT, align.padding_bottom, - "padding-left", G_TYPE_UINT, align.padding_left, - "padding-right", G_TYPE_UINT, align.padding_right, - NULL))) - { - gst_query_add_allocation_meta(query, GST_VIDEO_META_API_TYPE, params); - gst_structure_free(params); - } + if (!(config = gst_buffer_pool_get_config(pool))) + GST_ERROR("Failed to get %"GST_PTR_FORMAT" config.", pool); + else + { + gst_buffer_pool_config_add_option(config, GST_BUFFER_POOL_OPTION_VIDEO_META); + gst_buffer_pool_config_add_option(config, GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); + gst_buffer_pool_config_set_video_alignment(config, &align); + + gst_buffer_pool_config_set_params(config, caps, + info.size, 0, 0); + gst_buffer_pool_config_set_allocator(config, transform->allocator, NULL); + if (!gst_buffer_pool_set_config(pool, config)) + GST_ERROR("Failed to set %"GST_PTR_FORMAT" config.", pool); + } - if (!(config = gst_buffer_pool_get_config(pool))) - GST_ERROR("Failed to get pool %p config.", pool); - else - { - gst_buffer_pool_config_add_option(config, GST_BUFFER_POOL_OPTION_VIDEO_META); - gst_buffer_pool_config_add_option(config, GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); - gst_buffer_pool_config_set_video_alignment(config, &align); - - gst_buffer_pool_config_set_params(config, caps, - info.size, 0, 0); - gst_buffer_pool_config_set_allocator(config, transform->allocator, NULL); - if (!gst_buffer_pool_set_config(pool, config)) - GST_ERROR("Failed to set pool %p config.", pool); - } + /* Prevent pool reconfiguration, we don't want another alignment. */ + if (!gst_buffer_pool_set_active(pool, true)) + GST_ERROR("%"GST_PTR_FORMAT" failed to activate.", pool); - /* Prevent pool reconfiguration, we don't want another alignment. */ - if (!gst_buffer_pool_set_active(pool, true)) - GST_ERROR("Pool %p failed to activate.", pool); + gst_query_add_allocation_pool(query, pool, info.size, 0, 0); + gst_query_add_allocation_param(query, transform->allocator, NULL); - gst_query_add_allocation_pool(query, pool, info.size, 0, 0); - gst_query_add_allocation_param(query, transform->allocator, NULL); + GST_INFO("Proposing %"GST_PTR_FORMAT", buffer size %#zx, %"GST_PTR_FORMAT", for %"GST_PTR_FORMAT, + pool, info.size, transform->allocator, query); - GST_INFO("Proposing pool %p, buffer size %#zx, allocator %p, for query %p.", - pool, info.size, transform->allocator, query); + g_object_unref(pool); + return true; +} - g_object_unref(pool); - return true; - } +static GstCaps *transform_format_to_caps(struct wg_transform *transform, const struct wg_format *format) +{ + struct wg_format copy = *format; - case GST_QUERY_CAPS: - { - GstCaps *caps, *filter, *temp; + if (format->major_type == WG_MAJOR_TYPE_VIDEO) + { + if (transform->attrs.allow_size_change) + copy.u.video.width = copy.u.video.height = 0; + copy.u.video.fps_n = copy.u.video.fps_d = 0; + } - gst_query_parse_caps(query, &filter); - if (!(caps = wg_format_to_caps(&transform->output_format))) - break; + return wg_format_to_caps(©); +} - if (filter) - { - temp = gst_caps_intersect(caps, filter); - gst_caps_unref(caps); - caps = temp; - } +static gboolean transform_sink_query_caps(struct wg_transform *transform, GstQuery *query) +{ + GstCaps *caps, *filter, *temp; - GST_INFO("Returning caps %" GST_PTR_FORMAT, caps); + GST_LOG("transform %p, %"GST_PTR_FORMAT, transform, query); - gst_query_set_caps_result(query, caps); - gst_caps_unref(caps); - return true; - } + gst_query_parse_caps(query, &filter); + if (!(caps = transform_format_to_caps(transform, &transform->output_format))) + return false; - default: - GST_WARNING("Ignoring \"%s\" query.", gst_query_type_get_name(query->type)); - break; + if (filter) + { + temp = gst_caps_intersect(caps, filter); + gst_caps_unref(caps); + caps = temp; } - return gst_pad_query_default(pad, parent, query); + GST_INFO("Returning caps %" GST_PTR_FORMAT, caps); + + gst_query_set_caps_result(query, caps); + gst_caps_unref(caps); + return true; } -static gboolean transform_sink_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) +static gboolean transform_sink_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) { struct wg_transform *transform = gst_pad_get_element_private(pad); - GST_LOG("transform %p, type \"%s\".", transform, GST_EVENT_TYPE_NAME(event)); + switch (query->type) + { + case GST_QUERY_ALLOCATION: + if (transform_sink_query_allocation(transform, query)) + return true; + break; + case GST_QUERY_CAPS: + if (transform_sink_query_caps(transform, query)) + return true; + break; + default: + break; + } - switch (event->type) + GST_TRACE("transform %p, ignoring %"GST_PTR_FORMAT, transform, query); + return gst_pad_query_default(pad, parent, query); +} + +static gboolean transform_output_caps_is_compatible(struct wg_transform *transform, GstCaps *caps) +{ + GstCaps *copy = gst_caps_copy(caps); + gboolean ret; + gsize i; + + for (i = 0; i < gst_caps_get_size(copy); ++i) { - case GST_EVENT_CAPS: - { - GstCaps *caps; + GstStructure *structure = gst_caps_get_structure(copy, i); + gst_structure_remove_fields(structure, "framerate", NULL); + } - gst_event_parse_caps(event, &caps); + ret = gst_caps_is_always_compatible(transform->output_caps, copy); + gst_caps_unref(copy); + return ret; +} - transform->output_caps_changed = transform->output_caps_changed - || !gst_caps_is_always_compatible(transform->output_caps, caps); +static void transform_sink_event_caps(struct wg_transform *transform, GstEvent *event) +{ + GstCaps *caps; - gst_caps_unref(transform->output_caps); - transform->output_caps = gst_caps_ref(caps); - break; - } - default: - GST_WARNING("Ignoring \"%s\" event.", GST_EVENT_TYPE_NAME(event)); - break; + GST_LOG("transform %p, %"GST_PTR_FORMAT, transform, event); + + gst_event_parse_caps(event, &caps); + + transform->output_caps_changed = transform->output_caps_changed + || !transform_output_caps_is_compatible(transform, caps); + + gst_caps_unref(transform->output_caps); + transform->output_caps = gst_caps_ref(caps); +} + +static gboolean transform_sink_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) +{ + struct wg_transform *transform = gst_pad_get_element_private(pad); + + switch (event->type) + { + case GST_EVENT_CAPS: + transform_sink_event_caps(transform, event); + break; + default: + GST_TRACE("transform %p, ignoring %"GST_PTR_FORMAT, transform, event); + break; } gst_event_unref(event); @@ -319,7 +364,7 @@ NTSTATUS wg_transform_create(void *args) transform->attrs = *params->attrs; transform->output_format = output_format; - if (!(src_caps = wg_format_to_caps(&input_format))) + if (!(src_caps = transform_format_to_caps(transform, &input_format))) goto out; if (!(template = gst_pad_template_new("src", GST_PAD_SRC, GST_PAD_ALWAYS, src_caps))) goto out; @@ -328,10 +373,12 @@ NTSTATUS wg_transform_create(void *args) if (!transform->my_src) goto out; + GST_INFO("transform %p input caps %"GST_PTR_FORMAT, transform, src_caps); + gst_pad_set_element_private(transform->my_src, transform); gst_pad_set_query_function(transform->my_src, transform_src_query_cb); - if (!(transform->output_caps = wg_format_to_caps(&output_format))) + if (!(transform->output_caps = transform_format_to_caps(transform, &output_format))) goto out; if (!(template = gst_pad_template_new("sink", GST_PAD_SINK, GST_PAD_ALWAYS, transform->output_caps))) goto out; @@ -340,6 +387,8 @@ NTSTATUS wg_transform_create(void *args) if (!transform->my_sink) goto out; + GST_INFO("transform %p output caps %"GST_PTR_FORMAT, transform, transform->output_caps); + gst_pad_set_element_private(transform->my_sink, transform); gst_pad_set_event_function(transform->my_sink, transform_sink_event_cb); gst_pad_set_query_function(transform->my_sink, transform_sink_query_cb); @@ -355,10 +404,12 @@ NTSTATUS wg_transform_create(void *args) switch (input_format.major_type) { - case WG_MAJOR_TYPE_VIDEO_H264: + case WG_MAJOR_TYPE_AUDIO_ENCODED: case WG_MAJOR_TYPE_AUDIO_MPEG1: case WG_MAJOR_TYPE_AUDIO_MPEG4: case WG_MAJOR_TYPE_AUDIO_WMA: + case WG_MAJOR_TYPE_VIDEO_ENCODED: + case WG_MAJOR_TYPE_VIDEO_H264: case WG_MAJOR_TYPE_VIDEO_CINEPAK: case WG_MAJOR_TYPE_VIDEO_INDEO: case WG_MAJOR_TYPE_VIDEO_WMV: @@ -406,7 +457,7 @@ NTSTATUS wg_transform_create(void *args) case WG_MAJOR_TYPE_VIDEO: { const char *sgi; - if ((sgi = getenv("SteamGameId")) && (!strcmp(sgi, "2009100"))) + if ((sgi = getenv("SteamGameId")) && ((!strcmp(sgi, "2009100")) || (!strcmp(sgi, "2555360")))) { if (!(element = create_element("videoconvert", "base")) || !append_element(transform->container, element, &first, &last)) @@ -437,11 +488,13 @@ NTSTATUS wg_transform_create(void *args) case WG_MAJOR_TYPE_AUDIO_MPEG1: case WG_MAJOR_TYPE_AUDIO_MPEG4: case WG_MAJOR_TYPE_AUDIO_WMA: + case WG_MAJOR_TYPE_AUDIO_ENCODED: case WG_MAJOR_TYPE_VIDEO_CINEPAK: case WG_MAJOR_TYPE_VIDEO_H264: case WG_MAJOR_TYPE_VIDEO_INDEO: case WG_MAJOR_TYPE_VIDEO_WMV: case WG_MAJOR_TYPE_VIDEO_MPEG1: + case WG_MAJOR_TYPE_VIDEO_ENCODED: GST_FIXME("Format %u not implemented!", output_format.major_type); goto out; } @@ -530,14 +583,16 @@ NTSTATUS wg_transform_set_output_format(void *args) GstSample *sample; GstCaps *caps; - if (!(caps = wg_format_to_caps(format))) + if (!(caps = transform_format_to_caps(transform, format))) { GST_ERROR("Failed to convert format %p to caps.", format); return STATUS_UNSUCCESSFUL; } transform->output_format = *format; - if (gst_caps_is_always_compatible(transform->output_caps, caps)) + GST_INFO("transform %p output caps %"GST_PTR_FORMAT, transform, caps); + + if (transform_output_caps_is_compatible(transform, caps)) { gst_caps_unref(caps); return STATUS_SUCCESS; @@ -614,7 +669,7 @@ NTSTATUS wg_transform_push_data(void *args) else { InterlockedIncrement(&sample->refcount); - GST_INFO("Wrapped %u/%u bytes from sample %p to buffer %p", sample->size, sample->max_size, sample, buffer); + GST_INFO("Wrapped %u/%u bytes from sample %p to %"GST_PTR_FORMAT, sample->size, sample->max_size, sample, buffer); } if (sample->flags & WG_SAMPLE_FLAG_HAS_PTS) @@ -721,7 +776,7 @@ static NTSTATUS read_transform_output_data(GstBuffer *buffer, GstCaps *caps, gsi if (!gst_buffer_map(buffer, &info, GST_MAP_READ)) { - GST_ERROR("Failed to map buffer %p", buffer); + GST_ERROR("Failed to map buffer %"GST_PTR_FORMAT, buffer); sample->size = 0; return STATUS_UNSUCCESSFUL; } @@ -738,7 +793,7 @@ static NTSTATUS read_transform_output_data(GstBuffer *buffer, GstCaps *caps, gsi if (status) { - GST_ERROR("Failed to copy buffer %p", buffer); + GST_ERROR("Failed to copy buffer %"GST_PTR_FORMAT, buffer); sample->size = 0; return status; } @@ -827,6 +882,8 @@ NTSTATUS wg_transform_read_data(void *args) { GST_MINI_OBJECT_FLAG_UNSET(transform->output_sample, GST_SAMPLE_FLAG_WG_CAPS_CHANGED); + GST_INFO("transform %p output caps %"GST_PTR_FORMAT, transform, output_caps); + if (format) { gsize plane_align = transform->attrs.output_plane_align; diff --git a/dlls/winegstreamer/winegstreamer_classes.idl b/dlls/winegstreamer/winegstreamer_classes.idl index 16b24a23261..dc56ad2b0ab 100644 --- a/dlls/winegstreamer/winegstreamer_classes.idl +++ b/dlls/winegstreamer/winegstreamer_classes.idl @@ -83,6 +83,24 @@ coclass VideoProcessorMFT {} ] coclass GStreamerByteStreamHandler {} +[ + threading(both), + uuid(317df619-5e5a-468a-9f15-d827a9a08162) +] +coclass GStreamerByteStreamHandler2 {} + +[ + threading(both), + uuid(480b1517-c8e9-4eae-b006-e6300718d85d) +] +coclass GStreamerAudioDecoder {} + +[ + threading(both), + uuid(480b1518-c8e9-4eae-b006-e6300718d85d) +] +coclass GStreamerVideoDecoder {} + [ threading(both), uuid(2eeb4adf-4578-4d10-bca7-bb955f56320a) diff --git a/dlls/winegstreamer/wm_reader.c b/dlls/winegstreamer/wm_reader.c index ffb78f3cbc9..a172f1cfbb3 100644 --- a/dlls/winegstreamer/wm_reader.c +++ b/dlls/winegstreamer/wm_reader.c @@ -1597,7 +1597,7 @@ static HRESULT reinit_stream(struct wm_reader *reader, bool read_compressed) struct wm_stream *stream = &reader->streams[i]; struct wg_format format; - stream->wg_stream = wg_parser_get_stream(reader->wg_parser, i); + stream->wg_stream = wg_parser_get_stream(reader->wg_parser, reader->stream_count - i - 1); stream->reader = reader; wg_parser_stream_get_preferred_format(stream->wg_stream, &format); if (stream->selection == WMT_ON) @@ -1675,6 +1675,10 @@ static const char *get_major_type_string(enum wg_major_type type) return "indeo"; case WG_MAJOR_TYPE_VIDEO_MPEG1: return "mpeg1-video"; + case WG_MAJOR_TYPE_AUDIO_ENCODED: + return "unknown-audio"; + case WG_MAJOR_TYPE_VIDEO_ENCODED: + return "unknown-video"; case WG_MAJOR_TYPE_UNKNOWN: return "unknown"; } @@ -2043,11 +2047,13 @@ static HRESULT WINAPI reader_GetOutputFormat(IWMSyncReader2 *iface, case WG_MAJOR_TYPE_AUDIO_MPEG1: case WG_MAJOR_TYPE_AUDIO_MPEG4: case WG_MAJOR_TYPE_AUDIO_WMA: + case WG_MAJOR_TYPE_AUDIO_ENCODED: case WG_MAJOR_TYPE_VIDEO_CINEPAK: case WG_MAJOR_TYPE_VIDEO_H264: case WG_MAJOR_TYPE_VIDEO_WMV: case WG_MAJOR_TYPE_VIDEO_INDEO: case WG_MAJOR_TYPE_VIDEO_MPEG1: + case WG_MAJOR_TYPE_VIDEO_ENCODED: FIXME("Format %u not implemented!\n", format.major_type); break; case WG_MAJOR_TYPE_UNKNOWN: @@ -2086,11 +2092,13 @@ static HRESULT WINAPI reader_GetOutputFormatCount(IWMSyncReader2 *iface, DWORD o case WG_MAJOR_TYPE_AUDIO_MPEG1: case WG_MAJOR_TYPE_AUDIO_MPEG4: case WG_MAJOR_TYPE_AUDIO_WMA: + case WG_MAJOR_TYPE_AUDIO_ENCODED: case WG_MAJOR_TYPE_VIDEO_CINEPAK: case WG_MAJOR_TYPE_VIDEO_H264: case WG_MAJOR_TYPE_VIDEO_WMV: case WG_MAJOR_TYPE_VIDEO_INDEO: case WG_MAJOR_TYPE_VIDEO_MPEG1: + case WG_MAJOR_TYPE_VIDEO_ENCODED: FIXME("Format %u not implemented!\n", format.major_type); /* fallthrough */ case WG_MAJOR_TYPE_AUDIO: @@ -2242,18 +2250,36 @@ static HRESULT WINAPI reader_Open(IWMSyncReader2 *iface, const WCHAR *filename) static HRESULT WINAPI reader_OpenStream(IWMSyncReader2 *iface, IStream *stream) { + static const ULONG64 canary_size = 0xdeadbeeffeedcafe; struct wm_reader *reader = impl_from_IWMSyncReader2(iface); STATSTG stat; HRESULT hr; TRACE("reader %p, stream %p.\n", reader, stream); + stat.cbSize.QuadPart = canary_size; if (FAILED(hr = IStream_Stat(stream, &stat, STATFLAG_NONAME))) { ERR("Failed to stat stream, hr %#lx.\n", hr); return hr; } + if (stat.cbSize.QuadPart == canary_size) + { + /* Call of Juarez: Gunslinger implements IStream_Stat as an empty function returning S_OK, leaving + * the output stat unchanged. Windows doesn't call IStream_Seek(_SEEK_END) and probably validates + * the size against WMV file headers so the bigger cbSize doesn't change anything. + * Such streams work as soon as the uninitialized cbSize is big enough which is usually the case + * (if that is not the case Windows will favour shorter cbSize). */ + static const LARGE_INTEGER zero = { 0 }; + ULARGE_INTEGER pos = { .QuadPart = canary_size }; + + if (SUCCEEDED(hr = IStream_Seek(stream, zero, STREAM_SEEK_END, &pos))) + IStream_Seek(stream, zero, STREAM_SEEK_SET, NULL); + stat.cbSize.QuadPart = pos.QuadPart == canary_size ? 0 : pos.QuadPart; + ERR("IStream_Stat did not fill the stream size, size from _Seek %I64u.\n", stat.cbSize.QuadPart); + } + EnterCriticalSection(&reader->cs); if (reader->wg_parser) @@ -2439,8 +2465,11 @@ static HRESULT WINAPI reader_SetReadStreamSamples(IWMSyncReader2 *iface, WORD st return E_INVALIDARG; } - stream->read_compressed = compressed; - reinit_stream(reader, compressed); + if (stream->read_compressed != compressed) + { + stream->read_compressed = compressed; + reinit_stream(reader, compressed); + } LeaveCriticalSection(&reader->cs); return S_OK; diff --git a/dlls/winegstreamer/wma_decoder.c b/dlls/winegstreamer/wma_decoder.c index 9a9ea857e0d..a4380d3a3b5 100644 --- a/dlls/winegstreamer/wma_decoder.c +++ b/dlls/winegstreamer/wma_decoder.c @@ -501,7 +501,18 @@ static HRESULT WINAPI transform_ProcessEvent(IMFTransform *iface, DWORD id, IMFM static HRESULT WINAPI transform_ProcessMessage(IMFTransform *iface, MFT_MESSAGE_TYPE message, ULONG_PTR param) { - FIXME("iface %p, message %#x, param %p stub!\n", iface, message, (void *)param); + struct wma_decoder *decoder = impl_from_IMFTransform(iface); + + TRACE("iface %p, message %#x, param %p.\n", iface, message, (void *)param); + + if (!decoder->wg_transform) + return MF_E_TRANSFORM_TYPE_NOT_SET; + + if (message == MFT_MESSAGE_COMMAND_DRAIN) + return wg_transform_drain(decoder->wg_transform); + + FIXME("Ignoring message %#x.\n", message); + return S_OK; } diff --git a/dlls/winegstreamer/wmv_decoder.c b/dlls/winegstreamer/wmv_decoder.c index 48940573f08..edd6c744e94 100644 --- a/dlls/winegstreamer/wmv_decoder.c +++ b/dlls/winegstreamer/wmv_decoder.c @@ -511,16 +511,6 @@ static HRESULT WINAPI media_object_SetInputType(IMediaObject *iface, DWORD index if (!amt_to_wg_format((const AM_MEDIA_TYPE *)type, &wg_format)) return DMO_E_TYPE_NOT_ACCEPTED; - if (wg_format.major_type == WG_MAJOR_TYPE_VIDEO_WMV) - { - wg_format.u.video_wmv.fps_n = 0; - wg_format.u.video_wmv.fps_d = 0; - } - else if (wg_format.major_type == WG_MAJOR_TYPE_VIDEO) - { - wg_format.u.video.fps_n = 0; - wg_format.u.video.fps_d = 0; - } if (flags & DMO_SET_TYPEF_TEST_ONLY) return S_OK; @@ -578,8 +568,6 @@ static HRESULT WINAPI media_object_SetOutputType(IMediaObject *iface, DWORD inde if (!amt_to_wg_format((const AM_MEDIA_TYPE *)type, &wg_format)) return DMO_E_TYPE_NOT_ACCEPTED; assert(wg_format.major_type == WG_MAJOR_TYPE_VIDEO); - wg_format.u.video.fps_n = 0; - wg_format.u.video.fps_d = 0; if (flags & DMO_SET_TYPEF_TEST_ONLY) return S_OK; diff --git a/dlls/winevulkan/loader.c b/dlls/winevulkan/loader.c index 3f6204139fb..e35b4f42427 100644 --- a/dlls/winevulkan/loader.c +++ b/dlls/winevulkan/loader.c @@ -430,7 +430,7 @@ static void fixup_device_id(UINT *vendor_id, UINT *device_id) *vendor_id = 0x10de; /* NVIDIA */ *device_id = 0x2487; /* RTX 3060 */ } - else if (*vendor_id == 0x1002 && *device_id == 0x163f && (sgi = getenv("WINE_HIDE_VANGOGH_GPU")) && *sgi != '0') + else if (*vendor_id == 0x1002 && (*device_id == 0x163f || *device_id == 0x1435) && (sgi = getenv("WINE_HIDE_VANGOGH_GPU")) && *sgi != '0') { *device_id = 0x687f; /* Radeon RX Vega 56/64 */ } @@ -625,15 +625,19 @@ void WINAPI vkFreeCommandBuffers(VkDevice device, VkCommandPool cmd_pool, uint32 } } -static BOOL WINAPI call_vulkan_debug_report_callback( struct wine_vk_debug_report_params *params, ULONG size ) +static NTSTATUS WINAPI call_vulkan_debug_report_callback( void *args, ULONG size ) { - return params->user_callback(params->flags, params->object_type, params->object_handle, params->location, - params->code, params->layer_prefix, params->message, params->user_data); + struct wine_vk_debug_report_params *params = args; + VkBool32 ret = params->user_callback(params->flags, params->object_type, params->object_handle, params->location, + params->code, params->layer_prefix, params->message, params->user_data); + return NtCallbackReturn( &ret, sizeof(ret), STATUS_SUCCESS ); } -static BOOL WINAPI call_vulkan_debug_utils_callback( struct wine_vk_debug_utils_params *params, ULONG size ) +static NTSTATUS WINAPI call_vulkan_debug_utils_callback( void *args, ULONG size ) { - return params->user_callback(params->severity, params->message_types, ¶ms->data, params->user_data); + struct wine_vk_debug_utils_params *params = args; + VkBool32 ret = params->user_callback(params->severity, params->message_types, ¶ms->data, params->user_data); + return NtCallbackReturn( &ret, sizeof(ret), STATUS_SUCCESS ); } BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, void *reserved) diff --git a/dlls/winevulkan/make_vulkan b/dlls/winevulkan/make_vulkan index f8ac5976efe..467eee61c2a 100755 --- a/dlls/winevulkan/make_vulkan +++ b/dlls/winevulkan/make_vulkan @@ -64,7 +64,7 @@ from enum import Enum LOGGER = logging.Logger("vulkan") LOGGER.addHandler(logging.StreamHandler()) -VK_XML_VERSION = "1.3.272" +VK_XML_VERSION = "1.3.279" WINE_VK_VERSION = (1, 3) # Filenames to create. @@ -101,6 +101,7 @@ UNSUPPORTED_EXTENSIONS = [ "VK_KHR_external_fence_win32", # Relates to external_semaphore and needs type conversions in bitflags. "VK_KHR_shared_presentable_image", # Needs WSI work. + "VK_KHR_video_encode_queue", "VK_KHR_video_queue", # TODO Video extensions use separate headers + xml "VK_NV_external_memory_rdma", # Needs shared resources work. @@ -125,6 +126,7 @@ UNSUPPORTED_EXTENSIONS = [ # winevulkan may nonetheless use, or extensions we want to generate headers for # but not expose to applications (useful for test commits) UNEXPOSED_EXTENSIONS = { + "VK_EXT_map_memory_placed", } # The Vulkan loader provides entry-points for core functionality and important @@ -278,6 +280,7 @@ MANUAL_UNIX_THUNKS = { "vkFreeCommandBuffers", "vkFreeMemory", "vkGetCalibratedTimestampsEXT", + "vkGetCalibratedTimestampsKHR", "vkGetDeviceProcAddr", "vkGetMemoryWin32HandleKHR", "vkGetMemoryWin32HandlePropertiesKHR", @@ -285,6 +288,7 @@ MANUAL_UNIX_THUNKS = { "vkGetDeviceQueue2", "vkGetInstanceProcAddr", "vkGetPhysicalDeviceCalibrateableTimeDomainsEXT", + "vkGetPhysicalDeviceCalibrateableTimeDomainsKHR", "vkGetPhysicalDeviceExternalBufferProperties", "vkGetPhysicalDeviceExternalBufferPropertiesKHR", "vkGetPhysicalDeviceExternalFenceProperties", @@ -1396,7 +1400,7 @@ class VkVariable(object): Vulkan uses this for dynamically sized arrays for which there is a 'count' parameter. """ - return self.dyn_array_len is not None + return self.dyn_array_len is not None and self.array_len is None def is_static_array(self): """ Returns if the member is an array. @@ -2121,14 +2125,11 @@ class VkStruct(Sequence): # Those structs seem to be broken in spec, they are specified as # returned only, but documented as input structs. - if name in ["VkSubpassShadingPipelineCreateInfoHUAWEI", - "VkPipelineShaderStageRequiredSubgroupSizeCreateInfo"]: + if name in ["VkPipelineShaderStageRequiredSubgroupSizeCreateInfo"]: returnedonly = False # Those structs don't have returnedonly in spec, but they could (should?). - if name in ["VkSurfaceCapabilitiesPresentBarrierNV", - "VkCooperativeMatrixPropertiesNV", - "VkPerformanceValueINTEL"]: + if name in ["VkSurfaceCapabilitiesPresentBarrierNV"]: returnedonly = True structextends = struct.attrib.get("structextends") diff --git a/dlls/winevulkan/vk.xml b/dlls/winevulkan/vk.xml index a696de6f012..ee9221e9eb3 100644 --- a/dlls/winevulkan/vk.xml +++ b/dlls/winevulkan/vk.xml @@ -1,7 +1,7 @@ -Copyright 2015-2023 The Khronos Group Inc. +Copyright 2015-2024 The Khronos Group Inc. SPDX-License-Identifier: Apache-2.0 OR MIT @@ -175,11 +175,11 @@ branch of the member gitlab server. #define VKSC_API_VERSION_1_0 VK_MAKE_API_VERSION(VKSC_API_VARIANT, 1, 0, 0)// Patch version should always be set to 0 // Version of this file -#define VK_HEADER_VERSION 267 +#define VK_HEADER_VERSION 279 // Complete version of this file #define VK_HEADER_VERSION_COMPLETE VK_MAKE_API_VERSION(0, 1, 3, VK_HEADER_VERSION) // Version of this file -#define VK_HEADER_VERSION 13 +#define VK_HEADER_VERSION 14 // Complete version of this file #define VK_HEADER_VERSION_COMPLETE VK_MAKE_API_VERSION(VKSC_API_VARIANT, 1, 0, VK_HEADER_VERSION) @@ -338,8 +338,8 @@ typedef void* MTLSharedEvent_id; typedef VkFlags VkCommandBufferResetFlags; typedef VkFlags VkCommandBufferUsageFlags; typedef VkFlags VkQueryPipelineStatisticFlags; - typedef VkFlags VkMemoryMapFlags; - typedef VkFlags VkMemoryUnmapFlagsKHR; + typedef VkFlags VkMemoryMapFlags; + typedef VkFlags VkMemoryUnmapFlagsKHR; typedef VkFlags VkImageAspectFlags; typedef VkFlags VkSparseMemoryBindFlags; typedef VkFlags VkSparseImageFormatFlags; @@ -482,6 +482,7 @@ typedef void* MTLSharedEvent_id; typedef VkFlags VkPresentScalingFlagsEXT; typedef VkFlags VkPresentGravityFlagsEXT; typedef VkFlags VkShaderCreateFlagsEXT; + typedef VkFlags64 VkPhysicalDeviceSchedulingControlsFlagsARM; Video Core extension typedef VkFlags VkVideoCodecOperationFlagsKHR; @@ -501,7 +502,7 @@ typedef void* MTLSharedEvent_id; typedef VkFlags VkVideoDecodeH264PictureLayoutFlagsKHR; Video Encode Core extension - typedef VkFlags VkVideoEncodeFlagsKHR; + typedef VkFlags VkVideoEncodeFlagsKHR; typedef VkFlags VkVideoEncodeUsageFlagsKHR; typedef VkFlags VkVideoEncodeContentFlagsKHR; typedef VkFlags VkVideoEncodeCapabilityFlagsKHR; @@ -512,16 +513,16 @@ typedef void* MTLSharedEvent_id; typedef VkFlags VkVideoComponentBitDepthFlagsKHR; Video Encode H.264 extension - typedef VkFlags VkVideoEncodeH264CapabilityFlagsEXT; - typedef VkFlags VkVideoEncodeH264StdFlagsEXT; - typedef VkFlags VkVideoEncodeH264RateControlFlagsEXT; + typedef VkFlags VkVideoEncodeH264CapabilityFlagsKHR; + typedef VkFlags VkVideoEncodeH264StdFlagsKHR; + typedef VkFlags VkVideoEncodeH264RateControlFlagsKHR; Video Encode H.265 extension - typedef VkFlags VkVideoEncodeH265CapabilityFlagsEXT; - typedef VkFlags VkVideoEncodeH265StdFlagsEXT; - typedef VkFlags VkVideoEncodeH265RateControlFlagsEXT; - typedef VkFlags VkVideoEncodeH265CtbSizeFlagsEXT; - typedef VkFlags VkVideoEncodeH265TransformBlockSizeFlagsEXT; + typedef VkFlags VkVideoEncodeH265CapabilityFlagsKHR; + typedef VkFlags VkVideoEncodeH265StdFlagsKHR; + typedef VkFlags VkVideoEncodeH265RateControlFlagsKHR; + typedef VkFlags VkVideoEncodeH265CtbSizeFlagsKHR; + typedef VkFlags VkVideoEncodeH265TransformBlockSizeFlagsKHR; Types which can be void pointers or class pointers, selected at compile time VK_DEFINE_HANDLE(VkInstance) @@ -618,6 +619,7 @@ typedef void* MTLSharedEvent_id; + @@ -685,7 +687,8 @@ typedef void* MTLSharedEvent_id; - + + @@ -730,7 +733,8 @@ typedef void* MTLSharedEvent_id; - + + @@ -865,8 +869,11 @@ typedef void* MTLSharedEvent_id; + + + Enumerated types in the header, but not used by the API @@ -895,6 +902,7 @@ typedef void* MTLSharedEvent_id; Video H.265 Decode extensions Video Encode extensions + @@ -903,16 +911,16 @@ typedef void* MTLSharedEvent_id; Video H.264 Encode extensions - - - + + + Video H.265 Encode extensions - - - - - + + + + + The PFN_vk*Function types are used by VkAllocationCallbacks below typedef void (VKAPI_PTR *PFN_vkInternalAllocationNotification)( @@ -1037,20 +1045,20 @@ typedef void* MTLSharedEvent_id; uint32_t vendorID uint32_t deviceID VkPhysicalDeviceType deviceType - char deviceName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE] + char deviceName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE] uint8_t pipelineCacheUUID[VK_UUID_SIZE] VkPhysicalDeviceLimits limits VkPhysicalDeviceSparseProperties sparseProperties - char extensionName[VK_MAX_EXTENSION_NAME_SIZE]extension name - uint32_t specVersionversion of the extension specification implemented + char extensionName[VK_MAX_EXTENSION_NAME_SIZE]extension name + uint32_t specVersionversion of the extension specification implemented - char layerName[VK_MAX_EXTENSION_NAME_SIZE]layer name - uint32_t specVersionversion of the layer specification implemented - uint32_t implementationVersionbuild or release version of the layer's library - char description[VK_MAX_DESCRIPTION_SIZE]Free-form description of the layer + char layerName[VK_MAX_EXTENSION_NAME_SIZE]layer name + uint32_t specVersionversion of the layer specification implemented + uint32_t implementationVersionbuild or release version of the layer's library + char description[VK_MAX_DESCRIPTION_SIZE]Free-form description of the layer VkStructureType sType @@ -1106,10 +1114,10 @@ typedef void* MTLSharedEvent_id; VkExtent3D minImageTransferGranularityMinimum alignment requirement for image transfers - uint32_t memoryTypeCount - VkMemoryType memoryTypes[VK_MAX_MEMORY_TYPES] - uint32_t memoryHeapCount - VkMemoryHeap memoryHeaps[VK_MAX_MEMORY_HEAPS] + uint32_t memoryTypeCount + VkMemoryType memoryTypes[VK_MAX_MEMORY_TYPES] + uint32_t memoryHeapCount + VkMemoryHeap memoryHeaps[VK_MAX_MEMORY_HEAPS] VkStructureType sType @@ -1453,13 +1461,13 @@ typedef void* MTLSharedEvent_id; VkStructureType sType const void* pNext - VkPipelineCreateFlags flagsPipeline creation flags + VkPipelineCreateFlags flagsPipeline creation flags VkPipelineShaderStageCreateInfo stage VkPipelineLayout layoutInterface layout of the pipeline VkPipeline basePipelineHandleIf VK_PIPELINE_CREATE_DERIVATIVE_BIT is set and this value is nonzero, it specifies the handle of the base pipeline this is a derivative of int32_t basePipelineIndexIf VK_PIPELINE_CREATE_DERIVATIVE_BIT is set and this value is not -1, it specifies an index into pCreateInfos of the base pipeline this is a derivative of - + VkStructureType sType const void* pNext VkDeviceAddress deviceAddress @@ -1592,7 +1600,7 @@ typedef void* MTLSharedEvent_id; VkStructureType sType const void* pNext - VkPipelineCreateFlags flagsPipeline creation flags + VkPipelineCreateFlags flagsPipeline creation flags uint32_t stageCount const VkPipelineShaderStageCreateInfo* pStagesOne entry for each active shader stage const VkPipelineShaderStageCreateInfo* pStagesOne entry for each active shader stage @@ -1656,7 +1664,7 @@ typedef void* MTLSharedEvent_id; uint32_t offsetStart of the range, in bytes uint32_t sizeSize of the range, in bytes - + VkStructureType sType const void* pNext VkPipelineLayoutCreateFlags flags @@ -1859,7 +1867,7 @@ typedef void* MTLSharedEvent_id; VkBool32 residencyStandard2DBlockShapeSparse resources support: GPU will access all 2D (single sample) sparse resources using the standard sparse image block shapes (based on pixel format) VkBool32 residencyStandard2DMultisampleBlockShapeSparse resources support: GPU will access all 2D (multisample) sparse resources using the standard sparse image block shapes (based on pixel format) VkBool32 residencyStandard3DBlockShapeSparse resources support: GPU will access all 3D sparse resources using the standard sparse image block shapes (based on pixel format) - VkBool32 residencyAlignedMipSizeSparse resources support: Images with mip level dimensions that are NOT a multiple of the sparse image block dimensions will be placed in the mip tail + VkBool32 residencyAlignedMipSizeSparse resources support: Images with mip level dimensions that are NOT a multiple of the sparse image block dimensions will be placed in the mip tail VkBool32 residencyNonResidentStrictSparse resources support: GPU can consistently access non-resident regions of a resource, all reads return as if data is 0, writes are discarded @@ -2233,6 +2241,19 @@ typedef void* MTLSharedEvent_id; uint32_t disabledValidationFeatureCountNumber of validation features to disable const VkValidationFeatureDisableEXT* pDisabledValidationFeaturesValidation features to disable + + VkStructureType sTypeMust be VK_STRUCTURE_TYPE_LAYER_SETTINGS_CREATE_INFO_EXT + const void* pNext + uint32_t settingCountNumber of settings to configure + const VkLayerSettingEXT* pSettingsValidation features to enable + + + const char* pLayerName + const char* pSettingName + VkLayerSettingTypeEXT typeThe type of the object + uint32_t valueCountNumber of values of the setting + const void* pValuesValues to pass for a setting + VkStructureType sType const void* pNext @@ -2467,7 +2488,7 @@ typedef void* MTLSharedEvent_id; VkStructureType sType const void* pNext VkPipelineBindPoint pipelineBindPoint - VkPipeline pipeline + VkPipeline pipeline VkIndirectCommandsLayoutNV indirectCommandsLayout uint32_t streamCount const VkIndirectCommandsStreamNV* pStreams @@ -2575,8 +2596,8 @@ typedef void* MTLSharedEvent_id; VkStructureType sType void* pNext VkDriverId driverID - char driverName[VK_MAX_DRIVER_NAME_SIZE] - char driverInfo[VK_MAX_DRIVER_INFO_SIZE] + char driverName[VK_MAX_DRIVER_NAME_SIZE] + char driverInfo[VK_MAX_DRIVER_INFO_SIZE] VkConformanceVersion conformanceVersion @@ -2626,7 +2647,7 @@ typedef void* MTLSharedEvent_id; VkStructureType sType const void* pNext VkBufferCreateFlags flags - VkBufferUsageFlags usage + VkBufferUsageFlags usage VkExternalMemoryHandleTypeFlagBits handleType @@ -3007,7 +3028,7 @@ typedef void* MTLSharedEvent_id; VkStructureType sType void* pNext uint32_t physicalDeviceCount - VkPhysicalDevice physicalDevices[VK_MAX_DEVICE_GROUP_SIZE] + VkPhysicalDevice physicalDevices[VK_MAX_DEVICE_GROUP_SIZE] VkBool32 subsetAllocation @@ -3290,7 +3311,7 @@ typedef void* MTLSharedEvent_id; VkStructureType sType const void* pNext - VkSurfaceKHR surface + VkSurfaceKHR surface VkStructureType sType @@ -3701,6 +3722,18 @@ typedef void* MTLSharedEvent_id; VkBool32 nonStrictSinglePixelWideLinesUseParallelogram VkBool32 nonStrictWideLinesUseParallelogram + + VkStructureType sType + void* pNext + VkBool32 maintenance6 + + + VkStructureType sType + void* pNext + VkBool32 blockTexelViewCompatibleMultipleLayers + uint32_t maxCombinedImageSamplerDescriptorCount + VkBool32 fragmentShadingRateClampCombinerInputs + VkStructureType sType const void* pNext @@ -3809,11 +3842,11 @@ typedef void* MTLSharedEvent_id; VkBool32 globalPriorityQuery - + VkStructureType sType - void* pNext - uint32_t priorityCount - VkQueueGlobalPriorityKHR priorities[VK_MAX_GLOBAL_PRIORITY_SIZE_KHR] + void* pNext + uint32_t priorityCount + VkQueueGlobalPriorityKHR priorities[VK_MAX_GLOBAL_PRIORITY_SIZE_KHR] @@ -3853,7 +3886,7 @@ typedef void* MTLSharedEvent_id; VkDebugUtilsMessengerCallbackDataFlagsEXT flags const char* pMessageIdName int32_t messageIdNumber - const char* pMessage + const char* pMessage uint32_t queueLabelCount const VkDebugUtilsLabelEXT* pQueueLabels uint32_t cmdBufLabelCount @@ -3913,11 +3946,12 @@ typedef void* MTLSharedEvent_id; VkBool32 fullyCoveredFragmentShaderInputVariabletrue if the implementation supports the FullyCoveredEXT SPIR-V builtin fragment shader input variable VkBool32 conservativeRasterizationPostDepthCoveragetrue if the implementation supports both conservative rasterization and post depth coverage sample coverage mask - - VkStructureType sType + + VkStructureType sType const void* pNext - VkTimeDomainEXT timeDomain + VkTimeDomainKHR timeDomain + VkStructureType sType void* pNext @@ -4142,21 +4176,29 @@ typedef void* MTLSharedEvent_id; uint64_t value - + uint32_t binding uint32_t divisor - - VkStructureType sType + + + VkStructureType sType const void* pNext uint32_t vertexBindingDivisorCount - const VkVertexInputBindingDivisorDescriptionEXT* pVertexBindingDivisors + const VkVertexInputBindingDivisorDescriptionKHR* pVertexBindingDivisors + VkStructureType sType void* pNext uint32_t maxVertexAttribDivisormax value of vertex attribute divisor + + VkStructureType sType + void* pNext + uint32_t maxVertexAttribDivisormax value of vertex attribute divisor + VkBool32 supportsNonZeroFirstInstance + VkStructureType sType void* pNext @@ -4269,12 +4311,13 @@ typedef void* MTLSharedEvent_id; VkBool32 shaderImageFloat32AtomicMinMax VkBool32 sparseImageFloat32AtomicMinMax - - VkStructureType sType + + VkStructureType sType void* pNext VkBool32 vertexAttributeInstanceRateDivisor VkBool32 vertexAttributeInstanceRateZeroDivisor + VkStructureType sType void* pNext @@ -4545,7 +4588,7 @@ typedef void* MTLSharedEvent_id; VkStructureType sType const void* pNext - VkPipelineCreateFlags flagsPipeline creation flags + VkPipelineCreateFlags flagsPipeline creation flags uint32_t stageCount const VkPipelineShaderStageCreateInfo* pStagesOne entry for each active shader stage uint32_t groupCount @@ -4558,7 +4601,7 @@ typedef void* MTLSharedEvent_id; VkStructureType sType const void* pNext - VkPipelineCreateFlags flagsPipeline creation flags + VkPipelineCreateFlags flagsPipeline creation flags uint32_t stageCount const VkPipelineShaderStageCreateInfo* pStagesOne entry for each active shader stage uint32_t groupCount @@ -4977,7 +5020,7 @@ typedef void* MTLSharedEvent_id; void* pNext VkShaderStageFlags cooperativeMatrixSupportedStages - + VkStructureType sType void* pNext uint32_t MSize @@ -5078,9 +5121,9 @@ typedef void* MTLSharedEvent_id; VkStructureType sType void* pNext VkPerformanceCounterDescriptionFlagsKHR flags - char name[VK_MAX_DESCRIPTION_SIZE] - char category[VK_MAX_DESCRIPTION_SIZE] - char description[VK_MAX_DESCRIPTION_SIZE] + char name[VK_MAX_DESCRIPTION_SIZE] + char category[VK_MAX_DESCRIPTION_SIZE] + char description[VK_MAX_DESCRIPTION_SIZE] VkStructureType sType @@ -5149,7 +5192,7 @@ typedef void* MTLSharedEvent_id; VkBool32 valueBool const char* valueString - + VkPerformanceValueTypeINTEL type VkPerformanceValueDataINTEL data @@ -5192,11 +5235,12 @@ typedef void* MTLSharedEvent_id; VkBool32 shaderSubgroupClock VkBool32 shaderDeviceClock - - VkStructureType sType + + VkStructureType sType void* pNext VkBool32 indexTypeUint8 + VkStructureType sType void* pNext @@ -5254,10 +5298,10 @@ typedef void* MTLSharedEvent_id; VkStructureType sType void* pNext - VkShaderStageFlags stages - char name[VK_MAX_DESCRIPTION_SIZE] - char description[VK_MAX_DESCRIPTION_SIZE] - uint32_t subgroupSize + VkShaderStageFlags stages + char name[VK_MAX_DESCRIPTION_SIZE] + char description[VK_MAX_DESCRIPTION_SIZE] + uint32_t subgroupSize VkStructureType sType @@ -5273,19 +5317,19 @@ typedef void* MTLSharedEvent_id; VkStructureType sType - void* pNext - char name[VK_MAX_DESCRIPTION_SIZE] - char description[VK_MAX_DESCRIPTION_SIZE] + void* pNext + char name[VK_MAX_DESCRIPTION_SIZE] + char description[VK_MAX_DESCRIPTION_SIZE] VkPipelineExecutableStatisticFormatKHR format VkPipelineExecutableStatisticValueKHR value VkStructureType sType - void* pNext - char name[VK_MAX_DESCRIPTION_SIZE] - char description[VK_MAX_DESCRIPTION_SIZE] - VkBool32 isText - size_t dataSize + void* pNext + char name[VK_MAX_DESCRIPTION_SIZE] + char description[VK_MAX_DESCRIPTION_SIZE] + VkBool32 isText + size_t dataSize void* pData @@ -5331,7 +5375,7 @@ typedef void* MTLSharedEvent_id; - + VkStructureType sType void* pNext VkRenderPass renderPass @@ -5362,8 +5406,8 @@ typedef void* MTLSharedEvent_id; VkDeviceMemory memory - - VkStructureType sType + + VkStructureType sType void* pNext VkBool32 rectangularLines VkBool32 bresenhamLines @@ -5372,19 +5416,22 @@ typedef void* MTLSharedEvent_id; VkBool32 stippledBresenhamLines VkBool32 stippledSmoothLines - - VkStructureType sType + + + VkStructureType sType void* pNext uint32_t lineSubPixelPrecisionBits - - VkStructureType sType - const void* pNext - VkLineRasterizationModeEXT lineRasterizationMode + + + VkStructureType sType + const void* pNext + VkLineRasterizationModeKHR lineRasterizationMode VkBool32 stippledLineEnable uint32_t lineStippleFactor uint16_t lineStipplePattern + VkStructureType sType void* pNext @@ -5481,56 +5528,56 @@ typedef void* MTLSharedEvent_id; VkStructureType sType void* pNext VkDriverId driverID - char driverName[VK_MAX_DRIVER_NAME_SIZE] - char driverInfo[VK_MAX_DRIVER_INFO_SIZE] + char driverName[VK_MAX_DRIVER_NAME_SIZE] + char driverInfo[VK_MAX_DRIVER_INFO_SIZE] VkConformanceVersion conformanceVersion VkShaderFloatControlsIndependence denormBehaviorIndependence VkShaderFloatControlsIndependence roundingModeIndependence - VkBool32 shaderSignedZeroInfNanPreserveFloat16An implementation can preserve signed zero, nan, inf - VkBool32 shaderSignedZeroInfNanPreserveFloat32An implementation can preserve signed zero, nan, inf - VkBool32 shaderSignedZeroInfNanPreserveFloat64An implementation can preserve signed zero, nan, inf - VkBool32 shaderDenormPreserveFloat16An implementation can preserve denormals - VkBool32 shaderDenormPreserveFloat32An implementation can preserve denormals - VkBool32 shaderDenormPreserveFloat64An implementation can preserve denormals - VkBool32 shaderDenormFlushToZeroFloat16An implementation can flush to zero denormals - VkBool32 shaderDenormFlushToZeroFloat32An implementation can flush to zero denormals - VkBool32 shaderDenormFlushToZeroFloat64An implementation can flush to zero denormals - VkBool32 shaderRoundingModeRTEFloat16An implementation can support RTE - VkBool32 shaderRoundingModeRTEFloat32An implementation can support RTE - VkBool32 shaderRoundingModeRTEFloat64An implementation can support RTE - VkBool32 shaderRoundingModeRTZFloat16An implementation can support RTZ - VkBool32 shaderRoundingModeRTZFloat32An implementation can support RTZ - VkBool32 shaderRoundingModeRTZFloat64An implementation can support RTZ - uint32_t maxUpdateAfterBindDescriptorsInAllPools - VkBool32 shaderUniformBufferArrayNonUniformIndexingNative - VkBool32 shaderSampledImageArrayNonUniformIndexingNative - VkBool32 shaderStorageBufferArrayNonUniformIndexingNative - VkBool32 shaderStorageImageArrayNonUniformIndexingNative - VkBool32 shaderInputAttachmentArrayNonUniformIndexingNative - VkBool32 robustBufferAccessUpdateAfterBind - VkBool32 quadDivergentImplicitLod - uint32_t maxPerStageDescriptorUpdateAfterBindSamplers - uint32_t maxPerStageDescriptorUpdateAfterBindUniformBuffers - uint32_t maxPerStageDescriptorUpdateAfterBindStorageBuffers - uint32_t maxPerStageDescriptorUpdateAfterBindSampledImages - uint32_t maxPerStageDescriptorUpdateAfterBindStorageImages - uint32_t maxPerStageDescriptorUpdateAfterBindInputAttachments - uint32_t maxPerStageUpdateAfterBindResources - uint32_t maxDescriptorSetUpdateAfterBindSamplers - uint32_t maxDescriptorSetUpdateAfterBindUniformBuffers - uint32_t maxDescriptorSetUpdateAfterBindUniformBuffersDynamic - uint32_t maxDescriptorSetUpdateAfterBindStorageBuffers - uint32_t maxDescriptorSetUpdateAfterBindStorageBuffersDynamic - uint32_t maxDescriptorSetUpdateAfterBindSampledImages - uint32_t maxDescriptorSetUpdateAfterBindStorageImages - uint32_t maxDescriptorSetUpdateAfterBindInputAttachments - VkResolveModeFlags supportedDepthResolveModessupported depth resolve modes - VkResolveModeFlags supportedStencilResolveModessupported stencil resolve modes - VkBool32 independentResolveNonedepth and stencil resolve modes can be set independently if one of them is none - VkBool32 independentResolvedepth and stencil resolve modes can be set independently - VkBool32 filterMinmaxSingleComponentFormats - VkBool32 filterMinmaxImageComponentMapping - uint64_t maxTimelineSemaphoreValueDifference + VkBool32 shaderSignedZeroInfNanPreserveFloat16An implementation can preserve signed zero, nan, inf + VkBool32 shaderSignedZeroInfNanPreserveFloat32An implementation can preserve signed zero, nan, inf + VkBool32 shaderSignedZeroInfNanPreserveFloat64An implementation can preserve signed zero, nan, inf + VkBool32 shaderDenormPreserveFloat16An implementation can preserve denormals + VkBool32 shaderDenormPreserveFloat32An implementation can preserve denormals + VkBool32 shaderDenormPreserveFloat64An implementation can preserve denormals + VkBool32 shaderDenormFlushToZeroFloat16An implementation can flush to zero denormals + VkBool32 shaderDenormFlushToZeroFloat32An implementation can flush to zero denormals + VkBool32 shaderDenormFlushToZeroFloat64An implementation can flush to zero denormals + VkBool32 shaderRoundingModeRTEFloat16An implementation can support RTE + VkBool32 shaderRoundingModeRTEFloat32An implementation can support RTE + VkBool32 shaderRoundingModeRTEFloat64An implementation can support RTE + VkBool32 shaderRoundingModeRTZFloat16An implementation can support RTZ + VkBool32 shaderRoundingModeRTZFloat32An implementation can support RTZ + VkBool32 shaderRoundingModeRTZFloat64An implementation can support RTZ + uint32_t maxUpdateAfterBindDescriptorsInAllPools + VkBool32 shaderUniformBufferArrayNonUniformIndexingNative + VkBool32 shaderSampledImageArrayNonUniformIndexingNative + VkBool32 shaderStorageBufferArrayNonUniformIndexingNative + VkBool32 shaderStorageImageArrayNonUniformIndexingNative + VkBool32 shaderInputAttachmentArrayNonUniformIndexingNative + VkBool32 robustBufferAccessUpdateAfterBind + VkBool32 quadDivergentImplicitLod + uint32_t maxPerStageDescriptorUpdateAfterBindSamplers + uint32_t maxPerStageDescriptorUpdateAfterBindUniformBuffers + uint32_t maxPerStageDescriptorUpdateAfterBindStorageBuffers + uint32_t maxPerStageDescriptorUpdateAfterBindSampledImages + uint32_t maxPerStageDescriptorUpdateAfterBindStorageImages + uint32_t maxPerStageDescriptorUpdateAfterBindInputAttachments + uint32_t maxPerStageUpdateAfterBindResources + uint32_t maxDescriptorSetUpdateAfterBindSamplers + uint32_t maxDescriptorSetUpdateAfterBindUniformBuffers + uint32_t maxDescriptorSetUpdateAfterBindUniformBuffersDynamic + uint32_t maxDescriptorSetUpdateAfterBindStorageBuffers + uint32_t maxDescriptorSetUpdateAfterBindStorageBuffersDynamic + uint32_t maxDescriptorSetUpdateAfterBindSampledImages + uint32_t maxDescriptorSetUpdateAfterBindStorageImages + uint32_t maxDescriptorSetUpdateAfterBindInputAttachments + VkResolveModeFlags supportedDepthResolveModessupported depth resolve modes + VkResolveModeFlags supportedStencilResolveModessupported stencil resolve modes + VkBool32 independentResolveNonedepth and stencil resolve modes can be set independently if one of them is none + VkBool32 independentResolvedepth and stencil resolve modes can be set independently + VkBool32 filterMinmaxSingleComponentFormats + VkBool32 filterMinmaxImageComponentMapping + uint64_t maxTimelineSemaphoreValueDifference VkSampleCountFlags framebufferIntegerColorSampleCounts @@ -5626,12 +5673,12 @@ typedef void* MTLSharedEvent_id; VkStructureType sType - void* pNext - char name[VK_MAX_EXTENSION_NAME_SIZE] - char version[VK_MAX_EXTENSION_NAME_SIZE] - VkToolPurposeFlags purposes - char description[VK_MAX_DESCRIPTION_SIZE] - char layer[VK_MAX_EXTENSION_NAME_SIZE] + void* pNext + char name[VK_MAX_EXTENSION_NAME_SIZE] + char version[VK_MAX_EXTENSION_NAME_SIZE] + VkToolPurposeFlags purposes + char description[VK_MAX_DESCRIPTION_SIZE] + char layer[VK_MAX_EXTENSION_NAME_SIZE] @@ -5994,7 +6041,12 @@ typedef void* MTLSharedEvent_id; void*pNext VkBool32 clustercullingShader VkBool32 multiviewClusterCullingShader - + + + VkStructureType sType + void*pNext + VkBool32 clusterShadingRate + VkStructureType sType const void* pNext @@ -6183,7 +6235,7 @@ typedef void* MTLSharedEvent_id; VkFragmentShadingRateNV shadingRate VkFragmentShadingRateCombinerOpKHR combinerOps[2] - + VkStructureType sType const void* pNext VkDeviceSize accelerationStructureSize @@ -6674,6 +6726,18 @@ typedef void* MTLSharedEvent_id; const VkVideoReferenceSlotInfoKHR* pSetupReferenceSlot uint32_t referenceSlotCount const VkVideoReferenceSlotInfoKHR* pReferenceSlots + + + VkStructureType sType + void* pNext + VkBool32 videoMaintenance1 + + + VkStructureType sType + const void* pNext + VkQueryPool queryPool + uint32_t firstQuery + uint32_t queryCount Video Decode Codec Standard specific structures #include "vk_video/vulkan_video_codec_h264std.h" @@ -6805,6 +6869,44 @@ typedef void* MTLSharedEvent_id; const void* pNext const StdVideoDecodeH265ReferenceInfo* pStdReferenceInfo + #include "vk_video/vulkan_video_codec_av1std.h" + + + + #include "vk_video/vulkan_video_codec_av1std_decode.h" + + + + VkStructureType sType + const void* pNext + StdVideoAV1Profile stdProfile + VkBool32 filmGrainSupport + + + VkStructureType sType + void* pNext + StdVideoAV1Level maxLevel + + + VkStructureType sType + const void* pNext + const StdVideoAV1SequenceHeader* pStdSequenceHeader + + + VkStructureType sType + const void* pNext + const StdVideoDecodeAV1PictureInfo* pStdPictureInfo + int32_t referenceNameSlotIndices[VK_MAX_VIDEO_AV1_REFERENCES_PER_FRAME_KHR] + uint32_t frameHeaderOffset + uint32_t tileCount + const uint32_t* pTileOffsets + const uint32_t* pTileSizes + + + VkStructureType sType + const void* pNext + const StdVideoDecodeAV1ReferenceInfo* pStdReferenceInfo + VkStructureType sType const void* pNext @@ -6857,7 +6959,7 @@ typedef void* MTLSharedEvent_id; VkStructureType sType const void* pNext - VkVideoCodingControlFlagsKHR flags + VkVideoCodingControlFlagsKHR flags VkStructureType sType @@ -6930,10 +7032,10 @@ typedef void* MTLSharedEvent_id; VkExtent2D encodeInputPictureGranularity VkVideoEncodeFeedbackFlagsKHR supportedEncodeFeedbackFlags - - VkStructureType sType + + VkStructureType sType void* pNext - VkVideoEncodeH264CapabilityFlagsEXT flags + VkVideoEncodeH264CapabilityFlagsKHR flags StdVideoH264LevelIdc maxLevelIdc uint32_t maxSliceCount uint32_t maxPPictureL0ReferenceCount @@ -6945,17 +7047,17 @@ typedef void* MTLSharedEvent_id; int32_t maxQp VkBool32 prefersGopRemainingFrames VkBool32 requiresGopRemainingFrames - VkVideoEncodeH264StdFlagsEXT stdSyntaxFlags + VkVideoEncodeH264StdFlagsKHR stdSyntaxFlags - - VkStructureType sType + + VkStructureType sType void* pNext - VkVideoEncodeH264RateControlFlagsEXT preferredRateControlFlags + VkVideoEncodeH264RateControlFlagsKHR preferredRateControlFlags uint32_t preferredGopFrameCount uint32_t preferredIdrPeriod uint32_t preferredConsecutiveBFrameCount uint32_t preferredTemporalLayerCount - VkVideoEncodeH264QpEXT preferredConstantQp + VkVideoEncodeH264QpKHR preferredConstantQp uint32_t preferredMaxL0ReferenceCount uint32_t preferredMaxL1ReferenceCount VkBool32 preferredStdEntropyCodingModeFlag @@ -6971,111 +7073,111 @@ typedef void* MTLSharedEvent_id; - - VkStructureType sType + + VkStructureType sType const void* pNext VkBool32 useMaxLevelIdc StdVideoH264LevelIdc maxLevelIdc - - VkStructureType sType + + VkStructureType sType const void* pNext uint32_t stdSPSCount const StdVideoH264SequenceParameterSet* pStdSPSs uint32_t stdPPSCount const StdVideoH264PictureParameterSet* pStdPPSsList of Picture Parameters associated with the spsStd, above - - VkStructureType sType + + VkStructureType sType const void* pNext uint32_t maxStdSPSCount uint32_t maxStdPPSCount - const VkVideoEncodeH264SessionParametersAddInfoEXT* pParametersAddInfo + const VkVideoEncodeH264SessionParametersAddInfoKHR* pParametersAddInfo - - VkStructureType sType + + VkStructureType sType const void* pNext VkBool32 writeStdSPS VkBool32 writeStdPPS uint32_t stdSPSId uint32_t stdPPSId - - VkStructureType sType + + VkStructureType sType void* pNext VkBool32 hasStdSPSOverrides VkBool32 hasStdPPSOverrides - - VkStructureType sType + + VkStructureType sType const void* pNext const StdVideoEncodeH264ReferenceInfo* pStdReferenceInfo - - VkStructureType sType + + VkStructureType sType const void* pNext uint32_t naluSliceEntryCount - const VkVideoEncodeH264NaluSliceInfoEXT* pNaluSliceEntries + const VkVideoEncodeH264NaluSliceInfoKHR* pNaluSliceEntries const StdVideoEncodeH264PictureInfo* pStdPictureInfo VkBool32 generatePrefixNalu - - VkStructureType sType + + VkStructureType sType const void* pNext StdVideoH264ProfileIdc stdProfileIdc - - VkStructureType sType + + VkStructureType sType const void* pNext int32_t constantQp const StdVideoEncodeH264SliceHeader* pStdSliceHeader - - VkStructureType sType + + VkStructureType sType const void* pNext - VkVideoEncodeH264RateControlFlagsEXT flags + VkVideoEncodeH264RateControlFlagsKHR flags uint32_t gopFrameCount uint32_t idrPeriod uint32_t consecutiveBFrameCount uint32_t temporalLayerCount - + int32_t qpI int32_t qpP int32_t qpB - + uint32_t frameISize uint32_t framePSize uint32_t frameBSize - - VkStructureType sType + + VkStructureType sType const void* pNext VkBool32 useGopRemainingFrames uint32_t gopRemainingI uint32_t gopRemainingP uint32_t gopRemainingB - - VkStructureType sType + + VkStructureType sType const void* pNext VkBool32 useMinQp - VkVideoEncodeH264QpEXT minQp + VkVideoEncodeH264QpKHR minQp VkBool32 useMaxQp - VkVideoEncodeH264QpEXT maxQp + VkVideoEncodeH264QpKHR maxQp VkBool32 useMaxFrameSize - VkVideoEncodeH264FrameSizeEXT maxFrameSize + VkVideoEncodeH264FrameSizeKHR maxFrameSize - - VkStructureType sType + + VkStructureType sType void* pNext - VkVideoEncodeH265CapabilityFlagsEXT flags + VkVideoEncodeH265CapabilityFlagsKHR flags StdVideoH265LevelIdc maxLevelIdc uint32_t maxSliceSegmentCount VkExtent2D maxTiles - VkVideoEncodeH265CtbSizeFlagsEXT ctbSizes - VkVideoEncodeH265TransformBlockSizeFlagsEXT transformBlockSizes + VkVideoEncodeH265CtbSizeFlagsKHR ctbSizes + VkVideoEncodeH265TransformBlockSizeFlagsKHR transformBlockSizes uint32_t maxPPictureL0ReferenceCount uint32_t maxBPictureL0ReferenceCount uint32_t maxL1ReferenceCount @@ -7085,17 +7187,17 @@ typedef void* MTLSharedEvent_id; int32_t maxQp VkBool32 prefersGopRemainingFrames VkBool32 requiresGopRemainingFrames - VkVideoEncodeH265StdFlagsEXT stdSyntaxFlags + VkVideoEncodeH265StdFlagsKHR stdSyntaxFlags - - VkStructureType sType + + VkStructureType sType void* pNext - VkVideoEncodeH265RateControlFlagsEXT preferredRateControlFlags + VkVideoEncodeH265RateControlFlagsKHR preferredRateControlFlags uint32_t preferredGopFrameCount uint32_t preferredIdrPeriod uint32_t preferredConsecutiveBFrameCount uint32_t preferredSubLayerCount - VkVideoEncodeH265QpEXT preferredConstantQp + VkVideoEncodeH265QpKHR preferredConstantQp uint32_t preferredMaxL0ReferenceCount uint32_t preferredMaxL1ReferenceCount @@ -7108,14 +7210,14 @@ typedef void* MTLSharedEvent_id; - - VkStructureType sType + + VkStructureType sType const void* pNext VkBool32 useMaxLevelIdc StdVideoH265LevelIdc maxLevelIdc - - VkStructureType sType + + VkStructureType sType const void* pNext uint32_t stdVPSCount const StdVideoH265VideoParameterSet* pStdVPSs @@ -7124,16 +7226,16 @@ typedef void* MTLSharedEvent_id; uint32_t stdPPSCount const StdVideoH265PictureParameterSet* pStdPPSsList of Picture Parameters associated with the spsStd, above - - VkStructureType sType + + VkStructureType sType const void* pNext uint32_t maxStdVPSCount uint32_t maxStdSPSCount uint32_t maxStdPPSCount - const VkVideoEncodeH265SessionParametersAddInfoEXT* pParametersAddInfo + const VkVideoEncodeH265SessionParametersAddInfoKHR* pParametersAddInfo - - VkStructureType sType + + VkStructureType sType const void* pNext VkBool32 writeStdVPS VkBool32 writeStdSPS @@ -7142,70 +7244,70 @@ typedef void* MTLSharedEvent_id; uint32_t stdSPSId uint32_t stdPPSId - - VkStructureType sType + + VkStructureType sType void* pNext VkBool32 hasStdVPSOverrides VkBool32 hasStdSPSOverrides VkBool32 hasStdPPSOverrides - - VkStructureType sType + + VkStructureType sType const void* pNext uint32_t naluSliceSegmentEntryCount - const VkVideoEncodeH265NaluSliceSegmentInfoEXT* pNaluSliceSegmentEntries + const VkVideoEncodeH265NaluSliceSegmentInfoKHR* pNaluSliceSegmentEntries const StdVideoEncodeH265PictureInfo* pStdPictureInfo - - VkStructureType sType + + VkStructureType sType const void* pNext int32_t constantQp const StdVideoEncodeH265SliceSegmentHeader* pStdSliceSegmentHeader - - VkStructureType sType + + VkStructureType sType const void* pNext - VkVideoEncodeH265RateControlFlagsEXT flags + VkVideoEncodeH265RateControlFlagsKHR flags uint32_t gopFrameCount uint32_t idrPeriod uint32_t consecutiveBFrameCount uint32_t subLayerCount - + int32_t qpI int32_t qpP int32_t qpB - + uint32_t frameISize uint32_t framePSize uint32_t frameBSize - - VkStructureType sType + + VkStructureType sType const void* pNext VkBool32 useGopRemainingFrames uint32_t gopRemainingI uint32_t gopRemainingP uint32_t gopRemainingB - - VkStructureType sType + + VkStructureType sType const void* pNext VkBool32 useMinQp - VkVideoEncodeH265QpEXT minQp + VkVideoEncodeH265QpKHR minQp VkBool32 useMaxQp - VkVideoEncodeH265QpEXT maxQp + VkVideoEncodeH265QpKHR maxQp VkBool32 useMaxFrameSize - VkVideoEncodeH265FrameSizeEXT maxFrameSize + VkVideoEncodeH265FrameSizeKHR maxFrameSize - - VkStructureType sType + + VkStructureType sType const void* pNext StdVideoH265ProfileIdc stdProfileIdc - - VkStructureType sType + + VkStructureType sType const void* pNext const StdVideoEncodeH265ReferenceInfo* pStdReferenceInfo @@ -7246,8 +7348,8 @@ typedef void* MTLSharedEvent_id; VkStructureType sType const void* pNext - size_t dataSize - const void* pData + size_t dataSize + const void* pData VkStructureType sType @@ -7332,7 +7434,7 @@ typedef void* MTLSharedEvent_id; VkStructureType sType void* pNext VkDeviceAddress address - VkBufferUsageFlags usage + VkBufferUsageFlags usage VkStructureType sType @@ -7545,7 +7647,7 @@ typedef void* MTLSharedEvent_id; const void* pNext zx_handle_t collectionToken - + VkStructureType sType void* pNext uint32_t memoryTypeBits @@ -7599,6 +7701,36 @@ typedef void* MTLSharedEvent_id; uint32_t minBufferCountForDedicatedSlack uint32_t minBufferCountForSharedSlack + VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkCudaModuleNV) + VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkCudaFunctionNV) + + VkStructureType sType + const void* pNext + size_t dataSize + const void* pData + + + VkStructureType sType + const void* pNext + VkCudaModuleNV module + const char* pName + + + VkStructureType sType + const void* pNext + VkCudaFunctionNV function + uint32_t gridDimX + uint32_t gridDimY + uint32_t gridDimZ + uint32_t blockDimX + uint32_t blockDimY + uint32_t blockDimZ + uint32_t sharedMemBytes + size_t paramCount + const void* const * pParams + size_t extraCount + const void* const * pExtras + VkStructureType sType void* pNext @@ -7805,7 +7937,7 @@ typedef void* MTLSharedEvent_id; VkStructureType sType void* pNext uint32_t identifierSize - uint8_t identifier[VK_MAX_SHADER_MODULE_IDENTIFIER_SIZE_EXT] + uint8_t identifier[VK_MAX_SHADER_MODULE_IDENTIFIER_SIZE_EXT] VkStructureType sType @@ -7857,7 +7989,7 @@ typedef void* MTLSharedEvent_id; VkSubpassMergeStatusEXT subpassMergeStatus - char description[VK_MAX_DESCRIPTION_SIZE] + char description[VK_MAX_DESCRIPTION_SIZE] uint32_t postMergeIndex @@ -7998,7 +8130,7 @@ typedef void* MTLSharedEvent_id; VkMicromapEXT micromap - + VkStructureType sType void* pNext uint8_t pipelineIdentifier[VK_UUID_SIZE] @@ -8250,9 +8382,9 @@ typedef void* MTLSharedEvent_id; VkDeviceSize addressPrecision - char description[VK_MAX_DESCRIPTION_SIZE]Free-form description of the fault - uint64_t vendorFaultCode - uint64_t vendorFaultData + char description[VK_MAX_DESCRIPTION_SIZE]Free-form description of the fault + uint64_t vendorFaultCode + uint64_t vendorFaultData VkStructureType sType @@ -8261,13 +8393,13 @@ typedef void* MTLSharedEvent_id; uint32_t vendorInfoCount VkDeviceSize vendorBinarySizeSpecified in bytes - + VkStructureType sType - void* pNext - char description[VK_MAX_DESCRIPTION_SIZE]Free-form description of the fault - VkDeviceFaultAddressInfoEXT* pAddressInfos - VkDeviceFaultVendorInfoEXT* pVendorInfos - void* pVendorBinaryData + void* pNext + char description[VK_MAX_DESCRIPTION_SIZE]Free-form description of the fault + VkDeviceFaultAddressInfoEXT* pAddressInfos + VkDeviceFaultVendorInfoEXT* pVendorInfos + void* pVendorBinaryData The fields in this structure are non-normative since structure packing is implementation-defined in C. The specification defines the normative layout. @@ -8438,7 +8570,7 @@ typedef void* MTLSharedEvent_id; VkStructureType sType - void* pNext + const void* pNext VkDirectDriverLoadingModeLUNARG mode uint32_t driverCount const VkDirectDriverLoadingInfoLUNARG* pDrivers @@ -8577,7 +8709,7 @@ typedef void* MTLSharedEvent_id; VkBool32 cooperativeMatrix VkBool32 cooperativeMatrixRobustBufferAccess - + VkStructureType sType void* pNext uint32_t MSize @@ -8612,7 +8744,7 @@ typedef void* MTLSharedEvent_id; VkStructureType sType const void* pNext - VkPipelineCreateFlags flags + VkPipelineCreateFlags flags uint32_t stageCount const VkPipelineShaderStageCreateInfo* pStages const VkPipelineLibraryCreateInfoKHR* pLibraryInfo @@ -8642,6 +8774,65 @@ typedef void* MTLSharedEvent_id; VkDeviceOrHostAddressConstAMDX infos uint64_t stride + + VkStructureType sType + const void* pNext + VkResult* pResult + + + VkStructureType sType + const void* pNext + VkShaderStageFlags stageFlags + VkPipelineLayout layout + uint32_t firstSet + uint32_t descriptorSetCount + const VkDescriptorSet* pDescriptorSets + uint32_t dynamicOffsetCount + const uint32_t* pDynamicOffsets + + + VkStructureType sType + const void* pNext + VkPipelineLayout layout + VkShaderStageFlags stageFlags + uint32_t offset + uint32_t size + const void* pValues + + + VkStructureType sType + const void* pNext + VkShaderStageFlags stageFlags + VkPipelineLayout layout + uint32_t set + uint32_t descriptorWriteCount + const VkWriteDescriptorSet* pDescriptorWrites + + + VkStructureType sType + const void* pNext + VkDescriptorUpdateTemplate descriptorUpdateTemplate + VkPipelineLayout layout + uint32_t set + const void* pData + + + VkStructureType sType + const void* pNext + VkShaderStageFlags stageFlags + VkPipelineLayout layout + uint32_t firstSet + uint32_t setCount + const uint32_t* pBufferIndices + const VkDeviceSize* pOffsets + + + VkStructureType sType + const void* pNext + VkShaderStageFlags stageFlags + VkPipelineLayout layout + uint32_t set + VkStructureType sType void* pNext @@ -8699,6 +8890,12 @@ typedef void* MTLSharedEvent_id; void* pNext VkLayeredDriverUnderlyingApiMSFT underlyingAPI + + VkStructureType sType + void* pNext + VkBool32 perStageDescriptorSet + VkBool32 dynamicPipelineLayout + VkStructureType sType void* pNext @@ -8707,7 +8904,7 @@ typedef void* MTLSharedEvent_id; VkStructureType sType void* pNext - VkBool32 nullColorAttachmentWithExternalFormatResolve + VkBool32 nullColorAttachmentWithExternalFormatResolve VkChromaLocation externalFormatResolveChromaOffsetX VkChromaLocation externalFormatResolveChromaOffsetY @@ -8738,9 +8935,10 @@ typedef void* MTLSharedEvent_id; VkStructureType sType const void* pNext - VkLatencyTimingsFrameReportNV* pTimings + uint32_t timingCount + VkLatencyTimingsFrameReportNV* pTimings - + VkStructureType sType const void* pNext uint64_t presentID @@ -8776,9 +8974,140 @@ typedef void* MTLSharedEvent_id; VkStructureType sType const void* pNext - uint32_t presentModeCount + uint32_t presentModeCount VkPresentModeKHR* pPresentModes + + VkStructureType sType + void* pNext + VkBool32 cudaKernelLaunchFeatures + + + VkStructureType sType + void* pNext + uint32_t computeCapabilityMinor + uint32_t computeCapabilityMajor + + + VkStructureType sType + void* pNext + uint32_t shaderCoreCount + + + VkStructureType sType + void* pNext + VkBool32 schedulingControls + + + VkStructureType sType + void* pNext + VkPhysicalDeviceSchedulingControlsFlagsARM schedulingControlsFlags + + + VkStructureType sType + void* pNext + VkBool32 relaxedLineRasterization + + + VkStructureType sType + void* pNext + VkBool32 renderPassStriped + + + VkStructureType sType + void* pNext + VkExtent2D renderPassStripeGranularity + uint32_t maxRenderPassStripes + + + VkStructureType sType + const void* pNext + VkRect2D stripeArea + + + VkStructureType sType + const void* pNext + uint32_t stripeInfoCount + const VkRenderPassStripeInfoARM* pStripeInfos + + + VkStructureType sType + const void* pNext + uint32_t stripeSemaphoreInfoCount + const VkSemaphoreSubmitInfo* pStripeSemaphoreInfos + + + VkStructureType sType + void* pNext + VkBool32 shaderMaximalReconvergence + + + VkStructureType sType + void* pNext + VkBool32 shaderSubgroupRotate + VkBool32 shaderSubgroupRotateClustered + + + VkStructureType sType + void* pNext + VkBool32 shaderExpectAssume + + + VkStructureType sType + void* pNext + VkBool32 shaderFloatControls2 + + + VkStructureType sType + void* pNext + VkBool32 dynamicRenderingLocalRead + + + VkStructureType sType + const void* pNext + uint32_t colorAttachmentCount + const uint32_t* pColorAttachmentLocations + + + VkStructureType sType + const void* pNext + uint32_t colorAttachmentCount + const uint32_t* pColorAttachmentInputIndices + const uint32_t* pDepthInputAttachmentIndex + const uint32_t* pStencilInputAttachmentIndex + + + VkStructureType sType + void* pNext + VkBool32 shaderQuadControl + + + VkStructureType sType + void* pNext + VkBool32 shaderFloat16VectorAtomics + + + VkStructureType sType + void* pNext + VkBool32 memoryMapPlaced + VkBool32 memoryMapRangePlaced + VkBool32 memoryUnmapReserve + + + VkStructureType sType + void* pNext + VkDeviceSize minPlacedMemoryMapAlignment + + + VkStructureType sType + const void* pNext + void* pPlacedAddress + + + VkStructureType sType + void* pNext + VkBool32 shaderRawAccessChains + @@ -8817,6 +9146,7 @@ typedef void* MTLSharedEvent_id; + @@ -9537,6 +9867,8 @@ typedef void* MTLSharedEvent_id; + + @@ -9649,11 +9981,11 @@ typedef void* MTLSharedEvent_id; - - - - - + + + + + @@ -9745,6 +10077,16 @@ typedef void* MTLSharedEvent_id; + + + + + + + + + + @@ -10193,11 +10535,15 @@ typedef void* MTLSharedEvent_id; - - - - - + + + + + + + + + @@ -10436,6 +10782,9 @@ typedef void* MTLSharedEvent_id; + + + @@ -10483,6 +10832,8 @@ typedef void* MTLSharedEvent_id; + + @@ -10518,45 +10869,45 @@ typedef void* MTLSharedEvent_id; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -10634,58 +10985,58 @@ typedef void* MTLSharedEvent_id; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -10894,6 +11245,8 @@ typedef void* MTLSharedEvent_id; + + @@ -11388,11 +11741,11 @@ typedef void* MTLSharedEvent_id; const VkAllocationCallbacks* pAllocator VkPipeline* pPipelines - + VkResult vkGetDeviceSubpassShadingMaxWorkgroupSizeHUAWEI VkDevice device VkRenderPass renderpass - VkExtent2D* pMaxWorkgroupSize + VkExtent2D* pMaxWorkgroupSize void vkDestroyPipeline @@ -11655,7 +12008,7 @@ typedef void* MTLSharedEvent_id; void vkCmdBindIndexBuffer VkCommandBuffer commandBuffer - VkBuffer buffer + VkBuffer buffer VkDeviceSize offset VkIndexType indexType @@ -12980,8 +13333,8 @@ typedef void* MTLSharedEvent_id; VkDevice device VkImage image int nativeFenceFd - VkSemaphore semaphore - VkFence fence + VkSemaphore semaphore + VkFence fence VkResult vkQueueSignalReleaseImageANDROID @@ -13007,19 +13360,21 @@ typedef void* MTLSharedEvent_id; VkBool32 localDimmingEnable - VkResult vkGetPhysicalDeviceCalibrateableTimeDomainsEXT + VkResult vkGetPhysicalDeviceCalibrateableTimeDomainsKHR VkPhysicalDevice physicalDevice uint32_t* pTimeDomainCount - VkTimeDomainEXT* pTimeDomains + VkTimeDomainKHR* pTimeDomains + - VkResult vkGetCalibratedTimestampsEXT + VkResult vkGetCalibratedTimestampsKHR VkDevice device uint32_t timestampCount - const VkCalibratedTimestampInfoEXT* pTimestampInfos + const VkCalibratedTimestampInfoKHR* pTimestampInfos uint64_t* pTimestamps uint64_t* pMaxDeviation + VkResult vkSetDebugUtilsObjectNameEXT VkDevice device @@ -13738,17 +14093,18 @@ typedef void* MTLSharedEvent_id; VkPipelineExecutableInternalRepresentationKHR* pInternalRepresentations - void vkCmdSetLineStippleEXT + void vkCmdSetLineStippleKHR VkCommandBuffer commandBuffer uint32_t lineStippleFactor uint16_t lineStipplePattern + VkResult vkGetFaultData VkDevice device VkFaultQueryBehavior faultQueryBehavior VkBool32* pUnrecordedFaults - uint32_t* pFaultCount + uint32_t* pFaultCount VkFaultData* pFaults @@ -13867,7 +14223,7 @@ typedef void* MTLSharedEvent_id; void vkCmdBindIndexBuffer2KHR VkCommandBuffer commandBuffer - VkBuffer buffer + VkBuffer buffer VkDeviceSize offset VkDeviceSize size VkIndexType indexType @@ -14596,6 +14952,44 @@ typedef void* MTLSharedEvent_id; VkBufferCollectionFUCHSIA collection VkBufferCollectionPropertiesFUCHSIA* pProperties + + VkResult vkCreateCudaModuleNV + VkDevice device + const VkCudaModuleCreateInfoNV* pCreateInfo + const VkAllocationCallbacks* pAllocator + VkCudaModuleNV* pModule + + + VkResult vkGetCudaModuleCacheNV + VkDevice device + VkCudaModuleNV module + size_t* pCacheSize + void* pCacheData + + + VkResult vkCreateCudaFunctionNV + VkDevice device + const VkCudaFunctionCreateInfoNV* pCreateInfo + const VkAllocationCallbacks* pAllocator + VkCudaFunctionNV* pFunction + + + void vkDestroyCudaModuleNV + VkDevice device + VkCudaModuleNV module + const VkAllocationCallbacks* pAllocator + + + void vkDestroyCudaFunctionNV + VkDevice device + VkCudaFunctionNV function + const VkAllocationCallbacks* pAllocator + + + void vkCmdCudaLaunchKernelNV + VkCommandBuffer commandBuffer + const VkCudaLaunchInfoNV* pLaunchInfo + void vkCmdBeginRendering VkCommandBuffer commandBuffer @@ -14817,12 +15211,12 @@ typedef void* MTLSharedEvent_id; const VkMemoryMapInfoKHR* pMemoryMapInfo void** ppData - + VkResult vkUnmapMemory2KHR VkDevice device const VkMemoryUnmapInfoKHR* pMemoryUnmapInfo - + VkResult vkCreateShadersEXT VkDevice device uint32_t createInfoCount @@ -14833,7 +15227,7 @@ typedef void* MTLSharedEvent_id; void vkDestroyShaderEXT VkDevice device - VkShaderEXT shader + VkShaderEXT shader const VkAllocationCallbacks* pAllocator @@ -14907,6 +15301,36 @@ typedef void* MTLSharedEvent_id; VkDeviceAddress scratch VkDeviceAddress countInfo + + void vkCmdBindDescriptorSets2KHR + VkCommandBuffer commandBuffer + const VkBindDescriptorSetsInfoKHR* pBindDescriptorSetsInfo + + + void vkCmdPushConstants2KHR + VkCommandBuffer commandBuffer + const VkPushConstantsInfoKHR* pPushConstantsInfo + + + void vkCmdPushDescriptorSet2KHR + VkCommandBuffer commandBuffer + const VkPushDescriptorSetInfoKHR* pPushDescriptorSetInfo + + + void vkCmdPushDescriptorSetWithTemplate2KHR + VkCommandBuffer commandBuffer + const VkPushDescriptorSetWithTemplateInfoKHR* pPushDescriptorSetWithTemplateInfo + + + void vkCmdSetDescriptorBufferOffsets2EXT + VkCommandBuffer commandBuffer + const VkSetDescriptorBufferOffsetsInfoEXT* pSetDescriptorBufferOffsetsInfo + + + void vkCmdBindDescriptorBufferEmbeddedSamplers2EXT + VkCommandBuffer commandBuffer + const VkBindDescriptorBufferEmbeddedSamplersInfoEXT* pBindDescriptorBufferEmbeddedSamplersInfo + VkResult vkSetLatencySleepModeNV VkDevice device @@ -14929,7 +15353,6 @@ typedef void* MTLSharedEvent_id; void vkGetLatencyTimingsNV VkDevice device VkSwapchainKHR swapchain - uint32_t* pTimingCount VkGetLatencyMarkerInfoNV* pLatencyMarkerInfo @@ -14937,6 +15360,16 @@ typedef void* MTLSharedEvent_id; VkQueue queue const VkOutOfBandQueueTypeInfoNV* pQueueTypeInfo + + void vkCmdSetRenderingAttachmentLocationsKHR + VkCommandBuffer commandBuffer + const VkRenderingAttachmentLocationInfoKHR* pLocationInfo + + + void vkCmdSetRenderingInputAttachmentIndicesKHR + VkCommandBuffer commandBuffer + const VkRenderingInputAttachmentIndexInfoKHR* pLocationInfo + @@ -15094,6 +15527,7 @@ typedef void* MTLSharedEvent_id; + @@ -16773,7 +17207,7 @@ typedef void* MTLSharedEvent_id; - + @@ -16950,101 +17384,101 @@ typedef void* MTLSharedEvent_id; - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -17348,9 +17782,9 @@ typedef void* MTLSharedEvent_id; - + - + @@ -17826,7 +18260,7 @@ typedef void* MTLSharedEvent_id; - + @@ -17962,10 +18396,12 @@ typedef void* MTLSharedEvent_id; - + - - + + + + @@ -18332,10 +18768,18 @@ typedef void* MTLSharedEvent_id; - + - - + + + + + + + + + + @@ -19058,7 +19502,7 @@ typedef void* MTLSharedEvent_id; - + @@ -19070,7 +19514,7 @@ typedef void* MTLSharedEvent_id; - + @@ -19078,13 +19522,13 @@ typedef void* MTLSharedEvent_id; - + - + @@ -19176,11 +19620,15 @@ typedef void* MTLSharedEvent_id; - + - + + + + + @@ -19203,7 +19651,7 @@ typedef void* MTLSharedEvent_id; - + @@ -19246,13 +19694,13 @@ typedef void* MTLSharedEvent_id; - + - - - - - + + + + + @@ -19719,10 +20167,19 @@ typedef void* MTLSharedEvent_id; - + - - + + + + + + + + + + + @@ -19739,10 +20196,12 @@ typedef void* MTLSharedEvent_id; - + - - + + + + @@ -19861,9 +20320,9 @@ typedef void* MTLSharedEvent_id; - + - + @@ -20031,14 +20490,14 @@ typedef void* MTLSharedEvent_id; - + - - - - + + + + @@ -20081,12 +20540,12 @@ typedef void* MTLSharedEvent_id; - + - - + + @@ -20216,15 +20675,24 @@ typedef void* MTLSharedEvent_id; + - + - - + + + + + + + + + + @@ -20363,9 +20831,9 @@ typedef void* MTLSharedEvent_id; - + - + @@ -20571,43 +21039,43 @@ typedef void* MTLSharedEvent_id; - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -20645,8 +21113,8 @@ typedef void* MTLSharedEvent_id; - - + + @@ -20661,47 +21129,71 @@ typedef void* MTLSharedEvent_id; - + - + - + - + - + - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -20717,7 +21209,7 @@ typedef void* MTLSharedEvent_id; - + @@ -21134,9 +21626,9 @@ typedef void* MTLSharedEvent_id; - + - + @@ -21220,7 +21712,7 @@ typedef void* MTLSharedEvent_id; - + @@ -21839,7 +22331,7 @@ typedef void* MTLSharedEvent_id; - + @@ -21933,11 +22425,11 @@ typedef void* MTLSharedEvent_id; - + - - + + @@ -21961,10 +22453,11 @@ typedef void* MTLSharedEvent_id; - + + @@ -21972,6 +22465,7 @@ typedef void* MTLSharedEvent_id; + @@ -22061,16 +22555,28 @@ typedef void* MTLSharedEvent_id; - + - - + + + + + + - + - - + + + + + + + + + + @@ -22128,10 +22634,20 @@ typedef void* MTLSharedEvent_id; - + - - + + + + + + + + + + + + @@ -22197,8 +22713,10 @@ typedef void* MTLSharedEvent_id; - - + + + + @@ -22230,10 +22748,12 @@ typedef void* MTLSharedEvent_id; - + - - + + + + @@ -22264,7 +22784,7 @@ typedef void* MTLSharedEvent_id; - + @@ -22272,7 +22792,7 @@ typedef void* MTLSharedEvent_id; - + @@ -22357,7 +22877,7 @@ typedef void* MTLSharedEvent_id; - + @@ -22394,13 +22914,12 @@ typedef void* MTLSharedEvent_id; - + - @@ -22411,21 +22930,10 @@ typedef void* MTLSharedEvent_id; - - - - - - - - - - - @@ -22436,15 +22944,45 @@ typedef void* MTLSharedEvent_id; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -22683,8 +23221,8 @@ typedef void* MTLSharedEvent_id; - - + + @@ -22779,8 +23317,8 @@ typedef void* MTLSharedEvent_id; - - + + @@ -22845,6 +23383,7 @@ typedef void* MTLSharedEvent_id; + @@ -22879,7 +23418,8 @@ typedef void* MTLSharedEvent_id; - + + @@ -22925,15 +23465,31 @@ typedef void* MTLSharedEvent_id; + + + + + + + + + + + + + + + + @@ -22984,7 +23540,7 @@ typedef void* MTLSharedEvent_id; - + @@ -23021,7 +23577,7 @@ typedef void* MTLSharedEvent_id; - + @@ -23083,6 +23639,7 @@ typedef void* MTLSharedEvent_id; + @@ -23121,10 +23678,14 @@ typedef void* MTLSharedEvent_id; - + - - + + + + + + @@ -23183,9 +23744,9 @@ typedef void* MTLSharedEvent_id; - + - + @@ -23252,7 +23813,7 @@ typedef void* MTLSharedEvent_id; - + @@ -23268,10 +23829,22 @@ typedef void* MTLSharedEvent_id; - + - - + + + + + + + + + + + + + + @@ -23286,19 +23859,26 @@ typedef void* MTLSharedEvent_id; - + - - - - + + + + + + + + + - + - - - + + + + + @@ -23307,7 +23887,7 @@ typedef void* MTLSharedEvent_id; - + @@ -23320,7 +23900,7 @@ typedef void* MTLSharedEvent_id; - + @@ -23333,7 +23913,7 @@ typedef void* MTLSharedEvent_id; - + @@ -23343,7 +23923,7 @@ typedef void* MTLSharedEvent_id; - + @@ -23374,16 +23954,25 @@ typedef void* MTLSharedEvent_id; - + - - + + + + + + + + + - + - - + + + + @@ -23392,10 +23981,12 @@ typedef void* MTLSharedEvent_id; - + - - + + + + @@ -23437,19 +24028,31 @@ typedef void* MTLSharedEvent_id; - + - - + + + + + - + - - + + + + + + + + + + + - + @@ -23497,22 +24100,59 @@ typedef void* MTLSharedEvent_id; - + - - + + + + + + + - + - - + + + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -23534,7 +24174,173 @@ typedef void* MTLSharedEvent_id; - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -25115,6 +25921,9 @@ typedef void* MTLSharedEvent_id; + + + @@ -25156,6 +25965,33 @@ typedef void* MTLSharedEvent_id; + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -25228,6 +26064,9 @@ typedef void* MTLSharedEvent_id; + + + @@ -25651,6 +26490,24 @@ typedef void* MTLSharedEvent_id; + + + + + + + + + + + + + + + + + + diff --git a/dlls/winevulkan/vulkan.c b/dlls/winevulkan/vulkan.c index 27cf80ee907..c40309ee468 100644 --- a/dlls/winevulkan/vulkan.c +++ b/dlls/winevulkan/vulkan.c @@ -49,6 +49,28 @@ WINE_DEFAULT_DEBUG_CHANNEL(vulkan); +static void set_memory_being_allocated(struct wine_instance *instance, struct wine_device_memory *memory) +{ + struct ntuser_thread_info *thread_info; + if (!instance->enable_wrapper_list) return; + thread_info = NtUserGetThreadInfo(); + thread_info->vulkan_data = (uintptr_t) memory; +} + +static uint64_t get_memory_being_allocated(uint64_t host_handle) +{ + struct ntuser_thread_info *thread_info; + struct wine_device_memory *memory; + + thread_info = NtUserGetThreadInfo(); + if (!thread_info->vulkan_data) return 0; + + memory = (struct wine_device_memory*) (uintptr_t) thread_info->vulkan_data; + memory->host_memory = host_handle; + + return (uintptr_t) memory; +} + static int debug_level; static BOOL is_wow64(void) @@ -169,7 +191,6 @@ static VkBool32 debug_utils_callback_conversion(VkDebugUtilsMessageSeverityFlagB struct wine_debug_utils_messenger *object; void *ret_ptr; ULONG ret_len; - VkBool32 result; unsigned int i; TRACE("%i, %u, %p, %p\n", severity, message_types, callback_data, user_data); @@ -201,6 +222,8 @@ static VkBool32 debug_utils_callback_conversion(VkDebugUtilsMessageSeverityFlagB if (wine_vk_is_type_wrapped(callback_data->pObjects[i].objectType)) { object_name_infos[i].objectHandle = wine_vk_get_wrapper(object->instance, callback_data->pObjects[i].objectHandle); + if (!object_name_infos[i].objectHandle && object_name_infos[i].objectType == VK_OBJECT_TYPE_DEVICE_MEMORY) + object_name_infos[i].objectHandle = get_memory_being_allocated(callback_data->pObjects[i].objectHandle); if (!object_name_infos[i].objectHandle) { WARN("handle conversion failed 0x%s\n", wine_dbgstr_longlong(callback_data->pObjects[i].objectHandle)); @@ -217,12 +240,11 @@ static VkBool32 debug_utils_callback_conversion(VkDebugUtilsMessageSeverityFlagB params.data.pObjects = object_name_infos; /* applications should always return VK_FALSE */ - result = KeUserModeCallback( NtUserCallVulkanDebugUtilsCallback, ¶ms, sizeof(params), - &ret_ptr, &ret_len ); + KeUserModeCallback( NtUserCallVulkanDebugUtilsCallback, ¶ms, sizeof(params), &ret_ptr, &ret_len ); free(object_name_infos); - - return result; + if (ret_len == sizeof(VkBool32)) return *(VkBool32 *)ret_ptr; + return VK_FALSE; } static VkBool32 debug_report_callback_conversion(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT object_type, @@ -258,8 +280,9 @@ static VkBool32 debug_report_callback_conversion(VkDebugReportFlagsEXT flags, Vk if (!params.object_handle) params.object_type = VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT; - return KeUserModeCallback( NtUserCallVulkanDebugReportCallback, ¶ms, sizeof(params), - &ret_ptr, &ret_len ); + KeUserModeCallback( NtUserCallVulkanDebugReportCallback, ¶ms, sizeof(params), &ret_ptr, &ret_len ); + if (ret_len == sizeof(VkBool32)) return *(VkBool32 *)ret_ptr; + return VK_FALSE; } static void wine_vk_physical_device_free(struct wine_phys_dev *phys_dev) @@ -685,8 +708,8 @@ static void wine_vk_device_free(struct wine_device *device) if (device->host_device && device->funcs.p_vkDestroyDevice) { - WINE_VK_REMOVE_HANDLE_MAPPING(device->phys_dev->instance, device); device->funcs.p_vkDestroyDevice(device->host_device, NULL /* pAllocator */); + WINE_VK_REMOVE_HANDLE_MAPPING(device->phys_dev->instance, device); } free(device); @@ -1420,9 +1443,8 @@ void wine_vkDestroyCommandPool(VkDevice device_handle, VkCommandPool handle, if (allocator) FIXME("Support for allocation callbacks not implemented yet\n"); - WINE_VK_REMOVE_HANDLE_MAPPING(device->phys_dev->instance, pool); - device->funcs.p_vkDestroyCommandPool(device->host_device, pool->host_command_pool, NULL); + WINE_VK_REMOVE_HANDLE_MAPPING(device->phys_dev->instance, pool); free(pool); } @@ -1673,17 +1695,19 @@ static inline uint64_t convert_timestamp(VkTimeDomainEXT host_domain, VkTimeDoma return value; } -VkResult wine_vkGetCalibratedTimestampsEXT(VkDevice handle, uint32_t timestamp_count, - const VkCalibratedTimestampInfoEXT *timestamp_infos, - uint64_t *timestamps, uint64_t *max_deviation) +static VkResult wine_vk_get_timestamps(struct wine_device *device, uint32_t timestamp_count, + const VkCalibratedTimestampInfoEXT *timestamp_infos, + uint64_t *timestamps, uint64_t *max_deviation, + VkResult (*get_timestamps)(VkDevice, uint32_t, const VkCalibratedTimestampInfoEXT *, uint64_t *, uint64_t *)) { - struct wine_device *device = wine_device_from_handle(handle); VkCalibratedTimestampInfoEXT* host_timestamp_infos; unsigned int i; VkResult res; - TRACE("%p, %u, %p, %p, %p\n", device, timestamp_count, timestamp_infos, timestamps, max_deviation); - if (!(host_timestamp_infos = malloc(sizeof(VkCalibratedTimestampInfoEXT) * timestamp_count))) + if (timestamp_count == 0) + return VK_SUCCESS; + + if (!(host_timestamp_infos = calloc(sizeof(VkCalibratedTimestampInfoEXT), timestamp_count))) return VK_ERROR_OUT_OF_HOST_MEMORY; for (i = 0; i < timestamp_count; i++) @@ -1693,24 +1717,23 @@ VkResult wine_vkGetCalibratedTimestampsEXT(VkDevice handle, uint32_t timestamp_c host_timestamp_infos[i].timeDomain = map_to_host_time_domain(timestamp_infos[i].timeDomain); } - res = device->funcs.p_vkGetCalibratedTimestampsEXT(device->host_device, timestamp_count, host_timestamp_infos, - timestamps, max_deviation); - if (res != VK_SUCCESS) - return res; - - for (i = 0; i < timestamp_count; i++) - timestamps[i] = convert_timestamp(host_timestamp_infos[i].timeDomain, timestamp_infos[i].timeDomain, timestamps[i]); + res = get_timestamps(device->host_device, timestamp_count, host_timestamp_infos, timestamps, max_deviation); + if (res == VK_SUCCESS) + { + for (i = 0; i < timestamp_count; i++) + timestamps[i] = convert_timestamp(host_timestamp_infos[i].timeDomain, timestamp_infos[i].timeDomain, timestamps[i]); + } free(host_timestamp_infos); return res; } -VkResult wine_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT(VkPhysicalDevice handle, - uint32_t *time_domain_count, - VkTimeDomainEXT *time_domains) +static VkResult wine_vk_get_time_domains(struct wine_phys_dev *phys_dev, + uint32_t *time_domain_count, + VkTimeDomainEXT *time_domains, + VkResult (*get_domains)(VkPhysicalDevice, uint32_t *, VkTimeDomainEXT *)) { - struct wine_phys_dev *phys_dev = wine_phys_dev_from_handle(handle); BOOL supports_device = FALSE, supports_monotonic = FALSE, supports_monotonic_raw = FALSE; const VkTimeDomainEXT performance_counter_domain = get_performance_counter_time_domain(); VkTimeDomainEXT *host_time_domains; @@ -1721,16 +1744,14 @@ VkResult wine_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT(VkPhysicalDevice ha VkResult res; /* Find out the time domains supported on the host */ - res = phys_dev->instance->funcs.p_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT(phys_dev->host_physical_device, - &host_time_domain_count, NULL); + res = get_domains(phys_dev->host_physical_device, &host_time_domain_count, NULL); if (res != VK_SUCCESS) return res; if (!(host_time_domains = malloc(sizeof(VkTimeDomainEXT) * host_time_domain_count))) return VK_ERROR_OUT_OF_HOST_MEMORY; - res = phys_dev->instance->funcs.p_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT(phys_dev->host_physical_device, - &host_time_domain_count, host_time_domains); + res = get_domains(phys_dev->host_physical_device, &host_time_domain_count, host_time_domains); if (res != VK_SUCCESS) { free(host_time_domains); @@ -1780,6 +1801,54 @@ VkResult wine_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT(VkPhysicalDevice ha return res; } +VkResult wine_vkGetCalibratedTimestampsEXT(VkDevice handle, uint32_t timestamp_count, + const VkCalibratedTimestampInfoEXT *timestamp_infos, + uint64_t *timestamps, uint64_t *max_deviation) +{ + struct wine_device *device = wine_device_from_handle(handle); + + TRACE("%p, %u, %p, %p, %p\n", device, timestamp_count, timestamp_infos, timestamps, max_deviation); + + return wine_vk_get_timestamps(device, timestamp_count, timestamp_infos, timestamps, max_deviation, + device->funcs.p_vkGetCalibratedTimestampsEXT); +} + +VkResult wine_vkGetCalibratedTimestampsKHR(VkDevice handle, uint32_t timestamp_count, + const VkCalibratedTimestampInfoKHR *timestamp_infos, + uint64_t *timestamps, uint64_t *max_deviation) +{ + struct wine_device *device = wine_device_from_handle(handle); + + TRACE("%p, %u, %p, %p, %p\n", device, timestamp_count, timestamp_infos, timestamps, max_deviation); + + return wine_vk_get_timestamps(device, timestamp_count, timestamp_infos, timestamps, max_deviation, + device->funcs.p_vkGetCalibratedTimestampsKHR); +} + +VkResult wine_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT(VkPhysicalDevice handle, + uint32_t *time_domain_count, + VkTimeDomainEXT *time_domains) +{ + struct wine_phys_dev *phys_dev = wine_phys_dev_from_handle(handle); + + TRACE("%p, %p, %p\n", phys_dev, time_domain_count, time_domains); + + return wine_vk_get_time_domains(phys_dev, time_domain_count, time_domains, + phys_dev->instance->funcs.p_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT); +} + +VkResult wine_vkGetPhysicalDeviceCalibrateableTimeDomainsKHR(VkPhysicalDevice handle, + uint32_t *time_domain_count, + VkTimeDomainKHR *time_domains) +{ + struct wine_phys_dev *phys_dev = wine_phys_dev_from_handle(handle); + + TRACE("%p, %p, %p\n", phys_dev, time_domain_count, time_domains); + + return wine_vk_get_time_domains(phys_dev, time_domain_count, time_domains, + phys_dev->instance->funcs.p_vkGetPhysicalDeviceCalibrateableTimeDomainsKHR); +} + static inline void wine_vk_normalize_semaphore_handle_types_win(VkExternalSemaphoreHandleTypeFlags *types) { *types &= @@ -2520,24 +2589,20 @@ VkResult wine_vkCreateSwapchainKHR(VkDevice device_handle, const VkSwapchainCrea return res; } -VkResult wine_vkCreateWin32SurfaceKHR(VkInstance handle, const VkWin32SurfaceCreateInfoKHR *createInfo, +VkResult wine_vkCreateWin32SurfaceKHR(VkInstance handle, const VkWin32SurfaceCreateInfoKHR *create_info, const VkAllocationCallbacks *allocator, VkSurfaceKHR *surface) { struct wine_instance *instance = wine_instance_from_handle(handle); struct wine_surface *object; VkResult res; - if (allocator) - FIXME("Support for allocation callbacks not implemented yet\n"); - - object = calloc(1, sizeof(*object)); - - if (!object) - return VK_ERROR_OUT_OF_HOST_MEMORY; + if (allocator) FIXME("Support for allocation callbacks not implemented yet\n"); - res = instance->funcs.p_vkCreateWin32SurfaceKHR(instance->host_instance, createInfo, NULL, - &object->driver_surface); + if (!(object = calloc(1, sizeof(*object)))) return VK_ERROR_OUT_OF_HOST_MEMORY; + object->hwnd = create_info->hwnd; + res = instance->funcs.p_vkCreateWin32SurfaceKHR(instance->host_instance, create_info, + NULL /* allocator */, &object->driver_surface); if (res != VK_SUCCESS) { free(object); @@ -3210,7 +3275,10 @@ VkResult wine_vkAllocateMemory(VkDevice handle, const VkMemoryAllocateInfo *allo } } + set_memory_being_allocated(device->phys_dev->instance, memory); result = device->funcs.p_vkAllocateMemory(device->host_device, &info, NULL, &memory->host_memory); + set_memory_being_allocated(device->phys_dev->instance, NULL); + if (result == VK_SUCCESS && memory->handle == INVALID_HANDLE_VALUE && export_info && export_info->handleTypes & VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT) { get_fd_info.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR; @@ -3249,7 +3317,8 @@ VkResult wine_vkAllocateMemory(VkDevice handle, const VkMemoryAllocateInfo *allo return result; } - memory->mapping = mapping; + WINE_VK_ADD_NON_DISPATCHABLE_MAPPING(device->phys_dev->instance, memory, memory->host_memory, memory); + memory->vm_map = mapping; *ret = wine_device_memory_to_handle(memory); return VK_SUCCESS; } @@ -3265,11 +3334,12 @@ void wine_vkFreeMemory(VkDevice handle, VkDeviceMemory memory_handle, const VkAl destroy_keyed_mutex(device, memory); device->funcs.p_vkFreeMemory(device->host_device, memory->host_memory, NULL); + WINE_VK_REMOVE_HANDLE_MAPPING(device->phys_dev->instance, memory); - if (memory->mapping) + if (memory->vm_map) { SIZE_T alloc_size = 0; - NtFreeVirtualMemory(GetCurrentProcess(), &memory->mapping, &alloc_size, MEM_RELEASE); + NtFreeVirtualMemory(GetCurrentProcess(), &memory->vm_map, &alloc_size, MEM_RELEASE); } if (memory->handle != INVALID_HANDLE_VALUE) @@ -3301,9 +3371,9 @@ VkResult wine_vkMapMemory2KHR(VkDevice handle, const VkMemoryMapInfoKHR *map_inf VkResult result; info.memory = memory->host_memory; - if (memory->mapping) + if (memory->vm_map) { - *data = (char *)memory->mapping + info.offset; + *data = (char *)memory->vm_map + info.offset; TRACE("returning %p\n", *data); return VK_SUCCESS; } @@ -3349,7 +3419,7 @@ VkResult wine_vkUnmapMemory2KHR(VkDevice handle, const VkMemoryUnmapInfoKHR *unm struct wine_device_memory *memory = wine_device_memory_from_handle(unmap_info->memory); VkMemoryUnmapInfoKHR info; - if (memory->mapping) + if (memory->vm_map) return VK_SUCCESS; if (!device->funcs.p_vkUnmapMemory2KHR) @@ -3627,9 +3697,9 @@ void wine_vkDestroySwapchainKHR(VkDevice device_handle, VkSwapchainKHR handle, c free(swapchain->fs_hack_images); } - WINE_VK_REMOVE_HANDLE_MAPPING(device->phys_dev->instance, swapchain); device->funcs.p_vkDestroySwapchainKHR(device->host_device, swapchain->host_swapchain, NULL); + WINE_VK_REMOVE_HANDLE_MAPPING(device->phys_dev->instance, swapchain); free(swapchain); } @@ -4769,8 +4839,8 @@ void wine_vkDestroySemaphore(VkDevice device_handle, VkSemaphore semaphore_handl if (semaphore->d3d12_fence_shm) NtUnmapViewOfSection(GetCurrentProcess(), semaphore->d3d12_fence_shm); - WINE_VK_REMOVE_HANDLE_MAPPING(device->phys_dev->instance, semaphore); device->funcs.p_vkDestroySemaphore(device->host_device, semaphore->semaphore, NULL); + WINE_VK_REMOVE_HANDLE_MAPPING(device->phys_dev->instance, semaphore); if (semaphore->fence_timeline_semaphore) device->funcs.p_vkDestroySemaphore(device->host_device, semaphore->fence_timeline_semaphore, NULL); diff --git a/dlls/winevulkan/vulkan_private.h b/dlls/winevulkan/vulkan_private.h index ffae94adbe4..ecd148ea48a 100644 --- a/dlls/winevulkan/vulkan_private.h +++ b/dlls/winevulkan/vulkan_private.h @@ -269,10 +269,12 @@ struct wine_device_memory BOOL inherit; DWORD access; HANDLE handle; - void *mapping; + void *vm_map; struct keyed_mutex_shm *keyed_mutex_shm; VkSemaphore keyed_mutex_sem; uint64_t keyed_mutex_instance_id; + + struct wine_vk_mapping mapping; }; static inline VkDeviceMemory wine_device_memory_to_handle(struct wine_device_memory *device_memory) @@ -324,7 +326,8 @@ static inline VkDebugReportCallbackEXT wine_debug_report_callback_to_handle( struct wine_surface { VkSurfaceKHR host_surface; - VkSurfaceKHR driver_surface; /* wine driver surface */ + VkSurfaceKHR driver_surface; + HWND hwnd; struct wine_vk_mapping mapping; }; diff --git a/dlls/winex11.drv/bitblt.c b/dlls/winex11.drv/bitblt.c index e6890495a51..5d2392fe450 100644 --- a/dlls/winex11.drv/bitblt.c +++ b/dlls/winex11.drv/bitblt.c @@ -1642,6 +1642,8 @@ static void update_surface_region( struct x11drv_window_surface *surface ) if (!shape_layered_windows) return; + if (wm_is_steamcompmgr(gdi_display)) return; + if (!surface->is_argb && surface->color_key == CLR_INVALID) { XShapeCombineMask( gdi_display, surface->window, ShapeBounding, 0, 0, None, ShapeSet ); diff --git a/dlls/winex11.drv/display.c b/dlls/winex11.drv/display.c index d2c353250b9..a0cd93bac0f 100644 --- a/dlls/winex11.drv/display.c +++ b/dlls/winex11.drv/display.c @@ -544,7 +544,7 @@ static void fixup_device_id(UINT *vendor_id, UINT *device_id) *vendor_id = 0x10de; /* NVIDIA */ *device_id = 0x2487; /* RTX 3060 */ } - else if (*vendor_id == 0x1002 && *device_id == 0x163f && (sgi = getenv("WINE_HIDE_VANGOGH_GPU")) && *sgi != '0') + else if (*vendor_id == 0x1002 && (*device_id == 0x163f || *device_id == 0x1435) && (sgi = getenv("WINE_HIDE_VANGOGH_GPU")) && *sgi != '0') { *device_id = 0x687f; /* Radeon RX Vega 56/64 */ } diff --git a/dlls/winex11.drv/event.c b/dlls/winex11.drv/event.c index 89a7506b236..48903cd5623 100644 --- a/dlls/winex11.drv/event.c +++ b/dlls/winex11.drv/event.c @@ -233,28 +233,6 @@ static Bool filter_event( Display *display, XEvent *event, char *arg ) case ButtonPress: case ButtonRelease: return (mask & QS_MOUSEBUTTON) != 0; -#ifdef GenericEvent - case GenericEvent: -#ifdef HAVE_X11_EXTENSIONS_XINPUT2_H - if (event->xcookie.extension == xinput2_opcode) - { - switch (event->xcookie.evtype) - { - case XI_RawButtonPress: - case XI_RawButtonRelease: - return (mask & QS_MOUSEBUTTON) != 0; - case XI_RawMotion: - case XI_RawTouchBegin: - case XI_RawTouchUpdate: - case XI_RawTouchEnd: - return (mask & QS_INPUT) != 0; - case XI_DeviceChanged: - return (mask & (QS_INPUT|QS_MOUSEBUTTON)) != 0; - } - } -#endif - return (mask & QS_SENDMESSAGE) != 0; -#endif case MotionNotify: case EnterNotify: case LeaveNotify: @@ -269,6 +247,13 @@ static Bool filter_event( Display *display, XEvent *event, char *arg ) case PropertyNotify: case ClientMessage: return (mask & QS_POSTMESSAGE) != 0; +#ifdef GenericEvent + case GenericEvent: +#ifdef HAVE_X11_EXTENSIONS_XINPUT2_H + if (event->xcookie.extension == xinput2_opcode) return (mask & QS_INPUT) != 0; +#endif + /* fallthrough */ +#endif default: return (mask & QS_SENDMESSAGE) != 0; } @@ -278,6 +263,9 @@ static void wait_grab_pointer( Display *display ) { RECT rect; + /* unnecessary on gamescope, windows cannot be moved with the mouse */ + if (wm_is_steamcompmgr( display )) return; + /* release cursor grab held by any Wine process */ NtUserGetClipCursor( &rect ); NtUserClipCursor( NULL ); diff --git a/dlls/winex11.drv/fs.c b/dlls/winex11.drv/fs.c index bda79419c69..73824021060 100644 --- a/dlls/winex11.drv/fs.c +++ b/dlls/winex11.drv/fs.c @@ -720,21 +720,21 @@ RECT fs_hack_real_mode( HMONITOR monitor ) return rect; } -/* Return whether width and height are the same as the current mode used by a monitor */ -BOOL fs_hack_matches_current_mode( HMONITOR monitor, INT width, INT height ) +/* Return whether a window rectangle is fullscreen on a fshack monitor */ +BOOL fs_hack_is_window_rect_fullscreen( HMONITOR monitor, const RECT *rect ) { MONITORINFO info = {.cbSize = sizeof(MONITORINFO)}; - BOOL matched; + BOOL fullscreen; TRACE( "monitor %p\n", monitor ); if (!NtUserGetMonitorInfo( monitor, &info )) return FALSE; - matched = (width == info.rcMonitor.right - info.rcMonitor.left) && - (height == info.rcMonitor.bottom - info.rcMonitor.top); - TRACE( "matched: %s\n", matched ? "TRUE" : "FALSE" ); + fullscreen = rect->left <= info.rcMonitor.left && rect->right >= info.rcMonitor.right && + rect->top <= info.rcMonitor.top && rect->bottom >= info.rcMonitor.bottom; + TRACE( "fullscreen: %s\n", fullscreen ? "TRUE" : "FALSE" ); - return matched; + return fullscreen; } /* Transform a point in user virtual screen coordinates to real virtual screen coordinates */ diff --git a/dlls/winex11.drv/mouse.c b/dlls/winex11.drv/mouse.c index 1c853e9393f..655b0c67651 100644 --- a/dlls/winex11.drv/mouse.c +++ b/dlls/winex11.drv/mouse.c @@ -331,21 +331,39 @@ static void update_relative_valuators( XIAnyClassInfo **classes, int num_classes /*********************************************************************** - * X11DRV_XInput2_Init + * x11drv_xinput2_init */ -void X11DRV_XInput2_Init(void) +void x11drv_xinput2_init( struct x11drv_thread_data *data ) { #ifdef HAVE_X11_EXTENSIONS_XINPUT2_H - struct x11drv_thread_data *data = x11drv_thread_data(); + unsigned char mask_bits[XIMaskLen(XI_LASTEVENT)]; int major = 2, minor = 2; + XIEventMask mask; + int count; + + if (!xinput2_available || pXIQueryVersion( data->display, &major, &minor )) + { + WARN( "XInput 2.0 not available\n" ); + xinput2_available = FALSE; + return; + } + + TRACE( "XInput2 %d.%d available\n", major, minor ); + + mask.mask = mask_bits; + mask.mask_len = sizeof(mask_bits); + mask.deviceid = XIAllMasterDevices; + memset( mask_bits, 0, sizeof(mask_bits) ); + XISetMask( mask_bits, XI_DeviceChanged ); + pXISelectEvents( data->display, DefaultRootWindow( data->display ), &mask, 1 ); - if (xinput2_available && pXIQueryVersion( data->display, &major, &minor ) == Success && - pXIGetClientPointer( data->display, None, &data->xi2_core_pointer )) - TRACE( "XInput2 %d.%d available\n", major, minor ); + if (!pXIGetClientPointer( data->display, None, &data->xinput2_pointer )) + WARN( "Failed to get xinput2 master pointer device\n" ); else { - data->xi2_core_pointer = 0; - WARN( "XInput 2.2 not available\n" ); + XIDeviceInfo *pointer_info = pXIQueryDevice( data->display, data->xinput2_pointer, &count ); + update_relative_valuators( pointer_info->classes, pointer_info->num_classes ); + pXIFreeDeviceInfo( pointer_info ); } #endif } @@ -359,50 +377,31 @@ void X11DRV_XInput2_Enable( Display *display, Window window, long event_mask ) struct x11drv_thread_data *data = x11drv_thread_data(); unsigned char mask_bits[XIMaskLen(XI_LASTEVENT)]; BOOL raw = (window == None); - XIDeviceInfo *pointer_info; XIEventMask mask; - int count; mask.mask = mask_bits; mask.mask_len = sizeof(mask_bits); mask.deviceid = XIAllMasterDevices; memset( mask_bits, 0, sizeof(mask_bits) ); - if (NtUserGetWindowThread( NtUserGetDesktopWindow(), NULL ) == GetCurrentThreadId()) - data->xi2_rawinput_only = TRUE; - else - data->xi2_rawinput_only = FALSE; - /* FIXME: steam overlay doesn't like if we use XI2 for non-raw events */ - if (event_mask & PointerMotionMask) + if (raw && event_mask) { - XISetMask( mask_bits, XI_DeviceChanged ); - if (raw) + XISetMask( mask_bits, XI_RawMotion ); + if (data->xi2_rawinput_only) { - XISetMask( mask_bits, XI_RawMotion ); XISetMask( mask_bits, XI_RawTouchBegin ); XISetMask( mask_bits, XI_RawTouchUpdate ); XISetMask( mask_bits, XI_RawTouchEnd ); + XISetMask( mask_bits, XI_RawButtonPress ); + XISetMask( mask_bits, XI_RawButtonRelease ); } } - if (event_mask & ButtonPressMask) - { - XISetMask( mask_bits, XI_DeviceChanged ); - if (raw) XISetMask( mask_bits, XI_RawButtonPress ); - } - if (event_mask & ButtonReleaseMask) - { - XISetMask( mask_bits, XI_DeviceChanged ); - if (raw) XISetMask( mask_bits, XI_RawButtonRelease ); - } + XISetMask( mask_bits, XI_DeviceChanged ); pXISelectEvents( display, raw ? DefaultRootWindow( display ) : window, &mask, 1 ); if (!raw) XSelectInput( display, window, event_mask ); - - pointer_info = pXIQueryDevice( data->display, data->xi2_core_pointer, &count ); - update_relative_valuators( pointer_info->classes, pointer_info->num_classes ); - pXIFreeDeviceInfo( pointer_info ); } #endif @@ -1700,7 +1699,7 @@ static BOOL X11DRV_XIDeviceChangedEvent( XIDeviceChangedEvent *event ) { struct x11drv_thread_data *data = x11drv_thread_data(); - if (event->deviceid != data->xi2_core_pointer) return FALSE; + if (event->deviceid != data->xinput2_pointer) return FALSE; if (event->reason != XISlaveSwitch) return FALSE; update_relative_valuators( event->classes, event->num_classes ); @@ -1720,7 +1719,7 @@ static BOOL map_raw_event_coords( XIRawEvent *event, INPUT *input, RAWINPUT *raw if (x->number < 0 || y->number < 0) return FALSE; if (!event->valuators.mask_len) return FALSE; - if (event->deviceid != thread_data->xi2_core_pointer) return FALSE; + if (event->deviceid != thread_data->xinput2_pointer) return FALSE; if (x->mode == XIModeRelative && y->mode == XIModeRelative) input->mi.dwFlags &= ~(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK); @@ -1865,7 +1864,7 @@ static BOOL X11DRV_RawButtonEvent( XGenericEventCookie *cookie ) button = pointer_mapping->buttons[button] - 1; if (button < 0 || button >= NB_BUTTONS) return FALSE; - if (event->deviceid != thread_data->xi2_core_pointer) return FALSE; + if (event->deviceid != thread_data->xinput2_pointer) return FALSE; TRACE( "raw button %u (raw: %u) %s\n", button, event->detail, event->evtype == XI_RawButtonRelease ? "up" : "down" ); @@ -1947,9 +1946,9 @@ static BOOL X11DRV_XIDeviceEvent( XIDeviceEvent *event ) #endif /* HAVE_X11_EXTENSIONS_XINPUT2_H */ /*********************************************************************** - * X11DRV_XInput2_Load + * x11drv_xinput2_load */ -void X11DRV_XInput2_Load(void) +void x11drv_xinput2_load(void) { #if defined(SONAME_LIBXI) int event, error; @@ -2016,7 +2015,6 @@ static BOOL X11DRV_RawTouchEvent( XGenericEventCookie *xev ) int flags = 0; POINT pos; - if (!thread_data->xi2_rawinput_only) return FALSE; if (!map_raw_event_coords( event, &input, &rawinput )) return FALSE; if (!(input.mi.dwFlags & MOUSEEVENTF_ABSOLUTE)) return FALSE; pos.x = input.mi.dx; diff --git a/dlls/winex11.drv/opengl.c b/dlls/winex11.drv/opengl.c index 43f87e11b1d..23eb26420fa 100644 --- a/dlls/winex11.drv/opengl.c +++ b/dlls/winex11.drv/opengl.c @@ -1480,8 +1480,18 @@ static enum dc_gl_layered_type get_gl_layered_type( HWND hwnd ) static BOOL drawable_needs_clipping( HWND hwnd, BOOL known_child ) { - if (known_child) return TRUE; - return NtUserGetWindowRelative( hwnd, GW_CHILD ) || NtUserGetAncestor( hwnd, GA_PARENT ) != NtUserGetDesktopWindow(); + static int no_child_clipping_cached = -1; + + if (no_child_clipping_cached == -1) + { + const char *sgi = getenv( "SteamGameId" ); + + no_child_clipping_cached = sgi && (!strcmp( sgi, "2229850" ) || !strcmp( sgi, "2229880" )); + if (no_child_clipping_cached) FIXME( "HACK: disabling child GL window clipping.\n" ); + } + + if (known_child && !no_child_clipping_cached) return TRUE; + return (!no_child_clipping_cached && NtUserGetWindowRelative( hwnd, GW_CHILD )) || NtUserGetAncestor( hwnd, GA_PARENT ) != NtUserGetDesktopWindow(); } /*********************************************************************** @@ -1541,6 +1551,7 @@ static struct gl_drawable *create_gl_drawable( HWND hwnd, const struct wgl_pixel (visual->class == PseudoColor || visual->class == GrayScale || visual->class == DirectColor) ? AllocAll : AllocNone ); gl->window = create_client_window( hwnd, visual, gl->colormap ); + gl->swap_interval = 0; if (gl->window) { gl->drawable = pglXCreateWindow( gdi_display, gl->format->fbconfig, gl->window, NULL ); @@ -2728,7 +2739,9 @@ static void wglDrawBuffer( GLenum buffer ) TRACE( "buffer %#x.\n", buffer ); - ctx->drawing_to_front = (buffer == GL_FRONT); + if (!ctx->current_draw_fbo || (ctx->fs_hack && ctx->current_draw_fbo == ctx->fs_hack_fbo)) + ctx->drawing_to_front = (buffer == GL_FRONT || buffer == GL_FRONT_AND_BACK); + if (ctx->fs_hack && ctx->current_draw_fbo == ctx->fs_hack_fbo) { TRACE( "Overriding %#x with GL_COLOR_ATTACHMENT0\n", buffer ); @@ -3494,7 +3507,7 @@ static const GLubyte *wglGetString(GLenum name) int sz; override_vendor = 0; - if ((env = getenv("WINE_GL_HIDE_NVIDIA"))) + if ((env = getenv("WINE_GL_VENDOR_REPORT_AMD"))) { override_vendor = env[0] != '0'; } @@ -3522,13 +3535,19 @@ static const GLubyte *wglGetString(GLenum name) if (name == GL_RENDERER) { s = pglGetString(name); - if (s && strstr((const char *)s, "NVIDIA")) return (const GLubyte *)"AMD Radeon Graphics"; + if (s && (strstr((const char *)s, "NVIDIA") || strstr((const char *)s, "Intel"))) + { + return (const GLubyte *)"AMD Radeon Graphics"; + } return s; } else if (name == GL_VENDOR) { s = pglGetString(name); - if (s && strstr((const char *)s, "NVIDIA")) return (const GLubyte *)"AMD"; + if (s && (strstr((const char *)s, "NVIDIA") || strstr((const char *)s, "Intel"))) + { + return (const GLubyte *)"AMD"; + } return s; } } @@ -4893,7 +4912,7 @@ static BOOL glxdrv_wglSwapBuffers( HDC hdc ) case DC_GL_PIXMAP_WIN: if (ctx) sync_context( ctx ); if (!gl->layered_type) escape.drawable = gl->pixmap; - if (pglXCopySubBufferMESA) { + if (ctx && pglXCopySubBufferMESA) { /* (glX)SwapBuffers has an implicit glFlush effect, however * GLX_MESA_copy_sub_buffer doesn't. Make sure GL is flushed before * copying */ @@ -4902,7 +4921,7 @@ static BOOL glxdrv_wglSwapBuffers( HDC hdc ) gl->pixmap_size.cx, gl->pixmap_size.cy ); break; } - if (pglXSwapBuffersMscOML) + if (ctx && pglXSwapBuffersMscOML) { pglFlush(); target_sbc = pglXSwapBuffersMscOML( gdi_display, gl->drawable, 0, 0, 0 ); @@ -4928,7 +4947,7 @@ static BOOL glxdrv_wglSwapBuffers( HDC hdc ) ctx->fs_hack = FALSE; fs_hack_setup_context( ctx, gl ); } - if ((escape.drawable || gl->layered_type) && pglXSwapBuffersMscOML) + if (ctx && (escape.drawable || gl->layered_type) && pglXSwapBuffersMscOML) { pglFlush(); target_sbc = pglXSwapBuffersMscOML( gdi_display, gl->drawable, 0, 0, 0 ); @@ -4938,14 +4957,14 @@ static BOOL glxdrv_wglSwapBuffers( HDC hdc ) break; } - if ((escape.drawable || gl->layered_type) && pglXWaitForSbcOML) + if (ctx && (escape.drawable || gl->layered_type) && pglXWaitForSbcOML) pglXWaitForSbcOML( gdi_display, gl->drawable, target_sbc, &ust, &msc, &sbc ); update_window_surface( gl, hwnd ); release_gl_drawable( gl ); if (escape.drawable) - NtGdiExtEscape( ctx->hdc, NULL, 0, X11DRV_ESCAPE, sizeof(escape), (LPSTR)&escape, 0, NULL ); + NtGdiExtEscape( ctx ? ctx->hdc : hdc, NULL, 0, X11DRV_ESCAPE, sizeof(escape), (LPSTR)&escape, 0, NULL ); return TRUE; } diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c index f355bb20c8c..3f64774d2cb 100644 --- a/dlls/winex11.drv/window.c +++ b/dlls/winex11.drv/window.c @@ -2077,6 +2077,29 @@ void set_hwnd_style_props( Display *display, Window window, HWND hwnd ) } +void set_gamescope_overlay_prop( Display *display, Window window, HWND hwnd ) +{ + static const WCHAR class_name[] = {'X','a','l','i','a','O','v','e','r','l','a','y','B','o','x',0}; + WCHAR class_name_buf[16]; + UNICODE_STRING class_name_str; + INT ret; + + class_name_str.Buffer = class_name_buf; + class_name_str.MaximumLength = sizeof(class_name_buf); + + ret = NtUserGetClassName( hwnd, FALSE, &class_name_str ); + + if (ret && !wcscmp( class_name_buf, class_name )) { + DWORD one = 1; + + TRACE( "setting GAMESCOPE_XALIA_OVERLAY on window %lx, hwnd %p\n", window, hwnd ); + + XChangeProperty( display, window, x11drv_atom(GAMESCOPE_XALIA_OVERLAY), XA_CARDINAL, 32, + PropModeReplace, (unsigned char *)&one, sizeof(one) / 4 ); + } +} + + /********************************************************************** * create_whole_window * @@ -2147,6 +2170,8 @@ static void create_whole_window( struct x11drv_win_data *data ) set_hwnd_style_props( data->display, data->whole_window, data->hwnd ); + set_gamescope_overlay_prop( data->display, data->whole_window, data->hwnd ); + /* set the window text */ if (!NtUserInternalGetWindowText( data->hwnd, text, ARRAY_SIZE( text ))) text[0] = 0; sync_window_text( data->display, data->whole_window, text ); @@ -2488,6 +2513,9 @@ BOOL X11DRV_CreateWindow( HWND hwnd ) struct x11drv_thread_data *data = x11drv_init_thread_data(); XSetWindowAttributes attr; + data->xi2_rawinput_only = TRUE; + X11DRV_XInput2_Enable( data->display, None, PointerMotionMask | ButtonPressMask | ButtonReleaseMask ); + /* create the cursor clipping window */ attr.override_redirect = TRUE; attr.event_mask = StructureNotifyMask | FocusChangeMask; @@ -3107,9 +3135,9 @@ BOOL X11DRV_WindowPosChanging( HWND hwnd, HWND insert_after, UINT swp_flags, if (!data && !(data = X11DRV_create_win_data( hwnd, window_rect, client_rect ))) return TRUE; + monitor = fs_hack_monitor_from_rect( window_rect ); - if (fs_hack_enabled( monitor ) && fs_hack_matches_current_mode( monitor, window_rect->right - window_rect->left, - window_rect->bottom - window_rect->top )) + if (fs_hack_enabled( monitor ) && fs_hack_is_window_rect_fullscreen( monitor, window_rect )) window_update_fshack( data, window_rect, client_rect, monitor, TRUE ); else window_update_fshack( data, window_rect, client_rect, monitor, FALSE ); @@ -3472,7 +3500,7 @@ UINT X11DRV_ShowWindow( HWND hwnd, INT cmd, RECT *rect, UINT swp ) monitor = fs_hack_monitor_from_rect( rect ); if (data->fs_hack || (fs_hack_enabled( monitor ) && - fs_hack_matches_current_mode( monitor, rect->right - rect->left, rect->bottom - rect->top ))) + fs_hack_is_window_rect_fullscreen( monitor, rect ))) { MONITORINFO info = {.cbSize = sizeof(MONITORINFO)}; NtUserGetMonitorInfo( monitor, &info ); @@ -3729,8 +3757,7 @@ static void handle_window_desktop_resize( struct x11drv_win_data *data, UINT old HMONITOR monitor = fs_hack_monitor_from_hwnd( data->hwnd ); if (fs_hack_mapping_required( monitor ) && - fs_hack_matches_current_mode( monitor, data->whole_rect.right - data->whole_rect.left, - data->whole_rect.bottom - data->whole_rect.top )) + fs_hack_is_window_rect_fullscreen( monitor, &data->whole_rect )) { window_update_fshack( data, NULL, NULL, monitor, TRUE ); return; @@ -3755,8 +3782,7 @@ static void handle_window_desktop_resize( struct x11drv_win_data *data, UINT old } if (!fs_hack_mapping_required( monitor ) || - !fs_hack_matches_current_mode( monitor, data->whole_rect.right - data->whole_rect.left, - data->whole_rect.bottom - data->whole_rect.top )) + !fs_hack_is_window_rect_fullscreen( monitor, &data->whole_rect )) window_update_fshack( data, NULL, NULL, monitor, FALSE ); } diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h index 9d3f742cbad..da610e060a6 100644 --- a/dlls/winex11.drv/x11drv.h +++ b/dlls/winex11.drv/x11drv.h @@ -266,8 +266,6 @@ extern void X11DRV_ThreadDetach(void); /* X11 driver internal functions */ extern void X11DRV_Xcursor_Init(void); -extern void X11DRV_XInput2_Load(void); -extern void X11DRV_XInput2_Init(void); extern void X11DRV_XInput2_Enable( Display *display, Window window, long event_mask ); extern DWORD copy_image_bits( BITMAPINFO *info, BOOL is_r8g8b8, XImage *image, @@ -400,7 +398,7 @@ struct x11drv_thread_data #ifdef HAVE_X11_EXTENSIONS_XINPUT2_H XIValuatorClassInfo x_valuator; XIValuatorClassInfo y_valuator; - int xi2_core_pointer; /* XInput2 core pointer id */ + int xinput2_pointer; /* XInput2 core pointer id */ int xi2_rawinput_only; int xi2_active_touches; int xi2_primary_touchid; @@ -569,6 +567,7 @@ enum x11drv_atoms XATOM_text_uri_list, XATOM_GAMESCOPE_FOCUSED_APP, XATOM_GAMESCOPE_DISPLAY_EDID_PATH, + XATOM_GAMESCOPE_XALIA_OVERLAY, NB_XATOMS }; @@ -597,6 +596,9 @@ extern BOOL X11DRV_MappingNotify( HWND hWnd, XEvent *event ); extern BOOL X11DRV_GenericEvent( HWND hwnd, XEvent *event ); extern int xinput2_opcode; +extern void x11drv_xinput2_load(void); +extern void x11drv_xinput2_init( struct x11drv_thread_data *data ); + extern Bool (*pXGetEventData)( Display *display, XEvent /*XGenericEventCookie*/ *event ); extern void (*pXFreeEventData)( Display *display, XEvent /*XGenericEventCookie*/ *event ); @@ -708,7 +710,7 @@ extern BOOL fs_hack_mapping_required( HMONITOR monitor ); extern BOOL fs_hack_is_integer(void); extern HMONITOR fs_hack_monitor_from_hwnd( HWND hwnd ); extern HMONITOR fs_hack_monitor_from_rect( const RECT *rect ); -extern BOOL fs_hack_matches_current_mode( HMONITOR monitor, INT width, INT height ); +extern BOOL fs_hack_is_window_rect_fullscreen( HMONITOR monitor, const RECT *rect ); extern RECT fs_hack_current_mode( HMONITOR monitor ); extern RECT fs_hack_real_mode( HMONITOR monitor ); extern void fs_hack_point_user_to_real( POINT *pos ); diff --git a/dlls/winex11.drv/x11drv_main.c b/dlls/winex11.drv/x11drv_main.c index 2d3104c2641..b286e6cfde8 100644 --- a/dlls/winex11.drv/x11drv_main.c +++ b/dlls/winex11.drv/x11drv_main.c @@ -234,6 +234,7 @@ static const char * const atom_names[NB_XATOMS - FIRST_XATOM] = "text/uri-list", "GAMESCOPE_FOCUSED_APP", "GAMESCOPE_DISPLAY_EDID_PATH", + "GAMESCOPE_XALIA_OVERLAY", }; /*********************************************************************** @@ -804,7 +805,7 @@ static NTSTATUS x11drv_init( void *arg ) #ifdef SONAME_LIBXCOMPOSITE X11DRV_XComposite_Init(); #endif - X11DRV_XInput2_Load(); + x11drv_xinput2_load(); XkbUseExtension( gdi_display, NULL, NULL ); X11DRV_InitKeyboard( gdi_display ); @@ -930,10 +931,7 @@ struct x11drv_thread_data *x11drv_init_thread_data(void) NtUserGetThreadInfo()->driver_data = (UINT_PTR)data; if (use_xim) xim_thread_attach( data ); - - X11DRV_XInput2_Init(); - if (NtUserGetWindowThread( NtUserGetDesktopWindow(), NULL ) == GetCurrentThreadId()) - X11DRV_XInput2_Enable( data->display, None, PointerMotionMask|ButtonPressMask|ButtonReleaseMask ); + x11drv_xinput2_init( data ); return data; } diff --git a/dlls/winhttp/request.c b/dlls/winhttp/request.c index 84af8e58988..17621af8647 100644 --- a/dlls/winhttp/request.c +++ b/dlls/winhttp/request.c @@ -43,6 +43,14 @@ WINE_DEFAULT_DEBUG_CHANNEL(winhttp); #define DEFAULT_KEEP_ALIVE_TIMEOUT 30000 +#define ACTUAL_DEFAULT_RECEIVE_RESPONSE_TIMEOUT 21000 + +static int request_receive_response_timeout( struct request *req ) +{ + if (req->receive_response_timeout == -1) return ACTUAL_DEFAULT_RECEIVE_RESPONSE_TIMEOUT; + return req->receive_response_timeout; +} + static const WCHAR *attribute_table[] = { L"Mime-Version", /* WINHTTP_QUERY_MIME_VERSION = 0 */ @@ -257,6 +265,16 @@ static BOOL cancel_queue( struct queue *queue ) return cancelled; } +static void task_send_callback( void *ctx, BOOL abort ) +{ + struct send_callback *s = ctx; + + if (abort) return; + + TRACE( "running %p\n", ctx ); + send_callback( s->task_hdr.obj, s->status, s->info, s->buflen ); +} + static void free_header( struct header *header ) { free( header->field ); @@ -1648,7 +1666,7 @@ static DWORD open_connection( struct request *request ) return ret; } netconn_set_timeout( netconn, TRUE, request->send_timeout ); - netconn_set_timeout( netconn, FALSE, request->receive_response_timeout ); + netconn_set_timeout( netconn, FALSE, request_receive_response_timeout( request )); request->netconn = netconn; @@ -1686,7 +1704,7 @@ static DWORD open_connection( struct request *request ) TRACE("using connection %p\n", netconn); netconn_set_timeout( netconn, TRUE, request->send_timeout ); - netconn_set_timeout( netconn, FALSE, request->receive_response_timeout ); + netconn_set_timeout( netconn, FALSE, request_receive_response_timeout( request )); request->netconn = netconn; } @@ -1904,7 +1922,7 @@ static DWORD refill_buffer( struct request *request, BOOL notify ) static void finished_reading( struct request *request ) { - BOOL close = FALSE, notify; + BOOL close = FALSE, close_request_headers; WCHAR connection[20]; DWORD size = sizeof(connection); @@ -1919,18 +1937,16 @@ static void finished_reading( struct request *request ) } else if (!wcscmp( request->version, L"HTTP/1.0" )) close = TRUE; - if (close) + size = sizeof(connection); + close_request_headers = + (!query_headers( request, WINHTTP_QUERY_CONNECTION | WINHTTP_QUERY_FLAG_REQUEST_HEADERS, NULL, connection, &size, NULL ) + || !query_headers( request, WINHTTP_QUERY_PROXY_CONNECTION | WINHTTP_QUERY_FLAG_REQUEST_HEADERS, NULL, connection, &size, NULL )) + && !wcsicmp( connection, L"close" ); + if (close || close_request_headers) { - size = sizeof(connection); - notify = (!query_headers( request, WINHTTP_QUERY_CONNECTION | WINHTTP_QUERY_FLAG_REQUEST_HEADERS, - NULL, connection, &size, NULL ) - || !query_headers( request, WINHTTP_QUERY_PROXY_CONNECTION | WINHTTP_QUERY_FLAG_REQUEST_HEADERS, - NULL, connection, &size, NULL )) - && !wcsicmp( connection, L"close" ); - - if (notify) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION, 0, 0 ); + if (close_request_headers) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION, 0, 0 ); netconn_release( request->netconn ); - if (notify) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED, 0, 0 ); + if (close_request_headers) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED, 0, 0 ); } else cache_connection( request->netconn ); @@ -2319,7 +2335,7 @@ static DWORD send_request( struct request *request, const WCHAR *headers, DWORD if (!chunked && content_length <= optional_len) { - netconn_set_timeout( request->netconn, FALSE, request->receive_response_timeout ); + netconn_set_timeout( request->netconn, FALSE, request_receive_response_timeout( request )); request->read_reply_status = read_reply( request ); if (request->state == REQUEST_RESPONSE_STATE_READ_RESPONSE_QUEUED) request->state = REQUEST_RESPONSE_STATE_READ_RESPONSE_QUEUED_REPLY_RECEIVED; @@ -2922,7 +2938,7 @@ static DWORD receive_response( struct request *request ) } /* fallthrough */ case REQUEST_RESPONSE_STATE_READ_RESPONSE_QUEUED_REQUEST_SENT: - netconn_set_timeout( request->netconn, FALSE, request->receive_response_timeout ); + netconn_set_timeout( request->netconn, FALSE, request_receive_response_timeout( request )); request->read_reply_status = read_reply( request ); request->state = REQUEST_RESPONSE_STATE_REPLY_RECEIVED; break; @@ -3052,9 +3068,12 @@ static DWORD query_data_ready( struct request *request ) return count; } -static BOOL skip_async_queue( struct request *request ) +static BOOL skip_async_queue( struct request *request, BOOL *wont_block, DWORD to_read ) { - return request->hdr.recursion_count < 3 && (end_of_read_data( request ) || query_data_ready( request )); + if (!request->read_chunked) + to_read = min( to_read, request->content_length - request->content_read ); + *wont_block = end_of_read_data( request ) || query_data_ready( request ) >= to_read; + return request->hdr.recursion_count < 3 && *wont_block; } static DWORD query_data_available( struct request *request, DWORD *available, BOOL async ) @@ -3106,6 +3125,7 @@ BOOL WINAPI WinHttpQueryDataAvailable( HINTERNET hrequest, LPDWORD available ) DWORD ret; struct request *request; BOOL async; + BOOL wont_block = FALSE; TRACE("%p, %p\n", hrequest, available); @@ -3121,7 +3141,44 @@ BOOL WINAPI WinHttpQueryDataAvailable( HINTERNET hrequest, LPDWORD available ) return FALSE; } - if ((async = request->connect->hdr.flags & WINHTTP_FLAG_ASYNC) && !skip_async_queue( request )) + if (!(async = request->connect->hdr.flags & WINHTTP_FLAG_ASYNC) || skip_async_queue( request, &wont_block, 1 )) + { + ret = query_data_available( request, available, async ); + } + else if (wont_block) + { + /* Data available but recursion limit reached, only queue callback. */ + struct send_callback *s; + + if (!(s = malloc( sizeof(*s) ))) + { + release_object( &request->hdr ); + SetLastError( ERROR_OUTOFMEMORY ); + return FALSE; + } + + if (!(ret = query_data_available( request, &s->count, FALSE ))) + { + if (available) *available = s->count; + s->status = WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE; + s->info = &s->count; + s->buflen = sizeof(s->count); + } + else + { + s->result.dwResult = API_QUERY_DATA_AVAILABLE; + s->result.dwError = ret; + s->status = WINHTTP_CALLBACK_STATUS_REQUEST_ERROR; + s->info = &s->result; + s->buflen = sizeof(s->result); + } + + if ((ret = queue_task( &request->queue, task_send_callback, &s->task_hdr, &request->hdr ))) + free( s ); + else + ret = ERROR_IO_PENDING; + } + else { struct query_data *q; @@ -3139,7 +3196,6 @@ BOOL WINAPI WinHttpQueryDataAvailable( HINTERNET hrequest, LPDWORD available ) else ret = ERROR_IO_PENDING; } - else ret = query_data_available( request, available, async ); release_object( &request->hdr ); SetLastError( ret ); @@ -3165,6 +3221,7 @@ BOOL WINAPI WinHttpReadData( HINTERNET hrequest, void *buffer, DWORD to_read, DW DWORD ret; struct request *request; BOOL async; + BOOL wont_block = FALSE; TRACE( "%p, %p, %lu, %p\n", hrequest, buffer, to_read, read ); @@ -3180,7 +3237,44 @@ BOOL WINAPI WinHttpReadData( HINTERNET hrequest, void *buffer, DWORD to_read, DW return FALSE; } - if ((async = request->connect->hdr.flags & WINHTTP_FLAG_ASYNC) && !skip_async_queue( request )) + if (!(async = request->connect->hdr.flags & WINHTTP_FLAG_ASYNC) || skip_async_queue( request, &wont_block, to_read )) + { + ret = read_data( request, buffer, to_read, read, async ); + } + else if (wont_block) + { + /* Data available but recursion limit reached, only queue callback. */ + struct send_callback *s; + + if (!(s = malloc( sizeof(*s) ))) + { + release_object( &request->hdr ); + SetLastError( ERROR_OUTOFMEMORY ); + return FALSE; + } + + if (!(ret = read_data( request, buffer, to_read, &s->count, FALSE ))) + { + if (read) *read = s->count; + s->status = WINHTTP_CALLBACK_STATUS_READ_COMPLETE; + s->info = buffer; + s->buflen = s->count; + } + else + { + s->result.dwResult = API_READ_DATA; + s->result.dwError = ret; + s->status = WINHTTP_CALLBACK_STATUS_REQUEST_ERROR; + s->info = &s->result; + s->buflen = sizeof(s->result); + } + + if ((ret = queue_task( &request->queue, task_send_callback, &s->task_hdr, &request->hdr ))) + free( s ); + else + ret = ERROR_IO_PENDING; + } + else { struct read_data *r; @@ -3199,7 +3293,6 @@ BOOL WINAPI WinHttpReadData( HINTERNET hrequest, void *buffer, DWORD to_read, DW else ret = ERROR_IO_PENDING; } - else ret = read_data( request, buffer, to_read, read, async ); release_object( &request->hdr ); SetLastError( ret ); diff --git a/dlls/winhttp/tests/notification.c b/dlls/winhttp/tests/notification.c index 78319f0df5c..3e67c439bb5 100644 --- a/dlls/winhttp/tests/notification.c +++ b/dlls/winhttp/tests/notification.c @@ -1904,6 +1904,9 @@ static void CALLBACK test_recursion_callback( HINTERNET handle, DWORD_PTR contex break; case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE: + { + DWORD len; + if (!context->read_from_callback) { SetEvent( context->wait ); @@ -1920,15 +1923,23 @@ static void CALLBACK test_recursion_callback( HINTERNET handle, DWORD_PTR contex "got %lu, thread %#lx\n", context->recursion_count, GetCurrentThreadId() ); context->max_recursion_query = max( context->max_recursion_query, context->recursion_count ); InterlockedIncrement( &context->recursion_count ); - ret = WinHttpReadData( context->request, &b, 1, NULL ); + b = 0xff; + len = 0xdeadbeef; + ret = WinHttpReadData( context->request, &b, 1, &len ); err = GetLastError(); ok( ret, "failed to read data, GetLastError() %lu\n", err ); ok( err == ERROR_SUCCESS || err == ERROR_IO_PENDING, "got %lu\n", err ); + ok( b != 0xff, "got %#x.\n", b ); + ok( len == 1, "got %lu.\n", len ); if (err == ERROR_SUCCESS) context->have_sync_callback = TRUE; InterlockedDecrement( &context->recursion_count ); break; + } case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: + { + static DWORD len; + if (!buflen) { SetEvent( context->wait ); @@ -1939,13 +1950,19 @@ static void CALLBACK test_recursion_callback( HINTERNET handle, DWORD_PTR contex context->max_recursion_read = max( context->max_recursion_read, context->recursion_count ); context->read_from_callback = TRUE; InterlockedIncrement( &context->recursion_count ); - ret = WinHttpQueryDataAvailable( context->request, NULL ); + len = 0xdeadbeef; + /* Use static variable len here so write to it doesn't destroy the stack on old Windows which + * doesn't set the value at once. */ + ret = WinHttpQueryDataAvailable( context->request, &len ); err = GetLastError(); ok( ret, "failed to query data available, GetLastError() %lu\n", err ); ok( err == ERROR_SUCCESS || err == ERROR_IO_PENDING, "got %lu\n", err ); + ok( len != 0xdeadbeef || broken( len == 0xdeadbeef ) /* Win7 */, "got %lu.\n", len ); if (err == ERROR_SUCCESS) context->have_sync_callback = TRUE; InterlockedDecrement( &context->recursion_count ); break; + } + case WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE: if (!context->headers_available && context->call_receive_response_status == WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE) diff --git a/dlls/winhttp/tests/winhttp.c b/dlls/winhttp/tests/winhttp.c index 09abc4c5854..2c3f1791898 100644 --- a/dlls/winhttp/tests/winhttp.c +++ b/dlls/winhttp/tests/winhttp.c @@ -2197,6 +2197,12 @@ static const char okmsg[] = "Server: winetest\r\n" "\r\n"; +static const char okmsg_length0[] = +"HTTP/1.1 200 OK\r\n" +"Server: winetest\r\n" +"Content-length: 0\r\n" +"\r\n"; + static const char notokmsg[] = "HTTP/1.1 400 Bad Request\r\n" "\r\n"; @@ -2318,6 +2324,24 @@ static void create_websocket_accept(const char *key, char *buf, unsigned int buf CryptBinaryToStringA( (BYTE *)sha1, sizeof(sha1), CRYPT_STRING_BASE64, buf, &len); } +static int server_receive_request(int c, char *buffer, size_t buffer_size) +{ + int i, r; + + memset(buffer, 0, buffer_size); + for(i = 0; i < buffer_size - 1; i++) + { + r = recv(c, &buffer[i], 1, 0); + if (r != 1) + break; + if (i < 4) continue; + if (buffer[i - 2] == '\n' && buffer[i] == '\n' && + buffer[i - 3] == '\r' && buffer[i - 1] == '\r') + break; + } + return r; +} + static DWORD CALLBACK server_thread(LPVOID param) { struct server_info *si = param; @@ -2351,18 +2375,7 @@ static DWORD CALLBACK server_thread(LPVOID param) do { if (c == -1) c = accept(s, NULL, NULL); - - memset(buffer, 0, sizeof buffer); - for(i = 0; i < sizeof buffer - 1; i++) - { - r = recv(c, &buffer[i], 1, 0); - if (r != 1) - break; - if (i < 4) continue; - if (buffer[i - 2] == '\n' && buffer[i] == '\n' && - buffer[i - 3] == '\r' && buffer[i - 1] == '\r') - break; - } + server_receive_request(c, buffer, sizeof(buffer)); if (strstr(buffer, "GET /basic")) { send(c, okmsg, sizeof okmsg - 1, 0); @@ -2558,6 +2571,23 @@ static DWORD CALLBACK server_thread(LPVOID param) } send(c, okmsg, sizeof(okmsg) - 1, 0); } + + if (strstr(buffer, "GET /cached")) + { + send(c, okmsg_length0, sizeof okmsg_length0 - 1, 0); + r = server_receive_request(c, buffer, sizeof(buffer)); + ok(r > 0, "got %d.\n", r); + ok(!!strstr(buffer, "GET /cached"), "request not found.\n"); + send(c, okmsg_length0, sizeof okmsg_length0 - 1, 0); + r = server_receive_request(c, buffer, sizeof(buffer)); + ok(!r, "got %d, buffer[0] %d.\n", r, buffer[0]); + } + if (strstr(buffer, "GET /notcached")) + { + send(c, okmsg, sizeof okmsg - 1, 0); + r = server_receive_request(c, buffer, sizeof(buffer)); + ok(!r, "got %d, buffer[0] %d.\n", r, buffer[0] ); + } shutdown(c, 2); closesocket(c); c = -1; @@ -5822,6 +5852,65 @@ static void test_client_cert_authentication(void) WinHttpCloseHandle( ses ); } +static void test_connection_cache(int port) +{ + HINTERNET ses, con, req; + DWORD status, size; + char buffer[256]; + BOOL ret; + + ses = WinHttpOpen(L"winetest", WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0); + ok(ses != NULL, "failed to open session %lu\n", GetLastError()); + + con = WinHttpConnect(ses, L"localhost", port, 0); + ok(con != NULL, "failed to open a connection %lu\n", GetLastError()); + + req = WinHttpOpenRequest(con, L"GET", L"/cached", NULL, NULL, NULL, 0); + ok(req != NULL, "failed to open a request %lu\n", GetLastError()); + ret = WinHttpSendRequest(req, NULL, 0, NULL, 0, 0, 0); + ok(ret, "failed to send request %lu\n", GetLastError()); + ret = WinHttpReceiveResponse(req, NULL); + ok(ret, "failed to receive response %lu\n", GetLastError()); + size = sizeof(status); + ret = WinHttpQueryHeaders(req, WINHTTP_QUERY_STATUS_CODE|WINHTTP_QUERY_FLAG_NUMBER, NULL, &status, &size, NULL); + ok(ret, "failed to query status code %lu\n", GetLastError()); + ok(status == HTTP_STATUS_OK, "request failed unexpectedly %lu\n", status); + ret = WinHttpReadData(req, buffer, sizeof buffer, &size); + ok(ret, "failed to read data %lu\n", GetLastError()); + ok(!size, "got size %lu.\n", size); + WinHttpCloseHandle(req); + + req = WinHttpOpenRequest(con, L"GET", L"/cached", NULL, NULL, NULL, 0); + ok(req != NULL, "failed to open a request %lu\n", GetLastError()); + ret = WinHttpSendRequest(req, L"Connection: close", ~0ul, NULL, 0, 0, 0); + ok(ret, "failed to send request %lu\n", GetLastError()); + ret = WinHttpReceiveResponse(req, NULL); + ok(ret, "failed to receive response %lu\n", GetLastError()); + size = sizeof(status); + ret = WinHttpQueryHeaders(req, WINHTTP_QUERY_STATUS_CODE|WINHTTP_QUERY_FLAG_NUMBER, NULL, &status, &size, NULL); + ok(ret, "failed to query status code %lu\n", GetLastError()); + ok(status == HTTP_STATUS_OK, "request failed unexpectedly %lu\n", status); + ret = WinHttpReadData(req, buffer, sizeof buffer, &size); + ok(ret, "failed to read data %lu\n", GetLastError()); + ok(!size, "got size %lu.\n", size); + WinHttpCloseHandle(req); + + req = WinHttpOpenRequest(con, L"GET", L"/notcached", NULL, NULL, NULL, 0); + ok(req != NULL, "failed to open a request %lu\n", GetLastError()); + ret = WinHttpSendRequest(req, L"Connection: close", ~0ul, NULL, 0, 0, 0); + ok(ret, "failed to send request %lu\n", GetLastError()); + ret = WinHttpReceiveResponse(req, NULL); + ok(ret, "failed to receive response %lu\n", GetLastError()); + size = sizeof(status); + ret = WinHttpQueryHeaders(req, WINHTTP_QUERY_STATUS_CODE|WINHTTP_QUERY_FLAG_NUMBER, NULL, &status, &size, NULL); + ok(ret, "failed to query status code %lu\n", GetLastError()); + ok(status == HTTP_STATUS_OK, "request failed unexpectedly %lu\n", status); + WinHttpCloseHandle(req); + + WinHttpCloseHandle(con); + WinHttpCloseHandle(ses); +} + START_TEST (winhttp) { struct server_info si; @@ -5889,6 +5978,7 @@ START_TEST (winhttp) test_passport_auth(si.port); test_websocket(si.port); test_redirect(si.port); + test_connection_cache(si.port); /* send the basic request again to shutdown the server thread */ test_basic_request(si.port, NULL, L"/quit"); diff --git a/dlls/winhttp/winhttp_private.h b/dlls/winhttp/winhttp_private.h index 0bf1c5bc6b0..80ce2614ca1 100644 --- a/dlls/winhttp/winhttp_private.h +++ b/dlls/winhttp/winhttp_private.h @@ -312,6 +312,19 @@ struct task_header volatile LONG completion_sent; }; +struct send_callback +{ + struct task_header task_hdr; + DWORD status; + void *info; + DWORD buflen; + union + { + WINHTTP_ASYNC_RESULT result; + DWORD count; + }; +}; + struct send_request { struct task_header task_hdr; diff --git a/dlls/wintypes/main.c b/dlls/wintypes/main.c index d5bfb5ad11c..7d59ab92c41 100644 --- a/dlls/wintypes/main.c +++ b/dlls/wintypes/main.c @@ -34,6 +34,27 @@ WINE_DEFAULT_DEBUG_CHANNEL(wintypes); +static const struct +{ + const WCHAR *name; + unsigned int max_major; +} +present_contracts[] = +{ + { L"Windows.Foundation.UniversalApiContract", 10, }, +}; + +static BOOLEAN is_api_contract_present( const HSTRING hname, unsigned int version ) +{ + const WCHAR *name = WindowsGetStringRawBuffer( hname, NULL ); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(present_contracts); ++i) + if (!wcsicmp( name, present_contracts[i].name )) return version <= present_contracts[i].max_major; + + return FALSE; +} + struct wintypes { IActivationFactory IActivationFactory_iface; @@ -280,13 +301,13 @@ static HRESULT STDMETHODCALLTYPE api_information_statics_IsEnumNamedValuePresent static HRESULT STDMETHODCALLTYPE api_information_statics_IsApiContractPresentByMajor( IApiInformationStatics *iface, HSTRING contract_name, UINT16 major_version, BOOLEAN *value) { - FIXME("iface %p, contract_name %s, major_version %u, value %p stub!\n", iface, + FIXME("iface %p, contract_name %s, major_version %u, value %p semi-stub.\n", iface, debugstr_hstring(contract_name), major_version, value); if (!contract_name) return E_INVALIDARG; - *value = FALSE; + *value = is_api_contract_present( contract_name, major_version ); return S_OK; } diff --git a/dlls/wintypes/tests/wintypes.c b/dlls/wintypes/tests/wintypes.c index dd6401c212b..0703582d325 100644 --- a/dlls/wintypes/tests/wintypes.c +++ b/dlls/wintypes/tests/wintypes.c @@ -33,12 +33,23 @@ static void test_IApiInformationStatics(void) { + static const struct + { + const WCHAR *name; + unsigned int max_major; + } + present_contracts[] = + { + { L"Windows.Foundation.UniversalApiContract", 10, }, + }; + static const WCHAR *class_name = L"Windows.Foundation.Metadata.ApiInformation"; IAgileObject *agile_object = NULL, *tmp_agile_object = NULL; IInspectable *inspectable = NULL, *tmp_inspectable = NULL; IApiInformationStatics *statics = NULL; IActivationFactory *factory = NULL; HSTRING str, str2; + unsigned int i, j; BOOLEAN ret; HRESULT hr; @@ -424,6 +435,21 @@ static void test_IApiInformationStatics(void) WindowsDeleteString(str); + /* Test API contracts presence. */ + for (i = 0; i < ARRAY_SIZE(present_contracts); ++i) + { + hr = WindowsCreateString(present_contracts[i].name, wcslen(present_contracts[i].name), &str); + ok(hr == S_OK, "WindowsCreateString failed, hr %#lx.\n", hr); + for (j = 0; j <= present_contracts[i].max_major; ++j) + { + ret = FALSE; + hr = IApiInformationStatics_IsApiContractPresentByMajor(statics, str, i, &ret); + ok(hr == S_OK, "IsApiContractPresentByMajor failed, hr %#lx, i %u, major %u.\n", hr, i, j); + ok(ret == TRUE, "IsApiContractPresentByMajor returned FALSE, i %u, major %u.\n", i, j); + } + WindowsDeleteString(str); + } + IApiInformationStatics_Release(statics); IAgileObject_Release(agile_object); IInspectable_Release(inspectable); diff --git a/dlls/wow64/struct32.h b/dlls/wow64/struct32.h index 482f1297b28..daa89b84dfc 100644 --- a/dlls/wow64/struct32.h +++ b/dlls/wow64/struct32.h @@ -524,6 +524,12 @@ typedef struct ULONG Flags; } SYSTEM_CACHE_INFORMATION32; +typedef struct +{ + ULONG ProcessId; + UNICODE_STRING32 ImageName; +} SYSTEM_PROCESS_ID_INFORMATION32; + typedef struct { ULONG OwnerPid; diff --git a/dlls/wow64/system.c b/dlls/wow64/system.c index 51c568fedda..c3012dc2492 100644 --- a/dlls/wow64/system.c +++ b/dlls/wow64/system.c @@ -406,6 +406,24 @@ NTSTATUS WINAPI wow64_NtQuerySystemInformation( UINT *args ) } return status; + case SystemProcessIdInformation: + { + SYSTEM_PROCESS_ID_INFORMATION32 *info32 = ptr; + SYSTEM_PROCESS_ID_INFORMATION info; + + if (retlen) *retlen = sizeof(*info32); + if (len < sizeof(*info32)) return STATUS_INFO_LENGTH_MISMATCH; + + info.ProcessId = ULongToHandle( info32->ProcessId ); + unicode_str_32to64( &info.ImageName, &info32->ImageName ); + if (!(status = NtQuerySystemInformation( class, &info, sizeof(info), NULL ))) + { + info32->ImageName.MaximumLength = info.ImageName.MaximumLength; + info32->ImageName.Length = info.ImageName.Length; + } + return status; + } + case SystemHandleInformation: /* SYSTEM_HANDLE_INFORMATION */ if (len >= sizeof(SYSTEM_HANDLE_INFORMATION32)) { diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index a6ac748754d..c61e91ba708 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -7972,6 +7972,7 @@ static void test_write_watch(void) ok( count == 9 || !count /* Win 11 */, "wrong count %Iu\n", count ); ok( !base[0], "data set\n" ); + base[0x1000] = 1; send(src, "test message", sizeof("test message"), 0); ret = GetOverlappedResult( (HANDLE)dest, &ov, &bytesReturned, TRUE ); @@ -7980,10 +7981,19 @@ static void test_write_watch(void) ok( !memcmp( base, "test ", 5 ), "wrong data %s\n", base ); ok( !memcmp( base + 0x4000, "message", 8 ), "wrong data %s\n", base + 0x4000 ); + count = 64; + ret = pGetWriteWatch( 0, base, size, results, &count, &pagesize ); + ok( !ret, " GetWriteWatch failed %lu\n", GetLastError() ); + todo_wine_if( count == 3 ) ok( count == 1, "wrong count %Iu\n", count ); + todo_wine_if( count == 3 ) ok( results[0] == base + 0x1000, "got page %Iu.\n", ((char *)results[0] - base) / 0x1000 ); + + base[0x2000] = 1; count = 64; ret = pGetWriteWatch( WRITE_WATCH_FLAG_RESET, base, size, results, &count, &pagesize ); ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); - ok( count == 0, "wrong count %Iu\n", count ); + todo_wine_if( count == 4 ) ok( count == 2, "wrong count %Iu\n", count ); + todo_wine_if( count == 4 ) ok( results[0] == base + 0x1000, "got page %Iu.\n", ((char *)results[0] - base) / 0x1000 ); + todo_wine_if( count == 4 ) ok( results[1] == base + 0x2000, "got page %Iu.\n", ((char *)results[1] - base) / 0x1000 ); memset( base, 0, size ); count = 64; @@ -8014,7 +8024,7 @@ static void test_write_watch(void) count = 64; ret = pGetWriteWatch( WRITE_WATCH_FLAG_RESET, base, size, results, &count, &pagesize ); ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); - ok( count == 0, "wrong count %Iu\n", count ); + todo_wine_if( count == 2 ) ok( count == 0, "wrong count %Iu\n", count ); memset( base, 0, size ); count = 64; @@ -8043,7 +8053,7 @@ static void test_write_watch(void) count = 64; ret = pGetWriteWatch( WRITE_WATCH_FLAG_RESET, base, size, results, &count, &pagesize ); ok( !ret, "GetWriteWatch failed %lu\n", GetLastError() ); - ok( count == 0, "wrong count %Iu\n", count ); + todo_wine_if( count == 1 ) ok( count == 0, "wrong count %Iu\n", count ); } WSACloseEvent( event ); closesocket( dest ); diff --git a/dlls/xaudio2_7/tests/xaudio2.c b/dlls/xaudio2_7/tests/xaudio2.c index 2b3102d21fe..70d9ec78aee 100644 --- a/dlls/xaudio2_7/tests/xaudio2.c +++ b/dlls/xaudio2_7/tests/xaudio2.c @@ -837,7 +837,9 @@ static void test_submix(IXAudio2 *xa) HRESULT hr; IXAudio2MasteringVoice *master; XAUDIO2_VOICE_DETAILS details; - IXAudio2SubmixVoice *sub; + IXAudio2SubmixVoice *sub, *sub2; + XAUDIO2_SEND_DESCRIPTOR send_desc = { 0 }; + XAUDIO2_VOICE_SENDS sends = {1, &send_desc }; IXAudio2_StopEngine(xa); @@ -855,6 +857,26 @@ static void test_submix(IXAudio2 *xa) ok(details.InputChannels == 2, "Got wrong channel count: 0x%x\n", details.InputChannels); ok(details.InputSampleRate == 44100, "Got wrong sample rate: 0x%x\n", details.InputSampleRate); + hr = IXAudio2_CreateSubmixVoice(xa, &sub2, 2, 44100, 0, 0, NULL, NULL); + ok(hr == S_OK, "CreateSubmixVoice failed: %08lx\n", hr); + + send_desc.pOutputVoice = (IXAudio2Voice *)sub2; + hr = IXAudio2SubmixVoice_SetOutputVoices(sub, &sends); + ok(hr == S_OK, "CreateSubmixVoice failed: %08lx\n", hr); + + IXAudio2SubmixVoice_DestroyVoice(sub2); + IXAudio2SubmixVoice_GetVoiceDetails(sub2, &details); + + sends.SendCount = 0; + hr = IXAudio2SubmixVoice_SetOutputVoices(sub, &sends); + ok(hr == S_OK, "CreateSubmixVoice failed: %08lx\n", hr); + + IXAudio2SubmixVoice_DestroyVoice(sub2); + if (0) + { + IXAudio2SubmixVoice_GetVoiceDetails(sub2, &details); + } + IXAudio2SubmixVoice_DestroyVoice(sub); IXAudio2MasteringVoice_DestroyVoice(master); } diff --git a/dlls/xaudio2_7/xaudio_dll.c b/dlls/xaudio2_7/xaudio_dll.c index b821e9667b7..a58beb7b538 100644 --- a/dlls/xaudio2_7/xaudio_dll.c +++ b/dlls/xaudio2_7/xaudio_dll.c @@ -499,7 +499,11 @@ static const FAudioEngineCallback FAudioEngineCallback_Vtbl = { static inline void destroy_voice(XA2VoiceImpl *This) { - FAudioVoice_DestroyVoice(This->faudio_voice); + if (FAILED(FAudioVoice_DestroyVoiceSafeEXT(This->faudio_voice))) + { + ERR("Destroying voice %p failed.\n", This); + return; + } free_effect_chain(This->effect_chain); This->effect_chain = NULL; This->in_use = FALSE; diff --git a/dlls/xinput1_3/main.c b/dlls/xinput1_3/main.c index a18f63545cc..594010f8770 100644 --- a/dlls/xinput1_3/main.c +++ b/dlls/xinput1_3/main.c @@ -519,6 +519,16 @@ static BOOL find_opened_device(const WCHAR *device_path, int *free_slot) *free_slot = i - 1; } + /* CW-Bug-Id: #23185 Emulate Steam Input native hooks for native SDL */ + if ((swscanf(device_path, L"\\\\?\\hid#vid_28de&pid_11ff&xi_%02u#", &i) == 1 || + swscanf(device_path, L"\\\\?\\HID#VID_28DE&PID_11FF&XI_%02u#", &i) == 1) && + i < XUSER_MAX_COUNT && *free_slot != i) + { + controller_destroy(&controllers[i], TRUE); + if (*free_slot != XUSER_MAX_COUNT) open_device_at_index(controllers[i].device_path, *free_slot); + *free_slot = i; + } + return FALSE; } @@ -1116,25 +1126,15 @@ DWORD WINAPI DECLSPEC_HOTPATCH XInputGetKeystroke(DWORD index, DWORD reserved, P DWORD WINAPI DECLSPEC_HOTPATCH XInputGetCapabilities(DWORD index, DWORD flags, XINPUT_CAPABILITIES *capabilities) { - TRACE("index %lu, flags %#lx, capabilities %p.\n", index, flags, capabilities); - - start_update_thread(); - - if (index >= XUSER_MAX_COUNT) return ERROR_BAD_ARGUMENTS; - - if (!controller_lock(&controllers[index])) return ERROR_DEVICE_NOT_CONNECTED; - - if (flags & XINPUT_FLAG_GAMEPAD && controllers[index].caps.SubType != XINPUT_DEVSUBTYPE_GAMEPAD) - { - controller_unlock(&controllers[index]); - return ERROR_DEVICE_NOT_CONNECTED; - } + XINPUT_CAPABILITIES_EX caps_ex; + DWORD ret; - memcpy(capabilities, &controllers[index].caps, sizeof(*capabilities)); + TRACE("index %lu, flags %#lx, capabilities %p.\n", index, flags, capabilities); - controller_unlock(&controllers[index]); + ret = XInputGetCapabilitiesEx(0, index, flags, &caps_ex); + if (!ret) *capabilities = caps_ex.Capabilities; - return ERROR_SUCCESS; + return ret; } DWORD WINAPI DECLSPEC_HOTPATCH XInputGetDSoundAudioDeviceGuids(DWORD index, GUID *render_guid, GUID *capture_guid) @@ -1159,3 +1159,35 @@ DWORD WINAPI DECLSPEC_HOTPATCH XInputGetBatteryInformation(DWORD index, BYTE typ return ERROR_NOT_SUPPORTED; } + +DWORD WINAPI DECLSPEC_HOTPATCH XInputGetCapabilitiesEx(DWORD unk, DWORD index, DWORD flags, XINPUT_CAPABILITIES_EX *caps) +{ + DWORD ret = ERROR_SUCCESS; + HIDD_ATTRIBUTES attr; + + TRACE("unk %lu, index %lu, flags %#lx, capabilities %p.\n", unk, index, flags, caps); + + start_update_thread(); + + if (index >= XUSER_MAX_COUNT) return ERROR_BAD_ARGUMENTS; + + if (!controller_lock(&controllers[index])) return ERROR_DEVICE_NOT_CONNECTED; + + if (flags & XINPUT_FLAG_GAMEPAD && controllers[index].caps.SubType != XINPUT_DEVSUBTYPE_GAMEPAD) + ret = ERROR_DEVICE_NOT_CONNECTED; + else if (!HidD_GetAttributes(controllers[index].device, &attr)) + ret = ERROR_DEVICE_NOT_CONNECTED; + else + { + caps->Capabilities = controllers[index].caps; + caps->VendorId = attr.VendorID; + caps->ProductId = attr.ProductID; + caps->VersionNumber = attr.VersionNumber; + /* CW-Bug-Id: #23185 Emulate Steam Input native hooks for native SDL */ + caps->unk2 = index; + } + + controller_unlock(&controllers[index]); + + return ret; +} diff --git a/dlls/xinput1_4/xinput1_4.spec b/dlls/xinput1_4/xinput1_4.spec index 8c3f4c0cd73..22c511237dd 100644 --- a/dlls/xinput1_4/xinput1_4.spec +++ b/dlls/xinput1_4/xinput1_4.spec @@ -7,3 +7,4 @@ 8 stdcall XInputGetKeystroke(long long ptr) 10 stub XInputGetAudioDeviceIds(long ptr ptr ptr ptr) 100 stdcall XInputGetStateEx(long ptr) +108 stdcall XInputGetCapabilitiesEx(long long long ptr) diff --git a/include/Makefile.in b/include/Makefile.in index d4a166e265c..7149a364db8 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -345,6 +345,7 @@ SOURCES = \ highlevelmonitorconfigurationapi.h \ hlguids.h \ hlink.idl \ + holographicspaceinterop.idl \ hrtfapoapi.idl \ hstring.idl \ htiface.idl \ diff --git a/include/cfgmgr32.h b/include/cfgmgr32.h index a0bb89a2a67..ee1846b0e97 100644 --- a/include/cfgmgr32.h +++ b/include/cfgmgr32.h @@ -26,6 +26,7 @@ #endif #include +#include /* cfgmgr32 doesn't use the normal convention, it adds an underscore before A/W */ #ifdef WINE_NO_UNICODE_MACROS @@ -312,6 +313,7 @@ CMAPI CONFIGRET WINAPI CM_Get_Device_ID_List_ExW(PCWSTR,PWCHAR,ULONG,ULONG,HMACH #define CM_Get_Device_ID_List_Ex WINELIB_NAME_AW(CM_Get_Device_ID_List_Ex) CMAPI CONFIGRET WINAPI CM_Get_Device_ID_Size(PULONG,DEVINST,ULONG); CMAPI CONFIGRET WINAPI CM_Get_Device_ID_Size_Ex(PULONG,DEVINST,ULONG,HMACHINE); +CMAPI CONFIGRET WINAPI CM_Get_Device_Interface_PropertyW(LPCWSTR,const DEVPROPKEY*,DEVPROPTYPE*,PBYTE,PULONG,ULONG); CMAPI CONFIGRET WINAPI CM_Get_DevNode_Status(PULONG,PULONG,DEVINST,ULONG); CMAPI CONFIGRET WINAPI CM_Get_DevNode_Status_Ex(PULONG,PULONG,DEVINST,ULONG,HMACHINE); CMAPI CONFIGRET WINAPI CM_Get_Sibling(PDEVINST,DEVINST,ULONG); diff --git a/include/holographicspaceinterop.idl b/include/holographicspaceinterop.idl new file mode 100644 index 00000000000..913f60bc7a0 --- /dev/null +++ b/include/holographicspaceinterop.idl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 Paul Gofman for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +import "inspectable.idl"; + +[ + uuid(5c4ee536-6a98-4b86-a170-587013d6fd4b), +] +interface IHolographicSpaceInterop : IInspectable +{ + HRESULT CreateForWindow( [in] HWND window, [in] REFIID iid, [out, iid_is(iid)] void **holographic_space); +} diff --git a/include/mmreg.h b/include/mmreg.h index 1b19e1f068f..a8b0056064e 100644 --- a/include/mmreg.h +++ b/include/mmreg.h @@ -645,6 +645,22 @@ typedef struct mpeg1waveformat_tag { typedef MPEG1WAVEFORMAT *PMPEG1WAVEFORMAT, *NPMPEG1WAVEFORMAT, *LPMPEG1WAVEFORMAT; +typedef struct heaacwaveinfo_tag +{ + WAVEFORMATEX wfx; + WORD wPayloadType; + WORD wAudioProfileLevelIndication; + WORD wStructType; + WORD wReserved1; + DWORD dwReserved2; +} HEAACWAVEINFO, *PHEAACWAVEINFO; + +typedef struct heaacwaveformat_tag +{ + HEAACWAVEINFO wfInfo; + BYTE pbAudioSpecificConfig[1]; +} HEAACWAVEFORMAT, *PHEAACWAVEFORMAT; + #define ACM_MPEG_LAYER1 0x0001 #define ACM_MPEG_LAYER2 0x0002 #define ACM_MPEG_LAYER3 0x0004 diff --git a/include/ntuser.h b/include/ntuser.h index 738fe055ff7..bf65f214bc2 100644 --- a/include/ntuser.h +++ b/include/ntuser.h @@ -87,6 +87,7 @@ struct ntuser_thread_info UINT default_imc; /* default input context */ UINT64 client_imm; /* client IMM thread info */ UINT64 wmchar_data; /* client data for WM_CHAR mappings */ + UINT64 vulkan_data; /* used by winevulkan for tls */ }; static inline struct ntuser_thread_info *NtUserGetThreadInfo(void) @@ -489,6 +490,8 @@ enum wine_internal_message WM_WINE_KEYBOARD_LL_HOOK, WM_WINE_MOUSE_LL_HOOK, WM_WINE_UPDATEWINDOWSTATE, + WM_WINE_GETSCROLLBARINFO, + WM_WINE_GETSCROLLINFO, WM_WINE_FIRST_DRIVER_MSG = 0x80001000, /* range of messages reserved for the USER driver */ WM_WINE_CLIPCURSOR = 0x80001ff0, /* internal driver notification messages */ WM_WINE_SETCURSOR, diff --git a/include/sapi.idl b/include/sapi.idl index 16b8348d73b..6a593eb6b18 100644 --- a/include/sapi.idl +++ b/include/sapi.idl @@ -1293,7 +1293,7 @@ library SpeechLib coclass SpObjectToken { interface ISpObjectToken; - [default] interface ISpDataKey; + [default] interface ISpeechObjectToken; } [ diff --git a/include/windows.devices.haptics.idl b/include/windows.devices.haptics.idl index c056ab4941c..506525a6d3c 100644 --- a/include/windows.devices.haptics.idl +++ b/include/windows.devices.haptics.idl @@ -30,7 +30,10 @@ namespace Windows.Devices.Haptics { runtimeclass SimpleHapticsController; declare { + interface Windows.Foundation.Collections.IIterator; + interface Windows.Foundation.Collections.IIterable; interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; interface Windows.Foundation.Collections.IVectorView; } diff --git a/include/winnt.h b/include/winnt.h index 0e1a197d423..7f2a5f7b18b 100644 --- a/include/winnt.h +++ b/include/winnt.h @@ -1470,6 +1470,14 @@ typedef struct _XSTATE_CONFIGURATION ULONG64 EnabledUserVisibleSupervisorFeatures; } XSTATE_CONFIGURATION, *PXSTATE_CONFIGURATION; +typedef struct _XSAVE_AREA_HEADER +{ + DWORD64 Mask; + DWORD64 CompactionMask; + DWORD64 Reserved2[6]; +} +XSAVE_AREA_HEADER, *PXSAVE_AREA_HEADER; + typedef struct _YMMCONTEXT { M128A Ymm0; @@ -1515,6 +1523,11 @@ typedef struct _CONTEXT_EX #endif } CONTEXT_EX, *PCONTEXT_EX; +#define CONTEXT_EXCEPTION_ACTIVE 0x08000000 +#define CONTEXT_SERVICE_ACTIVE 0x10000000 +#define CONTEXT_EXCEPTION_REQUEST 0x40000000 +#define CONTEXT_EXCEPTION_REPORTING 0x80000000 + #define CONTEXT_ARM 0x0200000 #define CONTEXT_ARM_CONTROL (CONTEXT_ARM | 0x00000001) #define CONTEXT_ARM_INTEGER (CONTEXT_ARM | 0x00000002) diff --git a/include/winternl.h b/include/winternl.h index fb07df24eb3..d151719f898 100644 --- a/include/winternl.h +++ b/include/winternl.h @@ -2991,6 +2991,12 @@ typedef struct _SYSTEM_FIRMWARE_TABLE_INFORMATION UCHAR TableBuffer[1]; } SYSTEM_FIRMWARE_TABLE_INFORMATION, *PSYSTEM_FIRMWARE_TABLE_INFORMATION; +typedef struct _SYSTEM_PROCESS_ID_INFORMATION +{ + void *ProcessId; + UNICODE_STRING ImageName; +} SYSTEM_PROCESS_ID_INFORMATION, *PSYSTEM_PROCESS_ID_INFORMATION; + typedef struct _TIME_FIELDS { CSHORT Year; CSHORT Month; diff --git a/include/wmcodecdsp.idl b/include/wmcodecdsp.idl index a6f350ea2fc..7e91015f19e 100644 --- a/include/wmcodecdsp.idl +++ b/include/wmcodecdsp.idl @@ -72,6 +72,21 @@ coclass CWMAEncMediaObject {} ] coclass AACMFTEncoder {} +[ + uuid(01f36ce2-0907-4d8b-979d-f151be91c883) +] +coclass CFrameRateConvertDmo {} + +[ + uuid(1ea1ea14-48f4-4054-ad1a-e8aee10ac805) +] +coclass CResizerDMO {} + +[ + uuid(798059f0-89ca-4160-b325-aeb48efe4f9a) +] +coclass CColorControlDmo {} + [ uuid(98230571-0087-4204-b020-3282538e57d3) ] diff --git a/include/xinput.h b/include/xinput.h index f7c291630cb..96efae1858d 100644 --- a/include/xinput.h +++ b/include/xinput.h @@ -210,6 +210,16 @@ typedef struct _XINPUT_CAPABILITIES { XINPUT_VIBRATION Vibration; } XINPUT_CAPABILITIES, *PXINPUT_CAPABILITIES; +typedef struct _XINPUT_CAPABILITIES_EX +{ + XINPUT_CAPABILITIES Capabilities; + WORD VendorId; + WORD ProductId; + WORD VersionNumber; + WORD unk1; + DWORD unk2; +} XINPUT_CAPABILITIES_EX, *PXINPUT_CAPABILITIES_EX; + /* * Defines the structure for a joystick input event which is * retrieved using the function XInputGetKeystroke @@ -237,6 +247,7 @@ DWORD WINAPI XInputSetState(DWORD, XINPUT_VIBRATION*); DWORD WINAPI XInputGetState(DWORD, XINPUT_STATE*); DWORD WINAPI XInputGetKeystroke(DWORD, DWORD, PXINPUT_KEYSTROKE); DWORD WINAPI XInputGetCapabilities(DWORD, DWORD, XINPUT_CAPABILITIES*); +DWORD WINAPI XInputGetCapabilitiesEx(DWORD, DWORD, DWORD, XINPUT_CAPABILITIES_EX*); DWORD WINAPI XInputGetDSoundAudioDeviceGuids(DWORD, GUID*, GUID*); DWORD WINAPI XInputGetBatteryInformation(DWORD, BYTE, XINPUT_BATTERY_INFORMATION*); diff --git a/libs/faudio/include/FAudio.h b/libs/faudio/include/FAudio.h index 1a873ca2064..16837c39dbf 100644 --- a/libs/faudio/include/FAudio.h +++ b/libs/faudio/include/FAudio.h @@ -1064,6 +1064,11 @@ FAUDIOAPI void FAudioVoice_GetOutputMatrix( /* Removes this voice from the audio graph and frees memory. */ FAUDIOAPI void FAudioVoice_DestroyVoice(FAudioVoice *voice); +/* + * Returns S_OK on success and E_FAIL if voice could not be destroyed (e. g., because it is in use). + */ +FAUDIOAPI uint32_t FAudioVoice_DestroyVoiceSafeEXT(FAudioVoice *voice); + /* FAudioSourceVoice Interface */ /* Starts processing for a source voice. diff --git a/libs/faudio/src/FAudio.c b/libs/faudio/src/FAudio.c index 9366ad58208..67cbfb770e4 100644 --- a/libs/faudio/src/FAudio.c +++ b/libs/faudio/src/FAudio.c @@ -2265,11 +2265,71 @@ void FAudioVoice_GetOutputMatrix( LOG_API_EXIT(voice->audio) } -void FAudioVoice_DestroyVoice(FAudioVoice *voice) +static uint32_t check_for_sends_to_voice(FAudioVoice *voice) { + FAudio *audio = voice->audio; + uint32_t ret = 0; + FAudioSourceVoice *source; + FAudioSubmixVoice *submix; + LinkedList *list; uint32_t i; + + FAudio_PlatformLockMutex(audio->sourceLock); + list = audio->sources; + while (list != NULL) + { + source = (FAudioSourceVoice*) list->entry; + for (i = 0; i < source->sends.SendCount; i += 1) + if (source->sends.pSends[i].pOutputVoice == voice) + { + ret = 0x80004005; /* E_FAIL */ + break; + } + if (ret) + break; + list = list->next; + } + FAudio_PlatformUnlockMutex(audio->sourceLock); + + if (ret) + return ret; + + FAudio_PlatformLockMutex(audio->submixLock); + list = audio->submixes; + while (list != NULL) + { + submix = (FAudioSubmixVoice*) list->entry; + for (i = 0; i < submix->sends.SendCount; i += 1) + if (submix->sends.pSends[i].pOutputVoice == voice) + { + ret = 0x80004005; /* E_FAIL */ + break; + } + if (ret) + break; + list = list->next; + } + FAudio_PlatformUnlockMutex(audio->submixLock); + + return ret; +} + +uint32_t FAudioVoice_DestroyVoiceSafeEXT(FAudioVoice *voice) +{ + uint32_t i, ret; LOG_API_ENTER(voice->audio) + if ((ret = check_for_sends_to_voice(voice))) + { + LOG_ERROR( + voice->audio, + "Voice %p is an output for other voice(s)", + voice + ) + LOG_API_EXIT(voice->audio) + return ret; + } + /* TODO: Check for dependencies and remove from audio graph first! */ FAudio_OPERATIONSET_ClearAllForVoice(voice); @@ -2443,6 +2503,12 @@ void FAudioVoice_DestroyVoice(FAudioVoice *voice) LOG_API_EXIT(voice->audio) FAudio_Release(voice->audio); voice->audio->pFree(voice); + return 0; +} + +void FAudioVoice_DestroyVoice(FAudioVoice *voice) +{ + FAudioVoice_DestroyVoiceSafeEXT(voice); } /* FAudioSourceVoice Interface */ diff --git a/libs/faudio/src/FAudio_internal.c b/libs/faudio/src/FAudio_internal.c index f0d46cfa4b6..7ce57d52a16 100644 --- a/libs/faudio/src/FAudio_internal.c +++ b/libs/faudio/src/FAudio_internal.c @@ -212,9 +212,9 @@ void LinkedList_RemoveEntry( FAudioFreeFunc pFree ) { LinkedList *latest, *prev; + FAudio_PlatformLockMutex(lock); latest = *start; prev = latest; - FAudio_PlatformLockMutex(lock); while (latest != NULL) { if (latest->entry == toRemove) diff --git a/libs/faudio/src/FAudio_platform_win32.c b/libs/faudio/src/FAudio_platform_win32.c index c45b4a2f4f2..01a3a84d5e0 100644 --- a/libs/faudio/src/FAudio_platform_win32.c +++ b/libs/faudio/src/FAudio_platform_win32.c @@ -1300,6 +1300,7 @@ static void FAudio_INTERNAL_DecodeWMAMF( uint32_t FAudio_WMADEC_init(FAudioSourceVoice *voice, uint32_t type) { static const uint8_t fake_codec_data[16] = {0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t fake_codec_data_wma3[18] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0}; const FAudioWaveFormatExtensible *wfx = (FAudioWaveFormatExtensible *)voice->src.format; struct FAudioWMADEC *impl; MFT_OUTPUT_STREAM_INFO info = {0}; @@ -1361,11 +1362,17 @@ uint32_t FAudio_WMADEC_init(FAudioSourceVoice *voice, uint32_t type) FAudio_assert(!FAILED(hr) && "Failed set input block align!"); break; case FAUDIO_FORMAT_WMAUDIO3: + *(uint16_t *)fake_codec_data_wma3 = voice->src.format->wBitsPerSample; + for (i = 0; i < voice->src.format->nChannels; i++) + { + fake_codec_data_wma3[2] <<= 1; + fake_codec_data_wma3[2] |= 1; + } hr = IMFMediaType_SetBlob( media_type, &MF_MT_USER_DATA, - (void *)&wfx->Samples, - wfx->Format.cbSize + (void *)fake_codec_data_wma3, + sizeof(fake_codec_data_wma3) ); FAudio_assert(!FAILED(hr) && "Failed set codec private data!"); hr = IMFMediaType_SetGUID( diff --git a/loader/main.c b/loader/main.c index c258e025183..5a208cc048c 100644 --- a/loader/main.c +++ b/loader/main.c @@ -38,7 +38,8 @@ extern char **environ; -/* the preloader will set this variable */ +/* the preloader will set these variables */ +__attribute((visibility("default"))) struct r_debug *wine_r_debug = NULL; const __attribute((visibility("default"))) struct wine_preload_info *wine_main_preload_info = NULL; /* canonicalize path and return its directory name */ diff --git a/loader/preloader.c b/loader/preloader.c index d0551bae63a..f27d6df8c17 100644 --- a/loader/preloader.c +++ b/loader/preloader.c @@ -79,12 +79,16 @@ #ifdef HAVE_ELF_H # include #endif + +/* define _r_debug so we can re-define it as r_debug_extended */ +#define _r_debug no_r_debug; #ifdef HAVE_LINK_H # include #endif #ifdef HAVE_SYS_LINK_H # include #endif +#undef _r_debug #include "wine/asm.h" #include "main.h" @@ -1387,6 +1391,39 @@ static void set_process_name( int argc, char *argv[] ) for (i = 1; i < argc; i++) argv[i] -= off; } +struct wld_r_debug_extended +{ + struct r_debug base; + struct wld_r_debug_extended *r_next; +}; + +/* GDB integration, _r_debug_state is *required* for GDB to hook the preloader */ +__attribute((visibility("default"))) void _r_debug_state(void) {} +__attribute((visibility("default"))) struct wld_r_debug_extended _r_debug = {{0}}; + +/* sets the preloader r_debug address into DT_DEBUG */ +static void init_r_debug( struct wld_auxv *av ) +{ + ElfW(Phdr) *phdr, *ph; + ElfW(Dyn) *dyn = NULL; + char *l_addr; + int phnum; + + _r_debug.base.r_version = 2; + _r_debug.base.r_brk = (ElfW(Addr))_r_debug_state; + + if (!(phnum = get_auxiliary( av, AT_PHNUM, 0 ))) return; + if (!(phdr = (void *)get_auxiliary( av, AT_PHDR, 0 ))) return; + l_addr = (char *)phdr - sizeof(ElfW(Ehdr)); + _r_debug.base.r_ldbase = (ElfW(Addr))l_addr; + + for (ph = phdr; ph < &phdr[phnum]; ++ph) if (ph->p_type == PT_DYNAMIC) break; + if (ph >= &phdr[phnum]) return; + + dyn = (void *)(ph->p_vaddr + l_addr); + while (dyn->d_tag != DT_DEBUG && dyn->d_tag != DT_NULL) dyn++; + if (dyn->d_tag == DT_DEBUG) dyn->d_un.d_ptr = (uintptr_t)&_r_debug; +} /* * wld_start @@ -1403,6 +1440,7 @@ void* wld_start( void **stack ) struct wld_auxv new_av[8], delete_av[3], *av; struct wld_link_map main_binary_map, ld_so_map; struct wine_preload_info **wine_main_preload_info; + struct r_debug *ld_so_r_debug, **wine_r_debug; pargc = *stack; argv = (char **)pargc + 1; @@ -1432,6 +1470,8 @@ void* wld_start( void **stack ) dump_auxiliary( av ); #endif + init_r_debug( av ); + /* reserve memory that Wine needs */ if (reserve) preload_reserve( reserve ); for (i = 0; preload_info[i].size; i++) @@ -1470,6 +1510,17 @@ void* wld_start( void **stack ) interp = (char *)main_binary_map.l_addr + main_binary_map.l_interp; map_so_lib( interp, &ld_so_map ); + /* expose ld.so _r_debug as a separate namespace in r_next */ + ld_so_r_debug = find_symbol( &ld_so_map, "_r_debug", STT_OBJECT ); + if (ld_so_r_debug) _r_debug.r_next = (struct wld_r_debug_extended *)ld_so_r_debug; + else wld_printf( "_r_debug not found in ld.so\n" ); + + _r_debug_state(); /* notify GDB that _r_debug is ready */ + + wine_r_debug = find_symbol( &main_binary_map, "wine_r_debug", STT_OBJECT ); + if (wine_r_debug) *wine_r_debug = &_r_debug.base; + else wld_printf( "wine_r_debug not found\n" ); + /* store pointer to the preload info into the appropriate main binary variable */ wine_main_preload_info = find_symbol( &main_binary_map, "wine_main_preload_info", STT_OBJECT ); if (wine_main_preload_info) *wine_main_preload_info = preload_info; diff --git a/loader/wine.inf.in b/loader/wine.inf.in index 7ce0638c7df..1e5b16a53a6 100644 --- a/loader/wine.inf.in +++ b/loader/wine.inf.in @@ -603,6 +603,7 @@ HKLM,%FontsNT%,"Webdings (TrueType)",,"webdings.ttf" HKLM,%FontsNT%,"Wingdings (TrueType)",,"wingdings.ttf" HKCU,Software\Wine\Fonts\Replacements,"Palatino Linotype",,"Times New Roman" HKCU,Software\Wine\Fonts\Replacements,"Verdana",,"Times New Roman" +HKCU,Software\Wine\Fonts\Replacements,"Segoe UI",,"Times New Roman" [MCI] HKLM,%Mci32Str%,"AVIVideo",,"mciavi32.dll" @@ -2866,6 +2867,7 @@ HKCU,Software\Wine\DllOverrides,"ucrtbase",0x2,"native,builtin" HKCU,Software\Wine\DllOverrides,"vcomp140",0x2,"native,builtin" HKCU,Software\Wine\DllOverrides,"vcruntime140",0x2,"native,builtin" HKCU,Software\Wine\DllOverrides,"atiadlxx",,"disabled" +HKCU,Software\Wine\DllOverrides,"nvcuda",0x2,"disabled" ;;App-specific overrides to limit the number of resolutions HKCU,Software\Wine\AppDefaults\DarkSoulsIII.exe\X11 Driver,"LimitNumberOfResolutions",0x2,"32" HKCU,Software\Wine\AppDefaults\sekiro.exe\X11 Driver,"LimitNumberOfResolutions",0x2,"32" diff --git a/programs/explorer/desktop.c b/programs/explorer/desktop.c index 3571368b8f3..fe50586184f 100644 --- a/programs/explorer/desktop.c +++ b/programs/explorer/desktop.c @@ -1041,6 +1041,46 @@ static HANDLE start_tabtip_process(void) return pi.hProcess; } +static void start_xalia_process(void) +{ + PROCESS_INFORMATION pi; + STARTUPINFOW si = { sizeof(si) }; + WCHAR use_envvar[2]; + LPWSTR path; + LPCWSTR path_suffix = L"/../xalia/xalia.exe"; + DWORD ret, buffersize; + + /* check if xalia is enabled */ + ret = GetEnvironmentVariableW(L"PROTON_USE_XALIA", use_envvar, ARRAY_SIZE(use_envvar)); + if (!ret || (ret <= ARRAY_SIZE(use_envvar) && lstrcmpW(use_envvar, L"0") == 0)) + return; + + /* locate xalia.exe */ + ret = GetEnvironmentVariableW(L"WINEDATADIR", NULL, 0); + if (ret == 0) + return; + + buffersize = ret + lstrlenW(path_suffix); + path = HeapAlloc(GetProcessHeap(), 0, buffersize * sizeof(*path)); + + GetEnvironmentVariableW(L"WINEDATADIR", path, ret); + if (!memcmp(path, L"\\??\\", 8)) path[1] = '\\'; /* change \??\ into \\?\ */ + lstrcatW(path, path_suffix); + + /* setup environment */ + SetEnvironmentVariableW(L"XALIA_WINE_SYSTEM_PROCESS", L"1"); + + if (!CreateProcessW(path, NULL, + NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &si, &pi)) + { + WINE_ERR("Couldn't start xalia.exe: error %lu\n", GetLastError()); + return; + } + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + return; +} + /* main desktop management function */ void manage_desktop( WCHAR *arg ) { @@ -1169,6 +1209,8 @@ void manage_desktop( WCHAR *arg ) /* FIXME: hack, run tabtip.exe on startup. */ tabtip = start_tabtip_process(); + start_xalia_process(); + TRACE( "desktop message loop starting on hwnd %p\n", hwnd ); while (GetMessageW( &msg, 0, 0, 0 )) DispatchMessageW( &msg ); TRACE( "desktop message loop exiting for hwnd %p\n", hwnd ); diff --git a/programs/explorer/systray.c b/programs/explorer/systray.c index 84ae37d8b36..54f1abbb30f 100644 --- a/programs/explorer/systray.c +++ b/programs/explorer/systray.c @@ -33,6 +33,9 @@ WINE_DEFAULT_DEBUG_CHANNEL(systray); +#define TRAY_MINIMIZE_ALL 419 +#define TRAY_MINIMIZE_ALL_UNDO 416 + struct notify_data /* platform-independent format for NOTIFYICONDATA */ { LONG hWnd; @@ -1061,9 +1064,9 @@ static LRESULT WINAPI shell_traywnd_proc( HWND hwnd, UINT msg, WPARAM wparam, LP case WM_COMMAND: if (HIWORD(wparam) == BN_CLICKED) { - if (LOWORD(wparam) == 419) + if (LOWORD(wparam) == TRAY_MINIMIZE_ALL || LOWORD(wparam) == TRAY_MINIMIZE_ALL_UNDO) { - FIXME( "Minimize all windows command is not supported.\n" ); + FIXME( "Shell command %u is not supported.\n", LOWORD(wparam) ); break; } click_taskbar_button( (HWND)lparam ); diff --git a/programs/powershell/main.c b/programs/powershell/main.c index eb9a64ff0ec..25a4d13d340 100644 --- a/programs/powershell/main.c +++ b/programs/powershell/main.c @@ -24,10 +24,25 @@ int __cdecl wmain(int argc, WCHAR *argv[]) { int i; - WINE_FIXME("stub:"); + WINE_FIXME("stub.\n"); for (i = 0; i < argc; i++) - WINE_FIXME(" %s", wine_dbgstr_w(argv[i])); - WINE_FIXME("\n"); + { + WINE_FIXME("argv[%d] %s\n", i, wine_dbgstr_w(argv[i])); + if (!wcsicmp(argv[i], L"-command") && i < argc - 1 && !wcscmp(argv[i + 1], L"-")) + { + char command[4096], *p; + ++i; + while (fgets(command, sizeof(command), stdin)) + { + WINE_FIXME("command %s.\n", debugstr_a(command)); + p = command; + while (*p && !isspace(*p)) ++p; + *p = 0; + if (!stricmp(command, "exit")) + break; + } + } + } return 0; } diff --git a/programs/wineboot/wineboot.c b/programs/wineboot/wineboot.c index 0ea276f6876..ff6bd6cc38c 100644 --- a/programs/wineboot/wineboot.c +++ b/programs/wineboot/wineboot.c @@ -195,10 +195,27 @@ static DWORD set_reg_value_dword( HKEY hkey, const WCHAR *name, DWORD value ) #if defined(__i386__) || defined(__x86_64__) +extern UINT64 WINAPI do_xgetbv( unsigned int cx); +#ifdef __i386__ +__ASM_STDCALL_FUNC( do_xgetbv, 4, + "movl 4(%esp),%ecx\n\t" + "xgetbv\n\t" + "ret $4" ) +#else +__ASM_GLOBAL_FUNC( do_xgetbv, + "xgetbv\n\t" + "shlq $32,%rdx\n\t" + "orq %rdx,%rax\n\t" + "ret" ) +#endif + static void initialize_xstate_features(struct _KUSER_SHARED_DATA *data) { + static const ULONG64 wine_xstate_supported_features = 0xfc; /* XSTATE_AVX, XSTATE_MPX_BNDREGS, XSTATE_MPX_BNDCSR, + * XSTATE_AVX512_KMASK, XSTATE_AVX512_ZMM_H, XSTATE_AVX512_ZMM */ XSTATE_CONFIGURATION *xstate = &data->XState; - unsigned int i; + ULONG64 supported_mask; + unsigned int i, off; int regs[4]; if (!data->ProcessorFeatures[PF_AVX_INSTRUCTIONS_AVAILABLE]) @@ -217,29 +234,40 @@ static void initialize_xstate_features(struct _KUSER_SHARED_DATA *data) __cpuidex(regs, 0xd, 0); TRACE("XSAVE details %#x, %#x, %#x, %#x.\n", regs[0], regs[1], regs[2], regs[3]); - if (!(regs[0] & XSTATE_AVX)) + supported_mask = ((ULONG64)regs[3] << 32) | regs[0]; + supported_mask &= do_xgetbv(0) & wine_xstate_supported_features; + if (!(supported_mask >> 2)) return; - xstate->EnabledFeatures = (1 << XSTATE_LEGACY_FLOATING_POINT) | (1 << XSTATE_LEGACY_SSE) | (1 << XSTATE_AVX); + xstate->EnabledFeatures = (1 << XSTATE_LEGACY_FLOATING_POINT) | (1 << XSTATE_LEGACY_SSE) | supported_mask; xstate->EnabledVolatileFeatures = xstate->EnabledFeatures; - xstate->Size = sizeof(XSAVE_FORMAT) + sizeof(XSTATE); xstate->AllFeatureSize = regs[1]; - xstate->AllFeatures[0] = offsetof(XSAVE_FORMAT, XmmRegisters); - xstate->AllFeatures[1] = sizeof(M128A) * 16; - xstate->AllFeatures[2] = sizeof(YMMCONTEXT); - - for (i = 0; i < 3; ++i) - xstate->Features[i].Size = xstate->AllFeatures[i]; - - xstate->Features[1].Offset = xstate->Features[0].Size; - xstate->Features[2].Offset = sizeof(XSAVE_FORMAT) + offsetof(XSTATE, YmmContext); __cpuidex(regs, 0xd, 1); xstate->OptimizedSave = regs[0] & 1; xstate->CompactionEnabled = !!(regs[0] & 2); - __cpuidex(regs, 0xd, 2); - TRACE("XSAVE feature 2 %#x, %#x, %#x, %#x.\n", regs[0], regs[1], regs[2], regs[3]); + xstate->Features[0].Size = xstate->AllFeatures[0] = offsetof(XSAVE_FORMAT, XmmRegisters); + xstate->Features[1].Size = xstate->AllFeatures[1] = sizeof(M128A) * 16; + xstate->Features[1].Offset = xstate->Features[0].Size; + off = sizeof(XSAVE_FORMAT) + sizeof(XSAVE_AREA_HEADER); + supported_mask >>= 2; + for (i = 2; supported_mask; ++i, supported_mask >>= 1) + { + if (!(supported_mask & 1)) continue; + __cpuidex( regs, 0xd, i ); + xstate->Features[i].Offset = regs[1]; + xstate->Features[i].Size = xstate->AllFeatures[i] = regs[0]; + if (regs[2] & 2) + { + xstate->AlignedFeatures |= (ULONG64)1 << i; + off = (off + 63) & ~63; + } + off += xstate->Features[i].Size; + TRACE("xstate[%d] offset %lu, size %lu, aligned %d.\n", i, xstate->Features[i].Offset, xstate->Features[i].Size, !!(regs[2] & 2)); + } + xstate->Size = xstate->CompactionEnabled ? off : xstate->Features[i - 1].Offset + xstate->Features[i - 1].Size; + TRACE("xstate size %lu, compacted %d, optimized %d.\n", xstate->Size, xstate->CompactionEnabled, xstate->OptimizedSave); } static BOOL is_tsc_trusted_by_the_kernel(void) diff --git a/server/process.c b/server/process.c index 20d959b661f..050950bc206 100644 --- a/server/process.c +++ b/server/process.c @@ -1586,7 +1586,10 @@ DECL_HANDLER(get_process_debug_info) /* fetch the name of the process image */ DECL_HANDLER(get_process_image_name) { - struct process *process = get_process_from_handle( req->handle, PROCESS_QUERY_LIMITED_INFORMATION ); + struct process *process; + + if (req->pid) process = get_process_from_id( req->pid ); + else process = get_process_from_handle( req->handle, PROCESS_QUERY_LIMITED_INFORMATION ); if (!process) return; if (process->image) diff --git a/server/protocol.def b/server/protocol.def index cd78c8f2f26..4ec9b5a5f43 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -125,6 +125,14 @@ typedef union } unload_dll; } debug_event_t; + +enum context_exec_space +{ + EXEC_SPACE_USERMODE, + EXEC_SPACE_SYSCALL, + EXEC_SPACE_EXCEPTION, +}; + /* context data */ typedef struct { @@ -171,6 +179,10 @@ typedef struct unsigned char i386_regs[512]; } ext; /* selected by SERVER_CTX_EXTENDED_REGISTERS */ union + { + struct { enum context_exec_space space; int __pad; } space; + } exec_space; /* selected by SERVER_CTX_EXEC_SPACE */ + union { struct { struct { unsigned __int64 low, high; } ymm_high[16]; } regs; } ymm; /* selected by SERVER_CTX_YMM_REGISTERS */ @@ -183,6 +195,7 @@ typedef struct #define SERVER_CTX_DEBUG_REGISTERS 0x10 #define SERVER_CTX_EXTENDED_REGISTERS 0x20 #define SERVER_CTX_YMM_REGISTERS 0x40 +#define SERVER_CTX_EXEC_SPACE 0x80 /* structure used in sending an fd from client to server */ struct send_fd @@ -905,6 +918,7 @@ struct shared_cursor int y; unsigned int last_change; /* time of last position change */ rectangle_t clip; /* cursor clip rectangle */ + unsigned int clip_flags; /* cursor clip flags */ }; struct desktop_shared_memory @@ -914,6 +928,7 @@ struct desktop_shared_memory unsigned char keystate[256]; /* asynchronous key state */ thread_id_t foreground_tid; /* tid of the foreground thread */ __int64 update_serial; + unsigned int flags; }; typedef volatile struct desktop_shared_memory desktop_shm_t; @@ -1098,6 +1113,7 @@ typedef volatile struct input_shared_memory input_shm_t; /* Fetch the name of the process image */ @REQ(get_process_image_name) obj_handle_t handle; /* process handle */ + process_id_t pid; /* process id */ int win32; /* return a win32 filename? */ @REPLY data_size_t len; /* len in bytes required to store filename */ @@ -1182,9 +1198,11 @@ typedef volatile struct input_shared_memory input_shm_t; /* Suspend a thread */ @REQ(suspend_thread) - obj_handle_t handle; /* thread handle */ + obj_handle_t handle; /* thread handle */ + obj_handle_t waited_handle; /* handle waited on */ @REPLY - int count; /* new suspend count */ + int count; /* new suspend count */ + obj_handle_t wait_handle; /* handle to wait on */ @END @@ -3872,6 +3890,8 @@ struct handle_info @REQ(get_rawinput_buffer) data_size_t rawinput_size; /* size of RAWINPUT structure */ data_size_t buffer_size; /* size of output buffer */ + int clear_qs_rawinput; + int __pad; @REPLY data_size_t next_size; /* minimum size to get next message data */ unsigned int count; diff --git a/server/queue.c b/server/queue.c index 12592241c92..23423a239f1 100644 --- a/server/queue.c +++ b/server/queue.c @@ -535,15 +535,20 @@ static int update_desktop_cursor_window( struct desktop *desktop, user_handle_t struct thread_input *input; desktop->cursor_win = win; - if (updated && (input = get_desktop_cursor_thread_input( desktop ))) + if (!updated) return 0; + + if (!(input = get_desktop_cursor_thread_input( desktop ))) + desktop->cursor_handle = -1; + else { user_handle_t handle = input->shared->cursor_count < 0 ? 0 : input->shared->cursor; /* when clipping send the message to the foreground window as well, as some driver have an artificial overlay window */ if (is_cursor_clipped( desktop )) queue_cursor_message( desktop, 0, WM_WINE_SETCURSOR, win, handle ); queue_cursor_message( desktop, win, WM_WINE_SETCURSOR, win, handle ); + desktop->cursor_handle = handle; } - return updated; + return 1; } static int update_desktop_cursor_pos( struct desktop *desktop, user_handle_t win, int x, int y ) @@ -575,9 +580,12 @@ static void update_desktop_cursor_handle( struct desktop *desktop, struct thread if (input == get_desktop_cursor_thread_input( desktop )) { user_handle_t handle = input->shared->cursor_count < 0 ? 0 : input->shared->cursor, win = desktop->cursor_win; + if (handle == desktop->cursor_handle) return; + /* when clipping send the message to the foreground window as well, as some driver have an artificial overlay window */ if (is_cursor_clipped( desktop )) queue_cursor_message( desktop, 0, WM_WINE_SETCURSOR, win, handle ); queue_cursor_message( desktop, win, WM_WINE_SETCURSOR, win, handle ); + desktop->cursor_handle = handle; } } @@ -616,6 +624,7 @@ static void get_message_defaults( struct msg_queue *queue, int *x, int *y, unsig void set_clip_rectangle( struct desktop *desktop, const rectangle_t *rect, unsigned int flags, int reset ) { rectangle_t top_rect, new_rect; + unsigned int old_flags; int x, y; get_top_window_rectangle( desktop, &top_rect ); @@ -632,7 +641,9 @@ void set_clip_rectangle( struct desktop *desktop, const rectangle_t *rect, unsig SHARED_WRITE_BEGIN( desktop, desktop_shm_t ) { + old_flags = shared->cursor.clip_flags; shared->cursor.clip = new_rect; + shared->cursor.clip_flags = flags; } SHARED_WRITE_END @@ -645,7 +656,8 @@ void set_clip_rectangle( struct desktop *desktop, const rectangle_t *rect, unsig if (reset) post_desktop_message( desktop, WM_WINE_CLIPCURSOR, flags, FALSE ); /* notify foreground thread, of reset, or to apply new cursor clipping rect */ - queue_cursor_message( desktop, 0, WM_WINE_CLIPCURSOR, flags, reset ); + if (rect || reset || old_flags != SET_CURSOR_NOCLIP) + queue_cursor_message( desktop, 0, WM_WINE_CLIPCURSOR, flags, reset ); } /* change the foreground input and reset the cursor clip rect */ @@ -3971,6 +3983,8 @@ DECL_HANDLER(get_rawinput_buffer) count++; } + if (req->clear_qs_rawinput && !ptr) clear_queue_bits( current->queue, QS_RAWINPUT ); + reply->next_size = next_size; reply->count = count; set_reply_data_ptr( buf, pos ); diff --git a/server/sock.c b/server/sock.c index 3b9367a1b72..9877888b465 100644 --- a/server/sock.c +++ b/server/sock.c @@ -815,7 +815,7 @@ static void post_sock_messages( struct sock *sock ) } } -static inline int sock_error( struct sock *sock ) +static inline int sock_error( struct sock *sock, int *poll_event ) { int error = 0; socklen_t len = sizeof(error); @@ -841,8 +841,14 @@ static inline int sock_error( struct sock *sock ) error = sock->errors[AFD_POLL_BIT_ACCEPT]; break; - case SOCK_CONNECTED: case SOCK_CONNECTIONLESS: + if (error == ENETUNREACH || error == EHOSTUNREACH || error == ECONNRESET) + { + if (poll_event) *poll_event &= ~POLLERR; + return 0; + } + /* fallthrough */ + case SOCK_CONNECTED: if (error == ECONNRESET || error == EPIPE) { sock->reset = 1; @@ -1348,7 +1354,7 @@ static void sock_poll_event( struct fd *fd, int event ) fprintf(stderr, "socket %p select event: %x\n", sock, event); if (event & (POLLERR | POLLHUP)) - error = sock_error( sock ); + error = sock_error( sock, &event ); switch (sock->state) { @@ -3121,7 +3127,7 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) return; } - error = sock_error( sock ); + error = sock_error( sock, NULL ); if (!error) { for (i = 0; i < ARRAY_SIZE( sock->errors ); ++i) diff --git a/server/thread.c b/server/thread.c index a6039b35d9d..0620686399d 100644 --- a/server/thread.c +++ b/server/thread.c @@ -1564,6 +1564,7 @@ static void copy_context( context_t *to, const context_t *from, unsigned int fla if (flags & SERVER_CTX_DEBUG_REGISTERS) to->debug = from->debug; if (flags & SERVER_CTX_EXTENDED_REGISTERS) to->ext = from->ext; if (flags & SERVER_CTX_YMM_REGISTERS) to->ymm = from->ymm; + if (flags & SERVER_CTX_EXEC_SPACE) to->exec_space = from->exec_space; } /* gets the current impersonation token */ @@ -1815,12 +1816,32 @@ DECL_HANDLER(suspend_thread) { struct thread *thread; - if ((thread = get_thread_from_handle( req->handle, THREAD_SUSPEND_RESUME ))) + if (req->waited_handle) { - if (thread->state == TERMINATED) set_error( STATUS_ACCESS_DENIED ); - else reply->count = suspend_thread( thread ); - release_object( thread ); + struct context *context; + + if (!(context = (struct context *)get_handle_obj( current->process, req->waited_handle, + 0, &context_ops ))) + return; + close_handle( current->process, req->waited_handle ); /* avoid extra server call */ + set_error( context->status ); + release_object( context ); + return; } + + if (!(thread = get_thread_from_handle( req->handle, THREAD_SUSPEND_RESUME ))) return; + + if (thread->state != RUNNING) set_error( STATUS_ACCESS_DENIED ); + else + { + reply->count = suspend_thread( thread ); + if (!get_error() && thread != current && thread->context && thread->context->status == STATUS_PENDING) + { + set_error( STATUS_PENDING ); + reply->wait_handle = alloc_handle( current->process, thread->context, SYNCHRONIZE, 0 ); + } + } + release_object( thread ); } /* resume a thread */ @@ -2204,14 +2225,14 @@ DECL_HANDLER(set_thread_context) /* If context is in a pending state, we don't know if we will use WoW or native * context, so store both and discard irrevelant one in select request. */ const int is_pending = thread->context->status == STATUS_PENDING; - unsigned int native_flags = contexts[CTX_NATIVE].flags; + unsigned int native_flags = contexts[CTX_NATIVE].flags & ~SERVER_CTX_EXEC_SPACE; if (ctx_count == 2 && (is_pending || thread->context->regs[CTX_WOW].machine)) { context_t *ctx = &thread->context->regs[CTX_WOW]; /* some regs are always set from the native context */ - flags = contexts[CTX_WOW].flags & ~req->native_flags; + flags = contexts[CTX_WOW].flags & ~(req->native_flags | SERVER_CTX_EXEC_SPACE); if (is_pending) ctx->machine = contexts[CTX_WOW].machine; else native_flags &= req->native_flags; diff --git a/server/trace.c b/server/trace.c index 313a4541a1a..a44836e6543 100644 --- a/server/trace.c +++ b/server/trace.c @@ -840,6 +840,19 @@ static void dump_varargs_context( const char *prefix, data_size_t size ) fprintf( stderr, "%s{machine=%04x", prefix, ctx.machine ); break; } + if (ctx.flags & SERVER_CTX_EXEC_SPACE) + { + const char *space; + + switch (ctx.exec_space.space.space) + { + case EXEC_SPACE_USERMODE: space = "user"; break; + case EXEC_SPACE_SYSCALL: space = "syscall"; break; + case EXEC_SPACE_EXCEPTION: space = "exception"; break; + default: space = "invalid"; break; + } + fprintf( stderr, ",exec_space=%s", space ); + } fputc( '}', stderr ); remove_data( size ); } diff --git a/server/user.h b/server/user.h index a6ef5fc1f83..f06c9ba6b6e 100644 --- a/server/user.h +++ b/server/user.h @@ -68,6 +68,7 @@ struct desktop struct thread_input *foreground_input; /* thread input of foreground thread */ unsigned int users; /* processes and threads using this desktop */ user_handle_t cursor_win; /* window that contains the cursor */ + user_handle_t cursor_handle; /* last set cursor handle */ unsigned char keystate[256]; /* asynchronous key state */ unsigned int last_press_alt:1; /* last key press was Alt (used to determine msg on Alt release) */ struct object *shared_mapping; /* desktop shared memory mapping */ diff --git a/server/winstation.c b/server/winstation.c index 0cf17d00759..534078a5813 100644 --- a/server/winstation.c +++ b/server/winstation.c @@ -136,6 +136,27 @@ static const struct object_ops desktop_ops = desktop_destroy /* destroy */ }; +#if defined(__i386__) || defined(__x86_64__) +#define __SHARED_INCREMENT_SEQ( x ) ++(x) +#else +#define __SHARED_INCREMENT_SEQ( x ) __atomic_add_fetch( &(x), 1, __ATOMIC_RELEASE ) +#endif + +#define SHARED_WRITE_BEGIN( object, type ) \ + do { \ + const type *__shared = (object)->shared; \ + type *shared = (type *)__shared; \ + unsigned int __seq = __SHARED_INCREMENT_SEQ( shared->seq ); \ + assert( (__seq & 1) != 0 ); \ + do + +#define SHARED_WRITE_END \ + while(0); \ + __seq = __SHARED_INCREMENT_SEQ( shared->seq ) - __seq; \ + assert( __seq == 1 ); \ + } while(0); + + /* create a winstation object */ static struct winstation *create_winstation( struct object *root, const struct unicode_str *name, unsigned int attr, unsigned int flags ) @@ -276,6 +297,11 @@ static struct desktop *create_desktop( const struct unicode_str *name, unsigned desktop->flags |= (flags & DF_WINE_CREATE_DESKTOP); clear_error(); } + SHARED_WRITE_BEGIN( desktop, desktop_shm_t ) + { + shared->flags = desktop->flags; + } + SHARED_WRITE_END } return desktop; } @@ -707,7 +733,15 @@ DECL_HANDLER(set_user_object_info) struct desktop *desktop = (struct desktop *)obj; reply->is_desktop = 1; reply->old_obj_flags = desktop->flags; - if (req->flags & SET_USER_OBJECT_SET_FLAGS) desktop->flags = req->obj_flags; + if (req->flags & SET_USER_OBJECT_SET_FLAGS) + { + desktop->flags = req->obj_flags; + SHARED_WRITE_BEGIN( desktop, desktop_shm_t ) + { + shared->flags = desktop->flags; + } + SHARED_WRITE_END + } if (req->flags & SET_USER_OBJECT_SET_CLOSE_TIMEOUT) desktop->close_timeout_val = req->close_timeout; } else if (obj->ops == &winstation_ops) diff --git a/tools/gdbunwind.py b/tools/gdbunwind.py new file mode 100644 index 00000000000..8611d789f1f --- /dev/null +++ b/tools/gdbunwind.py @@ -0,0 +1,177 @@ +#!/bin/env python3 + +# Copyright 2021 RĂ©mi Bernon for CodeWeavers +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +from gdb.unwinder import Unwinder +import gdb +import re + +ARCH_OFFSETS_X86_64 = { + "rax": (0x0000, 'void *'), + "rbx": (0x0008, 'void *'), + "rcx": (0x0010, 'void *'), + "rdx": (0x0018, 'void *'), + "rsi": (0x0020, 'void *'), + "rdi": (0x0028, 'void *'), + "r8": (0x0030, 'void *'), + "r9": (0x0038, 'void *'), + "r10": (0x0040, 'void *'), + "r11": (0x0048, 'void *'), + "r12": (0x0050, 'void *'), + "r13": (0x0058, 'void *'), + "r14": (0x0060, 'void *'), + "r15": (0x0068, 'void *'), + "rip": (0x0070, 'void *'), + "cs": (0x0078, 'void *'), + "eflags": (0x0080, 'void *'), + "rsp": (0x0088, 'void *'), + "ss": (0x0090, 'void *'), + "rbp": (0x0098, 'void *'), + "prev_frame": (0x00a0, 'void *'), + "syscall_cfa": (0x00a8, 'void *'), + # "syscall_flags": (0x00b0, 'unsigned int'), + # "restore_flags": (0x00b4, 'unsigned int'), + "teb_offset": (0x0328, 'void *'), +} + +ARCH_OFFSETS_I386 = { + # "syscall_flags": (0x000, 'unsigned short'), + # "restore_flags": (0x002, 'unsigned short'), + "eflags": (0x004, 'unsigned int'), + "eip": (0x008, 'void *'), + "esp": (0x00c, 'void *'), + "cs": (0x010, 'unsigned short'), + "ss": (0x012, 'unsigned short'), + "ds": (0x014, 'unsigned short'), + "es": (0x016, 'unsigned short'), + "fs": (0x018, 'unsigned short'), + "gs": (0x01a, 'unsigned short'), + "eax": (0x01c, 'void *'), + "ebx": (0x020, 'void *'), + "ecx": (0x024, 'void *'), + "edx": (0x028, 'void *'), + "edi": (0x02c, 'void *'), + "esi": (0x030, 'void *'), + "ebp": (0x034, 'void *'), + "syscall_cfa": (0x038, 'void *'), + "prev_frame": (0x03c, 'void *'), + "teb_offset": (0x1f8, 'void *'), +} + + +def arch_offsets(arch): + if 'x86-64' in arch: + return ARCH_OFFSETS_X86_64 + if 'i386' in arch: + return ARCH_OFFSETS_I386 + + +def find_syscall_frame(sp, arch, teb): + (off, type) = arch_offsets(arch)['teb_offset'] + frame = int(gdb.parse_and_eval(f'*(void **){teb + off}')) + + while frame: + (off, type) = arch_offsets(arch)['prev_frame'] + next_frame = int(gdb.parse_and_eval(f'*(void **){(frame + off)}')) + if not next_frame: return frame + + (off, type) = arch_offsets(arch)['ebp'] + ebp = int(gdb.parse_and_eval(f'*(void **){(next_frame + off)}')) + if ebp >= sp: return frame + + +def callback_registers(pc, sp, pending_frame): + arch = pending_frame.architecture().name() + + if 'x86-64' in arch: + frame = int(pending_frame.read_register("rbp")) + frame = (frame - 0x448) & ~0x3f + + (off, type) = arch_offsets(arch)['syscall_cfa'] + val = gdb.parse_and_eval(f'*({type} *){int(frame + off)}') + yield 'rip', gdb.parse_and_eval(f'*(void **){int(val - 8)}') + yield 'rbp', val - 16 + + elif 'i386' in arch: + teb = int(pending_frame.read_register('edx')) + frame = find_syscall_frame(sp, arch, teb) + + (off, type) = arch_offsets(arch)['syscall_cfa'] + val = gdb.parse_and_eval(f'*({type} *){int(frame + off)}') + yield 'eip', gdb.parse_and_eval(f'*(void **){int(val - 4)}') + yield 'ebp', val - 8 + + +def registers(pc, sp, pending_frame): + arch = pending_frame.architecture().name() + frame = sp + + if 'x86-64' in arch: + if 'syscall' in str(pc): + rbp = pending_frame.read_register("rbp") + frame = rbp - 0x98 + elif 'i386' in arch: + if 'syscall' in str(pc): + ebp = pending_frame.read_register("ebp") + frame = ebp - 0x34 + else: + frame += 16 + + for reg, (off, type) in arch_offsets(arch).items(): + val = gdb.parse_and_eval(f'*({type} *){int(frame + off)}') + + if reg in ('eflags', 'cs', 'ss', 'ds', 'es', 'fs', 'gs'): + int32 = gdb.lookup_type('int') + val = val.cast(int32) + if reg in ('syscall_cfa', 'prev_frame', 'teb_offset'): + continue + + yield reg, val + + +class WineSyscallFrameId(object): + def __init__(self, sp, pc): + self.sp = sp + self.pc = pc + + +class WineSyscallUnwinder(Unwinder): + def __init__(self): + super().__init__("WineSyscallUnwinder", gdb.SIGTRAMP_FRAME) + self.pattern = re.compile('__wine_(syscall|unix_call)' + '|KiUserCallbackDispatcher') + + def __call__(self, pending_frame): + pc = pending_frame.read_register("pc") + if self.pattern.search(str(pc)) is None: + return None + + sp = pending_frame.read_register("sp") + frame = WineSyscallFrameId(sp, pc) + if 'KiUserCallbackDispatcher' in str(pc): + unwind_info = pending_frame.create_unwind_info(frame) + for reg, val in callback_registers(pc, sp, pending_frame): + unwind_info.add_saved_register(reg, val) + return unwind_info + + unwind_info = pending_frame.create_unwind_info(frame) + for reg, val in registers(pc, sp, pending_frame): + unwind_info.add_saved_register(reg, val) + return unwind_info + + +gdb.unwinder.register_unwinder(None, WineSyscallUnwinder(), replace=True) diff --git a/tools/gitlab/test.yml b/tools/gitlab/test.yml index 92c14d83996..aaad7b31c36 100644 --- a/tools/gitlab/test.yml +++ b/tools/gitlab/test.yml @@ -7,7 +7,7 @@ variables: GIT_STRATEGY: none GECKO_VER: 2.47.4 - MONO_VER: 8.1.0 + MONO_VER: 9.0.0 cache: - key: wine-gecko-$GECKO_VER paths: diff --git a/tools/make_requests b/tools/make_requests index 77b5ab331f4..b450e22c2b8 100755 --- a/tools/make_requests +++ b/tools/make_requests @@ -54,7 +54,7 @@ my %formats = "hw_input_t" => [ 40, 8, "&dump_hw_input" ], # varargs-only structures "apc_call_t" => [ 64, 8 ], - "context_t" => [ 1720, 8 ], + "context_t" => [ 1728, 8 ], "cursor_pos_t" => [ 24, 8 ], "debug_event_t" => [ 160, 8 ], "message_data_t" => [ 56, 8 ], diff --git a/tools/winebuild/spec32.c b/tools/winebuild/spec32.c index 6e3a0941310..26cb6878391 100644 --- a/tools/winebuild/spec32.c +++ b/tools/winebuild/spec32.c @@ -62,7 +62,7 @@ static unsigned int hash_filename( const char *name ) /* FNV-1 hash */ unsigned int ret = 2166136261u; while (*name) ret = (ret * 16777619) ^ *name++; - return ret; + return ret + target.cpu; } /* check if entry point needs a relay thunk */