|
|
"""
|
|
|
GPU Display Manager - Handles rendering and streaming of GPU output to the web interface
|
|
|
"""
|
|
|
|
|
|
import numpy as np
|
|
|
import base64
|
|
|
import io
|
|
|
from PIL import Image
|
|
|
import asyncio
|
|
|
from typing import Optional, Tuple
|
|
|
import threading
|
|
|
import queue
|
|
|
|
|
|
class GPUDisplayManager:
|
|
|
def __init__(self, resolution: Tuple[int, int] = (800, 600), fps: int = 30):
|
|
|
self.resolution = resolution
|
|
|
self.fps = fps
|
|
|
self.frame_interval = 1.0 / fps
|
|
|
self.frame_queue = queue.Queue(maxsize=10)
|
|
|
self.running = False
|
|
|
self.current_frame = None
|
|
|
|
|
|
def start_capture(self):
|
|
|
"""Start the GPU frame capture thread"""
|
|
|
self.running = True
|
|
|
self.capture_thread = threading.Thread(target=self._capture_loop)
|
|
|
self.capture_thread.daemon = True
|
|
|
self.capture_thread.start()
|
|
|
|
|
|
def stop_capture(self):
|
|
|
"""Stop the GPU frame capture"""
|
|
|
self.running = False
|
|
|
if hasattr(self, 'capture_thread'):
|
|
|
self.capture_thread.join()
|
|
|
|
|
|
def _capture_loop(self):
|
|
|
"""Main capture loop that reads from GPU framebuffer"""
|
|
|
while self.running:
|
|
|
try:
|
|
|
frame = self._capture_gpu_frame()
|
|
|
if frame is not None:
|
|
|
|
|
|
encoded_frame = self._encode_frame(frame)
|
|
|
|
|
|
try:
|
|
|
self.frame_queue.put_nowait(encoded_frame)
|
|
|
except queue.Full:
|
|
|
|
|
|
try:
|
|
|
self.frame_queue.get_nowait()
|
|
|
self.frame_queue.put_nowait(encoded_frame)
|
|
|
except queue.Empty:
|
|
|
pass
|
|
|
|
|
|
except Exception as e:
|
|
|
print(f"Error capturing frame: {e}")
|
|
|
|
|
|
|
|
|
asyncio.sleep(self.frame_interval)
|
|
|
|
|
|
def _capture_gpu_frame(self) -> Optional[np.ndarray]:
|
|
|
"""Capture current frame from GPU framebuffer"""
|
|
|
try:
|
|
|
|
|
|
|
|
|
frame = np.zeros((self.resolution[1], self.resolution[0], 3), dtype=np.uint8)
|
|
|
|
|
|
frame[::20, :] = [255, 0, 0]
|
|
|
frame[:, ::20] = [0, 255, 0]
|
|
|
return frame
|
|
|
except Exception as e:
|
|
|
print(f"Error reading GPU framebuffer: {e}")
|
|
|
return None
|
|
|
|
|
|
def _encode_frame(self, frame: np.ndarray) -> str:
|
|
|
"""Convert frame to base64 JPEG for streaming"""
|
|
|
img = Image.fromarray(frame)
|
|
|
buffer = io.BytesIO()
|
|
|
img.save(buffer, format='JPEG', quality=80)
|
|
|
img_str = base64.b64encode(buffer.getvalue()).decode('utf-8')
|
|
|
return f"data:image/jpeg;base64,{img_str}"
|
|
|
|
|
|
async def get_next_frame(self) -> Optional[str]:
|
|
|
"""Get the next available frame"""
|
|
|
try:
|
|
|
return self.frame_queue.get_nowait()
|
|
|
except queue.Empty:
|
|
|
return None
|
|
|
|
|
|
def set_resolution(self, width: int, height: int):
|
|
|
"""Update the display resolution"""
|
|
|
self.resolution = (width, height)
|
|
|
|
|
|
def set_fps(self, fps: int):
|
|
|
"""Update the target FPS"""
|
|
|
self.fps = fps
|
|
|
self.frame_interval = 1.0 / fps
|
|
|
|