Spaces:
Runtime error
Runtime error
Factor Studios
commited on
Upload 30 files
Browse files- virtual_gpu_setup/virtual_gpu/README.md +30 -0
- virtual_gpu_setup/virtual_gpu/__init__.py +0 -0
- virtual_gpu_setup/virtual_gpu/__pycache__/__init__.cpython-311.pyc +0 -0
- virtual_gpu_setup/virtual_gpu/__pycache__/ai.cpython-311.pyc +0 -0
- virtual_gpu_setup/virtual_gpu/__pycache__/display.cpython-311.pyc +0 -0
- virtual_gpu_setup/virtual_gpu/__pycache__/driver.cpython-311.pyc +0 -0
- virtual_gpu_setup/virtual_gpu/__pycache__/render.cpython-311.pyc +0 -0
- virtual_gpu_setup/virtual_gpu/__pycache__/shader.cpython-311.pyc +0 -0
- virtual_gpu_setup/virtual_gpu/__pycache__/vgpu.cpython-311.pyc +0 -0
- virtual_gpu_setup/virtual_gpu/__pycache__/virtual_ram.cpython-311.pyc +0 -0
- virtual_gpu_setup/virtual_gpu/__pycache__/vram.cpython-311.pyc +0 -0
- virtual_gpu_setup/virtual_gpu/ai.py +446 -0
- virtual_gpu_setup/virtual_gpu/bus.py +428 -0
- virtual_gpu_setup/virtual_gpu/display.py +501 -0
- virtual_gpu_setup/virtual_gpu/driver.py +312 -0
- virtual_gpu_setup/virtual_gpu/examples/__init__.py +0 -0
- virtual_gpu_setup/virtual_gpu/examples/__pycache__/__init__.cpython-311.pyc +0 -0
- virtual_gpu_setup/virtual_gpu/examples/__pycache__/test_virtual_ram_transfer.cpython-311.pyc +0 -0
- virtual_gpu_setup/virtual_gpu/examples/test_basic_rendering.py +245 -0
- virtual_gpu_setup/virtual_gpu/examples/test_virtual_ram_transfer.py +150 -0
- virtual_gpu_setup/virtual_gpu/render.py +382 -0
- virtual_gpu_setup/virtual_gpu/shader.py +386 -0
- virtual_gpu_setup/virtual_gpu/test_output/frame_000000.png +0 -0
- virtual_gpu_setup/virtual_gpu/test_output/frame_000001.png +0 -0
- virtual_gpu_setup/virtual_gpu/test_output/frame_000002.png +0 -0
- virtual_gpu_setup/virtual_gpu/test_output/frame_000003.png +0 -0
- virtual_gpu_setup/virtual_gpu/test_output/frame_000004.png +0 -0
- virtual_gpu_setup/virtual_gpu/vgpu.py +283 -0
- virtual_gpu_setup/virtual_gpu/virtual_ram.py +385 -0
- virtual_gpu_setup/virtual_gpu/vram.py +361 -0
virtual_gpu_setup/virtual_gpu/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Virtual GPU (vGPU) Project
|
| 2 |
+
|
| 3 |
+
This project aims to build a fully functional software-defined GPU (vGPU) using Python, without relying on any physical GPU hardware or existing low-level graphics APIs (like CUDA, Metal, Vulkan, or OpenGL). The vGPU is designed to simulate the behavior of a real GPU, including its core components, memory hierarchy, and parallel processing capabilities.
|
| 4 |
+
|
| 5 |
+
## Project Goals
|
| 6 |
+
|
| 7 |
+
* **Software-Defined Hardware**: Replace traditional GPU hardware components with pure software abstractions.
|
| 8 |
+
* **Massive Parallelism Simulation**: Simulate 50,000 processing cores and 800 Streaming Multiprocessors (SMs).
|
| 9 |
+
* **High-Bandwidth Memory Abstraction**: Implement a 500GB GDDR7 memory abstraction using symbolic memory management.
|
| 10 |
+
* **Graphical and AI Processing**: Capable of processing graphical logic, AI matrix operations, and rendering output.
|
| 11 |
+
* **Modular Architecture**: Designed with distinct modules for clear separation of concerns and extensibility.
|
| 12 |
+
|
| 13 |
+
## Modules Overview
|
| 14 |
+
|
| 15 |
+
This project is structured into several key modules, each responsible for a specific aspect of the vGPU's functionality:
|
| 16 |
+
|
| 17 |
+
* `vgpu.py`: The core GPU processor, managing overall state, workload distribution, and the main GPU tick cycle.
|
| 18 |
+
* `vram.py`: The video memory module, abstracting 500GB of GDDR7 memory using symbolic representation and efficient data handling.
|
| 19 |
+
* `driver.py`: The CPU-to-GPU command interpreter, responsible for receiving and queuing commands from a virtual CPU.
|
| 20 |
+
* `render.py`: The pixel renderer, implementing the software raster pipeline for drawing primitives and images.
|
| 21 |
+
* `ai.py`: The simulated AI accelerator, handling matrix and vector operations using the vGPU's simulated parallelism.
|
| 22 |
+
* `shader.py`: Provides a mechanism for simulating programmable shader logic.
|
| 23 |
+
* `display.py`: The output system, handling the presentation of rendered frames to a display (e.g., WebSocket to JS canvas, GUI window, or image files).
|
| 24 |
+
* `bus.py`: Simulates memory movement and data transfer logic between different logical components.
|
| 25 |
+
|
| 26 |
+
## Getting Started
|
| 27 |
+
|
| 28 |
+
Further instructions on setting up the environment, running examples, and contributing will be provided as the project develops.
|
| 29 |
+
|
| 30 |
+
|
virtual_gpu_setup/virtual_gpu/__init__.py
ADDED
|
File without changes
|
virtual_gpu_setup/virtual_gpu/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (145 Bytes). View file
|
|
|
virtual_gpu_setup/virtual_gpu/__pycache__/ai.cpython-311.pyc
ADDED
|
Binary file (19.1 kB). View file
|
|
|
virtual_gpu_setup/virtual_gpu/__pycache__/display.cpython-311.pyc
ADDED
|
Binary file (26.9 kB). View file
|
|
|
virtual_gpu_setup/virtual_gpu/__pycache__/driver.cpython-311.pyc
ADDED
|
Binary file (16.8 kB). View file
|
|
|
virtual_gpu_setup/virtual_gpu/__pycache__/render.cpython-311.pyc
ADDED
|
Binary file (16.6 kB). View file
|
|
|
virtual_gpu_setup/virtual_gpu/__pycache__/shader.cpython-311.pyc
ADDED
|
Binary file (22.7 kB). View file
|
|
|
virtual_gpu_setup/virtual_gpu/__pycache__/vgpu.cpython-311.pyc
ADDED
|
Binary file (14.6 kB). View file
|
|
|
virtual_gpu_setup/virtual_gpu/__pycache__/virtual_ram.cpython-311.pyc
ADDED
|
Binary file (18.3 kB). View file
|
|
|
virtual_gpu_setup/virtual_gpu/__pycache__/vram.cpython-311.pyc
ADDED
|
Binary file (19.3 kB). View file
|
|
|
virtual_gpu_setup/virtual_gpu/ai.py
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
AI Accelerator Module
|
| 3 |
+
|
| 4 |
+
This module implements AI-specific operations, treating the vGPU as a tensor engine
|
| 5 |
+
and leveraging the simulated parallelism of 50,000 cores and 800 SMs.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import time
|
| 10 |
+
from typing import Dict, Any, Optional, Tuple, Union, List
|
| 11 |
+
from enum import Enum
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class VectorOperation(Enum):
|
| 15 |
+
"""Enumeration of supported vector operations."""
|
| 16 |
+
ADD = "add"
|
| 17 |
+
SUBTRACT = "subtract"
|
| 18 |
+
MULTIPLY = "multiply"
|
| 19 |
+
DIVIDE = "divide"
|
| 20 |
+
DOT_PRODUCT = "dot_product"
|
| 21 |
+
CROSS_PRODUCT = "cross_product"
|
| 22 |
+
NORMALIZE = "normalize"
|
| 23 |
+
MAGNITUDE = "magnitude"
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class AIAccelerator:
|
| 27 |
+
"""
|
| 28 |
+
AI Accelerator that simulates GPU-based AI computations.
|
| 29 |
+
|
| 30 |
+
This class leverages NumPy's optimized operations to simulate the parallel
|
| 31 |
+
processing capabilities of the vGPU for AI workloads.
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
def __init__(self, vram=None, num_sms: int = 800, cores_per_sm: int = 62):
|
| 35 |
+
self.vram = vram
|
| 36 |
+
self.num_sms = num_sms
|
| 37 |
+
self.cores_per_sm = cores_per_sm
|
| 38 |
+
self.total_cores = num_sms * cores_per_sm
|
| 39 |
+
|
| 40 |
+
# AI operation statistics
|
| 41 |
+
self.operations_performed = 0
|
| 42 |
+
self.total_compute_time = 0.0
|
| 43 |
+
self.flops_performed = 0 # Floating point operations
|
| 44 |
+
|
| 45 |
+
# Matrix registry for storing matrices in VRAM
|
| 46 |
+
self.matrix_registry: Dict[str, str] = {} # matrix_id -> vram_address
|
| 47 |
+
self.matrix_counter = 0
|
| 48 |
+
|
| 49 |
+
def set_vram(self, vram):
|
| 50 |
+
"""Set the VRAM reference."""
|
| 51 |
+
self.vram = vram
|
| 52 |
+
|
| 53 |
+
def allocate_matrix(self, shape: Tuple[int, ...], dtype=np.float32,
|
| 54 |
+
name: Optional[str] = None) -> str:
|
| 55 |
+
"""Allocate a matrix in VRAM and return its ID."""
|
| 56 |
+
if not self.vram:
|
| 57 |
+
raise RuntimeError("VRAM not available")
|
| 58 |
+
|
| 59 |
+
if name is None:
|
| 60 |
+
name = f"matrix_{self.matrix_counter}"
|
| 61 |
+
self.matrix_counter += 1
|
| 62 |
+
|
| 63 |
+
# Create matrix data
|
| 64 |
+
matrix_data = np.zeros(shape, dtype=dtype)
|
| 65 |
+
|
| 66 |
+
# Store in VRAM as a texture (reusing texture storage mechanism)
|
| 67 |
+
matrix_id = self.vram.load_texture(matrix_data, name)
|
| 68 |
+
self.matrix_registry[name] = matrix_id
|
| 69 |
+
|
| 70 |
+
return name
|
| 71 |
+
|
| 72 |
+
def load_matrix(self, matrix_data: np.ndarray, name: Optional[str] = None) -> str:
|
| 73 |
+
"""Load matrix data into VRAM and return its ID."""
|
| 74 |
+
if not self.vram:
|
| 75 |
+
raise RuntimeError("VRAM not available")
|
| 76 |
+
|
| 77 |
+
if name is None:
|
| 78 |
+
name = f"matrix_{self.matrix_counter}"
|
| 79 |
+
self.matrix_counter += 1
|
| 80 |
+
|
| 81 |
+
# Store in VRAM
|
| 82 |
+
matrix_id = self.vram.load_texture(matrix_data, name)
|
| 83 |
+
self.matrix_registry[name] = matrix_id
|
| 84 |
+
|
| 85 |
+
return name
|
| 86 |
+
|
| 87 |
+
def get_matrix(self, matrix_id: str) -> Optional[np.ndarray]:
|
| 88 |
+
"""Retrieve matrix data from VRAM."""
|
| 89 |
+
if not self.vram or matrix_id not in self.matrix_registry:
|
| 90 |
+
return None
|
| 91 |
+
|
| 92 |
+
vram_id = self.matrix_registry[matrix_id]
|
| 93 |
+
return self.vram.get_texture(vram_id)
|
| 94 |
+
|
| 95 |
+
def matrix_multiply(self, matrix_a_id: str, matrix_b_id: str,
|
| 96 |
+
result_id: Optional[str] = None) -> Optional[str]:
|
| 97 |
+
"""Perform matrix multiplication using simulated GPU parallelism."""
|
| 98 |
+
start_time = time.time()
|
| 99 |
+
|
| 100 |
+
# Retrieve matrices from VRAM
|
| 101 |
+
matrix_a = self.get_matrix(matrix_a_id)
|
| 102 |
+
matrix_b = self.get_matrix(matrix_b_id)
|
| 103 |
+
|
| 104 |
+
if matrix_a is None or matrix_b is None:
|
| 105 |
+
print(f"Error: Could not retrieve matrices {matrix_a_id} or {matrix_b_id}")
|
| 106 |
+
return None
|
| 107 |
+
|
| 108 |
+
try:
|
| 109 |
+
# Check if matrices can be multiplied
|
| 110 |
+
if matrix_a.shape[-1] != matrix_b.shape[0]:
|
| 111 |
+
print(f"Error: Matrix dimensions incompatible for multiplication: "
|
| 112 |
+
f"{matrix_a.shape} x {matrix_b.shape}")
|
| 113 |
+
return None
|
| 114 |
+
|
| 115 |
+
# Simulate parallel processing by breaking down the operation
|
| 116 |
+
# In a real GPU, this would be distributed across SMs and cores
|
| 117 |
+
result = self._simulate_parallel_matmul(matrix_a, matrix_b)
|
| 118 |
+
|
| 119 |
+
# Store result in VRAM
|
| 120 |
+
if result_id is None:
|
| 121 |
+
result_id = f"result_{self.matrix_counter}"
|
| 122 |
+
self.matrix_counter += 1
|
| 123 |
+
|
| 124 |
+
result_matrix_id = self.load_matrix(result, result_id)
|
| 125 |
+
|
| 126 |
+
# Update statistics
|
| 127 |
+
compute_time = time.time() - start_time
|
| 128 |
+
self.total_compute_time += compute_time
|
| 129 |
+
self.operations_performed += 1
|
| 130 |
+
|
| 131 |
+
# Calculate FLOPs (2 * M * N * K for matrix multiplication)
|
| 132 |
+
m, k = matrix_a.shape
|
| 133 |
+
k2, n = matrix_b.shape
|
| 134 |
+
flops = 2 * m * n * k
|
| 135 |
+
self.flops_performed += flops
|
| 136 |
+
|
| 137 |
+
print(f"Matrix multiplication completed: {matrix_a.shape} x {matrix_b.shape} "
|
| 138 |
+
f"= {result.shape} in {compute_time:.4f}s")
|
| 139 |
+
print(f"Simulated {flops:,} FLOPs across {self.total_cores} cores")
|
| 140 |
+
|
| 141 |
+
return result_matrix_id
|
| 142 |
+
|
| 143 |
+
except Exception as e:
|
| 144 |
+
print(f"Error in matrix multiplication: {e}")
|
| 145 |
+
return None
|
| 146 |
+
|
| 147 |
+
def _simulate_parallel_matmul(self, matrix_a: np.ndarray, matrix_b: np.ndarray) -> np.ndarray:
|
| 148 |
+
"""Simulate parallel matrix multiplication across SMs."""
|
| 149 |
+
# Use NumPy's optimized matrix multiplication
|
| 150 |
+
# In a real implementation, this would be broken down into blocks
|
| 151 |
+
# and distributed across the simulated SMs
|
| 152 |
+
|
| 153 |
+
# For demonstration, we can show how the work would be distributed
|
| 154 |
+
m, k = matrix_a.shape
|
| 155 |
+
k2, n = matrix_b.shape
|
| 156 |
+
|
| 157 |
+
# Calculate work distribution
|
| 158 |
+
total_output_elements = m * n
|
| 159 |
+
elements_per_sm = max(1, total_output_elements // self.num_sms)
|
| 160 |
+
|
| 161 |
+
print(f"Distributing {total_output_elements:,} output elements across "
|
| 162 |
+
f"{self.num_sms} SMs ({elements_per_sm} elements per SM)")
|
| 163 |
+
|
| 164 |
+
# Perform the actual computation using NumPy
|
| 165 |
+
result = np.dot(matrix_a, matrix_b)
|
| 166 |
+
|
| 167 |
+
return result
|
| 168 |
+
|
| 169 |
+
def vector_operation(self, operation: VectorOperation, vector_a_id: str,
|
| 170 |
+
vector_b_id: Optional[str] = None,
|
| 171 |
+
result_id: Optional[str] = None) -> Optional[str]:
|
| 172 |
+
"""Perform vector operations using simulated GPU parallelism."""
|
| 173 |
+
start_time = time.time()
|
| 174 |
+
|
| 175 |
+
# Retrieve vectors from VRAM
|
| 176 |
+
vector_a = self.get_matrix(vector_a_id)
|
| 177 |
+
if vector_a is None:
|
| 178 |
+
print(f"Error: Could not retrieve vector {vector_a_id}")
|
| 179 |
+
return None
|
| 180 |
+
|
| 181 |
+
vector_b = None
|
| 182 |
+
if vector_b_id:
|
| 183 |
+
vector_b = self.get_matrix(vector_b_id)
|
| 184 |
+
if vector_b is None:
|
| 185 |
+
print(f"Error: Could not retrieve vector {vector_b_id}")
|
| 186 |
+
return None
|
| 187 |
+
|
| 188 |
+
try:
|
| 189 |
+
result = None
|
| 190 |
+
flops = 0
|
| 191 |
+
|
| 192 |
+
if operation == VectorOperation.ADD:
|
| 193 |
+
if vector_b is None:
|
| 194 |
+
raise ValueError("Vector B required for addition")
|
| 195 |
+
result = vector_a + vector_b
|
| 196 |
+
flops = vector_a.size
|
| 197 |
+
|
| 198 |
+
elif operation == VectorOperation.SUBTRACT:
|
| 199 |
+
if vector_b is None:
|
| 200 |
+
raise ValueError("Vector B required for subtraction")
|
| 201 |
+
result = vector_a - vector_b
|
| 202 |
+
flops = vector_a.size
|
| 203 |
+
|
| 204 |
+
elif operation == VectorOperation.MULTIPLY:
|
| 205 |
+
if vector_b is None:
|
| 206 |
+
raise ValueError("Vector B required for multiplication")
|
| 207 |
+
result = vector_a * vector_b
|
| 208 |
+
flops = vector_a.size
|
| 209 |
+
|
| 210 |
+
elif operation == VectorOperation.DIVIDE:
|
| 211 |
+
if vector_b is None:
|
| 212 |
+
raise ValueError("Vector B required for division")
|
| 213 |
+
result = vector_a / vector_b
|
| 214 |
+
flops = vector_a.size
|
| 215 |
+
|
| 216 |
+
elif operation == VectorOperation.DOT_PRODUCT:
|
| 217 |
+
if vector_b is None:
|
| 218 |
+
raise ValueError("Vector B required for dot product")
|
| 219 |
+
result = np.dot(vector_a.flatten(), vector_b.flatten())
|
| 220 |
+
flops = 2 * vector_a.size
|
| 221 |
+
|
| 222 |
+
elif operation == VectorOperation.CROSS_PRODUCT:
|
| 223 |
+
if vector_b is None:
|
| 224 |
+
raise ValueError("Vector B required for cross product")
|
| 225 |
+
result = np.cross(vector_a, vector_b)
|
| 226 |
+
flops = 6 # Approximate for 3D cross product
|
| 227 |
+
|
| 228 |
+
elif operation == VectorOperation.NORMALIZE:
|
| 229 |
+
magnitude = np.linalg.norm(vector_a)
|
| 230 |
+
result = vector_a / magnitude if magnitude > 0 else vector_a
|
| 231 |
+
flops = vector_a.size * 2 # Division + magnitude calculation
|
| 232 |
+
|
| 233 |
+
elif operation == VectorOperation.MAGNITUDE:
|
| 234 |
+
result = np.array([np.linalg.norm(vector_a)])
|
| 235 |
+
flops = vector_a.size * 2 # Squares and sum
|
| 236 |
+
|
| 237 |
+
else:
|
| 238 |
+
raise ValueError(f"Unsupported vector operation: {operation}")
|
| 239 |
+
|
| 240 |
+
# Store result in VRAM
|
| 241 |
+
if result_id is None:
|
| 242 |
+
result_id = f"vector_result_{self.matrix_counter}"
|
| 243 |
+
self.matrix_counter += 1
|
| 244 |
+
|
| 245 |
+
result_vector_id = self.load_matrix(result, result_id)
|
| 246 |
+
|
| 247 |
+
# Update statistics
|
| 248 |
+
compute_time = time.time() - start_time
|
| 249 |
+
self.total_compute_time += compute_time
|
| 250 |
+
self.operations_performed += 1
|
| 251 |
+
self.flops_performed += flops
|
| 252 |
+
|
| 253 |
+
print(f"Vector operation {operation.value} completed in {compute_time:.4f}s")
|
| 254 |
+
|
| 255 |
+
return result_vector_id
|
| 256 |
+
|
| 257 |
+
except Exception as e:
|
| 258 |
+
print(f"Error in vector operation {operation.value}: {e}")
|
| 259 |
+
return None
|
| 260 |
+
|
| 261 |
+
def convolution_2d(self, input_id: str, kernel_id: str,
|
| 262 |
+
stride: int = 1, padding: int = 0,
|
| 263 |
+
result_id: Optional[str] = None) -> Optional[str]:
|
| 264 |
+
"""Perform 2D convolution operation."""
|
| 265 |
+
start_time = time.time()
|
| 266 |
+
|
| 267 |
+
# Retrieve input and kernel from VRAM
|
| 268 |
+
input_data = self.get_matrix(input_id)
|
| 269 |
+
kernel = self.get_matrix(kernel_id)
|
| 270 |
+
|
| 271 |
+
if input_data is None or kernel is None:
|
| 272 |
+
print(f"Error: Could not retrieve input or kernel")
|
| 273 |
+
return None
|
| 274 |
+
|
| 275 |
+
try:
|
| 276 |
+
# Simple 2D convolution implementation
|
| 277 |
+
# In a real GPU implementation, this would be highly optimized
|
| 278 |
+
# and distributed across many cores
|
| 279 |
+
|
| 280 |
+
if len(input_data.shape) == 2:
|
| 281 |
+
input_h, input_w = input_data.shape
|
| 282 |
+
channels = 1
|
| 283 |
+
else:
|
| 284 |
+
input_h, input_w, channels = input_data.shape
|
| 285 |
+
|
| 286 |
+
kernel_h, kernel_w = kernel.shape[:2]
|
| 287 |
+
|
| 288 |
+
# Calculate output dimensions
|
| 289 |
+
output_h = (input_h + 2 * padding - kernel_h) // stride + 1
|
| 290 |
+
output_w = (input_w + 2 * padding - kernel_w) // stride + 1
|
| 291 |
+
|
| 292 |
+
# Initialize output
|
| 293 |
+
if channels == 1:
|
| 294 |
+
output = np.zeros((output_h, output_w))
|
| 295 |
+
else:
|
| 296 |
+
output = np.zeros((output_h, output_w, channels))
|
| 297 |
+
|
| 298 |
+
# Pad input if necessary
|
| 299 |
+
if padding > 0:
|
| 300 |
+
if channels == 1:
|
| 301 |
+
padded_input = np.pad(input_data, padding, mode='constant')
|
| 302 |
+
else:
|
| 303 |
+
padded_input = np.pad(input_data,
|
| 304 |
+
((padding, padding), (padding, padding), (0, 0)),
|
| 305 |
+
mode='constant')
|
| 306 |
+
else:
|
| 307 |
+
padded_input = input_data
|
| 308 |
+
|
| 309 |
+
# Perform convolution
|
| 310 |
+
flops = 0
|
| 311 |
+
for y in range(0, output_h):
|
| 312 |
+
for x in range(0, output_w):
|
| 313 |
+
y_start = y * stride
|
| 314 |
+
x_start = x * stride
|
| 315 |
+
|
| 316 |
+
if channels == 1:
|
| 317 |
+
patch = padded_input[y_start:y_start+kernel_h, x_start:x_start+kernel_w]
|
| 318 |
+
output[y, x] = np.sum(patch * kernel)
|
| 319 |
+
flops += kernel_h * kernel_w * 2 # Multiply and add
|
| 320 |
+
else:
|
| 321 |
+
for c in range(channels):
|
| 322 |
+
patch = padded_input[y_start:y_start+kernel_h,
|
| 323 |
+
x_start:x_start+kernel_w, c]
|
| 324 |
+
output[y, x, c] = np.sum(patch * kernel)
|
| 325 |
+
flops += kernel_h * kernel_w * 2
|
| 326 |
+
|
| 327 |
+
# Store result in VRAM
|
| 328 |
+
if result_id is None:
|
| 329 |
+
result_id = f"conv_result_{self.matrix_counter}"
|
| 330 |
+
self.matrix_counter += 1
|
| 331 |
+
|
| 332 |
+
result_conv_id = self.load_matrix(output, result_id)
|
| 333 |
+
|
| 334 |
+
# Update statistics
|
| 335 |
+
compute_time = time.time() - start_time
|
| 336 |
+
self.total_compute_time += compute_time
|
| 337 |
+
self.operations_performed += 1
|
| 338 |
+
self.flops_performed += flops
|
| 339 |
+
|
| 340 |
+
print(f"2D Convolution completed: {input_data.shape} * {kernel.shape} "
|
| 341 |
+
f"= {output.shape} in {compute_time:.4f}s")
|
| 342 |
+
print(f"Simulated {flops:,} FLOPs")
|
| 343 |
+
|
| 344 |
+
return result_conv_id
|
| 345 |
+
|
| 346 |
+
except Exception as e:
|
| 347 |
+
print(f"Error in 2D convolution: {e}")
|
| 348 |
+
return None
|
| 349 |
+
|
| 350 |
+
def get_stats(self) -> Dict[str, Any]:
|
| 351 |
+
"""Get AI accelerator statistics."""
|
| 352 |
+
avg_compute_time = self.total_compute_time / max(1, self.operations_performed)
|
| 353 |
+
flops_per_second = self.flops_performed / max(0.001, self.total_compute_time)
|
| 354 |
+
|
| 355 |
+
return {
|
| 356 |
+
"operations_performed": self.operations_performed,
|
| 357 |
+
"total_compute_time": self.total_compute_time,
|
| 358 |
+
"avg_compute_time": avg_compute_time,
|
| 359 |
+
"flops_performed": self.flops_performed,
|
| 360 |
+
"flops_per_second": flops_per_second,
|
| 361 |
+
"matrices_in_memory": len(self.matrix_registry),
|
| 362 |
+
"simulated_cores": self.total_cores,
|
| 363 |
+
"simulated_sms": self.num_sms
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
def reset_stats(self) -> None:
|
| 367 |
+
"""Reset AI accelerator statistics."""
|
| 368 |
+
self.operations_performed = 0
|
| 369 |
+
self.total_compute_time = 0.0
|
| 370 |
+
self.flops_performed = 0
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
if __name__ == "__main__":
|
| 374 |
+
# Test the AI accelerator
|
| 375 |
+
from vram import VRAM
|
| 376 |
+
|
| 377 |
+
# Create VRAM and AI accelerator
|
| 378 |
+
vram = VRAM(memory_size_gb=1)
|
| 379 |
+
ai = AIAccelerator(vram)
|
| 380 |
+
|
| 381 |
+
print("Testing AI Accelerator...")
|
| 382 |
+
|
| 383 |
+
# Test matrix operations
|
| 384 |
+
# Create test matrices
|
| 385 |
+
matrix_a = np.random.rand(100, 50).astype(np.float32)
|
| 386 |
+
matrix_b = np.random.rand(50, 75).astype(np.float32)
|
| 387 |
+
|
| 388 |
+
# Load matrices into VRAM
|
| 389 |
+
a_id = ai.load_matrix(matrix_a, "test_matrix_a")
|
| 390 |
+
b_id = ai.load_matrix(matrix_b, "test_matrix_b")
|
| 391 |
+
|
| 392 |
+
# Perform matrix multiplication
|
| 393 |
+
result_id = ai.matrix_multiply(a_id, b_id, "multiplication_result")
|
| 394 |
+
|
| 395 |
+
if result_id:
|
| 396 |
+
result = ai.get_matrix(result_id)
|
| 397 |
+
print(f"Matrix multiplication result shape: {result.shape}")
|
| 398 |
+
|
| 399 |
+
# Verify result
|
| 400 |
+
expected = np.dot(matrix_a, matrix_b)
|
| 401 |
+
if np.allclose(result, expected):
|
| 402 |
+
print("Matrix multiplication result is correct!")
|
| 403 |
+
else:
|
| 404 |
+
print("Matrix multiplication result is incorrect!")
|
| 405 |
+
|
| 406 |
+
# Test vector operations
|
| 407 |
+
vector_a = np.random.rand(1000).astype(np.float32)
|
| 408 |
+
vector_b = np.random.rand(1000).astype(np.float32)
|
| 409 |
+
|
| 410 |
+
va_id = ai.load_matrix(vector_a, "vector_a")
|
| 411 |
+
vb_id = ai.load_matrix(vector_b, "vector_b")
|
| 412 |
+
|
| 413 |
+
# Test vector addition
|
| 414 |
+
add_result_id = ai.vector_operation(VectorOperation.ADD, va_id, vb_id)
|
| 415 |
+
if add_result_id:
|
| 416 |
+
add_result = ai.get_matrix(add_result_id)
|
| 417 |
+
expected_add = vector_a + vector_b
|
| 418 |
+
if np.allclose(add_result, expected_add):
|
| 419 |
+
print("Vector addition result is correct!")
|
| 420 |
+
|
| 421 |
+
# Test dot product
|
| 422 |
+
dot_result_id = ai.vector_operation(VectorOperation.DOT_PRODUCT, va_id, vb_id)
|
| 423 |
+
if dot_result_id:
|
| 424 |
+
dot_result = ai.get_matrix(dot_result_id)
|
| 425 |
+
expected_dot = np.dot(vector_a, vector_b)
|
| 426 |
+
if np.allclose(dot_result[0], expected_dot):
|
| 427 |
+
print("Dot product result is correct!")
|
| 428 |
+
|
| 429 |
+
# Test 2D convolution
|
| 430 |
+
input_image = np.random.rand(32, 32).astype(np.float32)
|
| 431 |
+
kernel = np.array([[1, 0, -1], [2, 0, -2], [1, 0, -1]], dtype=np.float32) # Sobel edge detector
|
| 432 |
+
|
| 433 |
+
img_id = ai.load_matrix(input_image, "test_image")
|
| 434 |
+
kernel_id = ai.load_matrix(kernel, "sobel_kernel")
|
| 435 |
+
|
| 436 |
+
conv_result_id = ai.convolution_2d(img_id, kernel_id)
|
| 437 |
+
if conv_result_id:
|
| 438 |
+
conv_result = ai.get_matrix(conv_result_id)
|
| 439 |
+
print(f"Convolution result shape: {conv_result.shape}")
|
| 440 |
+
|
| 441 |
+
# Print final statistics
|
| 442 |
+
stats = ai.get_stats()
|
| 443 |
+
print(f"AI Accelerator stats: {stats}")
|
| 444 |
+
|
| 445 |
+
print("AI Accelerator test completed!")
|
| 446 |
+
|
virtual_gpu_setup/virtual_gpu/bus.py
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Bus Module - Data Transfer Logic
|
| 3 |
+
|
| 4 |
+
This module simulates memory movement and data transfer logic between
|
| 5 |
+
different logical components (SSD, RAM, VRAM) with bandwidth simulation.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import asyncio
|
| 9 |
+
import time
|
| 10 |
+
import numpy as np
|
| 11 |
+
from typing import Dict, Any, Optional, Tuple, List
|
| 12 |
+
from enum import Enum
|
| 13 |
+
from dataclasses import dataclass
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class BusType(Enum):
|
| 17 |
+
"""Types of data buses in the system."""
|
| 18 |
+
SYSTEM_RAM = "system_ram"
|
| 19 |
+
VRAM_BUS = "vram_bus"
|
| 20 |
+
STORAGE_BUS = "storage_bus"
|
| 21 |
+
PCIE = "pcie"
|
| 22 |
+
MEMORY_CONTROLLER = "memory_controller"
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@dataclass
|
| 26 |
+
class BusSpecification:
|
| 27 |
+
"""Specifications for a data bus."""
|
| 28 |
+
name: str
|
| 29 |
+
bandwidth_gbps: float # Gigabytes per second
|
| 30 |
+
latency_ms: float # Milliseconds
|
| 31 |
+
max_concurrent_transfers: int
|
| 32 |
+
bus_width_bits: int
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
@dataclass
|
| 36 |
+
class TransferRequest:
|
| 37 |
+
"""Represents a data transfer request."""
|
| 38 |
+
transfer_id: str
|
| 39 |
+
transfer_type: TransferType
|
| 40 |
+
source_address: int
|
| 41 |
+
destination_address: int
|
| 42 |
+
size_bytes: int
|
| 43 |
+
priority: int = 0
|
| 44 |
+
created_time: float = 0.0
|
| 45 |
+
start_time: float = 0.0
|
| 46 |
+
end_time: float = 0.0
|
| 47 |
+
status: str = "pending" # pending, in_progress, completed, failed
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class DataBus:
|
| 51 |
+
"""Represents a single data bus with bandwidth and latency simulation."""
|
| 52 |
+
|
| 53 |
+
def __init__(self, spec: BusSpecification):
|
| 54 |
+
self.spec = spec
|
| 55 |
+
self.active_transfers: List[TransferRequest] = []
|
| 56 |
+
self.completed_transfers: List[TransferRequest] = []
|
| 57 |
+
self.transfer_queue = asyncio.Queue()
|
| 58 |
+
|
| 59 |
+
# Statistics
|
| 60 |
+
self.total_bytes_transferred = 0
|
| 61 |
+
self.total_transfer_time = 0.0
|
| 62 |
+
self.transfer_count = 0
|
| 63 |
+
|
| 64 |
+
async def submit_transfer(self, request: TransferRequest) -> str:
|
| 65 |
+
"""Submit a transfer request to the bus."""
|
| 66 |
+
request.created_time = time.time()
|
| 67 |
+
await self.transfer_queue.put(request)
|
| 68 |
+
return request.transfer_id
|
| 69 |
+
|
| 70 |
+
async def process_transfers(self):
|
| 71 |
+
"""Process transfer requests with bandwidth and latency simulation."""
|
| 72 |
+
while True:
|
| 73 |
+
try:
|
| 74 |
+
# Wait for a transfer request
|
| 75 |
+
request = await self.transfer_queue.get()
|
| 76 |
+
|
| 77 |
+
# Check if we can start this transfer (concurrent limit)
|
| 78 |
+
if len(self.active_transfers) >= self.spec.max_concurrent_transfers:
|
| 79 |
+
# Put it back and wait
|
| 80 |
+
await self.transfer_queue.put(request)
|
| 81 |
+
await asyncio.sleep(0.001) # Small delay
|
| 82 |
+
continue
|
| 83 |
+
|
| 84 |
+
# Start the transfer
|
| 85 |
+
await self._execute_transfer(request)
|
| 86 |
+
|
| 87 |
+
except asyncio.CancelledError:
|
| 88 |
+
break
|
| 89 |
+
except Exception as e:
|
| 90 |
+
print(f"Error processing transfer on bus {self.spec.name}: {e}")
|
| 91 |
+
|
| 92 |
+
async def _execute_transfer(self, request: TransferRequest):
|
| 93 |
+
"""Execute a single transfer with realistic timing."""
|
| 94 |
+
request.status = "in_progress"
|
| 95 |
+
request.start_time = time.time()
|
| 96 |
+
self.active_transfers.append(request)
|
| 97 |
+
|
| 98 |
+
try:
|
| 99 |
+
# Calculate transfer time based on bandwidth
|
| 100 |
+
transfer_time_seconds = request.size_bytes / (self.spec.bandwidth_gbps * 1e9)
|
| 101 |
+
|
| 102 |
+
# Add latency
|
| 103 |
+
total_time = transfer_time_seconds + (self.spec.latency_ms / 1000.0)
|
| 104 |
+
|
| 105 |
+
# Simulate the transfer delay
|
| 106 |
+
await asyncio.sleep(total_time)
|
| 107 |
+
|
| 108 |
+
# Complete the transfer
|
| 109 |
+
request.status = "completed"
|
| 110 |
+
request.end_time = time.time()
|
| 111 |
+
|
| 112 |
+
# Update statistics
|
| 113 |
+
self.total_bytes_transferred += request.size_bytes
|
| 114 |
+
self.total_transfer_time += total_time
|
| 115 |
+
self.transfer_count += 1
|
| 116 |
+
|
| 117 |
+
print(f"Transfer {request.transfer_id} completed: "
|
| 118 |
+
f"{request.size_bytes:,} bytes in {total_time:.4f}s "
|
| 119 |
+
f"({request.size_bytes / (1024**2) / total_time:.2f} MB/s)")
|
| 120 |
+
|
| 121 |
+
except Exception as e:
|
| 122 |
+
request.status = "failed"
|
| 123 |
+
print(f"Transfer {request.transfer_id} failed: {e}")
|
| 124 |
+
|
| 125 |
+
finally:
|
| 126 |
+
# Remove from active transfers
|
| 127 |
+
if request in self.active_transfers:
|
| 128 |
+
self.active_transfers.remove(request)
|
| 129 |
+
self.completed_transfers.append(request)
|
| 130 |
+
|
| 131 |
+
def get_utilization(self) -> float:
|
| 132 |
+
"""Get current bus utilization (0.0 to 1.0)."""
|
| 133 |
+
return len(self.active_transfers) / max(1, self.spec.max_concurrent_transfers)
|
| 134 |
+
|
| 135 |
+
def get_stats(self) -> Dict[str, Any]:
|
| 136 |
+
"""Get bus statistics."""
|
| 137 |
+
avg_transfer_time = self.total_transfer_time / max(1, self.transfer_count)
|
| 138 |
+
effective_bandwidth = (self.total_bytes_transferred / (1024**3)) / max(0.001, self.total_transfer_time)
|
| 139 |
+
|
| 140 |
+
return {
|
| 141 |
+
"bus_name": self.spec.name,
|
| 142 |
+
"bandwidth_gbps": self.spec.bandwidth_gbps,
|
| 143 |
+
"latency_ms": self.spec.latency_ms,
|
| 144 |
+
"total_transfers": self.transfer_count,
|
| 145 |
+
"total_bytes_transferred": self.total_bytes_transferred,
|
| 146 |
+
"total_transfer_time": self.total_transfer_time,
|
| 147 |
+
"avg_transfer_time": avg_transfer_time,
|
| 148 |
+
"effective_bandwidth_gbps": effective_bandwidth,
|
| 149 |
+
"current_utilization": self.get_utilization(),
|
| 150 |
+
"active_transfers": len(self.active_transfers),
|
| 151 |
+
"queued_transfers": self.transfer_queue.qsize()
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
class BusManager:
|
| 156 |
+
"""Manages multiple data buses and coordinates transfers between components."""
|
| 157 |
+
|
| 158 |
+
def __init__(self):
|
| 159 |
+
self.buses: Dict[str, DataBus] = {}
|
| 160 |
+
self.transfer_counter = 0
|
| 161 |
+
self.running = False
|
| 162 |
+
|
| 163 |
+
# Initialize standard buses
|
| 164 |
+
self._initialize_standard_buses()
|
| 165 |
+
|
| 166 |
+
def _initialize_standard_buses(self):
|
| 167 |
+
"""Initialize standard system buses with realistic specifications."""
|
| 168 |
+
|
| 169 |
+
# GDDR7 VRAM Bus (500GB capacity, high bandwidth)
|
| 170 |
+
gddr7_spec = BusSpecification(
|
| 171 |
+
name="GDDR7_VRAM",
|
| 172 |
+
bandwidth_gbps=128.0, # 128 GB/s (realistic for GDDR7)
|
| 173 |
+
latency_ms=0.1, # Very low latency
|
| 174 |
+
max_concurrent_transfers=16,
|
| 175 |
+
bus_width_bits=512
|
| 176 |
+
)
|
| 177 |
+
self.add_bus("vram", gddr7_spec)
|
| 178 |
+
|
| 179 |
+
# PCIe 5.0 Bus (for GPU-CPU communication)
|
| 180 |
+
pcie_spec = BusSpecification(
|
| 181 |
+
name="PCIe_5.0_x16",
|
| 182 |
+
bandwidth_gbps=64.0, # 64 GB/s for PCIe 5.0 x16
|
| 183 |
+
latency_ms=0.5, # Higher latency than VRAM
|
| 184 |
+
max_concurrent_transfers=8,
|
| 185 |
+
bus_width_bits=256
|
| 186 |
+
)
|
| 187 |
+
self.add_bus("pcie", pcie_spec)
|
| 188 |
+
|
| 189 |
+
# System RAM Bus (DDR5)
|
| 190 |
+
ddr5_spec = BusSpecification(
|
| 191 |
+
name="DDR5_System_RAM",
|
| 192 |
+
bandwidth_gbps=51.2, # 51.2 GB/s for DDR5-6400
|
| 193 |
+
latency_ms=0.2,
|
| 194 |
+
max_concurrent_transfers=4,
|
| 195 |
+
bus_width_bits=128
|
| 196 |
+
)
|
| 197 |
+
self.add_bus("system_ram", ddr5_spec)
|
| 198 |
+
|
| 199 |
+
# NVMe SSD Bus
|
| 200 |
+
nvme_spec = BusSpecification(
|
| 201 |
+
name="NVMe_SSD",
|
| 202 |
+
bandwidth_gbps=7.0, # 7 GB/s for high-end NVMe
|
| 203 |
+
latency_ms=0.1,
|
| 204 |
+
max_concurrent_transfers=32,
|
| 205 |
+
bus_width_bits=64
|
| 206 |
+
)
|
| 207 |
+
self.add_bus("storage", nvme_spec)
|
| 208 |
+
|
| 209 |
+
def add_bus(self, bus_id: str, spec: BusSpecification):
|
| 210 |
+
"""Add a new bus to the system."""
|
| 211 |
+
self.buses[bus_id] = DataBus(spec)
|
| 212 |
+
|
| 213 |
+
async def start(self):
|
| 214 |
+
"""Start all bus processing tasks."""
|
| 215 |
+
if self.running:
|
| 216 |
+
return
|
| 217 |
+
|
| 218 |
+
self.running = True
|
| 219 |
+
|
| 220 |
+
# Start processing tasks for all buses
|
| 221 |
+
self.bus_tasks = []
|
| 222 |
+
for bus in self.buses.values():
|
| 223 |
+
task = asyncio.create_task(bus.process_transfers())
|
| 224 |
+
self.bus_tasks.append(task)
|
| 225 |
+
|
| 226 |
+
print(f"Bus manager started with {len(self.buses)} buses")
|
| 227 |
+
|
| 228 |
+
async def stop(self):
|
| 229 |
+
"""Stop all bus processing tasks."""
|
| 230 |
+
if not self.running:
|
| 231 |
+
return
|
| 232 |
+
|
| 233 |
+
self.running = False
|
| 234 |
+
|
| 235 |
+
# Cancel all bus tasks
|
| 236 |
+
for task in self.bus_tasks:
|
| 237 |
+
task.cancel()
|
| 238 |
+
|
| 239 |
+
await asyncio.gather(*self.bus_tasks, return_exceptions=True)
|
| 240 |
+
print("Bus manager stopped")
|
| 241 |
+
|
| 242 |
+
async def transfer_data(self, bus_id: str, transfer_type: TransferType,
|
| 243 |
+
source_address: int, destination_address: int,
|
| 244 |
+
size_bytes: int, priority: int = 0) -> str:
|
| 245 |
+
"""Initiate a data transfer on the specified bus."""
|
| 246 |
+
if bus_id not in self.buses:
|
| 247 |
+
raise ValueError(f"Bus {bus_id} not found")
|
| 248 |
+
|
| 249 |
+
transfer_id = f"transfer_{self.transfer_counter}"
|
| 250 |
+
self.transfer_counter += 1
|
| 251 |
+
|
| 252 |
+
request = TransferRequest(
|
| 253 |
+
transfer_id=transfer_id,
|
| 254 |
+
transfer_type=transfer_type,
|
| 255 |
+
source_address=source_address,
|
| 256 |
+
destination_address=destination_address,
|
| 257 |
+
size_bytes=size_bytes,
|
| 258 |
+
priority=priority
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
bus = self.buses[bus_id]
|
| 262 |
+
await bus.submit_transfer(request)
|
| 263 |
+
|
| 264 |
+
return transfer_id
|
| 265 |
+
|
| 266 |
+
async def copy_to_vram(self, source_address: int, vram_address: int,
|
| 267 |
+
size_bytes: int) -> str:
|
| 268 |
+
"""Copy data from system memory to VRAM."""
|
| 269 |
+
return await self.transfer_data(
|
| 270 |
+
"vram", TransferType.WRITE, source_address, vram_address, size_bytes
|
| 271 |
+
)
|
| 272 |
+
|
| 273 |
+
async def copy_from_vram(self, vram_address: int, destination_address: int,
|
| 274 |
+
size_bytes: int) -> str:
|
| 275 |
+
"""Copy data from VRAM to system memory."""
|
| 276 |
+
return await self.transfer_data(
|
| 277 |
+
"vram", TransferType.READ, vram_address, destination_address, size_bytes
|
| 278 |
+
)
|
| 279 |
+
|
| 280 |
+
async def load_from_storage(self, storage_address: int, ram_address: int,
|
| 281 |
+
size_bytes: int) -> str:
|
| 282 |
+
"""Load data from storage to system RAM."""
|
| 283 |
+
return await self.transfer_data(
|
| 284 |
+
"storage", TransferType.READ, storage_address, ram_address, size_bytes
|
| 285 |
+
)
|
| 286 |
+
|
| 287 |
+
async def save_to_storage(self, ram_address: int, storage_address: int,
|
| 288 |
+
size_bytes: int) -> str:
|
| 289 |
+
"""Save data from system RAM to storage."""
|
| 290 |
+
return await self.transfer_data(
|
| 291 |
+
"storage", TransferType.WRITE, ram_address, storage_address, size_bytes
|
| 292 |
+
)
|
| 293 |
+
|
| 294 |
+
def get_bus_stats(self, bus_id: str) -> Optional[Dict[str, Any]]:
|
| 295 |
+
"""Get statistics for a specific bus."""
|
| 296 |
+
if bus_id in self.buses:
|
| 297 |
+
return self.buses[bus_id].get_stats()
|
| 298 |
+
return None
|
| 299 |
+
|
| 300 |
+
def get_all_stats(self) -> Dict[str, Any]:
|
| 301 |
+
"""Get statistics for all buses."""
|
| 302 |
+
stats = {
|
| 303 |
+
"total_buses": len(self.buses),
|
| 304 |
+
"running": self.running,
|
| 305 |
+
"buses": {}
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
total_bandwidth = 0
|
| 309 |
+
total_utilization = 0
|
| 310 |
+
|
| 311 |
+
for bus_id, bus in self.buses.items():
|
| 312 |
+
bus_stats = bus.get_stats()
|
| 313 |
+
stats["buses"][bus_id] = bus_stats
|
| 314 |
+
total_bandwidth += bus_stats["bandwidth_gbps"]
|
| 315 |
+
total_utilization += bus_stats["current_utilization"]
|
| 316 |
+
|
| 317 |
+
stats["total_bandwidth_gbps"] = total_bandwidth
|
| 318 |
+
stats["avg_utilization"] = total_utilization / len(self.buses) if self.buses else 0
|
| 319 |
+
|
| 320 |
+
return stats
|
| 321 |
+
|
| 322 |
+
async def benchmark_bus(self, bus_id: str, test_size_mb: int = 100) -> Dict[str, Any]:
|
| 323 |
+
"""Benchmark a specific bus with test transfers."""
|
| 324 |
+
if bus_id not in self.buses:
|
| 325 |
+
raise ValueError(f"Bus {bus_id} not found")
|
| 326 |
+
|
| 327 |
+
print(f"Benchmarking bus {bus_id} with {test_size_mb} MB transfers...")
|
| 328 |
+
|
| 329 |
+
test_size_bytes = test_size_mb * 1024 * 1024
|
| 330 |
+
num_tests = 10
|
| 331 |
+
|
| 332 |
+
start_time = time.time()
|
| 333 |
+
transfer_ids = []
|
| 334 |
+
|
| 335 |
+
# Submit multiple test transfers
|
| 336 |
+
for i in range(num_tests):
|
| 337 |
+
transfer_id = await self.transfer_data(
|
| 338 |
+
bus_id, TransferType.COPY,
|
| 339 |
+
i * test_size_bytes, (i + 1000) * test_size_bytes,
|
| 340 |
+
test_size_bytes
|
| 341 |
+
)
|
| 342 |
+
transfer_ids.append(transfer_id)
|
| 343 |
+
|
| 344 |
+
# Wait for all transfers to complete
|
| 345 |
+
bus = self.buses[bus_id]
|
| 346 |
+
while len(bus.active_transfers) > 0 or bus.transfer_queue.qsize() > 0:
|
| 347 |
+
await asyncio.sleep(0.1)
|
| 348 |
+
|
| 349 |
+
end_time = time.time()
|
| 350 |
+
total_time = end_time - start_time
|
| 351 |
+
total_data_gb = (test_size_bytes * num_tests) / (1024**3)
|
| 352 |
+
effective_bandwidth = total_data_gb / total_time
|
| 353 |
+
|
| 354 |
+
return {
|
| 355 |
+
"bus_id": bus_id,
|
| 356 |
+
"test_size_mb": test_size_mb,
|
| 357 |
+
"num_transfers": num_tests,
|
| 358 |
+
"total_time_seconds": total_time,
|
| 359 |
+
"total_data_gb": total_data_gb,
|
| 360 |
+
"effective_bandwidth_gbps": effective_bandwidth,
|
| 361 |
+
"theoretical_bandwidth_gbps": bus.spec.bandwidth_gbps,
|
| 362 |
+
"efficiency_percent": (effective_bandwidth / bus.spec.bandwidth_gbps) * 100
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
if __name__ == "__main__":
|
| 367 |
+
# Test the bus system
|
| 368 |
+
async def test_bus_system():
|
| 369 |
+
print("Testing Bus System...")
|
| 370 |
+
|
| 371 |
+
# Create bus manager
|
| 372 |
+
bus_manager = BusManager()
|
| 373 |
+
await bus_manager.start()
|
| 374 |
+
|
| 375 |
+
# Test individual transfers
|
| 376 |
+
print("\nTesting individual transfers...")
|
| 377 |
+
|
| 378 |
+
# Test VRAM transfer (large texture upload)
|
| 379 |
+
texture_size = 64 * 1024 * 1024 # 64 MB texture
|
| 380 |
+
vram_transfer = await bus_manager.copy_to_vram(0x1000, 0x10000000, texture_size)
|
| 381 |
+
print(f"Submitted VRAM transfer: {vram_transfer}")
|
| 382 |
+
|
| 383 |
+
# Test storage transfer (loading assets)
|
| 384 |
+
asset_size = 128 * 1024 * 1024 # 128 MB asset
|
| 385 |
+
storage_transfer = await bus_manager.load_from_storage(0x0, 0x2000, asset_size)
|
| 386 |
+
print(f"Submitted storage transfer: {storage_transfer}")
|
| 387 |
+
|
| 388 |
+
# Test PCIe transfer (CPU-GPU communication)
|
| 389 |
+
command_size = 4 * 1024 # 4 KB command buffer
|
| 390 |
+
pcie_transfer = await bus_manager.transfer_data(
|
| 391 |
+
"pcie", TransferType.WRITE, 0x3000, 0x20000000, command_size
|
| 392 |
+
)
|
| 393 |
+
print(f"Submitted PCIe transfer: {pcie_transfer}")
|
| 394 |
+
|
| 395 |
+
# Wait for transfers to complete
|
| 396 |
+
print("\nWaiting for transfers to complete...")
|
| 397 |
+
await asyncio.sleep(2.0)
|
| 398 |
+
|
| 399 |
+
# Print statistics
|
| 400 |
+
print("\nBus Statistics:")
|
| 401 |
+
all_stats = bus_manager.get_all_stats()
|
| 402 |
+
for bus_id, bus_stats in all_stats["buses"].items():
|
| 403 |
+
print(f"\n{bus_id}:")
|
| 404 |
+
print(f" Bandwidth: {bus_stats["bandwidth_gbps"]:.1f} GB/s")
|
| 405 |
+
print(f" Transfers: {bus_stats["total_transfers"]}")
|
| 406 |
+
print(f" Data transferred: {bus_stats["total_bytes_transferred"] / (1024**2):.1f} MB")
|
| 407 |
+
print(f" Effective bandwidth: {bus_stats["effective_bandwidth_gbps"]:.2f} GB/s")
|
| 408 |
+
print(f" Utilization: {bus_stats["current_utilization"]:.1%}")
|
| 409 |
+
|
| 410 |
+
# Benchmark each bus
|
| 411 |
+
print("\nBenchmarking buses...")
|
| 412 |
+
for bus_id in ["vram", "pcie", "system_ram", "storage"]:
|
| 413 |
+
try:
|
| 414 |
+
benchmark_result = await bus_manager.benchmark_bus(bus_id, test_size_mb=50)
|
| 415 |
+
print(f"\n{bus_id} benchmark:")
|
| 416 |
+
print(f" Effective bandwidth: {benchmark_result["effective_bandwidth_gbps"]:.2f} GB/s")
|
| 417 |
+
print(f" Theoretical bandwidth: {benchmark_result["theoretical_bandwidth_gbps"]:.2f} GB/s")
|
| 418 |
+
print(f" Efficiency: {benchmark_result["efficiency_percent"]:.1f}%")
|
| 419 |
+
except Exception as e:
|
| 420 |
+
print(f"Benchmark failed for {bus_id}: {e}")
|
| 421 |
+
|
| 422 |
+
# Stop bus manager
|
| 423 |
+
await bus_manager.stop()
|
| 424 |
+
print("\nBus system test completed!")
|
| 425 |
+
|
| 426 |
+
# Run the test
|
| 427 |
+
asyncio.run(test_bus_system())
|
| 428 |
+
|
virtual_gpu_setup/virtual_gpu/display.py
ADDED
|
@@ -0,0 +1,501 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Display Module - Output System
|
| 3 |
+
|
| 4 |
+
This module handles the final output of rendered frames, supporting multiple
|
| 5 |
+
output methods including WebSocket to browser, GUI windows, and image files.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import asyncio
|
| 9 |
+
import json
|
| 10 |
+
import base64
|
| 11 |
+
import time
|
| 12 |
+
import numpy as np
|
| 13 |
+
from typing import Optional, Dict, Any, Callable
|
| 14 |
+
from io import BytesIO
|
| 15 |
+
import threading
|
| 16 |
+
|
| 17 |
+
try:
|
| 18 |
+
import websockets
|
| 19 |
+
WEBSOCKETS_AVAILABLE = True
|
| 20 |
+
except ImportError:
|
| 21 |
+
WEBSOCKETS_AVAILABLE = False
|
| 22 |
+
print("Warning: websockets not available. WebSocket display will not work.")
|
| 23 |
+
|
| 24 |
+
try:
|
| 25 |
+
import tkinter as tk
|
| 26 |
+
from tkinter import Canvas
|
| 27 |
+
from PIL import Image, ImageTk
|
| 28 |
+
TKINTER_AVAILABLE = True
|
| 29 |
+
except ImportError:
|
| 30 |
+
TKINTER_AVAILABLE = False
|
| 31 |
+
print("Warning: tkinter or PIL not available. GUI display will not work.")
|
| 32 |
+
|
| 33 |
+
try:
|
| 34 |
+
from PIL import Image
|
| 35 |
+
PIL_AVAILABLE = True
|
| 36 |
+
except ImportError:
|
| 37 |
+
PIL_AVAILABLE = False
|
| 38 |
+
print("Warning: PIL not available. Image saving will not work.")
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class DisplayMode:
|
| 42 |
+
"""Enumeration of display modes."""
|
| 43 |
+
WEBSOCKET = "websocket"
|
| 44 |
+
GUI = "gui"
|
| 45 |
+
FILE = "file"
|
| 46 |
+
CONSOLE = "console"
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class WebSocketDisplay:
|
| 50 |
+
"""WebSocket-based display that sends frames to a web browser."""
|
| 51 |
+
|
| 52 |
+
def __init__(self, host: str = "localhost", port: int = 8765):
|
| 53 |
+
self.host = host
|
| 54 |
+
self.port = port
|
| 55 |
+
self.server = None
|
| 56 |
+
self.clients = set()
|
| 57 |
+
self.is_running = False
|
| 58 |
+
|
| 59 |
+
async def start_server(self):
|
| 60 |
+
"""Start the WebSocket server."""
|
| 61 |
+
if not WEBSOCKETS_AVAILABLE:
|
| 62 |
+
raise RuntimeError("WebSocket support not available")
|
| 63 |
+
|
| 64 |
+
async def handle_client(websocket, path):
|
| 65 |
+
self.clients.add(websocket)
|
| 66 |
+
print(f"Client connected: {websocket.remote_address}")
|
| 67 |
+
try:
|
| 68 |
+
await websocket.wait_closed()
|
| 69 |
+
finally:
|
| 70 |
+
self.clients.remove(websocket)
|
| 71 |
+
print(f"Client disconnected: {websocket.remote_address}")
|
| 72 |
+
|
| 73 |
+
self.server = await websockets.serve(handle_client, self.host, self.port)
|
| 74 |
+
self.is_running = True
|
| 75 |
+
print(f"WebSocket server started on ws://{self.host}:{self.port}")
|
| 76 |
+
|
| 77 |
+
async def stop_server(self):
|
| 78 |
+
"""Stop the WebSocket server."""
|
| 79 |
+
if self.server:
|
| 80 |
+
self.server.close()
|
| 81 |
+
await self.server.wait_closed()
|
| 82 |
+
self.is_running = False
|
| 83 |
+
print("WebSocket server stopped")
|
| 84 |
+
|
| 85 |
+
async def send_frame(self, frame_data: np.ndarray, frame_id: int = 0):
|
| 86 |
+
"""Send a frame to all connected clients."""
|
| 87 |
+
if not self.clients or not PIL_AVAILABLE:
|
| 88 |
+
return
|
| 89 |
+
|
| 90 |
+
try:
|
| 91 |
+
# Convert numpy array to PIL Image
|
| 92 |
+
if len(frame_data.shape) == 3:
|
| 93 |
+
height, width, channels = frame_data.shape
|
| 94 |
+
if channels == 3:
|
| 95 |
+
image = Image.fromarray(frame_data.astype(np.uint8), 'RGB')
|
| 96 |
+
elif channels == 4:
|
| 97 |
+
image = Image.fromarray(frame_data.astype(np.uint8), 'RGBA')
|
| 98 |
+
else:
|
| 99 |
+
# Convert single channel to RGB
|
| 100 |
+
rgb_data = np.stack([frame_data[:,:,0]] * 3, axis=-1)
|
| 101 |
+
image = Image.fromarray(rgb_data.astype(np.uint8), 'RGB')
|
| 102 |
+
else:
|
| 103 |
+
# Grayscale
|
| 104 |
+
image = Image.fromarray(frame_data.astype(np.uint8), 'L')
|
| 105 |
+
|
| 106 |
+
# Convert to base64
|
| 107 |
+
buffer = BytesIO()
|
| 108 |
+
image.save(buffer, format='PNG')
|
| 109 |
+
img_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
|
| 110 |
+
|
| 111 |
+
# Create message
|
| 112 |
+
message = {
|
| 113 |
+
"type": "frame",
|
| 114 |
+
"frame_id": frame_id,
|
| 115 |
+
"width": image.width,
|
| 116 |
+
"height": image.height,
|
| 117 |
+
"data": f"data:image/png;base64,{img_base64}",
|
| 118 |
+
"timestamp": time.time()
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
# Send to all clients
|
| 122 |
+
if self.clients:
|
| 123 |
+
await asyncio.gather(
|
| 124 |
+
*[client.send(json.dumps(message)) for client in self.clients],
|
| 125 |
+
return_exceptions=True
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
except Exception as e:
|
| 129 |
+
print(f"Error sending frame via WebSocket: {e}")
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
class GUIDisplay:
|
| 133 |
+
"""Tkinter-based GUI display window."""
|
| 134 |
+
|
| 135 |
+
def __init__(self, title: str = "vGPU Display", width: int = 800, height: int = 600):
|
| 136 |
+
if not TKINTER_AVAILABLE:
|
| 137 |
+
raise RuntimeError("GUI display not available (tkinter/PIL missing)")
|
| 138 |
+
|
| 139 |
+
self.title = title
|
| 140 |
+
self.width = width
|
| 141 |
+
self.height = height
|
| 142 |
+
self.window = None
|
| 143 |
+
self.canvas = None
|
| 144 |
+
self.is_running = False
|
| 145 |
+
self.update_callback = None
|
| 146 |
+
|
| 147 |
+
def start(self):
|
| 148 |
+
"""Start the GUI display in a separate thread."""
|
| 149 |
+
if self.is_running:
|
| 150 |
+
return
|
| 151 |
+
|
| 152 |
+
def run_gui():
|
| 153 |
+
self.window = tk.Tk()
|
| 154 |
+
self.window.title(self.title)
|
| 155 |
+
self.window.geometry(f"{self.width}x{self.height}")
|
| 156 |
+
|
| 157 |
+
self.canvas = Canvas(self.window, width=self.width, height=self.height, bg='black')
|
| 158 |
+
self.canvas.pack()
|
| 159 |
+
|
| 160 |
+
self.is_running = True
|
| 161 |
+
|
| 162 |
+
# Set up periodic update
|
| 163 |
+
def update():
|
| 164 |
+
if self.update_callback:
|
| 165 |
+
self.update_callback()
|
| 166 |
+
if self.is_running:
|
| 167 |
+
self.window.after(16, update) # ~60 FPS
|
| 168 |
+
|
| 169 |
+
update()
|
| 170 |
+
|
| 171 |
+
self.window.protocol("WM_DELETE_WINDOW", self.stop)
|
| 172 |
+
self.window.mainloop()
|
| 173 |
+
|
| 174 |
+
self.gui_thread = threading.Thread(target=run_gui, daemon=True)
|
| 175 |
+
self.gui_thread.start()
|
| 176 |
+
|
| 177 |
+
def stop(self):
|
| 178 |
+
"""Stop the GUI display."""
|
| 179 |
+
self.is_running = False
|
| 180 |
+
if self.window:
|
| 181 |
+
self.window.quit()
|
| 182 |
+
|
| 183 |
+
def show_frame(self, frame_data: np.ndarray):
|
| 184 |
+
"""Display a frame in the GUI window."""
|
| 185 |
+
if not self.is_running or not self.canvas:
|
| 186 |
+
return
|
| 187 |
+
|
| 188 |
+
try:
|
| 189 |
+
# Convert numpy array to PIL Image
|
| 190 |
+
if len(frame_data.shape) == 3:
|
| 191 |
+
height, width, channels = frame_data.shape
|
| 192 |
+
if channels >= 3:
|
| 193 |
+
image = Image.fromarray(frame_data[:,:,:3].astype(np.uint8), 'RGB')
|
| 194 |
+
else:
|
| 195 |
+
# Convert single channel to RGB
|
| 196 |
+
rgb_data = np.stack([frame_data[:,:,0]] * 3, axis=-1)
|
| 197 |
+
image = Image.fromarray(rgb_data.astype(np.uint8), 'RGB')
|
| 198 |
+
else:
|
| 199 |
+
# Grayscale
|
| 200 |
+
image = Image.fromarray(frame_data.astype(np.uint8), 'L')
|
| 201 |
+
|
| 202 |
+
# Resize to fit canvas
|
| 203 |
+
image = image.resize((self.width, self.height), Image.Resampling.LANCZOS)
|
| 204 |
+
|
| 205 |
+
# Convert to PhotoImage
|
| 206 |
+
photo = ImageTk.PhotoImage(image)
|
| 207 |
+
|
| 208 |
+
# Update canvas
|
| 209 |
+
self.canvas.delete("all")
|
| 210 |
+
self.canvas.create_image(self.width//2, self.height//2, image=photo)
|
| 211 |
+
|
| 212 |
+
# Keep a reference to prevent garbage collection
|
| 213 |
+
self.canvas.image = photo
|
| 214 |
+
|
| 215 |
+
except Exception as e:
|
| 216 |
+
print(f"Error displaying frame in GUI: {e}")
|
| 217 |
+
|
| 218 |
+
def set_update_callback(self, callback: Callable):
|
| 219 |
+
"""Set a callback function to be called periodically."""
|
| 220 |
+
self.update_callback = callback
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
class FileDisplay:
|
| 224 |
+
"""File-based display that saves frames as image files."""
|
| 225 |
+
|
| 226 |
+
def __init__(self, output_dir: str = "./frames", format: str = "png"):
|
| 227 |
+
self.output_dir = output_dir
|
| 228 |
+
self.format = format.lower()
|
| 229 |
+
self.frame_counter = 0
|
| 230 |
+
|
| 231 |
+
# Create output directory
|
| 232 |
+
import os
|
| 233 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 234 |
+
|
| 235 |
+
def save_frame(self, frame_data: np.ndarray, filename: Optional[str] = None):
|
| 236 |
+
"""Save a frame to a file."""
|
| 237 |
+
if not PIL_AVAILABLE:
|
| 238 |
+
print("Error: PIL not available for saving images")
|
| 239 |
+
return False
|
| 240 |
+
|
| 241 |
+
try:
|
| 242 |
+
if filename is None:
|
| 243 |
+
filename = f"frame_{self.frame_counter:06d}.{self.format}"
|
| 244 |
+
self.frame_counter += 1
|
| 245 |
+
|
| 246 |
+
filepath = f"{self.output_dir}/{filename}"
|
| 247 |
+
|
| 248 |
+
# Convert numpy array to PIL Image
|
| 249 |
+
if len(frame_data.shape) == 3:
|
| 250 |
+
height, width, channels = frame_data.shape
|
| 251 |
+
if channels == 3:
|
| 252 |
+
image = Image.fromarray(frame_data.astype(np.uint8), 'RGB')
|
| 253 |
+
elif channels == 4:
|
| 254 |
+
image = Image.fromarray(frame_data.astype(np.uint8), 'RGBA')
|
| 255 |
+
else:
|
| 256 |
+
# Convert single channel to RGB
|
| 257 |
+
rgb_data = np.stack([frame_data[:,:,0]] * 3, axis=-1)
|
| 258 |
+
image = Image.fromarray(rgb_data.astype(np.uint8), 'RGB')
|
| 259 |
+
else:
|
| 260 |
+
# Grayscale
|
| 261 |
+
image = Image.fromarray(frame_data.astype(np.uint8), 'L')
|
| 262 |
+
|
| 263 |
+
# Save image
|
| 264 |
+
image.save(filepath)
|
| 265 |
+
print(f"Frame saved: {filepath}")
|
| 266 |
+
return True
|
| 267 |
+
|
| 268 |
+
except Exception as e:
|
| 269 |
+
print(f"Error saving frame: {e}")
|
| 270 |
+
return False
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
class ConsoleDisplay:
|
| 274 |
+
"""Console-based display that shows ASCII art representation."""
|
| 275 |
+
|
| 276 |
+
def __init__(self, width: int = 80, height: int = 24):
|
| 277 |
+
self.width = width
|
| 278 |
+
self.height = height
|
| 279 |
+
self.ascii_chars = " .:-=+*#%@"
|
| 280 |
+
|
| 281 |
+
def show_frame(self, frame_data: np.ndarray):
|
| 282 |
+
"""Display frame as ASCII art in console."""
|
| 283 |
+
try:
|
| 284 |
+
# Convert to grayscale if needed
|
| 285 |
+
if len(frame_data.shape) == 3:
|
| 286 |
+
# Convert RGB to grayscale
|
| 287 |
+
gray = np.dot(frame_data[...,:3], [0.299, 0.587, 0.114])
|
| 288 |
+
else:
|
| 289 |
+
gray = frame_data
|
| 290 |
+
|
| 291 |
+
# Resize to console dimensions
|
| 292 |
+
from scipy import ndimage
|
| 293 |
+
resized = ndimage.zoom(gray, (self.height / gray.shape[0], self.width / gray.shape[1]))
|
| 294 |
+
|
| 295 |
+
# Convert to ASCII
|
| 296 |
+
ascii_frame = []
|
| 297 |
+
for row in resized:
|
| 298 |
+
ascii_row = ""
|
| 299 |
+
for pixel in row:
|
| 300 |
+
# Map pixel value to ASCII character
|
| 301 |
+
char_index = int((pixel / 255.0) * (len(self.ascii_chars) - 1))
|
| 302 |
+
ascii_row += self.ascii_chars[char_index]
|
| 303 |
+
ascii_frame.append(ascii_row)
|
| 304 |
+
|
| 305 |
+
# Clear screen and display
|
| 306 |
+
print("\033[2J\033[H") # Clear screen and move cursor to top
|
| 307 |
+
for row in ascii_frame:
|
| 308 |
+
print(row)
|
| 309 |
+
|
| 310 |
+
except Exception as e:
|
| 311 |
+
print(f"Error displaying ASCII frame: {e}")
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
class DisplayManager:
|
| 315 |
+
"""Manages multiple display outputs and coordinates frame updates."""
|
| 316 |
+
|
| 317 |
+
def __init__(self, vram=None):
|
| 318 |
+
self.vram = vram
|
| 319 |
+
self.displays = {}
|
| 320 |
+
self.active_framebuffer = None
|
| 321 |
+
self.frame_counter = 0
|
| 322 |
+
self.fps_target = 60
|
| 323 |
+
self.last_frame_time = 0
|
| 324 |
+
|
| 325 |
+
# Statistics
|
| 326 |
+
self.frames_displayed = 0
|
| 327 |
+
self.total_display_time = 0.0
|
| 328 |
+
|
| 329 |
+
def add_display(self, name: str, display_type: str, **kwargs):
|
| 330 |
+
"""Add a display output."""
|
| 331 |
+
if display_type == DisplayMode.WEBSOCKET:
|
| 332 |
+
display = WebSocketDisplay(**kwargs)
|
| 333 |
+
elif display_type == DisplayMode.GUI:
|
| 334 |
+
display = GUIDisplay(**kwargs)
|
| 335 |
+
elif display_type == DisplayMode.FILE:
|
| 336 |
+
display = FileDisplay(**kwargs)
|
| 337 |
+
elif display_type == DisplayMode.CONSOLE:
|
| 338 |
+
display = ConsoleDisplay(**kwargs)
|
| 339 |
+
else:
|
| 340 |
+
raise ValueError(f"Unknown display type: {display_type}")
|
| 341 |
+
|
| 342 |
+
self.displays[name] = {
|
| 343 |
+
"display": display,
|
| 344 |
+
"type": display_type,
|
| 345 |
+
"enabled": True
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
return display
|
| 349 |
+
|
| 350 |
+
def remove_display(self, name: str):
|
| 351 |
+
"""Remove a display output."""
|
| 352 |
+
if name in self.displays:
|
| 353 |
+
display_info = self.displays[name]
|
| 354 |
+
if display_info["type"] == DisplayMode.WEBSOCKET:
|
| 355 |
+
asyncio.create_task(display_info["display"].stop_server())
|
| 356 |
+
elif display_info["type"] == DisplayMode.GUI:
|
| 357 |
+
display_info["display"].stop()
|
| 358 |
+
del self.displays[name]
|
| 359 |
+
|
| 360 |
+
def set_active_framebuffer(self, framebuffer_id: str):
|
| 361 |
+
"""Set the active framebuffer to display."""
|
| 362 |
+
self.active_framebuffer = framebuffer_id
|
| 363 |
+
|
| 364 |
+
async def update_displays(self):
|
| 365 |
+
"""Update all active displays with the current framebuffer."""
|
| 366 |
+
if not self.vram or not self.active_framebuffer:
|
| 367 |
+
return
|
| 368 |
+
|
| 369 |
+
start_time = time.time()
|
| 370 |
+
|
| 371 |
+
# Get framebuffer data
|
| 372 |
+
framebuffer = self.vram.get_framebuffer(self.active_framebuffer)
|
| 373 |
+
if not framebuffer:
|
| 374 |
+
return
|
| 375 |
+
|
| 376 |
+
frame_data = framebuffer.pixel_buffer
|
| 377 |
+
|
| 378 |
+
# Update each display
|
| 379 |
+
for name, display_info in self.displays.items():
|
| 380 |
+
if not display_info["enabled"]:
|
| 381 |
+
continue
|
| 382 |
+
|
| 383 |
+
display = display_info["display"]
|
| 384 |
+
display_type = display_info["type"]
|
| 385 |
+
|
| 386 |
+
try:
|
| 387 |
+
if display_type == DisplayMode.WEBSOCKET:
|
| 388 |
+
await display.send_frame(frame_data, self.frame_counter)
|
| 389 |
+
elif display_type == DisplayMode.GUI:
|
| 390 |
+
display.show_frame(frame_data)
|
| 391 |
+
elif display_type == DisplayMode.FILE:
|
| 392 |
+
display.save_frame(frame_data)
|
| 393 |
+
elif display_type == DisplayMode.CONSOLE:
|
| 394 |
+
display.show_frame(frame_data)
|
| 395 |
+
|
| 396 |
+
except Exception as e:
|
| 397 |
+
print(f"Error updating display {name}: {e}")
|
| 398 |
+
|
| 399 |
+
# Update statistics
|
| 400 |
+
self.frame_counter += 1
|
| 401 |
+
self.frames_displayed += 1
|
| 402 |
+
self.total_display_time += time.time() - start_time
|
| 403 |
+
self.last_frame_time = time.time()
|
| 404 |
+
|
| 405 |
+
def enable_display(self, name: str, enabled: bool = True):
|
| 406 |
+
"""Enable or disable a specific display."""
|
| 407 |
+
if name in self.displays:
|
| 408 |
+
self.displays[name]["enabled"] = enabled
|
| 409 |
+
|
| 410 |
+
def get_stats(self) -> Dict[str, Any]:
|
| 411 |
+
"""Get display manager statistics."""
|
| 412 |
+
avg_display_time = self.total_display_time / max(1, self.frames_displayed)
|
| 413 |
+
current_fps = 1.0 / max(0.001, time.time() - self.last_frame_time) if self.last_frame_time > 0 else 0
|
| 414 |
+
|
| 415 |
+
return {
|
| 416 |
+
"frames_displayed": self.frames_displayed,
|
| 417 |
+
"total_display_time": self.total_display_time,
|
| 418 |
+
"avg_display_time": avg_display_time,
|
| 419 |
+
"current_fps": current_fps,
|
| 420 |
+
"target_fps": self.fps_target,
|
| 421 |
+
"active_displays": len([d for d in self.displays.values() if d["enabled"]]),
|
| 422 |
+
"total_displays": len(self.displays),
|
| 423 |
+
"active_framebuffer": self.active_framebuffer
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
|
| 427 |
+
if __name__ == "__main__":
|
| 428 |
+
# Test the display system
|
| 429 |
+
async def test_display():
|
| 430 |
+
from vram import VRAM
|
| 431 |
+
from render import Renderer
|
| 432 |
+
|
| 433 |
+
print("Testing Display System...")
|
| 434 |
+
|
| 435 |
+
# Create VRAM and renderer
|
| 436 |
+
vram = VRAM(memory_size_gb=1)
|
| 437 |
+
renderer = Renderer(vram)
|
| 438 |
+
|
| 439 |
+
# Create display manager
|
| 440 |
+
display_manager = DisplayManager(vram)
|
| 441 |
+
|
| 442 |
+
# Create a test framebuffer
|
| 443 |
+
fb_id = vram.create_framebuffer(400, 300, 3)
|
| 444 |
+
display_manager.set_active_framebuffer(fb_id)
|
| 445 |
+
|
| 446 |
+
# Add displays
|
| 447 |
+
if WEBSOCKETS_AVAILABLE:
|
| 448 |
+
ws_display = display_manager.add_display("websocket", DisplayMode.WEBSOCKET)
|
| 449 |
+
await ws_display.start_server()
|
| 450 |
+
|
| 451 |
+
if TKINTER_AVAILABLE:
|
| 452 |
+
gui_display = display_manager.add_display("gui", DisplayMode.GUI, width=400, height=300)
|
| 453 |
+
gui_display.start()
|
| 454 |
+
|
| 455 |
+
file_display = display_manager.add_display("file", DisplayMode.FILE, output_dir="./test_frames")
|
| 456 |
+
console_display = display_manager.add_display("console", DisplayMode.CONSOLE, width=40, height=20)
|
| 457 |
+
|
| 458 |
+
# Render some test content
|
| 459 |
+
renderer.clear(fb_id, (64, 128, 255))
|
| 460 |
+
renderer.draw_rect(fb_id, 50, 50, 100, 80, (255, 0, 0))
|
| 461 |
+
renderer.draw_circle(fb_id, 200, 150, 40, (0, 255, 0), filled=True)
|
| 462 |
+
|
| 463 |
+
# Update displays
|
| 464 |
+
await display_manager.update_displays()
|
| 465 |
+
|
| 466 |
+
# Animate for a few seconds
|
| 467 |
+
for i in range(60): # 1 second at 60 FPS
|
| 468 |
+
# Clear and draw animated content
|
| 469 |
+
renderer.clear(fb_id, (32, 64, 128))
|
| 470 |
+
|
| 471 |
+
# Moving rectangle
|
| 472 |
+
x = 50 + int(50 * np.sin(i * 0.1))
|
| 473 |
+
renderer.draw_rect(fb_id, x, 50, 50, 50, (255, 255, 0))
|
| 474 |
+
|
| 475 |
+
# Rotating line effect
|
| 476 |
+
center_x, center_y = 200, 150
|
| 477 |
+
for j in range(8):
|
| 478 |
+
angle = (i + j * 8) * 0.1
|
| 479 |
+
end_x = center_x + int(40 * np.cos(angle))
|
| 480 |
+
end_y = center_y + int(40 * np.sin(angle))
|
| 481 |
+
renderer.draw_line(fb_id, center_x, center_y, end_x, end_y, (0, 255, 255))
|
| 482 |
+
|
| 483 |
+
# Update displays
|
| 484 |
+
await display_manager.update_displays()
|
| 485 |
+
await asyncio.sleep(1/60) # 60 FPS
|
| 486 |
+
|
| 487 |
+
# Print statistics
|
| 488 |
+
stats = display_manager.get_stats()
|
| 489 |
+
print(f"Display Manager stats: {stats}")
|
| 490 |
+
|
| 491 |
+
# Cleanup
|
| 492 |
+
if WEBSOCKETS_AVAILABLE:
|
| 493 |
+
await ws_display.stop_server()
|
| 494 |
+
if TKINTER_AVAILABLE:
|
| 495 |
+
gui_display.stop()
|
| 496 |
+
|
| 497 |
+
print("Display system test completed!")
|
| 498 |
+
|
| 499 |
+
# Run the test
|
| 500 |
+
asyncio.run(test_display())
|
| 501 |
+
|
virtual_gpu_setup/virtual_gpu/driver.py
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
GPU Driver Module
|
| 3 |
+
|
| 4 |
+
This module acts as the interface between a virtual CPU (or external command source)
|
| 5 |
+
and the vGPU, handling command queuing and interpretation.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import asyncio
|
| 9 |
+
from collections import deque
|
| 10 |
+
from enum import Enum
|
| 11 |
+
from typing import Dict, Any, Optional, List
|
| 12 |
+
from dataclasses import dataclass
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class CommandType(Enum):
|
| 16 |
+
"""Enumeration of supported GPU commands."""
|
| 17 |
+
CLEAR = "clear"
|
| 18 |
+
DRAW_RECT = "draw_rect"
|
| 19 |
+
DRAW_PIXEL = "draw_pixel"
|
| 20 |
+
DRAW_IMAGE = "draw_image"
|
| 21 |
+
SET_SHADER = "set_shader"
|
| 22 |
+
MATRIX_MULTIPLY = "matrix_multiply"
|
| 23 |
+
VECTOR_OP = "vector_op"
|
| 24 |
+
CREATE_FRAMEBUFFER = "create_framebuffer"
|
| 25 |
+
SET_FRAMEBUFFER = "set_framebuffer"
|
| 26 |
+
LOAD_TEXTURE = "load_texture"
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
@dataclass
|
| 30 |
+
class Command:
|
| 31 |
+
"""Represents a single command to be executed by the vGPU."""
|
| 32 |
+
command_id: str
|
| 33 |
+
command_type: CommandType
|
| 34 |
+
parameters: Dict[str, Any]
|
| 35 |
+
priority: int = 0
|
| 36 |
+
timestamp: float = 0.0
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class GPUDriver:
|
| 40 |
+
"""
|
| 41 |
+
GPU Driver that manages command queues and interfaces with the vGPU.
|
| 42 |
+
|
| 43 |
+
This class receives commands from external sources (virtual CPU, applications)
|
| 44 |
+
and translates them into tasks that can be processed by the vGPU.
|
| 45 |
+
"""
|
| 46 |
+
|
| 47 |
+
def __init__(self, vgpu=None):
|
| 48 |
+
self.vgpu = vgpu
|
| 49 |
+
|
| 50 |
+
# Command queue management
|
| 51 |
+
self.command_queue = deque()
|
| 52 |
+
self.command_counter = 0
|
| 53 |
+
|
| 54 |
+
# Current state
|
| 55 |
+
self.current_framebuffer = None
|
| 56 |
+
self.current_shader = None
|
| 57 |
+
|
| 58 |
+
# Command processing statistics
|
| 59 |
+
self.commands_processed = 0
|
| 60 |
+
self.commands_failed = 0
|
| 61 |
+
|
| 62 |
+
def set_vgpu(self, vgpu):
|
| 63 |
+
"""Set the vGPU reference."""
|
| 64 |
+
self.vgpu = vgpu
|
| 65 |
+
|
| 66 |
+
def submit_command(self, command_type: CommandType, parameters: Dict[str, Any],
|
| 67 |
+
priority: int = 0) -> str:
|
| 68 |
+
"""Submit a command to the GPU driver."""
|
| 69 |
+
command_id = f"cmd_{self.command_counter}"
|
| 70 |
+
self.command_counter += 1
|
| 71 |
+
|
| 72 |
+
command = Command(
|
| 73 |
+
command_id=command_id,
|
| 74 |
+
command_type=command_type,
|
| 75 |
+
parameters=parameters,
|
| 76 |
+
priority=priority,
|
| 77 |
+
timestamp=asyncio.get_event_loop().time()
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
# Insert command based on priority (higher priority first)
|
| 81 |
+
if priority > 0:
|
| 82 |
+
# Find insertion point for priority queue
|
| 83 |
+
inserted = False
|
| 84 |
+
for i, existing_cmd in enumerate(self.command_queue):
|
| 85 |
+
if existing_cmd.priority < priority:
|
| 86 |
+
self.command_queue.insert(i, command)
|
| 87 |
+
inserted = True
|
| 88 |
+
break
|
| 89 |
+
if not inserted:
|
| 90 |
+
self.command_queue.append(command)
|
| 91 |
+
else:
|
| 92 |
+
self.command_queue.append(command)
|
| 93 |
+
|
| 94 |
+
return command_id
|
| 95 |
+
|
| 96 |
+
async def process_commands(self) -> None:
|
| 97 |
+
"""Process all pending commands in the queue."""
|
| 98 |
+
while self.command_queue:
|
| 99 |
+
command = self.command_queue.popleft()
|
| 100 |
+
await self._execute_command(command)
|
| 101 |
+
|
| 102 |
+
async def _execute_command(self, command: Command) -> None:
|
| 103 |
+
"""Execute a single command."""
|
| 104 |
+
try:
|
| 105 |
+
if command.command_type == CommandType.CLEAR:
|
| 106 |
+
await self._handle_clear(command)
|
| 107 |
+
elif command.command_type == CommandType.DRAW_RECT:
|
| 108 |
+
await self._handle_draw_rect(command)
|
| 109 |
+
elif command.command_type == CommandType.DRAW_PIXEL:
|
| 110 |
+
await self._handle_draw_pixel(command)
|
| 111 |
+
elif command.command_type == CommandType.DRAW_IMAGE:
|
| 112 |
+
await self._handle_draw_image(command)
|
| 113 |
+
elif command.command_type == CommandType.SET_SHADER:
|
| 114 |
+
await self._handle_set_shader(command)
|
| 115 |
+
elif command.command_type == CommandType.MATRIX_MULTIPLY:
|
| 116 |
+
await self._handle_matrix_multiply(command)
|
| 117 |
+
elif command.command_type == CommandType.VECTOR_OP:
|
| 118 |
+
await self._handle_vector_op(command)
|
| 119 |
+
elif command.command_type == CommandType.CREATE_FRAMEBUFFER:
|
| 120 |
+
await self._handle_create_framebuffer(command)
|
| 121 |
+
elif command.command_type == CommandType.SET_FRAMEBUFFER:
|
| 122 |
+
await self._handle_set_framebuffer(command)
|
| 123 |
+
elif command.command_type == CommandType.LOAD_TEXTURE:
|
| 124 |
+
await self._handle_load_texture(command)
|
| 125 |
+
else:
|
| 126 |
+
print(f"Unknown command type: {command.command_type}")
|
| 127 |
+
self.commands_failed += 1
|
| 128 |
+
return
|
| 129 |
+
|
| 130 |
+
self.commands_processed += 1
|
| 131 |
+
|
| 132 |
+
except Exception as e:
|
| 133 |
+
print(f"Error executing command {command.command_id}: {e}")
|
| 134 |
+
self.commands_failed += 1
|
| 135 |
+
|
| 136 |
+
async def _handle_clear(self, command: Command) -> None:
|
| 137 |
+
"""Handle CLEAR command."""
|
| 138 |
+
if self.vgpu and self.current_framebuffer:
|
| 139 |
+
from vgpu import TaskType
|
| 140 |
+
task_id = self.vgpu.submit_task(
|
| 141 |
+
TaskType.RENDER_CLEAR,
|
| 142 |
+
{
|
| 143 |
+
"framebuffer_id": self.current_framebuffer,
|
| 144 |
+
**command.parameters
|
| 145 |
+
}
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
async def _handle_draw_rect(self, command: Command) -> None:
|
| 149 |
+
"""Handle DRAW_RECT command."""
|
| 150 |
+
if self.vgpu and self.current_framebuffer:
|
| 151 |
+
from vgpu import TaskType
|
| 152 |
+
task_id = self.vgpu.submit_task(
|
| 153 |
+
TaskType.RENDER_RECT,
|
| 154 |
+
{
|
| 155 |
+
"framebuffer_id": self.current_framebuffer,
|
| 156 |
+
**command.parameters
|
| 157 |
+
}
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
async def _handle_draw_pixel(self, command: Command) -> None:
|
| 161 |
+
"""Handle DRAW_PIXEL command."""
|
| 162 |
+
if self.vgpu and self.current_framebuffer:
|
| 163 |
+
from vgpu import TaskType
|
| 164 |
+
# Convert single pixel to a 1x1 rectangle
|
| 165 |
+
params = command.parameters.copy()
|
| 166 |
+
params.update({
|
| 167 |
+
"framebuffer_id": self.current_framebuffer,
|
| 168 |
+
"width": 1,
|
| 169 |
+
"height": 1
|
| 170 |
+
})
|
| 171 |
+
task_id = self.vgpu.submit_task(TaskType.RENDER_RECT, params)
|
| 172 |
+
|
| 173 |
+
async def _handle_draw_image(self, command: Command) -> None:
|
| 174 |
+
"""Handle DRAW_IMAGE command."""
|
| 175 |
+
if self.vgpu and self.current_framebuffer:
|
| 176 |
+
from vgpu import TaskType
|
| 177 |
+
task_id = self.vgpu.submit_task(
|
| 178 |
+
TaskType.RENDER_IMAGE,
|
| 179 |
+
{
|
| 180 |
+
"framebuffer_id": self.current_framebuffer,
|
| 181 |
+
**command.parameters
|
| 182 |
+
}
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
async def _handle_set_shader(self, command: Command) -> None:
|
| 186 |
+
"""Handle SET_SHADER command."""
|
| 187 |
+
shader_id = command.parameters.get("shader_id")
|
| 188 |
+
if shader_id:
|
| 189 |
+
self.current_shader = shader_id
|
| 190 |
+
|
| 191 |
+
async def _handle_matrix_multiply(self, command: Command) -> None:
|
| 192 |
+
"""Handle MATRIX_MULTIPLY command."""
|
| 193 |
+
if self.vgpu:
|
| 194 |
+
from vgpu import TaskType
|
| 195 |
+
task_id = self.vgpu.submit_task(
|
| 196 |
+
TaskType.AI_MATRIX_MULTIPLY,
|
| 197 |
+
command.parameters
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
async def _handle_vector_op(self, command: Command) -> None:
|
| 201 |
+
"""Handle VECTOR_OP command."""
|
| 202 |
+
if self.vgpu:
|
| 203 |
+
from vgpu import TaskType
|
| 204 |
+
task_id = self.vgpu.submit_task(
|
| 205 |
+
TaskType.AI_VECTOR_OP,
|
| 206 |
+
command.parameters
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
async def _handle_create_framebuffer(self, command: Command) -> None:
|
| 210 |
+
"""Handle CREATE_FRAMEBUFFER command."""
|
| 211 |
+
if self.vgpu and self.vgpu.vram:
|
| 212 |
+
width = command.parameters.get("width", 800)
|
| 213 |
+
height = command.parameters.get("height", 600)
|
| 214 |
+
channels = command.parameters.get("channels", 3)
|
| 215 |
+
name = command.parameters.get("name")
|
| 216 |
+
|
| 217 |
+
framebuffer_id = self.vgpu.vram.create_framebuffer(width, height, channels, name)
|
| 218 |
+
|
| 219 |
+
# Set as current framebuffer if none is set
|
| 220 |
+
if self.current_framebuffer is None:
|
| 221 |
+
self.current_framebuffer = framebuffer_id
|
| 222 |
+
|
| 223 |
+
async def _handle_set_framebuffer(self, command: Command) -> None:
|
| 224 |
+
"""Handle SET_FRAMEBUFFER command."""
|
| 225 |
+
framebuffer_id = command.parameters.get("framebuffer_id")
|
| 226 |
+
if framebuffer_id and self.vgpu and self.vgpu.vram:
|
| 227 |
+
if self.vgpu.vram.get_framebuffer(framebuffer_id):
|
| 228 |
+
self.current_framebuffer = framebuffer_id
|
| 229 |
+
|
| 230 |
+
async def _handle_load_texture(self, command: Command) -> None:
|
| 231 |
+
"""Handle LOAD_TEXTURE command."""
|
| 232 |
+
if self.vgpu and self.vgpu.vram:
|
| 233 |
+
texture_data = command.parameters.get("texture_data")
|
| 234 |
+
name = command.parameters.get("name")
|
| 235 |
+
|
| 236 |
+
if texture_data is not None:
|
| 237 |
+
texture_id = self.vgpu.vram.load_texture(texture_data, name)
|
| 238 |
+
|
| 239 |
+
def get_current_framebuffer(self) -> Optional[str]:
|
| 240 |
+
"""Get the current active framebuffer ID."""
|
| 241 |
+
return self.current_framebuffer
|
| 242 |
+
|
| 243 |
+
def get_current_shader(self) -> Optional[str]:
|
| 244 |
+
"""Get the current active shader ID."""
|
| 245 |
+
return self.current_shader
|
| 246 |
+
|
| 247 |
+
def get_stats(self) -> Dict[str, Any]:
|
| 248 |
+
"""Get driver statistics."""
|
| 249 |
+
return {
|
| 250 |
+
"commands_in_queue": len(self.command_queue),
|
| 251 |
+
"commands_processed": self.commands_processed,
|
| 252 |
+
"commands_failed": self.commands_failed,
|
| 253 |
+
"current_framebuffer": self.current_framebuffer,
|
| 254 |
+
"current_shader": self.current_shader
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
# Convenience methods for common operations
|
| 258 |
+
def clear_screen(self, color: tuple = (0, 0, 0)) -> str:
|
| 259 |
+
"""Clear the current framebuffer with the specified color."""
|
| 260 |
+
return self.submit_command(CommandType.CLEAR, {"color": color})
|
| 261 |
+
|
| 262 |
+
def draw_rectangle(self, x: int, y: int, width: int, height: int,
|
| 263 |
+
color: tuple = (255, 255, 255)) -> str:
|
| 264 |
+
"""Draw a rectangle on the current framebuffer."""
|
| 265 |
+
return self.submit_command(
|
| 266 |
+
CommandType.DRAW_RECT,
|
| 267 |
+
{"x": x, "y": y, "width": width, "height": height, "color": color}
|
| 268 |
+
)
|
| 269 |
+
|
| 270 |
+
def draw_pixel(self, x: int, y: int, color: tuple = (255, 255, 255)) -> str:
|
| 271 |
+
"""Draw a single pixel on the current framebuffer."""
|
| 272 |
+
return self.submit_command(
|
| 273 |
+
CommandType.DRAW_PIXEL,
|
| 274 |
+
{"x": x, "y": y, "color": color}
|
| 275 |
+
)
|
| 276 |
+
|
| 277 |
+
def create_framebuffer(self, width: int, height: int, channels: int = 3,
|
| 278 |
+
name: Optional[str] = None) -> str:
|
| 279 |
+
"""Create a new framebuffer."""
|
| 280 |
+
return self.submit_command(
|
| 281 |
+
CommandType.CREATE_FRAMEBUFFER,
|
| 282 |
+
{"width": width, "height": height, "channels": channels, "name": name}
|
| 283 |
+
)
|
| 284 |
+
|
| 285 |
+
def set_framebuffer(self, framebuffer_id: str) -> str:
|
| 286 |
+
"""Set the active framebuffer."""
|
| 287 |
+
return self.submit_command(
|
| 288 |
+
CommandType.SET_FRAMEBUFFER,
|
| 289 |
+
{"framebuffer_id": framebuffer_id}
|
| 290 |
+
)
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
if __name__ == "__main__":
|
| 294 |
+
# Test the driver
|
| 295 |
+
async def test_driver():
|
| 296 |
+
driver = GPUDriver()
|
| 297 |
+
|
| 298 |
+
# Submit some test commands
|
| 299 |
+
driver.create_framebuffer(800, 600)
|
| 300 |
+
driver.clear_screen((255, 0, 0))
|
| 301 |
+
driver.draw_rectangle(100, 100, 200, 150, (0, 255, 0))
|
| 302 |
+
driver.draw_pixel(400, 300, (0, 0, 255))
|
| 303 |
+
|
| 304 |
+
print(f"Driver stats: {driver.get_stats()}")
|
| 305 |
+
|
| 306 |
+
# Process commands (without vGPU, they won't actually execute)
|
| 307 |
+
await driver.process_commands()
|
| 308 |
+
|
| 309 |
+
print(f"Driver stats after processing: {driver.get_stats()}")
|
| 310 |
+
|
| 311 |
+
asyncio.run(test_driver())
|
| 312 |
+
|
virtual_gpu_setup/virtual_gpu/examples/__init__.py
ADDED
|
File without changes
|
virtual_gpu_setup/virtual_gpu/examples/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (154 Bytes). View file
|
|
|
virtual_gpu_setup/virtual_gpu/examples/__pycache__/test_virtual_ram_transfer.cpython-311.pyc
ADDED
|
Binary file (7.91 kB). View file
|
|
|
virtual_gpu_setup/virtual_gpu/examples/test_basic_rendering.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Basic Rendering Test Example
|
| 3 |
+
|
| 4 |
+
This example demonstrates basic rendering capabilities of the vGPU,
|
| 5 |
+
including creating framebuffers, drawing primitives, and displaying output.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import asyncio
|
| 9 |
+
import sys
|
| 10 |
+
import os
|
| 11 |
+
|
| 12 |
+
# Add parent directory to path to import vGPU modules
|
| 13 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 14 |
+
|
| 15 |
+
from vgpu import VirtualGPU, TaskType
|
| 16 |
+
from vram import VRAM
|
| 17 |
+
from driver import GPUDriver, CommandType
|
| 18 |
+
from render import Renderer
|
| 19 |
+
from ai import AIAccelerator
|
| 20 |
+
from display import DisplayManager, DisplayMode
|
| 21 |
+
from shader import ShaderManager
|
| 22 |
+
import numpy as np
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
async def basic_rendering_test():
|
| 26 |
+
"""Test basic rendering functionality of the vGPU."""
|
| 27 |
+
print("Starting Basic Rendering Test...")
|
| 28 |
+
print("=" * 50)
|
| 29 |
+
|
| 30 |
+
# Initialize components
|
| 31 |
+
print("Initializing vGPU components...")
|
| 32 |
+
|
| 33 |
+
# Create VRAM (1GB for testing)
|
| 34 |
+
vram = VRAM(memory_size_gb=1)
|
| 35 |
+
print(f"✓ VRAM initialized: {vram.get_stats()['total_memory_gb']} GB")
|
| 36 |
+
|
| 37 |
+
# Create renderer
|
| 38 |
+
renderer = Renderer(vram)
|
| 39 |
+
print("✓ Renderer initialized")
|
| 40 |
+
|
| 41 |
+
# Create AI accelerator
|
| 42 |
+
ai_accelerator = AIAccelerator(vram)
|
| 43 |
+
print("✓ AI Accelerator initialized")
|
| 44 |
+
|
| 45 |
+
# Create vGPU with 800 SMs and 50,000 cores
|
| 46 |
+
vgpu = VirtualGPU(num_sms=800, total_cores=50000)
|
| 47 |
+
vgpu.set_modules(vram, renderer, ai_accelerator, None)
|
| 48 |
+
print(f"✓ vGPU initialized: {vgpu.num_sms} SMs, {vgpu.total_cores} cores")
|
| 49 |
+
|
| 50 |
+
# Create driver
|
| 51 |
+
driver = GPUDriver(vgpu)
|
| 52 |
+
vgpu.driver = driver
|
| 53 |
+
print("✓ GPU Driver initialized")
|
| 54 |
+
|
| 55 |
+
# Create display manager
|
| 56 |
+
display_manager = DisplayManager(vram)
|
| 57 |
+
print("✓ Display Manager initialized")
|
| 58 |
+
|
| 59 |
+
# Create shader manager
|
| 60 |
+
shader_manager = ShaderManager()
|
| 61 |
+
print("✓ Shader Manager initialized")
|
| 62 |
+
|
| 63 |
+
print("\nComponent initialization complete!")
|
| 64 |
+
print("=" * 50)
|
| 65 |
+
|
| 66 |
+
# Create framebuffer
|
| 67 |
+
print("\nCreating framebuffer...")
|
| 68 |
+
fb_id = driver.create_framebuffer(800, 600, 3, "main_framebuffer")
|
| 69 |
+
await driver.process_commands()
|
| 70 |
+
|
| 71 |
+
framebuffer = vram.get_framebuffer("main_framebuffer")
|
| 72 |
+
if framebuffer:
|
| 73 |
+
print(f"✓ Framebuffer created: {framebuffer.width}x{framebuffer.height}")
|
| 74 |
+
display_manager.set_active_framebuffer("main_framebuffer")
|
| 75 |
+
else:
|
| 76 |
+
print("✗ Failed to create framebuffer")
|
| 77 |
+
return
|
| 78 |
+
|
| 79 |
+
# Add file display for saving frames
|
| 80 |
+
file_display = display_manager.add_display("file", DisplayMode.FILE,
|
| 81 |
+
output_dir="./test_output")
|
| 82 |
+
print("✓ File display added")
|
| 83 |
+
|
| 84 |
+
# Test 1: Clear screen
|
| 85 |
+
print("\nTest 1: Clear screen")
|
| 86 |
+
driver.clear_screen((64, 128, 255)) # Blue background
|
| 87 |
+
await driver.process_commands()
|
| 88 |
+
await vgpu.tick()
|
| 89 |
+
await display_manager.update_displays()
|
| 90 |
+
print("✓ Screen cleared to blue")
|
| 91 |
+
|
| 92 |
+
# Test 2: Draw rectangles
|
| 93 |
+
print("\nTest 2: Draw rectangles")
|
| 94 |
+
driver.draw_rectangle(100, 100, 200, 150, (255, 0, 0)) # Red rectangle
|
| 95 |
+
driver.draw_rectangle(300, 200, 150, 100, (0, 255, 0)) # Green rectangle
|
| 96 |
+
driver.draw_rectangle(500, 50, 100, 200, (255, 255, 0)) # Yellow rectangle
|
| 97 |
+
await driver.process_commands()
|
| 98 |
+
await vgpu.tick()
|
| 99 |
+
await display_manager.update_displays()
|
| 100 |
+
print("✓ Rectangles drawn")
|
| 101 |
+
|
| 102 |
+
# Test 3: Draw with shader
|
| 103 |
+
print("\nTest 3: Apply shader effects")
|
| 104 |
+
|
| 105 |
+
# Set grayscale shader
|
| 106 |
+
grayscale_shader = shader_manager.get_shader("grayscale")
|
| 107 |
+
renderer.set_shader(grayscale_shader)
|
| 108 |
+
|
| 109 |
+
# Draw some shapes with shader
|
| 110 |
+
driver.draw_rectangle(50, 400, 100, 100, (255, 0, 255)) # Magenta (will be grayscale)
|
| 111 |
+
driver.draw_rectangle(200, 450, 80, 80, (0, 255, 255)) # Cyan (will be grayscale)
|
| 112 |
+
await driver.process_commands()
|
| 113 |
+
await vgpu.tick()
|
| 114 |
+
await display_manager.update_displays()
|
| 115 |
+
print("✓ Shapes drawn with grayscale shader")
|
| 116 |
+
|
| 117 |
+
# Test 4: Remove shader and draw more
|
| 118 |
+
print("\nTest 4: Remove shader and draw more shapes")
|
| 119 |
+
renderer.set_shader(None) # Remove shader
|
| 120 |
+
|
| 121 |
+
# Draw circles using line approximation
|
| 122 |
+
center_x, center_y = 400, 400
|
| 123 |
+
radius = 50
|
| 124 |
+
for angle in range(0, 360, 10):
|
| 125 |
+
x1 = center_x + int(radius * np.cos(np.radians(angle)))
|
| 126 |
+
y1 = center_y + int(radius * np.sin(np.radians(angle)))
|
| 127 |
+
x2 = center_x + int(radius * np.cos(np.radians(angle + 10)))
|
| 128 |
+
y2 = center_y + int(radius * np.sin(np.radians(angle + 10)))
|
| 129 |
+
|
| 130 |
+
# Draw line segment
|
| 131 |
+
renderer.draw_line("main_framebuffer", x1, y1, x2, y2, (255, 255, 255))
|
| 132 |
+
|
| 133 |
+
await display_manager.update_displays()
|
| 134 |
+
print("✓ Circle drawn using line segments")
|
| 135 |
+
|
| 136 |
+
# Test 5: Performance test
|
| 137 |
+
print("\nTest 5: Performance test - drawing many pixels")
|
| 138 |
+
start_time = asyncio.get_event_loop().time()
|
| 139 |
+
|
| 140 |
+
# Draw a pattern of pixels
|
| 141 |
+
for y in range(500, 550):
|
| 142 |
+
for x in range(600, 700):
|
| 143 |
+
color_r = (x - 600) * 255 // 100
|
| 144 |
+
color_g = (y - 500) * 255 // 50
|
| 145 |
+
color_b = 128
|
| 146 |
+
driver.draw_pixel(x, y, (color_r, color_g, color_b))
|
| 147 |
+
|
| 148 |
+
await driver.process_commands()
|
| 149 |
+
|
| 150 |
+
# Process multiple ticks to handle all the pixel drawing tasks
|
| 151 |
+
for _ in range(10):
|
| 152 |
+
await vgpu.tick()
|
| 153 |
+
|
| 154 |
+
end_time = asyncio.get_event_loop().time()
|
| 155 |
+
await display_manager.update_displays()
|
| 156 |
+
|
| 157 |
+
pixels_drawn = 50 * 100 # 5000 pixels
|
| 158 |
+
time_taken = end_time - start_time
|
| 159 |
+
print(f"✓ Drew {pixels_drawn:,} pixels in {time_taken:.3f}s "
|
| 160 |
+
f"({pixels_drawn/time_taken:.0f} pixels/sec)")
|
| 161 |
+
|
| 162 |
+
# Test 6: AI operations
|
| 163 |
+
print("\nTest 6: AI matrix operations")
|
| 164 |
+
|
| 165 |
+
# Create test matrices
|
| 166 |
+
matrix_a = np.random.rand(100, 50).astype(np.float32)
|
| 167 |
+
matrix_b = np.random.rand(50, 75).astype(np.float32)
|
| 168 |
+
|
| 169 |
+
# Load matrices into vGPU
|
| 170 |
+
a_id = ai_accelerator.load_matrix(matrix_a, "test_matrix_a")
|
| 171 |
+
b_id = ai_accelerator.load_matrix(matrix_b, "test_matrix_b")
|
| 172 |
+
|
| 173 |
+
# Perform matrix multiplication
|
| 174 |
+
result_id = ai_accelerator.matrix_multiply(a_id, b_id, "result_matrix")
|
| 175 |
+
|
| 176 |
+
if result_id:
|
| 177 |
+
result = ai_accelerator.get_matrix(result_id)
|
| 178 |
+
expected = np.dot(matrix_a, matrix_b)
|
| 179 |
+
|
| 180 |
+
if np.allclose(result, expected, rtol=1e-5):
|
| 181 |
+
print(f"✓ Matrix multiplication successful: {matrix_a.shape} x {matrix_b.shape} = {result.shape}")
|
| 182 |
+
else:
|
| 183 |
+
print("✗ Matrix multiplication result incorrect")
|
| 184 |
+
else:
|
| 185 |
+
print("✗ Matrix multiplication failed")
|
| 186 |
+
|
| 187 |
+
# Final statistics
|
| 188 |
+
print("\n" + "=" * 50)
|
| 189 |
+
print("FINAL STATISTICS")
|
| 190 |
+
print("=" * 50)
|
| 191 |
+
|
| 192 |
+
# vGPU stats
|
| 193 |
+
vgpu_stats = vgpu.get_stats()
|
| 194 |
+
print(f"vGPU Statistics:")
|
| 195 |
+
print(f" Clock cycles: {vgpu_stats['clock_cycle']:,}")
|
| 196 |
+
print(f" Tasks processed: {vgpu_stats['total_tasks_processed']:,}")
|
| 197 |
+
print(f" Busy SMs: {vgpu_stats['busy_sms']}/{vgpu_stats['total_sms']}")
|
| 198 |
+
|
| 199 |
+
# Renderer stats
|
| 200 |
+
render_stats = renderer.get_stats()
|
| 201 |
+
print(f"\nRenderer Statistics:")
|
| 202 |
+
print(f" Pixels drawn: {render_stats['pixels_drawn']:,}")
|
| 203 |
+
print(f" Draw calls: {render_stats['draw_calls']:,}")
|
| 204 |
+
print(f" Render time: {render_stats['total_render_time']:.3f}s")
|
| 205 |
+
print(f" Pixels/second: {render_stats['pixels_per_second']:,.0f}")
|
| 206 |
+
|
| 207 |
+
# VRAM stats
|
| 208 |
+
vram_stats = vram.get_stats()
|
| 209 |
+
print(f"\nVRAM Statistics:")
|
| 210 |
+
print(f" Total memory: {vram_stats['total_memory_gb']:.1f} GB")
|
| 211 |
+
print(f" Utilization: {vram_stats['utilization_percent']:.2f}%")
|
| 212 |
+
print(f" Framebuffers: {vram_stats['framebuffers_count']}")
|
| 213 |
+
print(f" Textures: {vram_stats['textures_count']}")
|
| 214 |
+
|
| 215 |
+
# AI stats
|
| 216 |
+
ai_stats = ai_accelerator.get_stats()
|
| 217 |
+
print(f"\nAI Accelerator Statistics:")
|
| 218 |
+
print(f" Operations: {ai_stats['operations_performed']}")
|
| 219 |
+
print(f" FLOPs performed: {ai_stats['flops_performed']:,}")
|
| 220 |
+
print(f" FLOPs/second: {ai_stats['flops_per_second']:,.0f}")
|
| 221 |
+
print(f" Matrices in memory: {ai_stats['matrices_in_memory']}")
|
| 222 |
+
|
| 223 |
+
# Display stats
|
| 224 |
+
display_stats = display_manager.get_stats()
|
| 225 |
+
print(f"\nDisplay Statistics:")
|
| 226 |
+
print(f" Frames displayed: {display_stats['frames_displayed']}")
|
| 227 |
+
print(f" Display time: {display_stats['total_display_time']:.3f}s")
|
| 228 |
+
print(f" Active displays: {display_stats['active_displays']}")
|
| 229 |
+
|
| 230 |
+
# Driver stats
|
| 231 |
+
driver_stats = driver.get_stats()
|
| 232 |
+
print(f"\nDriver Statistics:")
|
| 233 |
+
print(f" Commands processed: {driver_stats['commands_processed']}")
|
| 234 |
+
print(f" Commands failed: {driver_stats['commands_failed']}")
|
| 235 |
+
|
| 236 |
+
print("\n" + "=" * 50)
|
| 237 |
+
print("Basic Rendering Test Complete!")
|
| 238 |
+
print("Check ./test_output/ for saved frame images.")
|
| 239 |
+
print("=" * 50)
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
if __name__ == "__main__":
|
| 243 |
+
# Run the basic rendering test
|
| 244 |
+
asyncio.run(basic_rendering_test())
|
| 245 |
+
|
virtual_gpu_setup/virtual_gpu/examples/test_virtual_ram_transfer.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
import time
|
| 3 |
+
import numpy as np
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
from virtual_gpu.vram import VRAM
|
| 7 |
+
from virtual_gpu.virtual_ram import VirtualRAM
|
| 8 |
+
from virtual_gpu.vgpu import VirtualGPU
|
| 9 |
+
from virtual_gpu.driver import GPUDriver
|
| 10 |
+
from virtual_gpu.render import Renderer
|
| 11 |
+
from virtual_gpu.ai import AIAccelerator
|
| 12 |
+
from virtual_gpu.shader import ShaderManager
|
| 13 |
+
from virtual_gpu.display import DisplayManager, DisplayMode
|
| 14 |
+
|
| 15 |
+
async def test_virtual_ram_transfer():
|
| 16 |
+
print("Starting Virtual RAM to VRAM Transfer Test...")
|
| 17 |
+
print("=" * 50)
|
| 18 |
+
|
| 19 |
+
# 1. Initialize VirtualRAM(128) and VirtualVRAM(500)
|
| 20 |
+
print("Initializing VirtualRAM and VRAM...")
|
| 21 |
+
ram = VirtualRAM(capacity_gb=128)
|
| 22 |
+
vram = VRAM(memory_size_gb=500)
|
| 23 |
+
print("✓ VirtualRAM (128GB) and VRAM (500GB) initialized.")
|
| 24 |
+
|
| 25 |
+
# Initialize other vGPU components for a complete environment
|
| 26 |
+
renderer = Renderer(vram)
|
| 27 |
+
ai_accelerator = AIAccelerator(vram)
|
| 28 |
+
vgpu = VirtualGPU(num_sms=800, total_cores=50000)
|
| 29 |
+
vgpu.set_modules(vram, renderer, ai_accelerator, None)
|
| 30 |
+
driver = GPUDriver(vgpu)
|
| 31 |
+
vgpu.driver = driver
|
| 32 |
+
shader_manager = ShaderManager()
|
| 33 |
+
display_manager = DisplayManager(vram)
|
| 34 |
+
file_display = display_manager.add_display("file", DisplayMode.FILE, output_dir="./test_output_ram_transfer")
|
| 35 |
+
print("✓ Other vGPU components initialized.")
|
| 36 |
+
|
| 37 |
+
print("\n" + "=" * 50)
|
| 38 |
+
|
| 39 |
+
# 2. Allocate a 1MB tensor in RAM.
|
| 40 |
+
tensor_size_mb = 1 # Use 1MB for actual data allocation
|
| 41 |
+
tensor_size_bytes = tensor_size_mb * 1024 * 1024
|
| 42 |
+
|
| 43 |
+
print(f"Allocating a {tensor_size_mb}MB tensor in VirtualRAM...")
|
| 44 |
+
ram.allocate_block("my_ai_tensor", tensor_size_bytes, store_data=True) # Request real data
|
| 45 |
+
ram.print_info()
|
| 46 |
+
print("✓ 1MB tensor allocated with real data in VirtualRAM.")
|
| 47 |
+
|
| 48 |
+
print("\n" + "=" * 50)
|
| 49 |
+
|
| 50 |
+
# 3. Transfer that tensor into VRAM as
|
| 51 |
+
print(f"Transferring \'my_ai_tensor\' from VirtualRAM to VRAM as \'ai_tensor_buffer\'")
|
| 52 |
+
transfer_start_time = time.time()
|
| 53 |
+
vram_tensor_id = ram.transfer_to_vram("my_ai_tensor", vram, "ai_tensor_buffer")
|
| 54 |
+
transfer_end_time = time.time()
|
| 55 |
+
time_taken_to_transfer = transfer_end_time - transfer_start_time
|
| 56 |
+
|
| 57 |
+
if vram_tensor_id:
|
| 58 |
+
print(f"✓ Tensor transferred to VRAM. VRAM ID: {vram_tensor_id}")
|
| 59 |
+
print(f"Time taken to transfer: {time_taken_to_transfer:.4f} seconds")
|
| 60 |
+
else:
|
| 61 |
+
print("✗ Failed to transfer tensor to VRAM.")
|
| 62 |
+
return
|
| 63 |
+
|
| 64 |
+
print("\n" + "=" * 50)
|
| 65 |
+
|
| 66 |
+
# 4. Apply a real AI operation (matrix multiplication) on the VRAM buffer.
|
| 67 |
+
print("Applying a real AI operation (matrix multiplication) on the VRAM buffer...")
|
| 68 |
+
# Retrieve the transferred data from VRAM (now it should contain actual data)
|
| 69 |
+
vram_block_data = vram.get_texture(vram_tensor_id) # This should now return actual data
|
| 70 |
+
if vram_block_data is None:
|
| 71 |
+
print("✗ Could not retrieve actual tensor data from VRAM for AI operation.")
|
| 72 |
+
return
|
| 73 |
+
|
| 74 |
+
# Create a dummy matrix B with real data for multiplication
|
| 75 |
+
# Ensure dimensions are compatible for multiplication
|
| 76 |
+
# Let\'s assume the transferred data is a flattened 1MB (1024*1024 bytes) of uint8
|
| 77 |
+
# We\'ll reshape it to (1024, 1024) for a dummy matrix A
|
| 78 |
+
try:
|
| 79 |
+
matrix_a_reshaped = vram_block_data.reshape((1024, 1024)).astype(np.float32)
|
| 80 |
+
except ValueError:
|
| 81 |
+
print("✗ Could not reshape transferred data for matrix A. Data size might not be suitable.")
|
| 82 |
+
return
|
| 83 |
+
|
| 84 |
+
# Create a dummy matrix B (e.g., 1024x512) for multiplication
|
| 85 |
+
matrix_b_data = np.random.rand(1024, 512).astype(np.float32)
|
| 86 |
+
|
| 87 |
+
# Load these real matrices into AI Accelerator (which will then load them into VRAM)
|
| 88 |
+
ai_accelerator.set_vram(vram) # Ensure AI accelerator has VRAM reference
|
| 89 |
+
matrix_a_id = ai_accelerator.load_matrix(matrix_a_reshaped, "real_ai_matrix_a")
|
| 90 |
+
matrix_b_id = ai_accelerator.load_matrix(matrix_b_data, "real_ai_matrix_b")
|
| 91 |
+
|
| 92 |
+
# Perform real matrix multiplication
|
| 93 |
+
result_id = ai_accelerator.matrix_multiply(matrix_a_id, matrix_b_id, "real_ai_op_result")
|
| 94 |
+
|
| 95 |
+
if result_id:
|
| 96 |
+
result_matrix = ai_accelerator.get_matrix(result_id)
|
| 97 |
+
if result_matrix is not None:
|
| 98 |
+
print(f"✓ Real AI operation (matrix multiplication) completed. Result shape: {result_matrix.shape}")
|
| 99 |
+
# Optional: Verify a small part of the result
|
| 100 |
+
expected_result = np.dot(matrix_a_reshaped, matrix_b_data)
|
| 101 |
+
if np.allclose(result_matrix[:5,:5], expected_result[:5,:5]): # Compare a small section
|
| 102 |
+
print("✓ Result verification (partial) successful.")
|
| 103 |
+
else:
|
| 104 |
+
print("✗ Result verification (partial) failed.")
|
| 105 |
+
else:
|
| 106 |
+
print("✗ Could not retrieve result matrix from VRAM.")
|
| 107 |
+
else:
|
| 108 |
+
print("✗ Real AI operation failed.")
|
| 109 |
+
|
| 110 |
+
print("\n" + "=" * 50)
|
| 111 |
+
|
| 112 |
+
# 5. Output logs showing:
|
| 113 |
+
# RAM usage
|
| 114 |
+
# VRAM usage
|
| 115 |
+
# Time taken to transfer
|
| 116 |
+
# Operation success
|
| 117 |
+
|
| 118 |
+
print("FINAL LOGS AND STATISTICS")
|
| 119 |
+
print("=" * 50)
|
| 120 |
+
|
| 121 |
+
print("\nVirtual RAM Usage:")
|
| 122 |
+
ram.print_info()
|
| 123 |
+
|
| 124 |
+
print("\nVRAM Usage:")
|
| 125 |
+
vram_stats = vram.get_stats()
|
| 126 |
+
print(f' Total memory: {vram_stats["total_memory_gb"]:.1f} GB')
|
| 127 |
+
print(f' Utilization: {vram_stats["utilization_percent"]:.2f}%')
|
| 128 |
+
print(f' Framebuffers: {vram_stats["framebuffers_count"]}')
|
| 129 |
+
print(f' Textures/Buffers: {vram_stats["textures_count"]}')
|
| 130 |
+
print(f' Allocated bytes: {vram_stats["allocated_bytes"]:,} bytes')
|
| 131 |
+
|
| 132 |
+
print(f"\nTime taken to transfer tensor from RAM to VRAM: {time_taken_to_transfer:.4f} seconds")
|
| 133 |
+
success_status = "Success" if result_id else "Failed"
|
| 134 |
+
print(f"AI Operation Success: {success_status}")
|
| 135 |
+
|
| 136 |
+
print("\n" + "=" * 50)
|
| 137 |
+
print("Virtual RAM Transfer Test Complete!")
|
| 138 |
+
print("Check ./test_output_ram_transfer/ for any saved frame images (if applicable).")
|
| 139 |
+
print("=" * 50)
|
| 140 |
+
|
| 141 |
+
# Clean up (optional)
|
| 142 |
+
# ram.release_block("my_ai_tensor")
|
| 143 |
+
# vram.delete_texture(vram_tensor_id)
|
| 144 |
+
|
| 145 |
+
if __name__ == "__main__":
|
| 146 |
+
# Ensure the output directory exists
|
| 147 |
+
os.makedirs("./test_output_ram_transfer", exist_ok=True)
|
| 148 |
+
asyncio.run(test_virtual_ram_transfer())
|
| 149 |
+
|
| 150 |
+
|
virtual_gpu_setup/virtual_gpu/render.py
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Render Module - Software Raster Pipeline
|
| 3 |
+
|
| 4 |
+
This module implements the software raster pipeline for drawing primitives
|
| 5 |
+
and images onto framebuffers stored in VRAM.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
from typing import Tuple, Optional, Any, Dict
|
| 10 |
+
import time
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class Renderer:
|
| 14 |
+
"""
|
| 15 |
+
Software-based renderer that implements basic drawing operations.
|
| 16 |
+
|
| 17 |
+
This renderer operates on framebuffers stored in VRAM and provides
|
| 18 |
+
functions for drawing primitives like rectangles, lines, and pixels.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
def __init__(self, vram=None):
|
| 22 |
+
self.vram = vram
|
| 23 |
+
self.current_shader = None
|
| 24 |
+
|
| 25 |
+
# Rendering statistics
|
| 26 |
+
self.pixels_drawn = 0
|
| 27 |
+
self.draw_calls = 0
|
| 28 |
+
self.render_time = 0.0
|
| 29 |
+
|
| 30 |
+
def set_vram(self, vram):
|
| 31 |
+
"""Set the VRAM reference."""
|
| 32 |
+
self.vram = vram
|
| 33 |
+
|
| 34 |
+
def set_shader(self, shader):
|
| 35 |
+
"""Set the current shader for rendering operations."""
|
| 36 |
+
self.current_shader = shader
|
| 37 |
+
|
| 38 |
+
def clear(self, framebuffer_id: str, color: Tuple[int, int, int] = (0, 0, 0)) -> bool:
|
| 39 |
+
"""Clear a framebuffer with the specified color."""
|
| 40 |
+
if not self.vram:
|
| 41 |
+
return False
|
| 42 |
+
|
| 43 |
+
start_time = time.time()
|
| 44 |
+
|
| 45 |
+
framebuffer = self.vram.get_framebuffer(framebuffer_id)
|
| 46 |
+
if not framebuffer:
|
| 47 |
+
return False
|
| 48 |
+
|
| 49 |
+
try:
|
| 50 |
+
framebuffer.clear(color)
|
| 51 |
+
self.pixels_drawn += framebuffer.width * framebuffer.height
|
| 52 |
+
self.draw_calls += 1
|
| 53 |
+
self.render_time += time.time() - start_time
|
| 54 |
+
return True
|
| 55 |
+
except Exception as e:
|
| 56 |
+
print(f"Error clearing framebuffer {framebuffer_id}: {e}")
|
| 57 |
+
return False
|
| 58 |
+
|
| 59 |
+
def draw_pixel(self, framebuffer_id: str, x: int, y: int,
|
| 60 |
+
color: Tuple[int, int, int] = (255, 255, 255)) -> bool:
|
| 61 |
+
"""Draw a single pixel on the framebuffer."""
|
| 62 |
+
if not self.vram:
|
| 63 |
+
return False
|
| 64 |
+
|
| 65 |
+
start_time = time.time()
|
| 66 |
+
|
| 67 |
+
framebuffer = self.vram.get_framebuffer(framebuffer_id)
|
| 68 |
+
if not framebuffer:
|
| 69 |
+
return False
|
| 70 |
+
|
| 71 |
+
try:
|
| 72 |
+
# Apply shader if available
|
| 73 |
+
final_color = color
|
| 74 |
+
if self.current_shader:
|
| 75 |
+
final_color = self.current_shader.process_pixel(x, y, color)
|
| 76 |
+
|
| 77 |
+
framebuffer.set_pixel(x, y, final_color)
|
| 78 |
+
self.pixels_drawn += 1
|
| 79 |
+
self.draw_calls += 1
|
| 80 |
+
self.render_time += time.time() - start_time
|
| 81 |
+
return True
|
| 82 |
+
except Exception as e:
|
| 83 |
+
print(f"Error drawing pixel at ({x}, {y}): {e}")
|
| 84 |
+
return False
|
| 85 |
+
|
| 86 |
+
def draw_rect(self, framebuffer_id: str, x: int, y: int, width: int, height: int,
|
| 87 |
+
color: Tuple[int, int, int] = (255, 255, 255)) -> bool:
|
| 88 |
+
"""Draw a filled rectangle on the framebuffer."""
|
| 89 |
+
if not self.vram:
|
| 90 |
+
return False
|
| 91 |
+
|
| 92 |
+
start_time = time.time()
|
| 93 |
+
|
| 94 |
+
framebuffer = self.vram.get_framebuffer(framebuffer_id)
|
| 95 |
+
if not framebuffer:
|
| 96 |
+
return False
|
| 97 |
+
|
| 98 |
+
try:
|
| 99 |
+
# Clamp rectangle to framebuffer bounds
|
| 100 |
+
x1 = max(0, x)
|
| 101 |
+
y1 = max(0, y)
|
| 102 |
+
x2 = min(framebuffer.width, x + width)
|
| 103 |
+
y2 = min(framebuffer.height, y + height)
|
| 104 |
+
|
| 105 |
+
if x2 <= x1 or y2 <= y1:
|
| 106 |
+
return True # Nothing to draw
|
| 107 |
+
|
| 108 |
+
# Use NumPy for efficient rectangle filling
|
| 109 |
+
if self.current_shader:
|
| 110 |
+
# Apply shader to each pixel (slower but more flexible)
|
| 111 |
+
for py in range(y1, y2):
|
| 112 |
+
for px in range(x1, x2):
|
| 113 |
+
final_color = self.current_shader.process_pixel(px, py, color)
|
| 114 |
+
framebuffer.pixel_buffer[py, px] = final_color[:framebuffer.channels]
|
| 115 |
+
else:
|
| 116 |
+
# Direct fill (faster)
|
| 117 |
+
framebuffer.pixel_buffer[y1:y2, x1:x2] = color[:framebuffer.channels]
|
| 118 |
+
|
| 119 |
+
pixels_affected = (x2 - x1) * (y2 - y1)
|
| 120 |
+
self.pixels_drawn += pixels_affected
|
| 121 |
+
self.draw_calls += 1
|
| 122 |
+
self.render_time += time.time() - start_time
|
| 123 |
+
return True
|
| 124 |
+
|
| 125 |
+
except Exception as e:
|
| 126 |
+
print(f"Error drawing rectangle at ({x}, {y}, {width}, {height}): {e}")
|
| 127 |
+
return False
|
| 128 |
+
|
| 129 |
+
def draw_line(self, framebuffer_id: str, x1: int, y1: int, x2: int, y2: int,
|
| 130 |
+
color: Tuple[int, int, int] = (255, 255, 255)) -> bool:
|
| 131 |
+
"""Draw a line using Bresenham's algorithm."""
|
| 132 |
+
if not self.vram:
|
| 133 |
+
return False
|
| 134 |
+
|
| 135 |
+
start_time = time.time()
|
| 136 |
+
|
| 137 |
+
framebuffer = self.vram.get_framebuffer(framebuffer_id)
|
| 138 |
+
if not framebuffer:
|
| 139 |
+
return False
|
| 140 |
+
|
| 141 |
+
try:
|
| 142 |
+
# Bresenham's line algorithm
|
| 143 |
+
dx = abs(x2 - x1)
|
| 144 |
+
dy = abs(y2 - y1)
|
| 145 |
+
sx = 1 if x1 < x2 else -1
|
| 146 |
+
sy = 1 if y1 < y2 else -1
|
| 147 |
+
err = dx - dy
|
| 148 |
+
|
| 149 |
+
x, y = x1, y1
|
| 150 |
+
pixels_drawn = 0
|
| 151 |
+
|
| 152 |
+
while True:
|
| 153 |
+
# Draw pixel if within bounds
|
| 154 |
+
if 0 <= x < framebuffer.width and 0 <= y < framebuffer.height:
|
| 155 |
+
final_color = color
|
| 156 |
+
if self.current_shader:
|
| 157 |
+
final_color = self.current_shader.process_pixel(x, y, color)
|
| 158 |
+
framebuffer.set_pixel(x, y, final_color)
|
| 159 |
+
pixels_drawn += 1
|
| 160 |
+
|
| 161 |
+
if x == x2 and y == y2:
|
| 162 |
+
break
|
| 163 |
+
|
| 164 |
+
e2 = 2 * err
|
| 165 |
+
if e2 > -dy:
|
| 166 |
+
err -= dy
|
| 167 |
+
x += sx
|
| 168 |
+
if e2 < dx:
|
| 169 |
+
err += dx
|
| 170 |
+
y += sy
|
| 171 |
+
|
| 172 |
+
self.pixels_drawn += pixels_drawn
|
| 173 |
+
self.draw_calls += 1
|
| 174 |
+
self.render_time += time.time() - start_time
|
| 175 |
+
return True
|
| 176 |
+
|
| 177 |
+
except Exception as e:
|
| 178 |
+
print(f"Error drawing line from ({x1}, {y1}) to ({x2}, {y2}): {e}")
|
| 179 |
+
return False
|
| 180 |
+
|
| 181 |
+
def draw_circle(self, framebuffer_id: str, center_x: int, center_y: int, radius: int,
|
| 182 |
+
color: Tuple[int, int, int] = (255, 255, 255), filled: bool = False) -> bool:
|
| 183 |
+
"""Draw a circle using the midpoint circle algorithm."""
|
| 184 |
+
if not self.vram:
|
| 185 |
+
return False
|
| 186 |
+
|
| 187 |
+
start_time = time.time()
|
| 188 |
+
|
| 189 |
+
framebuffer = self.vram.get_framebuffer(framebuffer_id)
|
| 190 |
+
if not framebuffer:
|
| 191 |
+
return False
|
| 192 |
+
|
| 193 |
+
try:
|
| 194 |
+
pixels_drawn = 0
|
| 195 |
+
|
| 196 |
+
if filled:
|
| 197 |
+
# Draw filled circle
|
| 198 |
+
for y in range(center_y - radius, center_y + radius + 1):
|
| 199 |
+
for x in range(center_x - radius, center_x + radius + 1):
|
| 200 |
+
if (x - center_x) ** 2 + (y - center_y) ** 2 <= radius ** 2:
|
| 201 |
+
if 0 <= x < framebuffer.width and 0 <= y < framebuffer.height:
|
| 202 |
+
final_color = color
|
| 203 |
+
if self.current_shader:
|
| 204 |
+
final_color = self.current_shader.process_pixel(x, y, color)
|
| 205 |
+
framebuffer.set_pixel(x, y, final_color)
|
| 206 |
+
pixels_drawn += 1
|
| 207 |
+
else:
|
| 208 |
+
# Draw circle outline using midpoint algorithm
|
| 209 |
+
x = 0
|
| 210 |
+
y = radius
|
| 211 |
+
d = 1 - radius
|
| 212 |
+
|
| 213 |
+
def draw_circle_points(cx, cy, x, y):
|
| 214 |
+
points = [
|
| 215 |
+
(cx + x, cy + y), (cx - x, cy + y),
|
| 216 |
+
(cx + x, cy - y), (cx - x, cy - y),
|
| 217 |
+
(cx + y, cy + x), (cx - y, cy + x),
|
| 218 |
+
(cx + y, cy - x), (cx - y, cy - x)
|
| 219 |
+
]
|
| 220 |
+
drawn = 0
|
| 221 |
+
for px, py in points:
|
| 222 |
+
if 0 <= px < framebuffer.width and 0 <= py < framebuffer.height:
|
| 223 |
+
final_color = color
|
| 224 |
+
if self.current_shader:
|
| 225 |
+
final_color = self.current_shader.process_pixel(px, py, color)
|
| 226 |
+
framebuffer.set_pixel(px, py, final_color)
|
| 227 |
+
drawn += 1
|
| 228 |
+
return drawn
|
| 229 |
+
|
| 230 |
+
pixels_drawn += draw_circle_points(center_x, center_y, x, y)
|
| 231 |
+
|
| 232 |
+
while x < y:
|
| 233 |
+
if d < 0:
|
| 234 |
+
d += 2 * x + 3
|
| 235 |
+
else:
|
| 236 |
+
d += 2 * (x - y) + 5
|
| 237 |
+
y -= 1
|
| 238 |
+
x += 1
|
| 239 |
+
pixels_drawn += draw_circle_points(center_x, center_y, x, y)
|
| 240 |
+
|
| 241 |
+
self.pixels_drawn += pixels_drawn
|
| 242 |
+
self.draw_calls += 1
|
| 243 |
+
self.render_time += time.time() - start_time
|
| 244 |
+
return True
|
| 245 |
+
|
| 246 |
+
except Exception as e:
|
| 247 |
+
print(f"Error drawing circle at ({center_x}, {center_y}) with radius {radius}: {e}")
|
| 248 |
+
return False
|
| 249 |
+
|
| 250 |
+
def draw_image(self, framebuffer_id: str, x: int, y: int, texture_id: str,
|
| 251 |
+
scale_x: float = 1.0, scale_y: float = 1.0) -> bool:
|
| 252 |
+
"""Draw an image/texture onto the framebuffer."""
|
| 253 |
+
if not self.vram:
|
| 254 |
+
return False
|
| 255 |
+
|
| 256 |
+
start_time = time.time()
|
| 257 |
+
|
| 258 |
+
framebuffer = self.vram.get_framebuffer(framebuffer_id)
|
| 259 |
+
texture = self.vram.get_texture(texture_id)
|
| 260 |
+
|
| 261 |
+
if not framebuffer or texture is None:
|
| 262 |
+
return False
|
| 263 |
+
|
| 264 |
+
try:
|
| 265 |
+
# Get texture dimensions
|
| 266 |
+
if len(texture.shape) == 3:
|
| 267 |
+
tex_height, tex_width, tex_channels = texture.shape
|
| 268 |
+
else:
|
| 269 |
+
tex_height, tex_width = texture.shape
|
| 270 |
+
tex_channels = 1
|
| 271 |
+
|
| 272 |
+
# Calculate scaled dimensions
|
| 273 |
+
scaled_width = int(tex_width * scale_x)
|
| 274 |
+
scaled_height = int(tex_height * scale_y)
|
| 275 |
+
|
| 276 |
+
pixels_drawn = 0
|
| 277 |
+
|
| 278 |
+
# Simple nearest-neighbor scaling and blitting
|
| 279 |
+
for dy in range(scaled_height):
|
| 280 |
+
for dx in range(scaled_width):
|
| 281 |
+
# Calculate destination pixel
|
| 282 |
+
dest_x = x + dx
|
| 283 |
+
dest_y = y + dy
|
| 284 |
+
|
| 285 |
+
# Check bounds
|
| 286 |
+
if (dest_x < 0 or dest_x >= framebuffer.width or
|
| 287 |
+
dest_y < 0 or dest_y >= framebuffer.height):
|
| 288 |
+
continue
|
| 289 |
+
|
| 290 |
+
# Calculate source pixel (nearest neighbor)
|
| 291 |
+
src_x = int(dx / scale_x)
|
| 292 |
+
src_y = int(dy / scale_y)
|
| 293 |
+
|
| 294 |
+
# Clamp source coordinates
|
| 295 |
+
src_x = min(src_x, tex_width - 1)
|
| 296 |
+
src_y = min(src_y, tex_height - 1)
|
| 297 |
+
|
| 298 |
+
# Get source pixel color
|
| 299 |
+
if tex_channels == 1:
|
| 300 |
+
color = (texture[src_y, src_x], texture[src_y, src_x], texture[src_y, src_x])
|
| 301 |
+
else:
|
| 302 |
+
color = tuple(texture[src_y, src_x, :min(3, tex_channels)])
|
| 303 |
+
|
| 304 |
+
# Apply shader if available
|
| 305 |
+
final_color = color
|
| 306 |
+
if self.current_shader:
|
| 307 |
+
final_color = self.current_shader.process_pixel(dest_x, dest_y, color)
|
| 308 |
+
|
| 309 |
+
# Set pixel
|
| 310 |
+
framebuffer.set_pixel(dest_x, dest_y, final_color)
|
| 311 |
+
pixels_drawn += 1
|
| 312 |
+
|
| 313 |
+
self.pixels_drawn += pixels_drawn
|
| 314 |
+
self.draw_calls += 1
|
| 315 |
+
self.render_time += time.time() - start_time
|
| 316 |
+
return True
|
| 317 |
+
|
| 318 |
+
except Exception as e:
|
| 319 |
+
print(f"Error drawing image {texture_id} at ({x}, {y}): {e}")
|
| 320 |
+
return False
|
| 321 |
+
|
| 322 |
+
def get_stats(self) -> Dict[str, Any]:
|
| 323 |
+
"""Get rendering statistics."""
|
| 324 |
+
return {
|
| 325 |
+
"pixels_drawn": self.pixels_drawn,
|
| 326 |
+
"draw_calls": self.draw_calls,
|
| 327 |
+
"total_render_time": self.render_time,
|
| 328 |
+
"avg_render_time": self.render_time / max(1, self.draw_calls),
|
| 329 |
+
"pixels_per_second": self.pixels_drawn / max(0.001, self.render_time)
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
def reset_stats(self) -> None:
|
| 333 |
+
"""Reset rendering statistics."""
|
| 334 |
+
self.pixels_drawn = 0
|
| 335 |
+
self.draw_calls = 0
|
| 336 |
+
self.render_time = 0.0
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
if __name__ == "__main__":
|
| 340 |
+
# Test the renderer
|
| 341 |
+
from vram import VRAM
|
| 342 |
+
|
| 343 |
+
# Create VRAM and renderer
|
| 344 |
+
vram = VRAM(memory_size_gb=1)
|
| 345 |
+
renderer = Renderer(vram)
|
| 346 |
+
|
| 347 |
+
# Create a test framebuffer
|
| 348 |
+
fb_id = vram.create_framebuffer(800, 600, 3)
|
| 349 |
+
|
| 350 |
+
# Test rendering operations
|
| 351 |
+
print("Testing renderer...")
|
| 352 |
+
|
| 353 |
+
# Clear screen
|
| 354 |
+
renderer.clear(fb_id, (64, 128, 255))
|
| 355 |
+
|
| 356 |
+
# Draw some rectangles
|
| 357 |
+
renderer.draw_rect(fb_id, 100, 100, 200, 150, (255, 0, 0))
|
| 358 |
+
renderer.draw_rect(fb_id, 200, 200, 100, 100, (0, 255, 0))
|
| 359 |
+
|
| 360 |
+
# Draw some lines
|
| 361 |
+
renderer.draw_line(fb_id, 0, 0, 799, 599, (255, 255, 255))
|
| 362 |
+
renderer.draw_line(fb_id, 799, 0, 0, 599, (255, 255, 255))
|
| 363 |
+
|
| 364 |
+
# Draw a circle
|
| 365 |
+
renderer.draw_circle(fb_id, 400, 300, 50, (255, 255, 0), filled=True)
|
| 366 |
+
|
| 367 |
+
# Draw some pixels
|
| 368 |
+
for i in range(100):
|
| 369 |
+
renderer.draw_pixel(fb_id, 50 + i, 50, (255, 0, 255))
|
| 370 |
+
|
| 371 |
+
# Print statistics
|
| 372 |
+
stats = renderer.get_stats()
|
| 373 |
+
print(f"Renderer stats: {stats}")
|
| 374 |
+
|
| 375 |
+
# Get framebuffer and check a pixel
|
| 376 |
+
fb = vram.get_framebuffer(fb_id)
|
| 377 |
+
if fb:
|
| 378 |
+
pixel = fb.get_pixel(100, 100)
|
| 379 |
+
print(f"Pixel at (100, 100): {pixel}")
|
| 380 |
+
|
| 381 |
+
print("Renderer test completed!")
|
| 382 |
+
|
virtual_gpu_setup/virtual_gpu/shader.py
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Shader Module - Programmable Shader Logic
|
| 3 |
+
|
| 4 |
+
This module provides a mechanism for simulating programmable shader logic,
|
| 5 |
+
allowing custom functions to be applied to pixels or vertices during rendering.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
from typing import Callable, Dict, Any, Tuple, Optional
|
| 10 |
+
from abc import ABC, abstractmethod
|
| 11 |
+
import math
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class Shader(ABC):
|
| 15 |
+
"""Abstract base class for all shaders."""
|
| 16 |
+
|
| 17 |
+
@abstractmethod
|
| 18 |
+
def process_pixel(self, x: int, y: int, color: Tuple[int, int, int],
|
| 19 |
+
**kwargs) -> Tuple[int, int, int]:
|
| 20 |
+
"""Process a single pixel and return the modified color."""
|
| 21 |
+
pass
|
| 22 |
+
|
| 23 |
+
@abstractmethod
|
| 24 |
+
def process_vertex(self, x: float, y: float, z: float = 0.0,
|
| 25 |
+
**kwargs) -> Tuple[float, float, float]:
|
| 26 |
+
"""Process a single vertex and return the modified position."""
|
| 27 |
+
pass
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class PixelShader(Shader):
|
| 31 |
+
"""Base class for pixel shaders that only modify pixel colors."""
|
| 32 |
+
|
| 33 |
+
def process_vertex(self, x: float, y: float, z: float = 0.0,
|
| 34 |
+
**kwargs) -> Tuple[float, float, float]:
|
| 35 |
+
"""Default vertex processing (no change)."""
|
| 36 |
+
return (x, y, z)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class VertexShader(Shader):
|
| 40 |
+
"""Base class for vertex shaders that only modify vertex positions."""
|
| 41 |
+
|
| 42 |
+
def process_pixel(self, x: int, y: int, color: Tuple[int, int, int],
|
| 43 |
+
**kwargs) -> Tuple[int, int, int]:
|
| 44 |
+
"""Default pixel processing (no change)."""
|
| 45 |
+
return color
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class ColorTintShader(PixelShader):
|
| 49 |
+
"""Shader that applies a color tint to all pixels."""
|
| 50 |
+
|
| 51 |
+
def __init__(self, tint_color: Tuple[float, float, float], strength: float = 0.5):
|
| 52 |
+
self.tint_color = tint_color
|
| 53 |
+
self.strength = strength
|
| 54 |
+
|
| 55 |
+
def process_pixel(self, x: int, y: int, color: Tuple[int, int, int],
|
| 56 |
+
**kwargs) -> Tuple[int, int, int]:
|
| 57 |
+
"""Apply color tint to the pixel."""
|
| 58 |
+
r, g, b = color
|
| 59 |
+
tr, tg, tb = self.tint_color
|
| 60 |
+
|
| 61 |
+
# Blend original color with tint
|
| 62 |
+
new_r = int(r * (1 - self.strength) + tr * 255 * self.strength)
|
| 63 |
+
new_g = int(g * (1 - self.strength) + tg * 255 * self.strength)
|
| 64 |
+
new_b = int(b * (1 - self.strength) + tb * 255 * self.strength)
|
| 65 |
+
|
| 66 |
+
# Clamp values
|
| 67 |
+
new_r = max(0, min(255, new_r))
|
| 68 |
+
new_g = max(0, min(255, new_g))
|
| 69 |
+
new_b = max(0, min(255, new_b))
|
| 70 |
+
|
| 71 |
+
return (new_r, new_g, new_b)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class GrayscaleShader(PixelShader):
|
| 75 |
+
"""Shader that converts colors to grayscale."""
|
| 76 |
+
|
| 77 |
+
def process_pixel(self, x: int, y: int, color: Tuple[int, int, int],
|
| 78 |
+
**kwargs) -> Tuple[int, int, int]:
|
| 79 |
+
"""Convert pixel to grayscale."""
|
| 80 |
+
r, g, b = color
|
| 81 |
+
|
| 82 |
+
# Use luminance formula for better grayscale conversion
|
| 83 |
+
gray = int(0.299 * r + 0.587 * g + 0.114 * b)
|
| 84 |
+
gray = max(0, min(255, gray))
|
| 85 |
+
|
| 86 |
+
return (gray, gray, gray)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
class SepiaShader(PixelShader):
|
| 90 |
+
"""Shader that applies a sepia tone effect."""
|
| 91 |
+
|
| 92 |
+
def process_pixel(self, x: int, y: int, color: Tuple[int, int, int],
|
| 93 |
+
**kwargs) -> Tuple[int, int, int]:
|
| 94 |
+
"""Apply sepia tone to the pixel."""
|
| 95 |
+
r, g, b = color
|
| 96 |
+
|
| 97 |
+
# Sepia transformation matrix
|
| 98 |
+
new_r = int(0.393 * r + 0.769 * g + 0.189 * b)
|
| 99 |
+
new_g = int(0.349 * r + 0.686 * g + 0.168 * b)
|
| 100 |
+
new_b = int(0.272 * r + 0.534 * g + 0.131 * b)
|
| 101 |
+
|
| 102 |
+
# Clamp values
|
| 103 |
+
new_r = max(0, min(255, new_r))
|
| 104 |
+
new_g = max(0, min(255, new_g))
|
| 105 |
+
new_b = max(0, min(255, new_b))
|
| 106 |
+
|
| 107 |
+
return (new_r, new_g, new_b)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
class InvertShader(PixelShader):
|
| 111 |
+
"""Shader that inverts pixel colors."""
|
| 112 |
+
|
| 113 |
+
def process_pixel(self, x: int, y: int, color: Tuple[int, int, int],
|
| 114 |
+
**kwargs) -> Tuple[int, int, int]:
|
| 115 |
+
"""Invert pixel colors."""
|
| 116 |
+
r, g, b = color
|
| 117 |
+
return (255 - r, 255 - g, 255 - b)
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
class BrightnessShader(PixelShader):
|
| 121 |
+
"""Shader that adjusts pixel brightness."""
|
| 122 |
+
|
| 123 |
+
def __init__(self, brightness: float = 0.0):
|
| 124 |
+
"""
|
| 125 |
+
Initialize brightness shader.
|
| 126 |
+
|
| 127 |
+
Args:
|
| 128 |
+
brightness: Brightness adjustment (-1.0 to 1.0)
|
| 129 |
+
-1.0 = completely dark, 0.0 = no change, 1.0 = completely bright
|
| 130 |
+
"""
|
| 131 |
+
self.brightness = max(-1.0, min(1.0, brightness))
|
| 132 |
+
|
| 133 |
+
def process_pixel(self, x: int, y: int, color: Tuple[int, int, int],
|
| 134 |
+
**kwargs) -> Tuple[int, int, int]:
|
| 135 |
+
"""Adjust pixel brightness."""
|
| 136 |
+
r, g, b = color
|
| 137 |
+
|
| 138 |
+
if self.brightness >= 0:
|
| 139 |
+
# Brighten
|
| 140 |
+
new_r = int(r + (255 - r) * self.brightness)
|
| 141 |
+
new_g = int(g + (255 - g) * self.brightness)
|
| 142 |
+
new_b = int(b + (255 - b) * self.brightness)
|
| 143 |
+
else:
|
| 144 |
+
# Darken
|
| 145 |
+
new_r = int(r * (1 + self.brightness))
|
| 146 |
+
new_g = int(g * (1 + self.brightness))
|
| 147 |
+
new_b = int(b * (1 + self.brightness))
|
| 148 |
+
|
| 149 |
+
# Clamp values
|
| 150 |
+
new_r = max(0, min(255, new_r))
|
| 151 |
+
new_g = max(0, min(255, new_g))
|
| 152 |
+
new_b = max(0, min(255, new_b))
|
| 153 |
+
|
| 154 |
+
return (new_r, new_g, new_b)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
class ContrastShader(PixelShader):
|
| 158 |
+
"""Shader that adjusts pixel contrast."""
|
| 159 |
+
|
| 160 |
+
def __init__(self, contrast: float = 0.0):
|
| 161 |
+
"""
|
| 162 |
+
Initialize contrast shader.
|
| 163 |
+
|
| 164 |
+
Args:
|
| 165 |
+
contrast: Contrast adjustment (-1.0 to 1.0)
|
| 166 |
+
-1.0 = no contrast, 0.0 = no change, 1.0 = maximum contrast
|
| 167 |
+
"""
|
| 168 |
+
self.contrast = max(-1.0, min(1.0, contrast))
|
| 169 |
+
self.factor = (259 * (self.contrast * 255 + 255)) / (255 * (259 - self.contrast * 255))
|
| 170 |
+
|
| 171 |
+
def process_pixel(self, x: int, y: int, color: Tuple[int, int, int],
|
| 172 |
+
**kwargs) -> Tuple[int, int, int]:
|
| 173 |
+
"""Adjust pixel contrast."""
|
| 174 |
+
r, g, b = color
|
| 175 |
+
|
| 176 |
+
new_r = int(self.factor * (r - 128) + 128)
|
| 177 |
+
new_g = int(self.factor * (g - 128) + 128)
|
| 178 |
+
new_b = int(self.factor * (b - 128) + 128)
|
| 179 |
+
|
| 180 |
+
# Clamp values
|
| 181 |
+
new_r = max(0, min(255, new_r))
|
| 182 |
+
new_g = max(0, min(255, new_g))
|
| 183 |
+
new_b = max(0, min(255, new_b))
|
| 184 |
+
|
| 185 |
+
return (new_r, new_g, new_b)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
class CheckerboardShader(PixelShader):
|
| 189 |
+
"""Shader that creates a checkerboard pattern overlay."""
|
| 190 |
+
|
| 191 |
+
def __init__(self, size: int = 8, color1: Tuple[int, int, int] = (255, 255, 255),
|
| 192 |
+
color2: Tuple[int, int, int] = (0, 0, 0), blend: float = 0.5):
|
| 193 |
+
self.size = size
|
| 194 |
+
self.color1 = color1
|
| 195 |
+
self.color2 = color2
|
| 196 |
+
self.blend = blend
|
| 197 |
+
|
| 198 |
+
def process_pixel(self, x: int, y: int, color: Tuple[int, int, int],
|
| 199 |
+
**kwargs) -> Tuple[int, int, int]:
|
| 200 |
+
"""Apply checkerboard pattern."""
|
| 201 |
+
# Determine which checker square we're in
|
| 202 |
+
checker_x = x // self.size
|
| 203 |
+
checker_y = y // self.size
|
| 204 |
+
|
| 205 |
+
# Determine checker color
|
| 206 |
+
if (checker_x + checker_y) % 2 == 0:
|
| 207 |
+
checker_color = self.color1
|
| 208 |
+
else:
|
| 209 |
+
checker_color = self.color2
|
| 210 |
+
|
| 211 |
+
# Blend with original color
|
| 212 |
+
r, g, b = color
|
| 213 |
+
cr, cg, cb = checker_color
|
| 214 |
+
|
| 215 |
+
new_r = int(r * (1 - self.blend) + cr * self.blend)
|
| 216 |
+
new_g = int(g * (1 - self.blend) + cg * self.blend)
|
| 217 |
+
new_b = int(b * (1 - self.blend) + cb * self.blend)
|
| 218 |
+
|
| 219 |
+
return (new_r, new_g, new_b)
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
class WaveDistortionShader(VertexShader):
|
| 223 |
+
"""Shader that applies wave distortion to vertices."""
|
| 224 |
+
|
| 225 |
+
def __init__(self, amplitude: float = 10.0, frequency: float = 0.1, time: float = 0.0):
|
| 226 |
+
self.amplitude = amplitude
|
| 227 |
+
self.frequency = frequency
|
| 228 |
+
self.time = time
|
| 229 |
+
|
| 230 |
+
def process_vertex(self, x: float, y: float, z: float = 0.0,
|
| 231 |
+
**kwargs) -> Tuple[float, float, float]:
|
| 232 |
+
"""Apply wave distortion to vertex position."""
|
| 233 |
+
# Apply sine wave distortion
|
| 234 |
+
offset_x = self.amplitude * math.sin(y * self.frequency + self.time)
|
| 235 |
+
offset_y = self.amplitude * math.sin(x * self.frequency + self.time)
|
| 236 |
+
|
| 237 |
+
return (x + offset_x, y + offset_y, z)
|
| 238 |
+
|
| 239 |
+
def update_time(self, time: float):
|
| 240 |
+
"""Update the time parameter for animation."""
|
| 241 |
+
self.time = time
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
class ShaderManager:
|
| 245 |
+
"""Manages shader instances and provides shader registry functionality."""
|
| 246 |
+
|
| 247 |
+
def __init__(self):
|
| 248 |
+
self.shaders: Dict[str, Shader] = {}
|
| 249 |
+
self.shader_counter = 0
|
| 250 |
+
|
| 251 |
+
# Register built-in shaders
|
| 252 |
+
self._register_builtin_shaders()
|
| 253 |
+
|
| 254 |
+
def _register_builtin_shaders(self):
|
| 255 |
+
"""Register built-in shader types."""
|
| 256 |
+
self.register_shader("grayscale", GrayscaleShader())
|
| 257 |
+
self.register_shader("sepia", SepiaShader())
|
| 258 |
+
self.register_shader("invert", InvertShader())
|
| 259 |
+
self.register_shader("red_tint", ColorTintShader((1.0, 0.0, 0.0), 0.3))
|
| 260 |
+
self.register_shader("blue_tint", ColorTintShader((0.0, 0.0, 1.0), 0.3))
|
| 261 |
+
self.register_shader("bright", BrightnessShader(0.3))
|
| 262 |
+
self.register_shader("dark", BrightnessShader(-0.3))
|
| 263 |
+
self.register_shader("high_contrast", ContrastShader(0.5))
|
| 264 |
+
self.register_shader("checkerboard", CheckerboardShader())
|
| 265 |
+
|
| 266 |
+
def register_shader(self, name: str, shader: Shader) -> None:
|
| 267 |
+
"""Register a shader with a given name."""
|
| 268 |
+
self.shaders[name] = shader
|
| 269 |
+
|
| 270 |
+
def get_shader(self, name: str) -> Optional[Shader]:
|
| 271 |
+
"""Get a shader by name."""
|
| 272 |
+
return self.shaders.get(name)
|
| 273 |
+
|
| 274 |
+
def create_custom_shader(self, pixel_func: Optional[Callable] = None,
|
| 275 |
+
vertex_func: Optional[Callable] = None,
|
| 276 |
+
name: Optional[str] = None) -> str:
|
| 277 |
+
"""Create a custom shader from functions."""
|
| 278 |
+
if name is None:
|
| 279 |
+
name = f"custom_shader_{self.shader_counter}"
|
| 280 |
+
self.shader_counter += 1
|
| 281 |
+
|
| 282 |
+
class CustomShader(Shader):
|
| 283 |
+
def __init__(self, pf, vf):
|
| 284 |
+
self.pixel_func = pf
|
| 285 |
+
self.vertex_func = vf
|
| 286 |
+
|
| 287 |
+
def process_pixel(self, x: int, y: int, color: Tuple[int, int, int],
|
| 288 |
+
**kwargs) -> Tuple[int, int, int]:
|
| 289 |
+
if self.pixel_func:
|
| 290 |
+
return self.pixel_func(x, y, color, **kwargs)
|
| 291 |
+
return color
|
| 292 |
+
|
| 293 |
+
def process_vertex(self, x: float, y: float, z: float = 0.0,
|
| 294 |
+
**kwargs) -> Tuple[float, float, float]:
|
| 295 |
+
if self.vertex_func:
|
| 296 |
+
return self.vertex_func(x, y, z, **kwargs)
|
| 297 |
+
return (x, y, z)
|
| 298 |
+
|
| 299 |
+
shader = CustomShader(pixel_func, vertex_func)
|
| 300 |
+
self.register_shader(name, shader)
|
| 301 |
+
return name
|
| 302 |
+
|
| 303 |
+
def list_shaders(self) -> list:
|
| 304 |
+
"""Get a list of all registered shader names."""
|
| 305 |
+
return list(self.shaders.keys())
|
| 306 |
+
|
| 307 |
+
def get_stats(self) -> Dict[str, Any]:
|
| 308 |
+
"""Get shader manager statistics."""
|
| 309 |
+
return {
|
| 310 |
+
"total_shaders": len(self.shaders),
|
| 311 |
+
"shader_names": self.list_shaders()
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
if __name__ == "__main__":
|
| 316 |
+
# Test the shader system
|
| 317 |
+
print("Testing Shader System...")
|
| 318 |
+
|
| 319 |
+
# Create shader manager
|
| 320 |
+
shader_manager = ShaderManager()
|
| 321 |
+
|
| 322 |
+
# Test built-in shaders
|
| 323 |
+
test_color = (128, 64, 192)
|
| 324 |
+
test_x, test_y = 100, 50
|
| 325 |
+
|
| 326 |
+
print(f"Original color: {test_color}")
|
| 327 |
+
|
| 328 |
+
# Test each built-in shader
|
| 329 |
+
for shader_name in shader_manager.list_shaders():
|
| 330 |
+
shader = shader_manager.get_shader(shader_name)
|
| 331 |
+
if shader:
|
| 332 |
+
result_color = shader.process_pixel(test_x, test_y, test_color)
|
| 333 |
+
print(f"{shader_name}: {result_color}")
|
| 334 |
+
|
| 335 |
+
# Test custom shader
|
| 336 |
+
def rainbow_pixel(x, y, color, **kwargs):
|
| 337 |
+
"""Custom shader that creates a rainbow effect based on position."""
|
| 338 |
+
r, g, b = color
|
| 339 |
+
|
| 340 |
+
# Create rainbow effect based on x position
|
| 341 |
+
hue = (x % 360) / 360.0
|
| 342 |
+
|
| 343 |
+
# Simple HSV to RGB conversion for rainbow effect
|
| 344 |
+
if hue < 1/6:
|
| 345 |
+
new_r, new_g, new_b = 255, int(255 * hue * 6), 0
|
| 346 |
+
elif hue < 2/6:
|
| 347 |
+
new_r, new_g, new_b = int(255 * (2/6 - hue) * 6), 255, 0
|
| 348 |
+
elif hue < 3/6:
|
| 349 |
+
new_r, new_g, new_b = 0, 255, int(255 * (hue - 2/6) * 6)
|
| 350 |
+
elif hue < 4/6:
|
| 351 |
+
new_r, new_g, new_b = 0, int(255 * (4/6 - hue) * 6), 255
|
| 352 |
+
elif hue < 5/6:
|
| 353 |
+
new_r, new_g, new_b = int(255 * (hue - 4/6) * 6), 0, 255
|
| 354 |
+
else:
|
| 355 |
+
new_r, new_g, new_b = 255, 0, int(255 * (1 - hue) * 6)
|
| 356 |
+
|
| 357 |
+
# Blend with original color
|
| 358 |
+
blend = 0.7
|
| 359 |
+
final_r = int(r * (1 - blend) + new_r * blend)
|
| 360 |
+
final_g = int(g * (1 - blend) + new_g * blend)
|
| 361 |
+
final_b = int(b * (1 - blend) + new_b * blend)
|
| 362 |
+
|
| 363 |
+
return (final_r, final_g, final_b)
|
| 364 |
+
|
| 365 |
+
# Register custom shader
|
| 366 |
+
custom_name = shader_manager.create_custom_shader(pixel_func=rainbow_pixel, name="rainbow")
|
| 367 |
+
custom_shader = shader_manager.get_shader(custom_name)
|
| 368 |
+
|
| 369 |
+
if custom_shader:
|
| 370 |
+
for x in range(0, 360, 60):
|
| 371 |
+
result = custom_shader.process_pixel(x, 0, test_color)
|
| 372 |
+
print(f"Rainbow shader at x={x}: {result}")
|
| 373 |
+
|
| 374 |
+
# Test vertex shader
|
| 375 |
+
wave_shader = WaveDistortionShader(amplitude=5.0, frequency=0.1)
|
| 376 |
+
test_vertex = (100.0, 50.0, 0.0)
|
| 377 |
+
distorted_vertex = wave_shader.process_vertex(*test_vertex)
|
| 378 |
+
print(f"Original vertex: {test_vertex}")
|
| 379 |
+
print(f"Distorted vertex: {distorted_vertex}")
|
| 380 |
+
|
| 381 |
+
# Print statistics
|
| 382 |
+
stats = shader_manager.get_stats()
|
| 383 |
+
print(f"Shader Manager stats: {stats}")
|
| 384 |
+
|
| 385 |
+
print("Shader system test completed!")
|
| 386 |
+
|
virtual_gpu_setup/virtual_gpu/test_output/frame_000000.png
ADDED
|
virtual_gpu_setup/virtual_gpu/test_output/frame_000001.png
ADDED
|
virtual_gpu_setup/virtual_gpu/test_output/frame_000002.png
ADDED
|
virtual_gpu_setup/virtual_gpu/test_output/frame_000003.png
ADDED
|
virtual_gpu_setup/virtual_gpu/test_output/frame_000004.png
ADDED
|
virtual_gpu_setup/virtual_gpu/vgpu.py
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
vGPU Core Processor Module
|
| 3 |
+
|
| 4 |
+
This module implements the central orchestrator of the virtual GPU, managing
|
| 5 |
+
workload distribution across 800 SMs and 50,000 cores, and coordinating
|
| 6 |
+
operations between all other modules.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import asyncio
|
| 10 |
+
import time
|
| 11 |
+
from collections import deque
|
| 12 |
+
from enum import Enum
|
| 13 |
+
from typing import Dict, List, Optional, Any
|
| 14 |
+
from dataclasses import dataclass
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class TaskType(Enum):
|
| 18 |
+
"""Enumeration of task types that can be processed by the vGPU."""
|
| 19 |
+
RENDER_PIXEL_BLOCK = "render_pixel_block"
|
| 20 |
+
RENDER_CLEAR = "render_clear"
|
| 21 |
+
RENDER_RECT = "render_rect"
|
| 22 |
+
RENDER_IMAGE = "render_image"
|
| 23 |
+
AI_MATRIX_MULTIPLY = "ai_matrix_multiply"
|
| 24 |
+
AI_VECTOR_OP = "ai_vector_op"
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class TaskStatus(Enum):
|
| 28 |
+
"""Enumeration of task statuses."""
|
| 29 |
+
PENDING = "pending"
|
| 30 |
+
IN_PROGRESS = "in_progress"
|
| 31 |
+
COMPLETED = "completed"
|
| 32 |
+
FAILED = "failed"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
@dataclass
|
| 36 |
+
class Task:
|
| 37 |
+
"""Represents a single task to be processed by the vGPU."""
|
| 38 |
+
task_id: str
|
| 39 |
+
task_type: TaskType
|
| 40 |
+
payload: Dict[str, Any]
|
| 41 |
+
sm_id: Optional[int] = None
|
| 42 |
+
status: TaskStatus = TaskStatus.PENDING
|
| 43 |
+
created_time: float = 0.0
|
| 44 |
+
start_time: float = 0.0
|
| 45 |
+
end_time: float = 0.0
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class StreamingMultiprocessor:
|
| 49 |
+
"""Represents a single Streaming Multiprocessor (SM) in the vGPU."""
|
| 50 |
+
|
| 51 |
+
def __init__(self, sm_id: int, cores_per_sm: int = 62):
|
| 52 |
+
self.sm_id = sm_id
|
| 53 |
+
self.cores_per_sm = cores_per_sm
|
| 54 |
+
self.task_queue = deque()
|
| 55 |
+
self.current_task: Optional[Task] = None
|
| 56 |
+
self.is_busy = False
|
| 57 |
+
self.total_tasks_processed = 0
|
| 58 |
+
|
| 59 |
+
def add_task(self, task: Task) -> None:
|
| 60 |
+
"""Add a task to this SM's queue."""
|
| 61 |
+
task.sm_id = self.sm_id
|
| 62 |
+
self.task_queue.append(task)
|
| 63 |
+
|
| 64 |
+
def get_next_task(self) -> Optional[Task]:
|
| 65 |
+
"""Get the next task from the queue."""
|
| 66 |
+
if self.task_queue and not self.is_busy:
|
| 67 |
+
task = self.task_queue.popleft()
|
| 68 |
+
self.current_task = task
|
| 69 |
+
self.is_busy = True
|
| 70 |
+
task.status = TaskStatus.IN_PROGRESS
|
| 71 |
+
task.start_time = time.time()
|
| 72 |
+
return task
|
| 73 |
+
return None
|
| 74 |
+
|
| 75 |
+
def complete_task(self) -> Optional[Task]:
|
| 76 |
+
"""Mark the current task as completed."""
|
| 77 |
+
if self.current_task:
|
| 78 |
+
self.current_task.status = TaskStatus.COMPLETED
|
| 79 |
+
self.current_task.end_time = time.time()
|
| 80 |
+
completed_task = self.current_task
|
| 81 |
+
self.current_task = None
|
| 82 |
+
self.is_busy = False
|
| 83 |
+
self.total_tasks_processed += 1
|
| 84 |
+
return completed_task
|
| 85 |
+
return None
|
| 86 |
+
|
| 87 |
+
def get_queue_length(self) -> int:
|
| 88 |
+
"""Get the current queue length."""
|
| 89 |
+
return len(self.task_queue)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class VirtualGPU:
|
| 93 |
+
"""
|
| 94 |
+
The main Virtual GPU class that orchestrates all operations.
|
| 95 |
+
|
| 96 |
+
This class manages 800 SMs with a total of 50,000 cores, handles task
|
| 97 |
+
distribution, and coordinates with other modules like VRAM, renderer, and AI.
|
| 98 |
+
"""
|
| 99 |
+
|
| 100 |
+
def __init__(self, num_sms: int = 800, total_cores: int = 50000):
|
| 101 |
+
self.num_sms = num_sms
|
| 102 |
+
self.total_cores = total_cores
|
| 103 |
+
self.cores_per_sm = total_cores // num_sms
|
| 104 |
+
|
| 105 |
+
# Initialize Streaming Multiprocessors
|
| 106 |
+
self.sms: List[StreamingMultiprocessor] = []
|
| 107 |
+
for i in range(num_sms):
|
| 108 |
+
# Distribute cores evenly, with some SMs getting an extra core if needed
|
| 109 |
+
cores_for_this_sm = self.cores_per_sm
|
| 110 |
+
if i < (total_cores % num_sms):
|
| 111 |
+
cores_for_this_sm += 1
|
| 112 |
+
self.sms.append(StreamingMultiprocessor(i, cores_for_this_sm))
|
| 113 |
+
|
| 114 |
+
# Global task management
|
| 115 |
+
self.pending_tasks = deque()
|
| 116 |
+
self.completed_tasks = deque()
|
| 117 |
+
self.task_counter = 0
|
| 118 |
+
|
| 119 |
+
# GPU state
|
| 120 |
+
self.is_running = False
|
| 121 |
+
self.clock_cycle = 0
|
| 122 |
+
self.tick_rate = 60 # Hz
|
| 123 |
+
|
| 124 |
+
# Module references (to be set by external initialization)
|
| 125 |
+
self.vram = None
|
| 126 |
+
self.renderer = None
|
| 127 |
+
self.ai_accelerator = None
|
| 128 |
+
self.driver = None
|
| 129 |
+
|
| 130 |
+
def set_modules(self, vram, renderer, ai_accelerator, driver):
|
| 131 |
+
"""Set references to other vGPU modules."""
|
| 132 |
+
self.vram = vram
|
| 133 |
+
self.renderer = renderer
|
| 134 |
+
self.ai_accelerator = ai_accelerator
|
| 135 |
+
self.driver = driver
|
| 136 |
+
|
| 137 |
+
def submit_task(self, task_type: TaskType, payload: Dict[str, Any]) -> str:
|
| 138 |
+
"""Submit a new task to the vGPU."""
|
| 139 |
+
task_id = f"task_{self.task_counter}"
|
| 140 |
+
self.task_counter += 1
|
| 141 |
+
|
| 142 |
+
task = Task(
|
| 143 |
+
task_id=task_id,
|
| 144 |
+
task_type=task_type,
|
| 145 |
+
payload=payload,
|
| 146 |
+
created_time=time.time()
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
self.pending_tasks.append(task)
|
| 150 |
+
return task_id
|
| 151 |
+
|
| 152 |
+
def distribute_tasks(self) -> None:
|
| 153 |
+
"""Distribute pending tasks to available SMs using round-robin."""
|
| 154 |
+
sm_index = 0
|
| 155 |
+
max_queue_length = 10 # Prevent any SM from being overloaded
|
| 156 |
+
|
| 157 |
+
while self.pending_tasks:
|
| 158 |
+
# Find an SM that's not overloaded
|
| 159 |
+
attempts = 0
|
| 160 |
+
while attempts < self.num_sms:
|
| 161 |
+
current_sm = self.sms[sm_index]
|
| 162 |
+
if current_sm.get_queue_length() < max_queue_length:
|
| 163 |
+
task = self.pending_tasks.popleft()
|
| 164 |
+
current_sm.add_task(task)
|
| 165 |
+
break
|
| 166 |
+
sm_index = (sm_index + 1) % self.num_sms
|
| 167 |
+
attempts += 1
|
| 168 |
+
|
| 169 |
+
if attempts >= self.num_sms:
|
| 170 |
+
# All SMs are overloaded, break to avoid infinite loop
|
| 171 |
+
break
|
| 172 |
+
|
| 173 |
+
sm_index = (sm_index + 1) % self.num_sms
|
| 174 |
+
|
| 175 |
+
def process_sm_tasks(self) -> None:
|
| 176 |
+
"""Process tasks on all SMs."""
|
| 177 |
+
for sm in self.sms:
|
| 178 |
+
# Start a new task if the SM is idle
|
| 179 |
+
if not sm.is_busy:
|
| 180 |
+
task = sm.get_next_task()
|
| 181 |
+
if task:
|
| 182 |
+
# Task will be processed in the next step
|
| 183 |
+
pass
|
| 184 |
+
|
| 185 |
+
# Process the current task (simulate work completion)
|
| 186 |
+
if sm.current_task:
|
| 187 |
+
# Simulate task processing by calling appropriate module
|
| 188 |
+
self._execute_task(sm.current_task)
|
| 189 |
+
completed_task = sm.complete_task()
|
| 190 |
+
if completed_task:
|
| 191 |
+
self.completed_tasks.append(completed_task)
|
| 192 |
+
|
| 193 |
+
def _execute_task(self, task: Task) -> None:
|
| 194 |
+
"""Execute a specific task by calling the appropriate module."""
|
| 195 |
+
try:
|
| 196 |
+
if task.task_type == TaskType.RENDER_CLEAR and self.renderer:
|
| 197 |
+
self.renderer.clear(**task.payload)
|
| 198 |
+
elif task.task_type == TaskType.RENDER_RECT and self.renderer:
|
| 199 |
+
self.renderer.draw_rect(**task.payload)
|
| 200 |
+
elif task.task_type == TaskType.RENDER_IMAGE and self.renderer:
|
| 201 |
+
self.renderer.draw_image(**task.payload)
|
| 202 |
+
elif task.task_type == TaskType.AI_MATRIX_MULTIPLY and self.ai_accelerator:
|
| 203 |
+
self.ai_accelerator.matrix_multiply(**task.payload)
|
| 204 |
+
elif task.task_type == TaskType.AI_VECTOR_OP and self.ai_accelerator:
|
| 205 |
+
self.ai_accelerator.vector_operation(**task.payload)
|
| 206 |
+
else:
|
| 207 |
+
print(f"Unknown task type: {task.task_type}")
|
| 208 |
+
task.status = TaskStatus.FAILED
|
| 209 |
+
except Exception as e:
|
| 210 |
+
print(f"Error executing task {task.task_id}: {e}")
|
| 211 |
+
task.status = TaskStatus.FAILED
|
| 212 |
+
|
| 213 |
+
async def tick(self) -> None:
|
| 214 |
+
"""Main GPU tick cycle."""
|
| 215 |
+
self.clock_cycle += 1
|
| 216 |
+
|
| 217 |
+
# 1. Distribute pending tasks to SMs
|
| 218 |
+
self.distribute_tasks()
|
| 219 |
+
|
| 220 |
+
# 2. Process tasks on all SMs
|
| 221 |
+
self.process_sm_tasks()
|
| 222 |
+
|
| 223 |
+
# 3. Handle any driver commands
|
| 224 |
+
if self.driver:
|
| 225 |
+
await self.driver.process_commands()
|
| 226 |
+
|
| 227 |
+
async def run(self) -> None:
|
| 228 |
+
"""Main GPU execution loop."""
|
| 229 |
+
self.is_running = True
|
| 230 |
+
tick_interval = 1.0 / self.tick_rate
|
| 231 |
+
|
| 232 |
+
print(f"Starting vGPU with {self.num_sms} SMs and {self.total_cores} cores")
|
| 233 |
+
print(f"Tick rate: {self.tick_rate} Hz")
|
| 234 |
+
|
| 235 |
+
while self.is_running:
|
| 236 |
+
start_time = time.time()
|
| 237 |
+
|
| 238 |
+
await self.tick()
|
| 239 |
+
|
| 240 |
+
# Maintain consistent tick rate
|
| 241 |
+
elapsed = time.time() - start_time
|
| 242 |
+
if elapsed < tick_interval:
|
| 243 |
+
await asyncio.sleep(tick_interval - elapsed)
|
| 244 |
+
|
| 245 |
+
def stop(self) -> None:
|
| 246 |
+
"""Stop the GPU execution."""
|
| 247 |
+
self.is_running = False
|
| 248 |
+
|
| 249 |
+
def get_stats(self) -> Dict[str, Any]:
|
| 250 |
+
"""Get current GPU statistics."""
|
| 251 |
+
total_tasks_processed = sum(sm.total_tasks_processed for sm in self.sms)
|
| 252 |
+
total_queue_length = sum(sm.get_queue_length() for sm in self.sms)
|
| 253 |
+
busy_sms = sum(1 for sm in self.sms if sm.is_busy)
|
| 254 |
+
|
| 255 |
+
return {
|
| 256 |
+
"clock_cycle": self.clock_cycle,
|
| 257 |
+
"total_sms": self.num_sms,
|
| 258 |
+
"total_cores": self.total_cores,
|
| 259 |
+
"busy_sms": busy_sms,
|
| 260 |
+
"total_tasks_processed": total_tasks_processed,
|
| 261 |
+
"pending_tasks": len(self.pending_tasks),
|
| 262 |
+
"total_queue_length": total_queue_length,
|
| 263 |
+
"completed_tasks": len(self.completed_tasks)
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
if __name__ == "__main__":
|
| 268 |
+
# Basic test of the vGPU
|
| 269 |
+
async def test_vgpu():
|
| 270 |
+
vgpu = VirtualGPU()
|
| 271 |
+
|
| 272 |
+
# Submit some test tasks
|
| 273 |
+
vgpu.submit_task(TaskType.RENDER_CLEAR, {"color": (255, 0, 0)})
|
| 274 |
+
vgpu.submit_task(TaskType.RENDER_RECT, {"x": 10, "y": 10, "width": 100, "height": 50, "color": (0, 255, 0)})
|
| 275 |
+
|
| 276 |
+
# Run a few ticks
|
| 277 |
+
for _ in range(5):
|
| 278 |
+
await vgpu.tick()
|
| 279 |
+
print(f"Stats: {vgpu.get_stats()}")
|
| 280 |
+
await asyncio.sleep(0.1)
|
| 281 |
+
|
| 282 |
+
asyncio.run(test_vgpu())
|
| 283 |
+
|
virtual_gpu_setup/virtual_gpu/virtual_ram.py
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Virtual RAM Module - 128GB System Memory Abstraction
|
| 3 |
+
|
| 4 |
+
This module implements a symbolic representation of 128GB system RAM using
|
| 5 |
+
efficient data structures and lazy allocation strategies. It avoids allocating
|
| 6 |
+
real memory and uses dictionaries or sparse mappings to simulate blocks.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import time
|
| 10 |
+
from typing import Dict, Any, Optional, Union
|
| 11 |
+
from dataclasses import dataclass
|
| 12 |
+
import numpy as np
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@dataclass
|
| 16 |
+
class RAMBlock:
|
| 17 |
+
"""Represents a block of memory in the symbolic RAM."""
|
| 18 |
+
name: str
|
| 19 |
+
size_bytes: int
|
| 20 |
+
allocated_time: float
|
| 21 |
+
last_accessed: float
|
| 22 |
+
access_count: int = 0
|
| 23 |
+
# We use a symbolic representation instead of actual data
|
| 24 |
+
# The data field will be None for large blocks to avoid memory allocation
|
| 25 |
+
data: Optional[Union[np.ndarray, bytes]] = None
|
| 26 |
+
is_symbolic: bool = True # True if this is a symbolic block (no real data)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class VirtualRAM:
|
| 30 |
+
"""
|
| 31 |
+
Virtual RAM class that simulates 128GB of system memory symbolically.
|
| 32 |
+
|
| 33 |
+
This class provides block allocation, tracking, and transfer capabilities
|
| 34 |
+
without actually allocating large amounts of physical memory.
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
def __init__(self, capacity_gb: int = 128):
|
| 38 |
+
self.capacity_bytes = capacity_gb * 1024 * 1024 * 1024 # Convert GB to bytes
|
| 39 |
+
self.capacity_gb = capacity_gb
|
| 40 |
+
|
| 41 |
+
# Block registry - stores metadata about allocated blocks
|
| 42 |
+
self.blocks: Dict[str, RAMBlock] = {}
|
| 43 |
+
|
| 44 |
+
# Memory usage tracking
|
| 45 |
+
self.allocated_bytes = 0
|
| 46 |
+
self.allocation_counter = 0
|
| 47 |
+
|
| 48 |
+
# Access simulation parameters
|
| 49 |
+
self.access_delay_ms = 0.1 # Simulated RAM access delay
|
| 50 |
+
self.transfer_bandwidth_gbps = 51.2 # DDR5-6400 bandwidth
|
| 51 |
+
|
| 52 |
+
# Statistics
|
| 53 |
+
self.total_allocations = 0
|
| 54 |
+
self.total_deallocations = 0
|
| 55 |
+
self.total_accesses = 0
|
| 56 |
+
self.total_transfers = 0
|
| 57 |
+
|
| 58 |
+
print(f"VirtualRAM initialized with {capacity_gb}GB capacity")
|
| 59 |
+
|
| 60 |
+
def allocate_block(self, name: str, size_bytes: int,
|
| 61 |
+
store_data: bool = False) -> bool:
|
| 62 |
+
"""
|
| 63 |
+
Allocate a block of memory symbolically.
|
| 64 |
+
|
| 65 |
+
Args:
|
| 66 |
+
name: Unique name for the block
|
| 67 |
+
size_bytes: Size of the block in bytes
|
| 68 |
+
store_data: If True, actually allocate small amounts of real data for testing
|
| 69 |
+
If False (default), only store metadata symbolically
|
| 70 |
+
|
| 71 |
+
Returns:
|
| 72 |
+
True if allocation successful, False if not enough space or name exists
|
| 73 |
+
"""
|
| 74 |
+
# Check if name already exists
|
| 75 |
+
if name in self.blocks:
|
| 76 |
+
print(f"Block '{name}' already exists")
|
| 77 |
+
return False
|
| 78 |
+
|
| 79 |
+
# Check if we have enough capacity
|
| 80 |
+
if self.allocated_bytes + size_bytes > self.capacity_bytes:
|
| 81 |
+
print(f"Not enough capacity: requested {size_bytes:,} bytes, "
|
| 82 |
+
f"available {self.capacity_bytes - self.allocated_bytes:,} bytes")
|
| 83 |
+
return False
|
| 84 |
+
|
| 85 |
+
# Create the block
|
| 86 |
+
current_time = time.time()
|
| 87 |
+
# For all blocks, we store only metadata to avoid memory issues
|
| 88 |
+
actual_data = None
|
| 89 |
+
is_symbolic = True
|
| 90 |
+
|
| 91 |
+
# If store_data is explicitly requested and size is very small, we can store actual data
|
| 92 |
+
if store_data and size_bytes <= 1024 * 1024 * 10: # Up to 10MB for actual data
|
| 93 |
+
actual_data = np.zeros(size_bytes, dtype=np.uint8)
|
| 94 |
+
is_symbolic = False
|
| 95 |
+
print(f"Allocated real data for block \'{name}\' ({size_bytes:,} bytes)")
|
| 96 |
+
else:
|
| 97 |
+
print(f"Created symbolic block \'{name}\' of {size_bytes:,} bytes")
|
| 98 |
+
block = RAMBlock(
|
| 99 |
+
name=name,
|
| 100 |
+
size_bytes=size_bytes,
|
| 101 |
+
allocated_time=current_time,
|
| 102 |
+
last_accessed=current_time,
|
| 103 |
+
data=actual_data,
|
| 104 |
+
is_symbolic=is_symbolic
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
self.blocks[name] = block
|
| 108 |
+
self.allocated_bytes += size_bytes
|
| 109 |
+
self.total_allocations += 1
|
| 110 |
+
self.allocation_counter += 1
|
| 111 |
+
|
| 112 |
+
print(f"Allocated block '{name}': {size_bytes:,} bytes "
|
| 113 |
+
f"({'symbolic' if is_symbolic else 'real data'})")
|
| 114 |
+
|
| 115 |
+
return True
|
| 116 |
+
|
| 117 |
+
def get_block(self, name: str) -> Optional[RAMBlock]:
|
| 118 |
+
"""
|
| 119 |
+
Retrieve a block by name and simulate access delay.
|
| 120 |
+
|
| 121 |
+
Args:
|
| 122 |
+
name: Name of the block to retrieve
|
| 123 |
+
|
| 124 |
+
Returns:
|
| 125 |
+
RAMBlock if found, None otherwise
|
| 126 |
+
"""
|
| 127 |
+
if name not in self.blocks:
|
| 128 |
+
return None
|
| 129 |
+
|
| 130 |
+
# Simulate access delay
|
| 131 |
+
time.sleep(self.access_delay_ms / 1000.0)
|
| 132 |
+
|
| 133 |
+
# Update access statistics
|
| 134 |
+
block = self.blocks[name]
|
| 135 |
+
block.last_accessed = time.time()
|
| 136 |
+
block.access_count += 1
|
| 137 |
+
self.total_accesses += 1
|
| 138 |
+
|
| 139 |
+
return block
|
| 140 |
+
|
| 141 |
+
def release_block(self, name: str) -> bool:
|
| 142 |
+
"""
|
| 143 |
+
Deallocate a block of memory.
|
| 144 |
+
|
| 145 |
+
Args:
|
| 146 |
+
name: Name of the block to deallocate
|
| 147 |
+
|
| 148 |
+
Returns:
|
| 149 |
+
True if deallocation successful, False if block not found
|
| 150 |
+
"""
|
| 151 |
+
if name not in self.blocks:
|
| 152 |
+
print(f"Block '{name}' not found")
|
| 153 |
+
return False
|
| 154 |
+
|
| 155 |
+
block = self.blocks[name]
|
| 156 |
+
self.allocated_bytes -= block.size_bytes
|
| 157 |
+
self.total_deallocations += 1
|
| 158 |
+
|
| 159 |
+
del self.blocks[name]
|
| 160 |
+
|
| 161 |
+
print(f"Released block '{name}': {block.size_bytes:,} bytes")
|
| 162 |
+
return True
|
| 163 |
+
|
| 164 |
+
def transfer_to_vram(self, block_name: str, vram_instance,
|
| 165 |
+
vram_name: Optional[str] = None) -> Optional[str]:
|
| 166 |
+
"""
|
| 167 |
+
Transfer a RAM block to VRAM with delay simulation.
|
| 168 |
+
|
| 169 |
+
Args:
|
| 170 |
+
block_name: Name of the RAM block to transfer
|
| 171 |
+
vram_instance: Instance of VRAM to transfer to
|
| 172 |
+
vram_name: Optional name for the block in VRAM
|
| 173 |
+
|
| 174 |
+
Returns:
|
| 175 |
+
VRAM block ID if successful, None otherwise
|
| 176 |
+
"""
|
| 177 |
+
# Get the block from RAM
|
| 178 |
+
block = self.get_block(block_name)
|
| 179 |
+
if block is None:
|
| 180 |
+
print(f"Block '{block_name}' not found in RAM")
|
| 181 |
+
return None
|
| 182 |
+
|
| 183 |
+
# Calculate transfer time based on bandwidth
|
| 184 |
+
transfer_time_ms = (block.size_bytes / (self.transfer_bandwidth_gbps * 1e9)) * 1000
|
| 185 |
+
|
| 186 |
+
print(f"Transferring '{block_name}' ({block.size_bytes:,} bytes) "
|
| 187 |
+
f"from RAM to VRAM (estimated {transfer_time_ms:.2f}ms)")
|
| 188 |
+
|
| 189 |
+
# Prepare data for transfer
|
| 190 |
+
if block.is_symbolic:
|
| 191 |
+
# For symbolic blocks, create a small representative data sample
|
| 192 |
+
sample_size = min(1024, block.size_bytes) # 1KB sample
|
| 193 |
+
transfer_data = np.random.randint(0, 256, sample_size, dtype=np.uint8)
|
| 194 |
+
print(f"Using {sample_size} byte sample for symbolic block transfer")
|
| 195 |
+
else:
|
| 196 |
+
# Use actual data
|
| 197 |
+
transfer_data = block.data
|
| 198 |
+
|
| 199 |
+
# Perform the transfer to VRAM
|
| 200 |
+
if vram_name is None:
|
| 201 |
+
vram_name = f"ram_transfer_{block_name}"
|
| 202 |
+
|
| 203 |
+
vram_id = vram_instance.transfer_from_ram(vram_name, transfer_data,
|
| 204 |
+
delay_ms=transfer_time_ms)
|
| 205 |
+
|
| 206 |
+
if vram_id:
|
| 207 |
+
self.total_transfers += 1
|
| 208 |
+
print(f"Successfully transferred '{block_name}' to VRAM as '{vram_id}'")
|
| 209 |
+
else:
|
| 210 |
+
print(f"Failed to transfer '{block_name}' to VRAM")
|
| 211 |
+
|
| 212 |
+
return vram_id
|
| 213 |
+
|
| 214 |
+
def create_tensor_block(self, name: str, shape: tuple, dtype=np.float32) -> bool:
|
| 215 |
+
"""
|
| 216 |
+
Create a tensor block with specified shape and data type.
|
| 217 |
+
|
| 218 |
+
Args:
|
| 219 |
+
name: Name for the tensor block
|
| 220 |
+
shape: Shape of the tensor (e.g., (1024, 1024, 3))
|
| 221 |
+
dtype: Data type of the tensor
|
| 222 |
+
|
| 223 |
+
Returns:
|
| 224 |
+
True if creation successful, False otherwise
|
| 225 |
+
"""
|
| 226 |
+
# Calculate size in bytes
|
| 227 |
+
element_size = np.dtype(dtype).itemsize
|
| 228 |
+
total_elements = np.prod(shape)
|
| 229 |
+
size_bytes = total_elements * element_size
|
| 230 |
+
|
| 231 |
+
# Allocate the block symbolically
|
| 232 |
+
success = self.allocate_block(name, size_bytes, store_data=False)
|
| 233 |
+
|
| 234 |
+
if success:
|
| 235 |
+
# Store tensor metadata
|
| 236 |
+
block = self.blocks[name]
|
| 237 |
+
block.tensor_shape = shape
|
| 238 |
+
block.tensor_dtype = dtype
|
| 239 |
+
print(f"Created tensor block '{name}' with shape {shape} and dtype {dtype}")
|
| 240 |
+
|
| 241 |
+
return success
|
| 242 |
+
|
| 243 |
+
def info(self) -> Dict[str, Any]:
|
| 244 |
+
"""
|
| 245 |
+
Get comprehensive information about the Virtual RAM state.
|
| 246 |
+
|
| 247 |
+
Returns:
|
| 248 |
+
Dictionary containing RAM usage statistics and metadata
|
| 249 |
+
"""
|
| 250 |
+
used_bytes = self.allocated_bytes
|
| 251 |
+
free_bytes = self.capacity_bytes - used_bytes
|
| 252 |
+
utilization_percent = (used_bytes / self.capacity_bytes) * 100
|
| 253 |
+
|
| 254 |
+
# Calculate average block size
|
| 255 |
+
avg_block_size = used_bytes / len(self.blocks) if self.blocks else 0
|
| 256 |
+
|
| 257 |
+
# Find largest and smallest blocks
|
| 258 |
+
largest_block = max(self.blocks.values(), key=lambda b: b.size_bytes) if self.blocks else None
|
| 259 |
+
smallest_block = min(self.blocks.values(), key=lambda b: b.size_bytes) if self.blocks else None
|
| 260 |
+
|
| 261 |
+
# Count symbolic vs real blocks
|
| 262 |
+
symbolic_blocks = sum(1 for b in self.blocks.values() if b.is_symbolic)
|
| 263 |
+
real_blocks = len(self.blocks) - symbolic_blocks
|
| 264 |
+
|
| 265 |
+
info_dict = {
|
| 266 |
+
"capacity_gb": self.capacity_gb,
|
| 267 |
+
"capacity_bytes": self.capacity_bytes,
|
| 268 |
+
"used_bytes": used_bytes,
|
| 269 |
+
"free_bytes": free_bytes,
|
| 270 |
+
"utilization_percent": utilization_percent,
|
| 271 |
+
"total_blocks": len(self.blocks),
|
| 272 |
+
"symbolic_blocks": symbolic_blocks,
|
| 273 |
+
"real_data_blocks": real_blocks,
|
| 274 |
+
"avg_block_size_bytes": avg_block_size,
|
| 275 |
+
"largest_block_name": largest_block.name if largest_block else None,
|
| 276 |
+
"largest_block_size": largest_block.size_bytes if largest_block else 0,
|
| 277 |
+
"smallest_block_name": smallest_block.name if smallest_block else None,
|
| 278 |
+
"smallest_block_size": smallest_block.size_bytes if smallest_block else 0,
|
| 279 |
+
"total_allocations": self.total_allocations,
|
| 280 |
+
"total_deallocations": self.total_deallocations,
|
| 281 |
+
"total_accesses": self.total_accesses,
|
| 282 |
+
"total_transfers": self.total_transfers,
|
| 283 |
+
"block_names": list(self.blocks.keys())
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
return info_dict
|
| 287 |
+
|
| 288 |
+
def print_info(self) -> None:
|
| 289 |
+
"""Print a formatted summary of Virtual RAM information."""
|
| 290 |
+
info = self.info()
|
| 291 |
+
|
| 292 |
+
print("\n" + "="*50)
|
| 293 |
+
print("VIRTUAL RAM INFORMATION")
|
| 294 |
+
print("="*50)
|
| 295 |
+
print(f"Capacity: {info['capacity_gb']} GB ({info['capacity_bytes']:,} bytes)")
|
| 296 |
+
print(f"Used: {info['used_bytes']:,} bytes ({info['utilization_percent']:.2f}%)")
|
| 297 |
+
print(f"Free: {info['free_bytes']:,} bytes")
|
| 298 |
+
print(f"Total Blocks: {info['total_blocks']}")
|
| 299 |
+
print(f" - Symbolic blocks: {info['symbolic_blocks']}")
|
| 300 |
+
print(f" - Real data blocks: {info['real_data_blocks']}")
|
| 301 |
+
|
| 302 |
+
if info['total_blocks'] > 0:
|
| 303 |
+
print(f"Average block size: {info['avg_block_size_bytes']:,.0f} bytes")
|
| 304 |
+
print(f"Largest block: '{info['largest_block_name']}' ({info['largest_block_size']:,} bytes)")
|
| 305 |
+
print(f"Smallest block: '{info['smallest_block_name']}' ({info['smallest_block_size']:,} bytes)")
|
| 306 |
+
|
| 307 |
+
print(f"\nStatistics:")
|
| 308 |
+
print(f" - Total allocations: {info['total_allocations']}")
|
| 309 |
+
print(f" - Total deallocations: {info['total_deallocations']}")
|
| 310 |
+
print(f" - Total accesses: {info['total_accesses']}")
|
| 311 |
+
print(f" - Total transfers: {info['total_transfers']}")
|
| 312 |
+
|
| 313 |
+
if info['block_names']:
|
| 314 |
+
print(f"\nBlock names: {', '.join(info['block_names'])}")
|
| 315 |
+
|
| 316 |
+
print("="*50)
|
| 317 |
+
|
| 318 |
+
def simulate_workload(self, num_operations: int = 100) -> None:
|
| 319 |
+
"""
|
| 320 |
+
Simulate a typical workload with allocations, accesses, and deallocations.
|
| 321 |
+
|
| 322 |
+
Args:
|
| 323 |
+
num_operations: Number of operations to simulate
|
| 324 |
+
"""
|
| 325 |
+
print(f"\nSimulating workload with {num_operations} operations...")
|
| 326 |
+
|
| 327 |
+
import random
|
| 328 |
+
|
| 329 |
+
for i in range(num_operations):
|
| 330 |
+
operation = random.choice(['allocate', 'access', 'deallocate'])
|
| 331 |
+
|
| 332 |
+
if operation == 'allocate' and len(self.blocks) < 50: # Limit to 50 blocks
|
| 333 |
+
size = random.randint(1024, 100 * 1024 * 1024) # 1KB to 100MB
|
| 334 |
+
name = f"workload_block_{i}"
|
| 335 |
+
self.allocate_block(name, size)
|
| 336 |
+
|
| 337 |
+
elif operation == 'access' and self.blocks:
|
| 338 |
+
block_name = random.choice(list(self.blocks.keys()))
|
| 339 |
+
self.get_block(block_name)
|
| 340 |
+
|
| 341 |
+
elif operation == 'deallocate' and self.blocks:
|
| 342 |
+
block_name = random.choice(list(self.blocks.keys()))
|
| 343 |
+
self.release_block(block_name)
|
| 344 |
+
|
| 345 |
+
print(f"Workload simulation completed.")
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
if __name__ == "__main__":
|
| 349 |
+
# Test the VirtualRAM module
|
| 350 |
+
print("Testing VirtualRAM module...")
|
| 351 |
+
|
| 352 |
+
# Create a VirtualRAM instance with 128GB capacity
|
| 353 |
+
vram = VirtualRAM(capacity_gb=128)
|
| 354 |
+
|
| 355 |
+
# Test basic allocation
|
| 356 |
+
print("\n1. Testing basic allocation...")
|
| 357 |
+
vram.allocate_block("small_buffer", 1024 * 1024, store_data=True) # 1MB with real data
|
| 358 |
+
vram.allocate_block("medium_buffer", 50 * 1024 * 1024) # 50MB symbolic
|
| 359 |
+
vram.allocate_block("large_tensor", 16 * 1024 * 1024 * 1024) # 16GB symbolic
|
| 360 |
+
|
| 361 |
+
# Test tensor creation
|
| 362 |
+
print("\n2. Testing tensor creation...")
|
| 363 |
+
vram.create_tensor_block("ai_weights", (1000, 1000, 512), np.float32)
|
| 364 |
+
vram.create_tensor_block("image_batch", (32, 224, 224, 3), np.uint8)
|
| 365 |
+
|
| 366 |
+
# Test block access
|
| 367 |
+
print("\n3. Testing block access...")
|
| 368 |
+
block = vram.get_block("small_buffer")
|
| 369 |
+
if block:
|
| 370 |
+
print(f"Accessed block: {block.name}, size: {block.size_bytes:,} bytes")
|
| 371 |
+
|
| 372 |
+
# Test info display
|
| 373 |
+
print("\n4. Testing info display...")
|
| 374 |
+
vram.print_info()
|
| 375 |
+
|
| 376 |
+
# Test workload simulation
|
| 377 |
+
print("\n5. Testing workload simulation...")
|
| 378 |
+
vram.simulate_workload(20)
|
| 379 |
+
|
| 380 |
+
# Final info
|
| 381 |
+
print("\n6. Final state...")
|
| 382 |
+
vram.print_info()
|
| 383 |
+
|
| 384 |
+
print("\nVirtualRAM test completed!")
|
| 385 |
+
|
virtual_gpu_setup/virtual_gpu/vram.py
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from collections import OrderedDict
|
| 3 |
+
from typing import Dict, Any, Optional, Tuple, Union
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
import time
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@dataclass
|
| 9 |
+
class MemoryBlock:
|
| 10 |
+
"""Represents a block of memory in the symbolic VRAM."""
|
| 11 |
+
address: int
|
| 12 |
+
size: int
|
| 13 |
+
data: Optional[Any]
|
| 14 |
+
allocated_time: float
|
| 15 |
+
last_accessed: float
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class Framebuffer:
|
| 19 |
+
"""Represents a 2D drawing surface in VRAM."""
|
| 20 |
+
|
| 21 |
+
def __init__(self, width: int, height: int, channels: int = 3, dtype=np.uint8):
|
| 22 |
+
self.width = width
|
| 23 |
+
self.height = height
|
| 24 |
+
self.channels = channels
|
| 25 |
+
self.dtype = dtype
|
| 26 |
+
|
| 27 |
+
# Create the pixel buffer symbolically to avoid large allocations
|
| 28 |
+
# The actual pixel data will be managed by the MemoryManager
|
| 29 |
+
self.pixel_buffer_address: Optional[int] = None
|
| 30 |
+
self.pixel_buffer_size: int = width * height * channels * np.dtype(dtype).itemsize
|
| 31 |
+
self.pixel_buffer = np.zeros((height, width, channels), dtype=dtype)
|
| 32 |
+
self.vram_address: Optional[int] = None # This is the address in the MemoryManager
|
| 33 |
+
|
| 34 |
+
def resize(self, new_width: int, new_height: int) -> None:
|
| 35 |
+
# No actual data to resize, just update symbolic size
|
| 36 |
+
self.width = new_width
|
| 37 |
+
self.height = new_height
|
| 38 |
+
self.pixel_buffer_size = new_width * new_height * self.channels * np.dtype(self.dtype).itemsize
|
| 39 |
+
|
| 40 |
+
def clear(self, color: Tuple[int, int, int]) -> None:
|
| 41 |
+
self.pixel_buffer[:, :] = color
|
| 42 |
+
|
| 43 |
+
def get_pixel(self, x: int, y: int) -> np.ndarray:
|
| 44 |
+
if 0 <= x < self.width and 0 <= y < self.height:
|
| 45 |
+
return self.pixel_buffer[y, x]
|
| 46 |
+
return np.zeros(self.channels, dtype=self.dtype)
|
| 47 |
+
def set_pixel(self, x: int, y: int, color: Tuple[int, int, int]) -> None:
|
| 48 |
+
if 0 <= x < self.width and 0 <= y < self.height:
|
| 49 |
+
self.pixel_buffer[y, x] = color[:self.channels]
|
| 50 |
+
|
| 51 |
+
def get_memory_usage(self) -> int:
|
| 52 |
+
"""Get the memory usage of this framebuffer in bytes."""
|
| 53 |
+
return self.pixel_buffer_size
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class MemoryManager:
|
| 57 |
+
"""Manages the symbolic 500GB GDDR7 memory space."""
|
| 58 |
+
|
| 59 |
+
def __init__(self, total_memory_gb: int = 500, block_size_kb: int = 4):
|
| 60 |
+
self.total_memory_bytes = total_memory_gb * 1024 * 1024 * 1024 # 500GB
|
| 61 |
+
self.block_size_bytes = block_size_kb * 1024 # 4KB blocks
|
| 62 |
+
self.total_blocks = self.total_memory_bytes // self.block_size_bytes
|
| 63 |
+
|
| 64 |
+
# Symbolic memory space - only allocated blocks are stored
|
| 65 |
+
self.memory_blocks: Dict[int, MemoryBlock] = {}
|
| 66 |
+
|
| 67 |
+
# Free block tracking - use a list of free block ranges instead of a set of all blocks
|
| 68 |
+
self.free_block_ranges = [(0, self.total_blocks - 1)] # (start_block_id, end_block_id)
|
| 69 |
+
self.allocated_blocks = set() # Still track allocated blocks for quick lookup
|
| 70 |
+
|
| 71 |
+
# Address allocation counter
|
| 72 |
+
self.next_address = 0
|
| 73 |
+
|
| 74 |
+
def allocate_block(self, size_bytes: int) -> Optional[int]:
|
| 75 |
+
"""Allocate a block of memory and return its address."""
|
| 76 |
+
blocks_needed = (size_bytes + self.block_size_bytes - 1) // self.block_size_bytes
|
| 77 |
+
|
| 78 |
+
# Find a suitable contiguous block range
|
| 79 |
+
for i, (start, end) in enumerate(self.free_block_ranges):
|
| 80 |
+
available_blocks = end - start + 1
|
| 81 |
+
if available_blocks >= blocks_needed:
|
| 82 |
+
# Found a suitable range
|
| 83 |
+
base_block_id = start
|
| 84 |
+
|
| 85 |
+
# Update free_block_ranges
|
| 86 |
+
new_start = start + blocks_needed
|
| 87 |
+
if new_start <= end:
|
| 88 |
+
self.free_block_ranges[i] = (new_start, end)
|
| 89 |
+
else:
|
| 90 |
+
self.free_block_ranges.pop(i)
|
| 91 |
+
|
| 92 |
+
# Add to allocated_blocks
|
| 93 |
+
for j in range(blocks_needed):
|
| 94 |
+
self.allocated_blocks.add(base_block_id + j)
|
| 95 |
+
|
| 96 |
+
# Create memory block
|
| 97 |
+
base_address = base_block_id * self.block_size_bytes
|
| 98 |
+
|
| 99 |
+
memory_block = MemoryBlock(
|
| 100 |
+
address=base_address,
|
| 101 |
+
size=size_bytes,
|
| 102 |
+
data=bytearray(size_bytes), # Allocate actual bytearray for data
|
| 103 |
+
allocated_time=time.time(),
|
| 104 |
+
last_accessed=time.time()
|
| 105 |
+
)
|
| 106 |
+
self.memory_blocks[base_address] = memory_block
|
| 107 |
+
return base_address
|
| 108 |
+
|
| 109 |
+
return None # Out of memory
|
| 110 |
+
|
| 111 |
+
def deallocate_block(self, address: int) -> bool:
|
| 112 |
+
"""Deallocate a block of memory."""
|
| 113 |
+
if address in self.memory_blocks:
|
| 114 |
+
memory_block = self.memory_blocks[address]
|
| 115 |
+
blocks_to_free = (memory_block.size + self.block_size_bytes - 1) // self.block_size_bytes
|
| 116 |
+
|
| 117 |
+
base_block_id = address // self.block_size_bytes
|
| 118 |
+
for i in range(blocks_to_free):
|
| 119 |
+
block_id = base_block_id + i
|
| 120 |
+
if block_id in self.allocated_blocks:
|
| 121 |
+
self.allocated_blocks.remove(block_id)
|
| 122 |
+
# Add back to free_block_ranges (simple merge for now)
|
| 123 |
+
self.free_block_ranges.append((block_id, block_id))
|
| 124 |
+
self.free_block_ranges.sort() # Keep sorted for efficient merging
|
| 125 |
+
|
| 126 |
+
del self.memory_blocks[address]
|
| 127 |
+
return True
|
| 128 |
+
return False
|
| 129 |
+
|
| 130 |
+
def read_data(self, address: int, size: int) -> Optional[np.ndarray]:
|
| 131 |
+
"""Read data from memory."""
|
| 132 |
+
if address in self.memory_blocks:
|
| 133 |
+
memory_block = self.memory_blocks[address]
|
| 134 |
+
if memory_block.data is not None and size <= memory_block.size:
|
| 135 |
+
return np.frombuffer(memory_block.data[:size], dtype=np.uint8) # Return as numpy array
|
| 136 |
+
return None
|
| 137 |
+
|
| 138 |
+
def write_data(self, address: int, data: Union[np.ndarray, bytes]) -> bool:
|
| 139 |
+
"""Write data to memory."""
|
| 140 |
+
if address in self.memory_blocks:
|
| 141 |
+
memory_block = self.memory_blocks[address]
|
| 142 |
+
if memory_block.data is not None:
|
| 143 |
+
if isinstance(data, np.ndarray):
|
| 144 |
+
data_bytes = data.tobytes()
|
| 145 |
+
elif isinstance(data, bytes):
|
| 146 |
+
data_bytes = data
|
| 147 |
+
else:
|
| 148 |
+
raise TypeError("Data must be a NumPy array or bytes.")
|
| 149 |
+
|
| 150 |
+
if len(data_bytes) <= memory_block.size:
|
| 151 |
+
memory_block.data[:len(data_bytes)] = data_bytes
|
| 152 |
+
return True
|
| 153 |
+
return False
|
| 154 |
+
|
| 155 |
+
def get_memory_stats(self) -> Dict[str, Any]:
|
| 156 |
+
"""Get memory usage statistics."""
|
| 157 |
+
allocated_bytes = sum(block.size for block in self.memory_blocks.values())
|
| 158 |
+
free_bytes = self.total_memory_bytes - allocated_bytes
|
| 159 |
+
|
| 160 |
+
return {
|
| 161 |
+
"total_memory_gb": self.total_memory_bytes / (1024**3),
|
| 162 |
+
"allocated_bytes": allocated_bytes,
|
| 163 |
+
"free_bytes": free_bytes,
|
| 164 |
+
"allocated_blocks_count": len(self.allocated_blocks),
|
| 165 |
+
"free_block_ranges_count": len(self.free_block_ranges),
|
| 166 |
+
"utilization_percent": (allocated_bytes / self.total_memory_bytes) * 100 if self.total_memory_bytes > 0 else 0
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
class VRAM:
|
| 171 |
+
"""
|
| 172 |
+
Main VRAM class that provides the interface for the 500GB GDDR7 memory.
|
| 173 |
+
|
| 174 |
+
This class combines the MemoryManager for low-level memory operations
|
| 175 |
+
with higher-level abstractions like Framebuffers.
|
| 176 |
+
"""
|
| 177 |
+
|
| 178 |
+
def __init__(self, memory_size_gb: int = 500):
|
| 179 |
+
self.memory_manager = MemoryManager(memory_size_gb)
|
| 180 |
+
|
| 181 |
+
# Cache for frequently accessed data (simulates L1/L2 cache)
|
| 182 |
+
self.cache_size = 1000 # Number of cache entries
|
| 183 |
+
self.cache = OrderedDict()
|
| 184 |
+
|
| 185 |
+
# Framebuffer registry
|
| 186 |
+
self.framebuffers: Dict[str, Framebuffer] = {}
|
| 187 |
+
self.framebuffer_counter = 0
|
| 188 |
+
|
| 189 |
+
# Texture registry
|
| 190 |
+
self.textures: Dict[str, np.ndarray] = {}
|
| 191 |
+
self.texture_counter = 0
|
| 192 |
+
|
| 193 |
+
def create_framebuffer(self, width: int, height: int, channels: int = 3,
|
| 194 |
+
name: Optional[str] = None) -> str:
|
| 195 |
+
"""Create a new framebuffer and return its ID."""
|
| 196 |
+
if name is None:
|
| 197 |
+
name = f"framebuffer_{self.framebuffer_counter}"
|
| 198 |
+
self.framebuffer_counter += 1
|
| 199 |
+
|
| 200 |
+
framebuffer = Framebuffer(width, height, channels)
|
| 201 |
+
|
| 202 |
+
# Allocate memory for the framebuffer
|
| 203 |
+
memory_size = framebuffer.get_memory_usage()
|
| 204 |
+
address = self.memory_manager.allocate_block(memory_size)
|
| 205 |
+
|
| 206 |
+
if address is not None:
|
| 207 |
+
framebuffer.vram_address = address
|
| 208 |
+
self.framebuffers[name] = framebuffer
|
| 209 |
+
return name
|
| 210 |
+
else:
|
| 211 |
+
raise MemoryError("Failed to allocate memory for framebuffer")
|
| 212 |
+
|
| 213 |
+
def get_framebuffer(self, name: str) -> Optional[Framebuffer]:
|
| 214 |
+
"""Get a framebuffer by name."""
|
| 215 |
+
return self.framebuffers.get(name)
|
| 216 |
+
|
| 217 |
+
def delete_framebuffer(self, name: str) -> bool:
|
| 218 |
+
"""Delete a framebuffer and free its memory."""
|
| 219 |
+
if name in self.framebuffers:
|
| 220 |
+
framebuffer = self.framebuffers[name]
|
| 221 |
+
if framebuffer.vram_address is not None:
|
| 222 |
+
self.memory_manager.deallocate_block(framebuffer.vram_address)
|
| 223 |
+
del self.framebuffers[name]
|
| 224 |
+
return True
|
| 225 |
+
return False
|
| 226 |
+
|
| 227 |
+
def load_texture(self, texture_data: Union[np.ndarray, bytes], name: Optional[str] = None) -> str:
|
| 228 |
+
"""Load texture data into VRAM and return its ID."""
|
| 229 |
+
if name is None:
|
| 230 |
+
name = f"texture_{self.texture_counter}"
|
| 231 |
+
self.texture_counter += 1
|
| 232 |
+
|
| 233 |
+
size_bytes = 0
|
| 234 |
+
if isinstance(texture_data, np.ndarray):
|
| 235 |
+
size_bytes = texture_data.nbytes
|
| 236 |
+
elif isinstance(texture_data, bytes):
|
| 237 |
+
size_bytes = len(texture_data)
|
| 238 |
+
else:
|
| 239 |
+
raise TypeError("Texture data must be a NumPy array or bytes.")
|
| 240 |
+
|
| 241 |
+
# Allocate memory for the texture
|
| 242 |
+
address = self.memory_manager.allocate_block(size_bytes)
|
| 243 |
+
|
| 244 |
+
if address is not None:
|
| 245 |
+
self.memory_manager.write_data(address, texture_data) # Write actual data
|
| 246 |
+
self.textures[name] = texture_data # Store actual data for reference
|
| 247 |
+
return name
|
| 248 |
+
else:
|
| 249 |
+
raise MemoryError("Failed to allocate memory for texture")
|
| 250 |
+
|
| 251 |
+
def get_texture(self, name: str) -> Optional[np.ndarray]:
|
| 252 |
+
"""Get texture data by name."""
|
| 253 |
+
return self.textures.get(name)
|
| 254 |
+
|
| 255 |
+
def cache_read(self, address: int, size: int) -> Optional[np.ndarray]:
|
| 256 |
+
"""Read data with caching support."""
|
| 257 |
+
cache_key = (address, size)
|
| 258 |
+
|
| 259 |
+
# Check cache first
|
| 260 |
+
if cache_key in self.cache:
|
| 261 |
+
# Move to end (most recently used)
|
| 262 |
+
data = self.cache.pop(cache_key)
|
| 263 |
+
self.cache[cache_key] = data
|
| 264 |
+
return data.copy()
|
| 265 |
+
|
| 266 |
+
# Read from memory
|
| 267 |
+
data = self.memory_manager.read_data(address, size)
|
| 268 |
+
if data is not None:
|
| 269 |
+
# Add to cache
|
| 270 |
+
if len(self.cache) >= self.cache_size:
|
| 271 |
+
# Remove least recently used item
|
| 272 |
+
self.cache.popitem(last=False)
|
| 273 |
+
self.cache[cache_key] = data.copy()
|
| 274 |
+
|
| 275 |
+
return data
|
| 276 |
+
|
| 277 |
+
def transfer_from_ram(self, name: str, data: Union[np.ndarray, bytes],
|
| 278 |
+
delay_ms: float = 0.0) -> Optional[str]:
|
| 279 |
+
"""Transfer a block of data from RAM to VRAM."""
|
| 280 |
+
if isinstance(data, np.ndarray):
|
| 281 |
+
size_bytes = data.nbytes
|
| 282 |
+
data_to_store = data.flatten()
|
| 283 |
+
elif isinstance(data, bytes):
|
| 284 |
+
size_bytes = len(data)
|
| 285 |
+
data_to_store = np.frombuffer(data, dtype=np.uint8)
|
| 286 |
+
else:
|
| 287 |
+
raise TypeError("Data must be a NumPy array or bytes.")
|
| 288 |
+
|
| 289 |
+
# Simulate delay
|
| 290 |
+
if delay_ms > 0:
|
| 291 |
+
time.sleep(delay_ms / 1000.0)
|
| 292 |
+
|
| 293 |
+
# Allocate memory in VRAM
|
| 294 |
+
address = self.memory_manager.allocate_block(size_bytes)
|
| 295 |
+
|
| 296 |
+
if address is not None:
|
| 297 |
+
# Store data in VRAM
|
| 298 |
+
self.memory_manager.write_data(address, data_to_store)
|
| 299 |
+
|
| 300 |
+
# Register the transferred data as a texture/buffer in VRAM
|
| 301 |
+
# For simplicity, we\"ll register it as a texture for now
|
| 302 |
+
texture_id = f"ram_transfer_{self.texture_counter}"
|
| 303 |
+
self.texture_counter += 1
|
| 304 |
+
self.textures[texture_id] = data # Store actual data for reference
|
| 305 |
+
print(f"Transferred {size_bytes} bytes from RAM to VRAM at address {address} as {texture_id}")
|
| 306 |
+
return texture_id
|
| 307 |
+
else:
|
| 308 |
+
print(f"Failed to transfer {size_bytes} bytes from RAM to VRAM: Out of VRAM memory.")
|
| 309 |
+
return None
|
| 310 |
+
|
| 311 |
+
def get_stats(self) -> Dict[str, Any]:
|
| 312 |
+
"""Get comprehensive VRAM statistics."""
|
| 313 |
+
memory_stats = self.memory_manager.get_memory_stats()
|
| 314 |
+
|
| 315 |
+
framebuffer_memory = sum(fb.get_memory_usage() for fb in self.framebuffers.values())
|
| 316 |
+
texture_memory = sum(tex.nbytes for tex in self.textures.values())
|
| 317 |
+
|
| 318 |
+
return {
|
| 319 |
+
**memory_stats,
|
| 320 |
+
"framebuffers_count": len(self.framebuffers),
|
| 321 |
+
"textures_count": len(self.textures),
|
| 322 |
+
"framebuffer_memory_bytes": framebuffer_memory,
|
| 323 |
+
"texture_memory_bytes": texture_memory,
|
| 324 |
+
"cache_entries": len(self.cache),
|
| 325 |
+
"cache_hit_ratio": 0.0 # TODO: Implement cache hit tracking
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
if __name__ == "__main__":
|
| 330 |
+
# Test the VRAM module
|
| 331 |
+
vram = VRAM(memory_size_gb=1) # Use 1GB for testing
|
| 332 |
+
|
| 333 |
+
# Create a framebuffer
|
| 334 |
+
fb_id = vram.create_framebuffer(1920, 1080, 3)
|
| 335 |
+
print(f"Created framebuffer: {fb_id}")
|
| 336 |
+
|
| 337 |
+
# Get the framebuffer and modify it
|
| 338 |
+
fb = vram.get_framebuffer(fb_id)
|
| 339 |
+
if fb:
|
| 340 |
+
fb.clear((255, 0, 0)) # Clear to red
|
| 341 |
+
fb.set_pixel(100, 100, (0, 255, 0)) # Set a green pixel
|
| 342 |
+
print(f"Framebuffer size: {fb.width}x{fb.height}")
|
| 343 |
+
print(f"Pixel at (100, 100): {fb.get_pixel(100, 100)}")
|
| 344 |
+
|
| 345 |
+
# Load a test texture
|
| 346 |
+
test_texture = np.random.randint(0, 256, (256, 256, 3), dtype=np.uint8)
|
| 347 |
+
tex_id = vram.load_texture(test_texture)
|
| 348 |
+
print(f"Loaded texture: {tex_id}")
|
| 349 |
+
|
| 350 |
+
# Test transfer_from_ram
|
| 351 |
+
ram_data = b"\x01\x02\x03\x04\x05\x06\x07\x08"
|
| 352 |
+
transferred_id = vram.transfer_from_ram("test_ram_data", ram_data, delay_ms=10)
|
| 353 |
+
print(f"Transferred RAM data ID: {transferred_id}")
|
| 354 |
+
|
| 355 |
+
# Print statistics
|
| 356 |
+
stats = vram.get_stats()
|
| 357 |
+
print(f"VRAM Stats: {stats}")
|
| 358 |
+
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
|