1use std::collections::HashMap;
12use std::sync::Arc;
13use wgpu;
14
15pub type GpuHandle = u32;
17
18pub struct GpuState {
20 device: Arc<wgpu::Device>,
21 queue: Arc<wgpu::Queue>,
22 next_handle: GpuHandle,
23 buffers: HashMap<GpuHandle, wgpu::Buffer>,
24 textures: HashMap<GpuHandle, GpuTexture>,
25 shaders: HashMap<GpuHandle, wgpu::ShaderModule>,
26 pipelines: HashMap<GpuHandle, GpuPipeline>,
27 #[allow(dead_code)]
29 readback_buffer: Option<ReadbackBuffer>,
30}
31
32#[allow(dead_code)]
33struct GpuTexture {
34 texture: wgpu::Texture,
35 view: wgpu::TextureView,
36 width: u32,
37 height: u32,
38}
39
40enum GpuPipeline {
41 Render(wgpu::RenderPipeline),
42 Compute(wgpu::ComputePipeline),
43}
44
45#[allow(dead_code)]
46struct ReadbackBuffer {
47 buffer: wgpu::Buffer,
48 width: u32,
49 height: u32,
50}
51
52const MAX_BUFFER_SIZE: u64 = 64 * 1024 * 1024;
54
55const MAX_TEXTURE_DIM: u32 = 4096;
57
58impl GpuState {
59 fn alloc_handle(&mut self) -> GpuHandle {
60 let h = self.next_handle;
61 self.next_handle += 1;
62 h
63 }
64
65 pub fn create_buffer(&mut self, size: u64, usage_bits: u32) -> GpuHandle {
68 if size == 0 || size > MAX_BUFFER_SIZE {
69 return 0;
70 }
71 let usage = wgpu::BufferUsages::from_bits_truncate(usage_bits)
72 | wgpu::BufferUsages::COPY_DST
73 | wgpu::BufferUsages::COPY_SRC;
74 let buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
75 label: Some("oxide_guest_buffer"),
76 size,
77 usage,
78 mapped_at_creation: false,
79 });
80 let h = self.alloc_handle();
81 self.buffers.insert(h, buffer);
82 h
83 }
84
85 pub fn create_texture(&mut self, width: u32, height: u32) -> GpuHandle {
87 if width == 0 || height == 0 || width > MAX_TEXTURE_DIM || height > MAX_TEXTURE_DIM {
88 return 0;
89 }
90 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
91 label: Some("oxide_guest_texture"),
92 size: wgpu::Extent3d {
93 width,
94 height,
95 depth_or_array_layers: 1,
96 },
97 mip_level_count: 1,
98 sample_count: 1,
99 dimension: wgpu::TextureDimension::D2,
100 format: wgpu::TextureFormat::Rgba8UnormSrgb,
101 usage: wgpu::TextureUsages::TEXTURE_BINDING
102 | wgpu::TextureUsages::COPY_DST
103 | wgpu::TextureUsages::RENDER_ATTACHMENT,
104 view_formats: &[],
105 });
106 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
107 let h = self.alloc_handle();
108 self.textures.insert(
109 h,
110 GpuTexture {
111 texture,
112 view,
113 width,
114 height,
115 },
116 );
117 h
118 }
119
120 pub fn create_shader(&mut self, source: &str) -> GpuHandle {
122 let module = self
123 .device
124 .create_shader_module(wgpu::ShaderModuleDescriptor {
125 label: Some("oxide_guest_shader"),
126 source: wgpu::ShaderSource::Wgsl(source.into()),
127 });
128 let h = self.alloc_handle();
129 self.shaders.insert(h, module);
130 h
131 }
132
133 pub fn create_render_pipeline(
137 &mut self,
138 shader_handle: GpuHandle,
139 vertex_entry: &str,
140 fragment_entry: &str,
141 ) -> GpuHandle {
142 let shader = match self.shaders.get(&shader_handle) {
143 Some(s) => s,
144 None => return 0,
145 };
146 let pipeline = self
147 .device
148 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
149 label: Some("oxide_guest_render_pipeline"),
150 layout: None,
151 vertex: wgpu::VertexState {
152 module: shader,
153 entry_point: Some(vertex_entry),
154 compilation_options: Default::default(),
155 buffers: &[],
156 },
157 fragment: Some(wgpu::FragmentState {
158 module: shader,
159 entry_point: Some(fragment_entry),
160 compilation_options: Default::default(),
161 targets: &[Some(wgpu::ColorTargetState {
162 format: wgpu::TextureFormat::Rgba8UnormSrgb,
163 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
164 write_mask: wgpu::ColorWrites::ALL,
165 })],
166 }),
167 primitive: wgpu::PrimitiveState {
168 topology: wgpu::PrimitiveTopology::TriangleList,
169 ..Default::default()
170 },
171 depth_stencil: None,
172 multisample: wgpu::MultisampleState::default(),
173 multiview: None,
174 cache: None,
175 });
176 let h = self.alloc_handle();
177 self.pipelines.insert(h, GpuPipeline::Render(pipeline));
178 h
179 }
180
181 pub fn create_compute_pipeline(
183 &mut self,
184 shader_handle: GpuHandle,
185 entry_point: &str,
186 ) -> GpuHandle {
187 let shader = match self.shaders.get(&shader_handle) {
188 Some(s) => s,
189 None => return 0,
190 };
191 let pipeline = self
192 .device
193 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
194 label: Some("oxide_guest_compute_pipeline"),
195 layout: None,
196 module: shader,
197 entry_point: Some(entry_point),
198 compilation_options: Default::default(),
199 cache: None,
200 });
201 let h = self.alloc_handle();
202 self.pipelines.insert(h, GpuPipeline::Compute(pipeline));
203 h
204 }
205
206 pub fn write_buffer(&self, handle: GpuHandle, offset: u64, data: &[u8]) -> bool {
208 match self.buffers.get(&handle) {
209 Some(buf) => {
210 self.queue.write_buffer(buf, offset, data);
211 true
212 }
213 None => false,
214 }
215 }
216
217 pub fn draw(
220 &self,
221 pipeline_handle: GpuHandle,
222 target_texture: GpuHandle,
223 vertex_count: u32,
224 instance_count: u32,
225 ) -> bool {
226 let pipeline = match self.pipelines.get(&pipeline_handle) {
227 Some(GpuPipeline::Render(p)) => p,
228 _ => return false,
229 };
230 let target = match self.textures.get(&target_texture) {
231 Some(t) => t,
232 None => return false,
233 };
234
235 let mut encoder = self
236 .device
237 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
238 label: Some("oxide_guest_draw"),
239 });
240
241 {
242 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
243 label: Some("oxide_guest_render_pass"),
244 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
245 view: &target.view,
246 resolve_target: None,
247 ops: wgpu::Operations {
248 load: wgpu::LoadOp::Load,
249 store: wgpu::StoreOp::Store,
250 },
251 })],
252 depth_stencil_attachment: None,
253 timestamp_writes: None,
254 occlusion_query_set: None,
255 });
256 pass.set_pipeline(pipeline);
257 pass.draw(0..vertex_count, 0..instance_count.max(1));
258 }
259
260 self.queue.submit(std::iter::once(encoder.finish()));
261 true
262 }
263
264 pub fn dispatch_compute(
266 &self,
267 pipeline_handle: GpuHandle,
268 workgroups_x: u32,
269 workgroups_y: u32,
270 workgroups_z: u32,
271 ) -> bool {
272 let pipeline = match self.pipelines.get(&pipeline_handle) {
273 Some(GpuPipeline::Compute(p)) => p,
274 _ => return false,
275 };
276
277 let mut encoder = self
278 .device
279 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
280 label: Some("oxide_guest_compute"),
281 });
282
283 {
284 let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
285 label: Some("oxide_guest_compute_pass"),
286 timestamp_writes: None,
287 });
288 pass.set_pipeline(pipeline);
289 pass.dispatch_workgroups(workgroups_x, workgroups_y, workgroups_z);
290 }
291
292 self.queue.submit(std::iter::once(encoder.finish()));
293 true
294 }
295
296 pub fn destroy_buffer(&mut self, handle: GpuHandle) -> bool {
298 if let Some(buf) = self.buffers.remove(&handle) {
299 buf.destroy();
300 true
301 } else {
302 false
303 }
304 }
305
306 pub fn destroy_texture(&mut self, handle: GpuHandle) -> bool {
308 if let Some(tex) = self.textures.remove(&handle) {
309 tex.texture.destroy();
310 true
311 } else {
312 false
313 }
314 }
315}
316
317pub fn init_gpu() -> Option<GpuState> {
322 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
323 backends: wgpu::Backends::all(),
324 ..Default::default()
325 });
326
327 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
328 power_preference: wgpu::PowerPreference::LowPower,
329 compatible_surface: None,
330 force_fallback_adapter: false,
331 }))
332 .ok()?;
333
334 let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
335 label: Some("oxide_gpu"),
336 required_features: wgpu::Features::empty(),
337 required_limits: wgpu::Limits::downlevel_defaults(),
338 memory_hints: Default::default(),
339 trace: wgpu::Trace::Off,
340 }))
341 .ok()?;
342
343 Some(GpuState {
344 device: Arc::new(device),
345 queue: Arc::new(queue),
346 next_handle: 1,
347 buffers: HashMap::new(),
348 textures: HashMap::new(),
349 shaders: HashMap::new(),
350 pipelines: HashMap::new(),
351 readback_buffer: None,
352 })
353}