diff --git a/README.md b/README.md index e4b47c4c..a27a31ec 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ This program can be used to automatically start, split, and reset your preferred The smaller the selected region, the more efficient it is. - **Windows Graphics Capture** (fast, most compatible, capped at 60fps) Only available in Windows 10.0.17134 and up. - Due to current technical limitations, it requires having at least one audio or video Capture Device connected and enabled. Even if it won't be used. + Due to current technical limitations, Windows versions below 10.0.0.17763 require having at least one audio or video Capture Device connected and enabled. Allows recording UWP apps, Hardware Accelerated and Exclusive Fullscreen windows. Adds a yellow border on Windows 10 (not on Windows 11). Caps at around 60 FPS. diff --git a/src/capture_method/WindowsGraphicsCaptureMethod.py b/src/capture_method/WindowsGraphicsCaptureMethod.py index b5ca8abd..35874999 100644 --- a/src/capture_method/WindowsGraphicsCaptureMethod.py +++ b/src/capture_method/WindowsGraphicsCaptureMethod.py @@ -11,10 +11,9 @@ from winsdk.windows.graphics.capture.interop import create_for_window from winsdk.windows.graphics.directx import DirectXPixelFormat from winsdk.windows.graphics.imaging import BitmapBufferAccessMode, SoftwareBitmap -from winsdk.windows.media.capture import MediaCapture from capture_method.CaptureMethodBase import CaptureMethodBase -from utils import WINDOWS_BUILD_NUMBER, is_valid_hwnd +from utils import WINDOWS_BUILD_NUMBER, get_direct3d_device, is_valid_hwnd if TYPE_CHECKING: from AutoSplit import AutoSplit @@ -33,19 +32,10 @@ def __init__(self, autosplit: AutoSplit): super().__init__(autosplit) if not is_valid_hwnd(autosplit.hwnd): return - # Note: Must create in the same thread (can't use a global) otherwise when ran from LiveSplit it will raise: - # OSError: The application called an interface that was marshalled for a different thread - media_capture = MediaCapture() - item = create_for_window(autosplit.hwnd) - - async def coroutine(): - await (media_capture.initialize_async() or asyncio.sleep(0)) - asyncio.run(coroutine()) - if not media_capture.media_capture_settings: - raise OSError("Unable to initialize a Direct3D Device.") + item = create_for_window(autosplit.hwnd) frame_pool = Direct3D11CaptureFramePool.create_free_threaded( - media_capture.media_capture_settings.direct3_d11_device, + get_direct3d_device(), DirectXPixelFormat.B8_G8_R8_A8_UINT_NORMALIZED, 1, item.size, diff --git a/src/capture_method/__init__.py b/src/capture_method/__init__.py index 1d051b5a..8e3bab51 100644 --- a/src/capture_method/__init__.py +++ b/src/capture_method/__init__.py @@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, TypedDict from pygrabber import dshow_graph -from winsdk.windows.media.capture import MediaCapture from capture_method.BitBltCaptureMethod import BitBltCaptureMethod from capture_method.CaptureMethodBase import CaptureMethodBase @@ -15,13 +14,15 @@ from capture_method.ForceFullContentRenderingCaptureMethod import ForceFullContentRenderingCaptureMethod from capture_method.VideoCaptureDeviceCaptureMethod import VideoCaptureDeviceCaptureMethod from capture_method.WindowsGraphicsCaptureMethod import WindowsGraphicsCaptureMethod -from utils import WINDOWS_BUILD_NUMBER +from utils import WINDOWS_BUILD_NUMBER, get_direct3d_device if TYPE_CHECKING: from AutoSplit import AutoSplit WGC_MIN_BUILD = 17134 """https://docs.microsoft.com/en-us/uwp/api/windows.graphics.capture.graphicscapturepicker#applies-to""" +LEARNING_MODE_DEVICE_BUILD = 17763 +"""https://learn.microsoft.com/en-us/uwp/api/windows.ai.machinelearning.learningmodeldevice""" class Region(TypedDict): @@ -121,8 +122,8 @@ def __getitem__(self, key: CaptureMethodEnum): short_description="fast, most compatible, capped at 60fps", description=( f"\nOnly available in Windows 10.0.{WGC_MIN_BUILD} and up. " - "\nDue to current technical limitations, it requires having at least one " - "\naudio or video Capture Device connected and enabled. Even if it won't be used. " + f"\nDue to current technical limitations, Windows versions below 10.0.0.{LEARNING_MODE_DEVICE_BUILD}" + "\nrequire having at least one audio or video Capture Device connected and enabled." "\nAllows recording UWP apps, Hardware Accelerated and Exclusive Fullscreen windows. " "\nAdds a yellow border on Windows 10 (not on Windows 11)." "\nCaps at around 60 FPS. " @@ -166,21 +167,18 @@ def __getitem__(self, key: CaptureMethodEnum): }) -def test_for_media_capture(): - async def coroutine(): - return await (MediaCapture().initialize_async() or asyncio.sleep(0)) +def try_get_direct3d_device(): try: - asyncio.run(coroutine()) - return True + return get_direct3d_device() except OSError: - return False + return None # Detect and remove unsupported capture methods if ( # Windows Graphics Capture requires a minimum Windows Build WINDOWS_BUILD_NUMBER < WGC_MIN_BUILD - # Our current implementation of Windows Graphics Capture requires at least one CaptureDevice - or not test_for_media_capture() + # Our current implementation of Windows Graphics Capture does not ensure we can get an ID3DDevice + or not try_get_direct3d_device() ): CAPTURE_METHODS.pop(CaptureMethodEnum.WINDOWS_GRAPHICS_CAPTURE) diff --git a/src/utils.py b/src/utils.py index 31266caa..e96eb3f6 100644 --- a/src/utils.py +++ b/src/utils.py @@ -12,6 +12,8 @@ import cv2 from win32 import win32gui +from winsdk.windows.ai.machinelearning import LearningModelDevice, LearningModelDeviceKind +from winsdk.windows.media.capture import MediaCapture from gen.build_vars import AUTOSPLIT_BUILD_NUMBER, AUTOSPLIT_GITHUB_REPOSITORY @@ -76,6 +78,23 @@ def get_window_bounds(hwnd: int) -> tuple[int, int, int, int]: return window_left_bounds, window_top_bounds, window_width, window_height +def get_direct3d_device(): + direct_3d_device = LearningModelDevice(LearningModelDeviceKind.DIRECT_X_HIGH_PERFORMANCE).direct3_d11_device + if not direct_3d_device: + # Note: Must create in the same thread (can't use a global) otherwise when ran from LiveSplit it will raise: + # OSError: The application called an interface that was marshalled for a different thread + media_capture = MediaCapture() + + async def coroutine(): + await (media_capture.initialize_async() or asyncio.sleep(0)) + asyncio.run(coroutine()) + direct_3d_device = media_capture.media_capture_settings and \ + media_capture.media_capture_settings.direct3_d11_device + if not direct_3d_device: + raise OSError("Unable to initialize a Direct3D Device.") + return direct_3d_device + + def fire_and_forget(func: Callable[..., Any]): """ Runs synchronous function asynchronously without waiting for a response