From c63add3801506beec8c079d6af591fcf43a074ef Mon Sep 17 00:00:00 2001 From: Avasam Date: Fri, 17 Mar 2023 22:34:04 -0400 Subject: [PATCH 1/2] Fix D3DDD issue on hybrid gpu laptops 1. Some Desktop Duplication setup was done in advance, even if that method won't be used 2. Hybrid GPU laptops can have issues with D3D Desktop Duplication. The issue and a workaround is now documented --- D3DDD-Note-Laptops.md | 47 +++++++++++++++++++ README.md | 1 + .../DesktopDuplicationCaptureMethod.py | 17 ++++--- src/capture_method/__init__.py | 34 +++++++++----- 4 files changed, 80 insertions(+), 19 deletions(-) create mode 100644 D3DDD-Note-Laptops.md diff --git a/D3DDD-Note-Laptops.md b/D3DDD-Note-Laptops.md new file mode 100644 index 00000000..bbf456f3 --- /dev/null +++ b/D3DDD-Note-Laptops.md @@ -0,0 +1,47 @@ +# Installation Note: D3D Desktop Duplication on Laptops + +Windows has a little quirk when running Desktop Duplication on laptops with hybrid GPU systems (integrated + dedicated). You will need to perform an additional tweak to get _D3D Desktop Duplication_ to work correctly on your system. + +## Problem + +The problem is fully documented in [this article](https://support.microsoft.com/en-us/help/3019314/error-generated-when-desktop-duplication-api-capable-application-is-ru) + +## Solution + +The solution is presented as such: + +> Run the application on the integrated GPU instead of on the discrete GPU + +Therefore, to be able to use _D3D Desktop Duplication_ on hybrid GPU laptops, we need to force Python to run on the integrated GPU. + +## Approach 1: Windows 10 Settings + +_You must be running Windows 10 1809 or later for this to work._ + +1. Press the Windows Key, type `Graphics settings` and press enter +2. You should see the following window: +![image](https://user-images.githubusercontent.com/35039/84433008-a3b65d00-abfb-11ea-8343-81b8f265afc4.png) +3. Make sure the dropdown is set to `Desktop App` and click `Browse` +4. Find the `python.exe` used by your _D3D Desktop Duplication_ project. Example: +![image](https://user-images.githubusercontent.com/35039/84433419-3d7e0a00-abfc-11ea-99a4-b5176535b0e5.png) +5. Click on `Options` +6. Select `Power saving` and click `Save` +![image](https://user-images.githubusercontent.com/35039/84433562-7918d400-abfc-11ea-807a-e3c0b15d9fb2.png) +7. If you did everything right it should look like this: +![image](https://user-images.githubusercontent.com/35039/84433706-bda46f80-abfc-11ea-9c64-a702b96095b8.png) +8. Repeat the process for other potentially relevant executables for your project: `ipython.exe`, `jupyter-kernel.exe` etc. + +## Approach 2: Nvidia Control Panel + +Need help to fill in this section. See issue [SerpentAI/D3DShot#27](https://github.com/SerpentAI/D3DShot/issues/27) + +## Approach 3: AMD Catalyst Control Center + +Need help to fill in this section. See issue [SerpentAI/D3DShot#28](https://github.com/SerpentAI/D3DShot/issues/28) + +## Question: Won't this impede on my ability to use CUDA, OpenCL etc? + +Preliminary answer: No. This is telling Windows how to _render_ Python processes with the Desktop Window Manager. Most Python applications are console applications that don't have a window. Even if you have a GUI application with one or more windows, this should only affect the rendering aspect (i.e. your windows won't be rendered through the dedicated GPU) and shouldn't limit hardware access in any way. + +--- +(copied and adapted from ) diff --git a/README.md b/README.md index 04244620..49685abc 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ Refer to the [build instructions](build%20instructions.md) if you'd like to buil It can record OpenGL and Hardware Accelerated windows. About 10-15x slower than BitBlt. Not affected by window size. overlapping windows will show up and can't record across displays. + This option may not be available for hybrid GPU laptops, see [D3DDD-Note-Laptops.md](/D3DDD-Note-Laptops.md) for a solution. - **Force Full Content Rendering** (very slow, can affect rendering pipeline) Uses BitBlt behind the scene, but passes a special flag to PrintWindow to force rendering the entire desktop. About 10-15x slower than BitBlt based on original window size and can mess up some applications' rendering pipelines. diff --git a/src/capture_method/DesktopDuplicationCaptureMethod.py b/src/capture_method/DesktopDuplicationCaptureMethod.py index 07706501..7ee1dec0 100644 --- a/src/capture_method/DesktopDuplicationCaptureMethod.py +++ b/src/capture_method/DesktopDuplicationCaptureMethod.py @@ -14,10 +14,13 @@ if TYPE_CHECKING: from AutoSplit import AutoSplit -desktop_duplication = d3dshot.create(capture_output="numpy") - class DesktopDuplicationCaptureMethod(BitBltCaptureMethod): # pylint: disable=too-few-public-methods + def __init__(self): + super().__init__() + # Must not set statically as some laptops will throw an error + self.desktop_duplication = d3dshot.create(capture_output="numpy") + def get_frame(self, autosplit: AutoSplit): selection = autosplit.settings_dict["capture_region"] hwnd = autosplit.hwnd @@ -26,19 +29,19 @@ def get_frame(self, autosplit: AutoSplit): return None, False left_bounds, top_bounds, *_ = get_window_bounds(hwnd) - desktop_duplication.display = [ + self.desktop_duplication.display = [ display for display - in desktop_duplication.displays + in self.desktop_duplication.displays if display.hmonitor == hmonitor ][0] offset_x, offset_y, *_ = win32gui.GetWindowRect(hwnd) - offset_x -= desktop_duplication.display.position["left"] - offset_y -= desktop_duplication.display.position["top"] + offset_x -= self.desktop_duplication.display.position["left"] + offset_y -= self.desktop_duplication.display.position["top"] left = selection["x"] + offset_x + left_bounds top = selection["y"] + offset_y + top_bounds right = selection["width"] + left bottom = selection["height"] + top - screenshot = desktop_duplication.screenshot((left, top, right, bottom)) + screenshot = self.desktop_duplication.screenshot((left, top, right, bottom)) if screenshot is None: return None, False return cv2.cvtColor(cast(cv2.Mat, screenshot), cv2.COLOR_RGBA2BGRA), False diff --git a/src/capture_method/__init__.py b/src/capture_method/__init__.py index ba2715f6..bcba5e6d 100644 --- a/src/capture_method/__init__.py +++ b/src/capture_method/__init__.py @@ -6,6 +6,7 @@ from enum import Enum, EnumMeta, unique from typing import TYPE_CHECKING, TypedDict, cast +from _ctypes import COMError # pylint: disable=C2701 from pygrabber.dshow_graph import FilterGraph from capture_method.BitBltCaptureMethod import BitBltCaptureMethod @@ -14,7 +15,7 @@ from capture_method.ForceFullContentRenderingCaptureMethod import ForceFullContentRenderingCaptureMethod from capture_method.VideoCaptureDeviceCaptureMethod import VideoCaptureDeviceCaptureMethod from capture_method.WindowsGraphicsCaptureMethod import WindowsGraphicsCaptureMethod -from utils import WINDOWS_BUILD_NUMBER, first, try_get_direct3d_device +from utils import GITHUB_REPOSITORY, WINDOWS_BUILD_NUMBER, first, try_get_direct3d_device if TYPE_CHECKING: from AutoSplit import AutoSplit @@ -147,17 +148,26 @@ def get(self, __key: CaptureMethodEnum): implementation=BitBltCaptureMethod, ) -CAPTURE_METHODS[CaptureMethodEnum.DESKTOP_DUPLICATION] = CaptureMethodInfo( - name="Direct3D Desktop Duplication", - short_description="slower, bound to display", - description=( - "\nDuplicates the desktop using Direct3D. " - "\nIt can record OpenGL and Hardware Accelerated windows. " - "\nAbout 10-15x slower than BitBlt. Not affected by window size. " - "\nOverlapping windows will show up and can't record across displays. " - ), - implementation=DesktopDuplicationCaptureMethod, -) +try: + import d3dshot + d3dshot.create(capture_output="numpy") +except (ModuleNotFoundError, COMError): + pass +else: + CAPTURE_METHODS[CaptureMethodEnum.DESKTOP_DUPLICATION] = CaptureMethodInfo( + name="Direct3D Desktop Duplication", + short_description="slower, bound to display", + description=( + "\nDuplicates the desktop using Direct3D. " + "\nIt can record OpenGL and Hardware Accelerated windows. " + "\nAbout 10-15x slower than BitBlt. Not affected by window size. " + "\nOverlapping windows will show up and can't record across displays. " + "\nThis option may not be available for hybrid GPU laptops, " + "\nsee D3DDD-Note-Laptops.md for a solution. " + f"\nhttps://www.github.com/{GITHUB_REPOSITORY}#capture-method " + ), + implementation=DesktopDuplicationCaptureMethod, + ) CAPTURE_METHODS[CaptureMethodEnum.PRINTWINDOW_RENDERFULLCONTENT] = CaptureMethodInfo( name="Force Full Content Rendering", short_description="very slow, can affect rendering pipeline", From 22d832f83dfdac0d8fa2dc215d7b7ed2ac9b180e Mon Sep 17 00:00:00 2001 From: Avasam Date: Fri, 17 Mar 2023 22:34:16 -0400 Subject: [PATCH 2/2] Fixed some blocking typing and linting issues --- src/AutoSplit.py | 9 +++++++-- src/capture_method/__init__.py | 7 +++---- src/menu_bar.py | 5 ++++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/AutoSplit.py b/src/AutoSplit.py index b2fb5e3d..4d239b82 100644 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -149,8 +149,13 @@ def __init__(self, parent: QWidget | None = None): # pylint: disable=too-many-s self.action_about_qt_for_python.triggered.connect(about_qt_for_python) self.action_check_for_updates.triggered.connect(lambda: check_for_updates(self)) self.action_settings.triggered.connect(lambda: open_settings(self)) - self.action_save_profile.triggered.connect(lambda: user_profile.save_settings(self)) - self.action_save_profile_as.triggered.connect(lambda: user_profile.save_settings_as(self)) + # PyQt6 typing is wrong + self.action_save_profile.triggered.connect( + lambda: user_profile.save_settings(self), # pyright: ignore[reportGeneralTypeIssues] + ) + self.action_save_profile_as.triggered.connect( + lambda: user_profile.save_settings_as(self), # pyright: ignore[reportGeneralTypeIssues] + ) self.action_load_profile.triggered.connect(lambda: user_profile.load_settings(self)) # Shortcut context can't be set through the designer because of a bug in pyuic6 that generates invalid code diff --git a/src/capture_method/__init__.py b/src/capture_method/__init__.py index bcba5e6d..4eccc037 100644 --- a/src/capture_method/__init__.py +++ b/src/capture_method/__init__.py @@ -45,11 +45,10 @@ class CaptureMethodMeta(EnumMeta): # Allow checking if simple string is enum def __contains__(self, other: str): try: - self(other) # pyright: ignore [reportGeneralTypeIssues] pylint: disable=no-value-for-parameter + self(other) # pylint: disable=no-value-for-parameter except ValueError: return False - else: - return True + return True @unique @@ -222,7 +221,7 @@ def get_input_device_resolution(index: int): async def get_all_video_capture_devices() -> list[CameraInfo]: - named_video_inputs = FilterGraph().get_input_devices() + named_video_inputs: list[str] = FilterGraph().get_input_devices() async def get_camera_info(index: int, device_name: str): backend = "" diff --git a/src/menu_bar.py b/src/menu_bar.py index df01fd40..d4b2d1a7 100644 --- a/src/menu_bar.py +++ b/src/menu_bar.py @@ -211,7 +211,10 @@ def __set_readme_link(self): # HACK: This is a workaround because custom_image_settings_info_label # simply will not open links with a left click no matter what we tried. self.readme_link_button.clicked.connect( - lambda: webbrowser.open(f"https://github.com/{GITHUB_REPOSITORY}#readme"), + # PyQt6 typing is wrong + lambda: webbrowser.open( # pyright: ignore[reportGeneralTypeIssues] + f"https://github.com/{GITHUB_REPOSITORY}#readme", + ), ) self.readme_link_button.setStyleSheet("border: 0px; background-color:rgba(0,0,0,0%);")