Revise the screen capture method to continuous grabbing and extract the latest frame to address the issue of "DXCAM failed to capture frame, trying to use the latest screenshot"
This commit is contained in:
+36
-33
@@ -21,44 +21,45 @@ import win32gui
|
|||||||
import win32api
|
import win32api
|
||||||
import win32con
|
import win32con
|
||||||
|
|
||||||
|
|
||||||
def get_process_info(process_name):
|
def get_process_info(process_name):
|
||||||
"""
|
"""
|
||||||
Get process information for a given process name on Windows.
|
Get process information for a given process name on Windows.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
process_name (str): Name of the process (e.g., "isaac-ng.exe")
|
process_name (str): Name of the process (e.g., "isaac-ng.exe")
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list: List of dictionaries containing PID, window_name, and architecture
|
list: List of dictionaries containing PID, window_name, and architecture
|
||||||
for each matching process. Returns empty list if no process found.
|
for each matching process. Returns empty list if no process found.
|
||||||
"""
|
"""
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
# Find all processes with the given name
|
# Find all processes with the given name
|
||||||
for proc in psutil.process_iter(['pid', 'name']):
|
for proc in psutil.process_iter(['pid', 'name']):
|
||||||
try:
|
try:
|
||||||
if proc.info['name'].lower() == process_name.lower():
|
if proc.info['name'].lower() == process_name.lower():
|
||||||
pid = proc.info['pid']
|
pid = proc.info['pid']
|
||||||
|
|
||||||
# Get architecture
|
# Get architecture
|
||||||
try:
|
try:
|
||||||
# Check if process is 32-bit or 64-bit
|
# Check if process is 32-bit or 64-bit
|
||||||
process_handle = win32api.OpenProcess(
|
process_handle = win32api.OpenProcess(
|
||||||
win32con.PROCESS_QUERY_INFORMATION,
|
win32con.PROCESS_QUERY_INFORMATION,
|
||||||
False,
|
False,
|
||||||
pid
|
pid
|
||||||
)
|
)
|
||||||
is_wow64 = win32process.IsWow64Process(process_handle)
|
is_wow64 = win32process.IsWow64Process(process_handle)
|
||||||
win32api.CloseHandle(process_handle)
|
win32api.CloseHandle(process_handle)
|
||||||
|
|
||||||
# On 64-bit Windows: WOW64 means "Windows 32-bit on Windows 64-bit", i.e. a 32-bit process
|
# On 64-bit Windows: WOW64 means "Windows 32-bit on Windows 64-bit", i.e. a 32-bit process
|
||||||
architecture = "x86" if is_wow64 else "x64"
|
architecture = "x86" if is_wow64 else "x64"
|
||||||
except:
|
except:
|
||||||
architecture = "unknown"
|
architecture = "unknown"
|
||||||
|
|
||||||
# Find windows associated with this PID
|
# Find windows associated with this PID
|
||||||
windows = []
|
windows = []
|
||||||
|
|
||||||
def enum_window_callback(hwnd, pid_to_find):
|
def enum_window_callback(hwnd, pid_to_find):
|
||||||
_, found_pid = win32process.GetWindowThreadProcessId(hwnd)
|
_, found_pid = win32process.GetWindowThreadProcessId(hwnd)
|
||||||
if found_pid == pid_to_find:
|
if found_pid == pid_to_find:
|
||||||
@@ -70,13 +71,13 @@ def get_process_info(process_name):
|
|||||||
'visible': win32gui.IsWindowVisible(hwnd)
|
'visible': win32gui.IsWindowVisible(hwnd)
|
||||||
})
|
})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Find all windows for this PID
|
# Find all windows for this PID
|
||||||
try:
|
try:
|
||||||
win32gui.EnumWindows(enum_window_callback, pid)
|
win32gui.EnumWindows(enum_window_callback, pid)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Choose the best window
|
# Choose the best window
|
||||||
window_name = None
|
window_name = None
|
||||||
if windows:
|
if windows:
|
||||||
@@ -85,31 +86,31 @@ def get_process_info(process_name):
|
|||||||
print("Using heuristics to select the correct window...")
|
print("Using heuristics to select the correct window...")
|
||||||
# Filter out common proxy/helper windows
|
# Filter out common proxy/helper windows
|
||||||
proxy_keywords = ['d3dproxywindow', 'proxy', 'helper', 'overlay']
|
proxy_keywords = ['d3dproxywindow', 'proxy', 'helper', 'overlay']
|
||||||
|
|
||||||
# First try to find a visible window without proxy keywords
|
# First try to find a visible window without proxy keywords
|
||||||
for win in windows:
|
for win in windows:
|
||||||
if not any(keyword in win['title'].lower() for keyword in proxy_keywords):
|
if not any(keyword in win['title'].lower() for keyword in proxy_keywords):
|
||||||
window_name = win['title']
|
window_name = win['title']
|
||||||
break
|
break
|
||||||
|
|
||||||
# If no good window found, just use the first one
|
# If no good window found, just use the first one
|
||||||
if window_name is None and windows:
|
if window_name is None and windows:
|
||||||
window_name = windows[0]['title']
|
window_name = windows[0]['title']
|
||||||
|
|
||||||
results.append({
|
results.append({
|
||||||
'pid': pid,
|
'pid': pid,
|
||||||
'window_name': window_name,
|
'window_name': window_name,
|
||||||
'architecture': architecture
|
'architecture': architecture
|
||||||
})
|
})
|
||||||
|
|
||||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if len(results) == 0:
|
if len(results) == 0:
|
||||||
raise ValueError(f"No process found with name: {process_name}")
|
raise ValueError(f"No process found with name: {process_name}")
|
||||||
elif len(results) > 1:
|
elif len(results) > 1:
|
||||||
print(f"Warning: Multiple processes found with name '{process_name}'. Returning first match.")
|
print(f"Warning: Multiple processes found with name '{process_name}'. Returning first match.")
|
||||||
|
|
||||||
return results[0]
|
return results[0]
|
||||||
|
|
||||||
|
|
||||||
@@ -338,6 +339,7 @@ class GamepadEmulator:
|
|||||||
self.gamepad.reset()
|
self.gamepad.reset()
|
||||||
self.gamepad.update()
|
self.gamepad.update()
|
||||||
|
|
||||||
|
|
||||||
class PyautoguiScreenshotBackend:
|
class PyautoguiScreenshotBackend:
|
||||||
|
|
||||||
def __init__(self, bbox):
|
def __init__(self, bbox):
|
||||||
@@ -346,15 +348,17 @@ class PyautoguiScreenshotBackend:
|
|||||||
def screenshot(self):
|
def screenshot(self):
|
||||||
return pyautogui.screenshot(region=self.bbox)
|
return pyautogui.screenshot(region=self.bbox)
|
||||||
|
|
||||||
|
|
||||||
class DxcamScreenshotBackend:
|
class DxcamScreenshotBackend:
|
||||||
def __init__(self, bbox):
|
def __init__(self, bbox, fps):
|
||||||
import dxcam
|
import dxcam
|
||||||
self.camera = dxcam.create()
|
self.camera = dxcam.create()
|
||||||
self.bbox = bbox
|
self.bbox = bbox
|
||||||
self.last_screenshot = None
|
self.last_screenshot = None
|
||||||
|
self.camera.start(region=self.bbox, target_fps=fps, video_mode=True)
|
||||||
|
|
||||||
def screenshot(self):
|
def screenshot(self):
|
||||||
screenshot = self.camera.grab(region=self.bbox)
|
screenshot = self.camera.get_latest_frame()
|
||||||
if screenshot is None:
|
if screenshot is None:
|
||||||
print("DXCAM failed to capture frame, trying to use the latest screenshot")
|
print("DXCAM failed to capture frame, trying to use the latest screenshot")
|
||||||
if self.last_screenshot is not None:
|
if self.last_screenshot is not None:
|
||||||
@@ -381,15 +385,15 @@ class GamepadEnv(Env):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
game,
|
game,
|
||||||
image_height=1440,
|
image_height=1440,
|
||||||
image_width=2560,
|
image_width=2560,
|
||||||
controller_type="xbox",
|
controller_type="xbox",
|
||||||
game_speed=1.0,
|
game_speed=1.0,
|
||||||
env_fps=10,
|
env_fps=10,
|
||||||
async_mode=True,
|
async_mode=True,
|
||||||
screenshot_backend="dxcam",
|
screenshot_backend="dxcam",
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
@@ -414,12 +418,12 @@ class GamepadEnv(Env):
|
|||||||
self.game_arch = proc_info["architecture"]
|
self.game_arch = proc_info["architecture"]
|
||||||
self.game_window_name = proc_info["window_name"]
|
self.game_window_name = proc_info["window_name"]
|
||||||
|
|
||||||
print(f"Game process found: {self.game} (PID: {self.game_pid}, Arch: {self.game_arch}, Window: {self.game_window_name})")
|
print(
|
||||||
|
f"Game process found: {self.game} (PID: {self.game_pid}, Arch: {self.game_arch}, Window: {self.game_window_name})")
|
||||||
|
|
||||||
if self.game_pid is None:
|
if self.game_pid is None:
|
||||||
raise Exception(f"Could not find PID for game: {game}")
|
raise Exception(f"Could not find PID for game: {game}")
|
||||||
|
|
||||||
|
|
||||||
self.observation_space = Box(
|
self.observation_space = Box(
|
||||||
low=0, high=255, shape=(self.image_height, self.image_width, 3), dtype="uint8"
|
low=0, high=255, shape=(self.image_height, self.image_width, 3), dtype="uint8"
|
||||||
)
|
)
|
||||||
@@ -464,20 +468,19 @@ class GamepadEnv(Env):
|
|||||||
|
|
||||||
self.game_window.activate()
|
self.game_window.activate()
|
||||||
l, t, r, b = self.game_window.left, self.game_window.top, self.game_window.right, self.game_window.bottom
|
l, t, r, b = self.game_window.left, self.game_window.top, self.game_window.right, self.game_window.bottom
|
||||||
self.bbox = (l, t, r-l, b-t)
|
self.bbox = (l, t, r - l, b - t)
|
||||||
|
|
||||||
# Initialize speedhack client if using DLL injection
|
# Initialize speedhack client if using DLL injection
|
||||||
self.speedhack_client = xsh.Client(process_id=self.game_pid, arch=self.game_arch)
|
self.speedhack_client = xsh.Client(process_id=self.game_pid, arch=self.game_arch)
|
||||||
|
|
||||||
# Get the screenshot backend
|
# Get the screenshot backend
|
||||||
if screenshot_backend == "dxcam":
|
if screenshot_backend == "dxcam":
|
||||||
self.screenshot_backend = DxcamScreenshotBackend(self.bbox)
|
self.screenshot_backend = DxcamScreenshotBackend(self.bbox, self.env_fps)
|
||||||
elif screenshot_backend == "pyautogui":
|
elif screenshot_backend == "pyautogui":
|
||||||
self.screenshot_backend = PyautoguiScreenshotBackend(self.bbox)
|
self.screenshot_backend = PyautoguiScreenshotBackend(self.bbox)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unsupported screenshot backend. Use 'dxcam' or 'pyautogui'.")
|
raise ValueError("Unsupported screenshot backend. Use 'dxcam' or 'pyautogui'.")
|
||||||
|
|
||||||
|
|
||||||
def calculate_step_duration(self):
|
def calculate_step_duration(self):
|
||||||
"""
|
"""
|
||||||
Calculate the step duration based on game speed and environment FPS.
|
Calculate the step duration based on game speed and environment FPS.
|
||||||
|
|||||||
Reference in New Issue
Block a user