1use crate::ontology::*;
8use crate::resource::*;
9use crate::types::*;
10
11pub struct GpuContext {
16 instance: wgpu::Instance,
17 adapter: wgpu::Adapter,
18 device: wgpu::Device,
19 queue: wgpu::Queue,
20 backend_preference: BackendPreference,
21}
22
23impl GpuContext {
24 pub async fn new(
30 surface: &wgpu::Surface<'_>,
31 preference: BackendPreference,
32 ) -> Result<Self, GpuError> {
33 let backends = preference.to_backends();
34
35 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
36 backends,
37 ..Default::default()
38 });
39
40 let (instance, adapter) = match instance
42 .request_adapter(&wgpu::RequestAdapterOptions {
43 power_preference: wgpu::PowerPreference::HighPerformance,
44 compatible_surface: Some(surface),
45 force_fallback_adapter: false,
46 })
47 .await
48 {
49 Some(a) => (instance, a),
50 None if matches!(preference, BackendPreference::VulkanPreferred) => {
51 log::info!("agpu: Vulkan adapter not found, falling back to platform default");
53 let fallback_instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
54 backends: wgpu::Backends::PRIMARY,
55 ..Default::default()
56 });
57 let adapter = fallback_instance
58 .request_adapter(&wgpu::RequestAdapterOptions {
59 power_preference: wgpu::PowerPreference::HighPerformance,
60 compatible_surface: Some(surface),
61 force_fallback_adapter: false,
62 })
63 .await
64 .ok_or(GpuError::NoAdapter)?;
65 (fallback_instance, adapter)
66 }
67 None if matches!(preference, BackendPreference::OpenGLPreferred) => {
68 log::info!("agpu: OpenGL adapter not found, falling back to platform default");
70 let fallback_instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
71 backends: wgpu::Backends::PRIMARY,
72 ..Default::default()
73 });
74 let adapter = fallback_instance
75 .request_adapter(&wgpu::RequestAdapterOptions {
76 power_preference: wgpu::PowerPreference::HighPerformance,
77 compatible_surface: Some(surface),
78 force_fallback_adapter: false,
79 })
80 .await
81 .ok_or(GpuError::NoAdapter)?;
82 (fallback_instance, adapter)
83 }
84 None => return Err(GpuError::NoAdapter),
85 };
86
87 let supported_features = adapter.features();
90
91 let (device, queue) = adapter
92 .request_device(
93 &wgpu::DeviceDescriptor {
94 label: Some("agpu_device"),
95 required_features: supported_features,
96 required_limits: adapter.limits(),
97 memory_hints: wgpu::MemoryHints::Performance,
98 },
99 None,
100 )
101 .await
102 .map_err(|e| GpuError::DeviceRequest(e.to_string()))?;
103
104 log::info!(
105 "agpu: GPU initialised — {} ({:?}, {:?})",
106 adapter.get_info().name,
107 adapter.get_info().backend,
108 adapter.get_info().device_type,
109 );
110
111 Ok(Self {
112 instance,
113 adapter,
114 device,
115 queue,
116 backend_preference: preference,
117 })
118 }
119
120 pub async fn from_instance(
126 instance: wgpu::Instance,
127 surface: &wgpu::Surface<'_>,
128 preference: BackendPreference,
129 ) -> Result<Self, GpuError> {
130 let adapter = instance
131 .request_adapter(&wgpu::RequestAdapterOptions {
132 power_preference: wgpu::PowerPreference::HighPerformance,
133 compatible_surface: Some(surface),
134 force_fallback_adapter: false,
135 })
136 .await;
137
138 let (instance, adapter) = match adapter {
140 Some(a) => (instance, a),
141 None if matches!(
142 preference,
143 BackendPreference::VulkanPreferred | BackendPreference::OpenGLPreferred
144 ) =>
145 {
146 log::info!("agpu: preferred adapter not found, falling back to platform default");
147 let fallback = wgpu::Instance::new(&wgpu::InstanceDescriptor {
148 backends: wgpu::Backends::PRIMARY,
149 ..Default::default()
150 });
151 let a = fallback
152 .request_adapter(&wgpu::RequestAdapterOptions {
153 power_preference: wgpu::PowerPreference::HighPerformance,
154 compatible_surface: None, force_fallback_adapter: false,
156 })
157 .await
158 .ok_or(GpuError::NoAdapter)?;
159 (fallback, a)
160 }
161 None => return Err(GpuError::NoAdapter),
162 };
163
164 let supported_features = adapter.features();
165 let (device, queue) = adapter
166 .request_device(
167 &wgpu::DeviceDescriptor {
168 label: Some("agpu_device"),
169 required_features: supported_features,
170 required_limits: adapter.limits(),
171 memory_hints: wgpu::MemoryHints::Performance,
172 },
173 None,
174 )
175 .await
176 .map_err(|e| GpuError::DeviceRequest(e.to_string()))?;
177
178 log::info!(
179 "agpu: GPU initialised — {} ({:?}, {:?})",
180 adapter.get_info().name,
181 adapter.get_info().backend,
182 adapter.get_info().device_type,
183 );
184
185 Ok(Self {
186 instance,
187 adapter,
188 device,
189 queue,
190 backend_preference: preference,
191 })
192 }
193
194 pub fn device(&self) -> &wgpu::Device {
198 &self.device
199 }
200 pub fn queue(&self) -> &wgpu::Queue {
202 &self.queue
203 }
204 pub fn adapter(&self) -> &wgpu::Adapter {
206 &self.adapter
207 }
208 pub fn instance(&self) -> &wgpu::Instance {
210 &self.instance
211 }
212
213 pub fn adapter_name(&self) -> String {
215 self.adapter.get_info().name
216 }
217 pub fn backend(&self) -> String {
219 format!("{:?}", self.adapter.get_info().backend)
220 }
221 pub fn device_type(&self) -> String {
223 format!("{:?}", self.adapter.get_info().device_type)
224 }
225 pub fn driver(&self) -> String {
227 self.adapter.get_info().driver
228 }
229 pub fn driver_info(&self) -> String {
231 self.adapter.get_info().driver_info
232 }
233 pub fn max_texture_dimension(&self) -> u32 {
235 self.device.limits().max_texture_dimension_2d
236 }
237 pub fn max_buffer_size(&self) -> u64 {
239 self.device.limits().max_buffer_size
240 }
241 pub fn features(&self) -> Features {
243 self.device.features()
244 }
245 pub fn limits(&self) -> Limits {
247 self.device.limits()
248 }
249 pub fn backend_preference(&self) -> BackendPreference {
251 self.backend_preference
252 }
253
254 pub fn create_buffer(&self, desc: &GpuBufferDescriptor) -> GpuBuffer {
258 let inner = self.device.create_buffer(&wgpu::BufferDescriptor {
259 label: desc.label.as_deref(),
260 size: desc.size,
261 usage: desc.usage,
262 mapped_at_creation: desc.mapped_at_creation,
263 });
264 GpuBuffer::from_raw(inner, desc)
265 }
266
267 pub fn create_texture(&self, desc: &GpuTextureDescriptor) -> GpuTexture {
269 let inner = self.device.create_texture(&wgpu::TextureDescriptor {
270 label: desc.label.as_deref(),
271 size: desc.size,
272 mip_level_count: desc.mip_level_count,
273 sample_count: desc.sample_count,
274 dimension: desc.dimension,
275 format: desc.format,
276 usage: desc.usage,
277 view_formats: &desc.view_formats,
278 });
279 GpuTexture::from_raw(inner, desc)
280 }
281
282 pub fn create_sampler(&self, desc: &GpuSamplerDescriptor) -> GpuSampler {
284 let inner = self.device.create_sampler(&wgpu::SamplerDescriptor {
285 label: desc.label.as_deref(),
286 address_mode_u: desc.address_mode_u,
287 address_mode_v: desc.address_mode_v,
288 address_mode_w: desc.address_mode_w,
289 mag_filter: desc.mag_filter,
290 min_filter: desc.min_filter,
291 mipmap_filter: desc.mipmap_filter,
292 compare: desc.compare,
293 anisotropy_clamp: desc.anisotropy_clamp,
294 lod_min_clamp: desc.lod_min_clamp,
295 lod_max_clamp: desc.lod_max_clamp,
296 ..Default::default()
297 });
298 GpuSampler::from_raw(inner, desc.label.clone())
299 }
300
301 pub fn create_shader_wgsl(&self, label: &str, source: &str) -> GpuShaderModule {
303 let inner = self
304 .device
305 .create_shader_module(wgpu::ShaderModuleDescriptor {
306 label: Some(label),
307 source: wgpu::ShaderSource::Wgsl(source.into()),
308 });
309 GpuShaderModule::from_raw(inner, Some(label.into()), ShaderSourceKind::Wgsl)
310 }
311
312 pub fn create_bind_group_layout(
314 &self,
315 label: &str,
316 entries: &[wgpu::BindGroupLayoutEntry],
317 ) -> GpuBindGroupLayout {
318 let inner = self
319 .device
320 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
321 label: Some(label),
322 entries,
323 });
324 GpuBindGroupLayout::from_raw(inner, Some(label.into()))
325 }
326
327 pub fn create_bind_group(
329 &self,
330 label: &str,
331 layout: &GpuBindGroupLayout,
332 entries: &[wgpu::BindGroupEntry<'_>],
333 ) -> GpuBindGroup {
334 let inner = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
335 label: Some(label),
336 layout: layout.raw(),
337 entries,
338 });
339 GpuBindGroup::from_raw(inner, Some(label.into()))
340 }
341
342 pub fn create_pipeline_layout(
344 &self,
345 label: &str,
346 bind_group_layouts: &[&GpuBindGroupLayout],
347 push_constant_ranges: &[wgpu::PushConstantRange],
348 ) -> GpuPipelineLayout {
349 let raw_layouts: Vec<&wgpu::BindGroupLayout> =
350 bind_group_layouts.iter().map(|l| l.raw()).collect();
351 let inner = self
352 .device
353 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
354 label: Some(label),
355 bind_group_layouts: &raw_layouts,
356 push_constant_ranges,
357 });
358 GpuPipelineLayout::from_raw(inner, Some(label.into()))
359 }
360
361 pub fn create_render_pipeline(
363 &self,
364 desc: &wgpu::RenderPipelineDescriptor<'_>,
365 ) -> GpuRenderPipeline {
366 let label = desc.label.map(String::from);
367 let inner = self.device.create_render_pipeline(desc);
368 GpuRenderPipeline::from_raw(inner, label)
369 }
370
371 pub fn create_compute_pipeline(
373 &self,
374 desc: &wgpu::ComputePipelineDescriptor<'_>,
375 ) -> GpuComputePipeline {
376 let label = desc.label.map(String::from);
377 let inner = self.device.create_compute_pipeline(desc);
378 GpuComputePipeline::from_raw(inner, label)
379 }
380
381 pub fn create_command_encoder(&self, label: &str) -> crate::command::GpuCommandEncoder {
383 let inner = self
384 .device
385 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some(label) });
386 crate::command::GpuCommandEncoder::from_raw(inner)
387 }
388
389 pub fn create_query_set(&self, label: &str, ty: wgpu::QueryType, count: u32) -> GpuQuerySet {
391 let inner = self.device.create_query_set(&wgpu::QuerySetDescriptor {
392 label: Some(label),
393 ty,
394 count,
395 });
396 GpuQuerySet::from_raw(inner, Some(label.into()), count)
397 }
398
399 pub fn write_buffer(&self, buffer: &GpuBuffer, offset: u64, data: &[u8]) {
401 self.queue.write_buffer(buffer.raw(), offset, data);
402 }
403
404 pub fn submit(&self, commands: impl IntoIterator<Item = crate::command::GpuCommandBuffer>) {
406 self.queue
407 .submit(commands.into_iter().map(|c| c.into_inner()));
408 }
409
410 pub fn write_texture(
412 &self,
413 texture: wgpu::TexelCopyTextureInfo<'_>,
414 data: &[u8],
415 data_layout: wgpu::TexelCopyBufferLayout,
416 size: Extent3d,
417 ) {
418 self.queue.write_texture(texture, data, data_layout, size);
419 }
420}
421
422#[derive(Debug)]
424pub enum GpuError {
425 NoAdapter,
426 DeviceRequest(String),
427 SurfaceConfig(String),
428}
429
430impl std::fmt::Display for GpuError {
431 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
432 match self {
433 Self::NoAdapter => write!(f, "No suitable GPU adapter found"),
434 Self::DeviceRequest(e) => write!(f, "GPU device request failed: {e}"),
435 Self::SurfaceConfig(e) => write!(f, "Surface configuration failed: {e}"),
436 }
437 }
438}
439
440impl std::error::Error for GpuError {}
441
442impl Discoverable for GpuContext {
445 fn schema(&self) -> WidgetSchema {
446 let mut schema = WidgetSchema::new(
447 "GpuContext",
448 "Vulkan-first GPU device context — adapter, device, queue, and hardware capabilities",
449 SemanticRole::System,
450 );
451 schema.usage_hint =
452 Some("GpuContext::new(&surface, BackendPreference::default()).await".into());
453 schema.tags = vec![
454 "gpu".into(),
455 "vulkan".into(),
456 "device".into(),
457 "adapter".into(),
458 "hardware".into(),
459 ];
460 schema
461 }
462
463 fn capabilities(&self) -> Vec<AgentCapability> {
464 vec![
465 AgentCapability::Custom("gpu_compute".into()),
466 AgentCapability::Custom("gpu_render".into()),
467 AgentCapability::Custom("vulkan_preferred".into()),
468 ]
469 }
470
471 fn actions(&self) -> Vec<AgentAction> {
472 vec![
473 AgentAction::simple(
474 "get_adapter_info",
475 "Query GPU adapter name, backend, and device type",
476 false,
477 ),
478 AgentAction::simple(
479 "get_limits",
480 "Query device limits (max texture size, buffer size, etc.)",
481 false,
482 ),
483 AgentAction::simple("get_features", "List enabled GPU features", false),
484 ]
485 }
486
487 fn semantic_role(&self) -> SemanticRole {
488 SemanticRole::System
489 }
490
491 fn agent_state(&self) -> serde_json::Value {
492 let info = self.adapter.get_info();
493 let limits = self.device.limits();
494 serde_json::json!({
495 "adapter_name": info.name,
496 "backend": format!("{:?}", info.backend),
497 "device_type": format!("{:?}", info.device_type),
498 "driver": info.driver,
499 "driver_info": info.driver_info,
500 "backend_preference": format!("{:?}", self.backend_preference),
501 "limits": {
502 "max_texture_dimension_2d": limits.max_texture_dimension_2d,
503 "max_buffer_size": limits.max_buffer_size,
504 "max_bind_groups": limits.max_bind_groups,
505 "max_vertex_buffers": limits.max_vertex_buffers,
506 "max_vertex_attributes": limits.max_vertex_attributes,
507 "max_compute_workgroup_size_x": limits.max_compute_workgroup_size_x,
508 "max_compute_workgroups_per_dimension": limits.max_compute_workgroups_per_dimension,
509 }
510 })
511 }
512
513 fn execute_action(
514 &mut self,
515 action: &str,
516 _params: &serde_json::Value,
517 ) -> Result<serde_json::Value, String> {
518 match action {
519 "get_adapter_info" => Ok(serde_json::json!({
520 "name": self.adapter.get_info().name,
521 "backend": format!("{:?}", self.adapter.get_info().backend),
522 "device_type": format!("{:?}", self.adapter.get_info().device_type),
523 "driver": self.adapter.get_info().driver,
524 })),
525 "get_limits" => {
526 let l = self.device.limits();
527 Ok(serde_json::json!({
528 "max_texture_dimension_2d": l.max_texture_dimension_2d,
529 "max_buffer_size": l.max_buffer_size,
530 "max_bind_groups": l.max_bind_groups,
531 }))
532 }
533 "get_features" => {
534 let features = self.adapter.features();
535 Ok(serde_json::json!({
536 "features": format!("{features:?}"),
537 }))
538 }
539 _ => Err(format!("Unknown action: {action}")),
540 }
541 }
542
543 fn agent_id(&self) -> Option<&str> {
544 Some("gpu_context")
545 }
546
547 fn accessibility_label(&self) -> Option<String> {
548 Some(format!("GPU: {} ({})", self.adapter_name(), self.backend()))
549 }
550}