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 b63956d8..2b17ebc6 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,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) 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 2d77f1f4..35dd93ee 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 @@ -44,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 @@ -147,17 +147,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",