Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
283
vendor/ruvector/examples/edge-net/src/compute/backend.rs
vendored
Normal file
283
vendor/ruvector/examples/edge-net/src/compute/backend.rs
vendored
Normal file
@@ -0,0 +1,283 @@
|
||||
//! Compute backend detection and abstraction
|
||||
//!
|
||||
//! Detects available compute capabilities (WebGPU, WebGL2, WebWorkers)
|
||||
//! and provides a unified interface for selecting the best backend.
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Compute capabilities detected on the current device
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ComputeCapability {
|
||||
/// WebGPU is available (best performance)
|
||||
pub has_webgpu: bool,
|
||||
/// WebGL2 is available (fallback for GPU compute)
|
||||
pub has_webgl2: bool,
|
||||
/// WebGL2 supports floating point textures
|
||||
pub has_float_textures: bool,
|
||||
/// Transform feedback is available (for GPU readback)
|
||||
pub has_transform_feedback: bool,
|
||||
/// WebWorkers are available
|
||||
pub has_workers: bool,
|
||||
/// SharedArrayBuffer is available (for shared memory)
|
||||
pub has_shared_memory: bool,
|
||||
/// Number of logical CPU cores
|
||||
pub worker_count: usize,
|
||||
/// Maximum texture size (for WebGL2)
|
||||
pub max_texture_size: u32,
|
||||
/// Estimated GPU memory (MB)
|
||||
pub gpu_memory_mb: u32,
|
||||
/// Device description
|
||||
pub device_info: String,
|
||||
}
|
||||
|
||||
impl ComputeCapability {
|
||||
/// Convert to JavaScript object
|
||||
pub fn to_js(&self) -> JsValue {
|
||||
let obj = js_sys::Object::new();
|
||||
|
||||
js_sys::Reflect::set(&obj, &"hasWebGPU".into(), &self.has_webgpu.into()).ok();
|
||||
js_sys::Reflect::set(&obj, &"hasWebGL2".into(), &self.has_webgl2.into()).ok();
|
||||
js_sys::Reflect::set(&obj, &"hasFloatTextures".into(), &self.has_float_textures.into()).ok();
|
||||
js_sys::Reflect::set(&obj, &"hasTransformFeedback".into(), &self.has_transform_feedback.into()).ok();
|
||||
js_sys::Reflect::set(&obj, &"hasWorkers".into(), &self.has_workers.into()).ok();
|
||||
js_sys::Reflect::set(&obj, &"hasSharedMemory".into(), &self.has_shared_memory.into()).ok();
|
||||
js_sys::Reflect::set(&obj, &"workerCount".into(), &(self.worker_count as u32).into()).ok();
|
||||
js_sys::Reflect::set(&obj, &"maxTextureSize".into(), &self.max_texture_size.into()).ok();
|
||||
js_sys::Reflect::set(&obj, &"gpuMemoryMB".into(), &self.gpu_memory_mb.into()).ok();
|
||||
js_sys::Reflect::set(&obj, &"deviceInfo".into(), &self.device_info.clone().into()).ok();
|
||||
|
||||
obj.into()
|
||||
}
|
||||
|
||||
/// Get recommended backend for a given operation size
|
||||
pub fn recommend_backend(&self, operation_size: usize) -> ComputeBackend {
|
||||
// WebGPU is always preferred if available
|
||||
if self.has_webgpu {
|
||||
return ComputeBackend::WebGPU;
|
||||
}
|
||||
|
||||
// For large operations, prefer GPU
|
||||
if operation_size > 4096 && self.has_webgl2 && self.has_float_textures {
|
||||
return ComputeBackend::WebGL2;
|
||||
}
|
||||
|
||||
// For medium operations with multiple cores, use workers
|
||||
if operation_size > 1024 && self.has_workers && self.worker_count > 1 {
|
||||
return ComputeBackend::WebWorkers;
|
||||
}
|
||||
|
||||
// Fall back to single-threaded CPU
|
||||
ComputeBackend::CPU
|
||||
}
|
||||
}
|
||||
|
||||
/// Available compute backends
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ComputeBackend {
|
||||
/// WebGPU compute shaders (best performance)
|
||||
WebGPU,
|
||||
/// WebGL2 texture-based compute (fallback GPU)
|
||||
WebGL2,
|
||||
/// WebWorker pool (CPU parallelism)
|
||||
WebWorkers,
|
||||
/// Single-threaded CPU (last resort)
|
||||
CPU,
|
||||
}
|
||||
|
||||
impl ComputeBackend {
|
||||
/// Get backend name
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
ComputeBackend::WebGPU => "WebGPU",
|
||||
ComputeBackend::WebGL2 => "WebGL2",
|
||||
ComputeBackend::WebWorkers => "WebWorkers",
|
||||
ComputeBackend::CPU => "CPU",
|
||||
}
|
||||
}
|
||||
|
||||
/// Get relative performance (higher is better)
|
||||
pub fn relative_performance(&self) -> f32 {
|
||||
match self {
|
||||
ComputeBackend::WebGPU => 10.0,
|
||||
ComputeBackend::WebGL2 => 5.0,
|
||||
ComputeBackend::WebWorkers => 2.0,
|
||||
ComputeBackend::CPU => 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect compute capabilities on the current device
|
||||
pub fn detect_capabilities() -> Result<ComputeCapability, JsValue> {
|
||||
let window = web_sys::window()
|
||||
.ok_or_else(|| JsValue::from_str("No window object"))?;
|
||||
|
||||
let navigator = window.navigator();
|
||||
|
||||
// Detect WebGPU
|
||||
let has_webgpu = js_sys::Reflect::has(&navigator, &"gpu".into())
|
||||
.unwrap_or(false);
|
||||
|
||||
// Detect WebWorkers
|
||||
let has_workers = js_sys::Reflect::has(&window, &"Worker".into())
|
||||
.unwrap_or(false);
|
||||
|
||||
// Detect SharedArrayBuffer
|
||||
let has_shared_memory = js_sys::Reflect::has(&window, &"SharedArrayBuffer".into())
|
||||
.unwrap_or(false);
|
||||
|
||||
// Get hardware concurrency (CPU cores)
|
||||
let worker_count = navigator.hardware_concurrency() as usize;
|
||||
|
||||
// Detect WebGL2 capabilities
|
||||
let document = window.document()
|
||||
.ok_or_else(|| JsValue::from_str("No document"))?;
|
||||
|
||||
let (has_webgl2, has_float_textures, has_transform_feedback, max_texture_size, gpu_memory_mb, device_info) =
|
||||
detect_webgl2_capabilities(&document)?;
|
||||
|
||||
Ok(ComputeCapability {
|
||||
has_webgpu,
|
||||
has_webgl2,
|
||||
has_float_textures,
|
||||
has_transform_feedback,
|
||||
has_workers,
|
||||
has_shared_memory,
|
||||
worker_count: worker_count.max(1),
|
||||
max_texture_size,
|
||||
gpu_memory_mb,
|
||||
device_info,
|
||||
})
|
||||
}
|
||||
|
||||
/// Detect WebGL2-specific capabilities
|
||||
fn detect_webgl2_capabilities(document: &web_sys::Document) -> Result<(bool, bool, bool, u32, u32, String), JsValue> {
|
||||
// Create a temporary canvas to probe WebGL2
|
||||
let canvas = document.create_element("canvas")?;
|
||||
let canvas: web_sys::HtmlCanvasElement = canvas.dyn_into()?;
|
||||
|
||||
// Try to get WebGL2 context
|
||||
let context = match canvas.get_context("webgl2")? {
|
||||
Some(ctx) => ctx,
|
||||
None => return Ok((false, false, false, 0, 0, "No WebGL2".to_string())),
|
||||
};
|
||||
|
||||
let gl: web_sys::WebGl2RenderingContext = context.dyn_into()?;
|
||||
|
||||
// Check for float texture support (required for compute)
|
||||
let ext_color_buffer_float = gl.get_extension("EXT_color_buffer_float")?;
|
||||
let has_float_textures = ext_color_buffer_float.is_some();
|
||||
|
||||
// Transform feedback is built into WebGL2
|
||||
let has_transform_feedback = true;
|
||||
|
||||
// Get max texture size
|
||||
let max_texture_size = gl.get_parameter(web_sys::WebGl2RenderingContext::MAX_TEXTURE_SIZE)?
|
||||
.as_f64()
|
||||
.unwrap_or(4096.0) as u32;
|
||||
|
||||
// Try to get GPU memory info (vendor-specific)
|
||||
let gpu_memory_mb = get_gpu_memory_mb(&gl);
|
||||
|
||||
// Get renderer info
|
||||
let renderer_info = gl.get_extension("WEBGL_debug_renderer_info")?;
|
||||
let device_info = if renderer_info.is_some() {
|
||||
// UNMASKED_RENDERER_WEBGL = 0x9246
|
||||
let renderer = gl.get_parameter(0x9246)?;
|
||||
renderer.as_string().unwrap_or_else(|| "Unknown GPU".to_string())
|
||||
} else {
|
||||
"Unknown GPU".to_string()
|
||||
};
|
||||
|
||||
Ok((true, has_float_textures, has_transform_feedback, max_texture_size, gpu_memory_mb, device_info))
|
||||
}
|
||||
|
||||
/// Try to get GPU memory size (vendor-specific extension)
|
||||
fn get_gpu_memory_mb(gl: &web_sys::WebGl2RenderingContext) -> u32 {
|
||||
// Try WEBGL_memory_info extension (available on some browsers)
|
||||
if let Ok(Some(_ext)) = gl.get_extension("WEBGL_memory_info") {
|
||||
// GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX = 0x9048
|
||||
if let Ok(mem) = gl.get_parameter(0x9048) {
|
||||
if let Some(kb) = mem.as_f64() {
|
||||
return (kb / 1024.0) as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default estimate based on typical mobile/desktop GPUs
|
||||
// Most modern GPUs have at least 2GB
|
||||
2048
|
||||
}
|
||||
|
||||
/// Configuration for compute operations
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ComputeConfig {
|
||||
/// Preferred backend (None = auto-select)
|
||||
pub preferred_backend: Option<ComputeBackend>,
|
||||
/// Maximum memory to use (bytes)
|
||||
pub max_memory: usize,
|
||||
/// Timeout for operations (ms)
|
||||
pub timeout_ms: u32,
|
||||
/// Enable profiling
|
||||
pub profiling: bool,
|
||||
}
|
||||
|
||||
impl Default for ComputeConfig {
|
||||
fn default() -> Self {
|
||||
ComputeConfig {
|
||||
preferred_backend: None,
|
||||
max_memory: 256 * 1024 * 1024, // 256MB
|
||||
timeout_ms: 30_000, // 30 seconds
|
||||
profiling: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_backend_recommendation() {
|
||||
let caps = ComputeCapability {
|
||||
has_webgpu: false,
|
||||
has_webgl2: true,
|
||||
has_float_textures: true,
|
||||
has_transform_feedback: true,
|
||||
has_workers: true,
|
||||
has_shared_memory: true,
|
||||
worker_count: 4,
|
||||
max_texture_size: 4096,
|
||||
gpu_memory_mb: 2048,
|
||||
device_info: "Test GPU".to_string(),
|
||||
};
|
||||
|
||||
// Large operations should use WebGL2
|
||||
assert_eq!(caps.recommend_backend(10000), ComputeBackend::WebGL2);
|
||||
|
||||
// Medium operations with workers should use workers
|
||||
assert_eq!(caps.recommend_backend(2000), ComputeBackend::WebWorkers);
|
||||
|
||||
// Small operations should use CPU
|
||||
assert_eq!(caps.recommend_backend(100), ComputeBackend::CPU);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_backend_with_webgpu() {
|
||||
let caps = ComputeCapability {
|
||||
has_webgpu: true,
|
||||
has_webgl2: true,
|
||||
has_float_textures: true,
|
||||
has_transform_feedback: true,
|
||||
has_workers: true,
|
||||
has_shared_memory: true,
|
||||
worker_count: 4,
|
||||
max_texture_size: 4096,
|
||||
gpu_memory_mb: 2048,
|
||||
device_info: "Test GPU".to_string(),
|
||||
};
|
||||
|
||||
// WebGPU should always be preferred
|
||||
assert_eq!(caps.recommend_backend(100), ComputeBackend::WebGPU);
|
||||
assert_eq!(caps.recommend_backend(10000), ComputeBackend::WebGPU);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user