1#![allow(clippy::missing_safety_doc)]
11
12use mouser_engine_abi::*;
13use std::collections::HashMap;
14use std::ffi::{c_char, c_int, c_void};
15
16const SHADER: &str = r#"
17struct U { sa: vec4<f32>, flags: vec4<f32> };
18@group(0) @binding(0) var<uniform> u: U;
19@group(0) @binding(1) var tex: texture_2d<f32>;
20@group(0) @binding(2) var samp: sampler;
21struct VOut {
22 @builtin(position) pos: vec4<f32>,
23 @location(0) uv: vec2<f32>,
24 @location(1) color: vec4<f32>,
25};
26@vertex
27fn vs(@builtin(vertex_index) vi: u32, @location(0) rect: vec4<f32>, @location(1) uvr: vec4<f32>, @location(2) color: vec4<f32>) -> VOut {
28 let corner = vec2<f32>(f32(vi & 1u), f32((vi >> 1u) & 1u));
29 let p = rect.xy + corner * rect.zw;
30 var o: VOut;
31 o.pos = vec4<f32>(p.x / u.sa.x * 2.0 - 1.0, 1.0 - p.y / u.sa.y * 2.0, 0.0, 1.0);
32 o.uv = (uvr.xy + corner * uvr.zw) / u.sa.zw;
33 o.color = color;
34 return o;
35}
36@vertex
37fn vs_img(@builtin(vertex_index) vi: u32, @location(0) rect: vec4<f32>, @location(1) uvr: vec4<f32>, @location(2) color: vec4<f32>) -> VOut {
38 let corner = vec2<f32>(f32(vi & 1u), f32((vi >> 1u) & 1u));
39 let p = rect.xy + corner * rect.zw;
40 var o: VOut;
41 o.pos = vec4<f32>(p.x / u.sa.x * 2.0 - 1.0, 1.0 - p.y / u.sa.y * 2.0, 0.0, 1.0);
42 o.uv = uvr.xy + corner * uvr.zw;
43 o.color = color;
44 return o;
45}
46@fragment
47fn fs_img(i: VOut) -> @location(0) vec4<f32> {
48 return textureSample(tex, samp, i.uv);
49}
50@fragment
51fn fs(i: VOut) -> @location(0) vec4<f32> {
52 let a = textureSample(tex, samp, i.uv).r;
53 return vec4<f32>(i.color.rgb, i.color.a * a);
54}
55"#;
56
57const INSTANCE_FLOATS: usize = 12;
59const ATLAS_START: u32 = 1024;
60const ATLAS_MAX: u32 = 4096;
61
62enum Segment {
63 Quads(std::ops::Range<u32>),
64 Image(u64, u32),
65}
66
67struct Atlas {
68 size: u32,
69 texture: wgpu::Texture,
70 x: u32,
71 y: u32,
72 row_h: u32,
73 slots: HashMap<u64, [u32; 4]>,
75}
76
77fn new_atlas(device: &wgpu::Device, queue: &wgpu::Queue, size: u32) -> Atlas {
78 let texture = device.create_texture(&wgpu::TextureDescriptor {
79 label: Some("mouser glyph atlas"),
80 size: wgpu::Extent3d { width: size, height: size, depth_or_array_layers: 1 },
81 mip_level_count: 1,
82 sample_count: 1,
83 dimension: wgpu::TextureDimension::D2,
84 format: wgpu::TextureFormat::R8Unorm,
85 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
86 view_formats: &[],
87 });
88 write_region(queue, &texture, 0, 0, 2, 2, 2, &[255; 4]);
89 Atlas { size, texture, x: 3, y: 0, row_h: 2, slots: HashMap::new() }
90}
91
92#[allow(clippy::too_many_arguments)]
93fn write_region(queue: &wgpu::Queue, texture: &wgpu::Texture, x: u32, y: u32, w: u32, h: u32, stride: u32, data: &[u8]) {
94 queue.write_texture(
95 wgpu::ImageCopyTexture { texture, mip_level: 0, origin: wgpu::Origin3d { x, y, z: 0 }, aspect: wgpu::TextureAspect::All },
96 data,
97 wgpu::ImageDataLayout { offset: 0, bytes_per_row: Some(stride), rows_per_image: Some(h) },
98 wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
99 );
100}
101
102fn srgb_to_linear(c: f32) -> f32 {
103 if c <= 0.04045 { c / 12.92 } else { ((c + 0.055) / 1.055).powf(2.4) }
104}
105
106fn opacity(c: u32) -> u32 {
108 255 - (c >> 24)
109}
110
111pub struct Renderer {
113 surface: Option<wgpu::Surface<'static>>,
114 target: Option<wgpu::Texture>,
115 device: wgpu::Device,
116 queue: wgpu::Queue,
117 config: wgpu::SurfaceConfiguration,
118 pipeline: wgpu::RenderPipeline,
119 image_pipeline: wgpu::RenderPipeline,
120 layout: wgpu::BindGroupLayout,
121 sampler: wgpu::Sampler,
122 image_sampler: wgpu::Sampler,
123 uniforms: wgpu::Buffer,
124 bind_group: wgpu::BindGroup,
125 atlas: Atlas,
126 instances: wgpu::Buffer,
127 capacity: usize,
128 srgb: bool,
129 textures: HashMap<u64, (wgpu::Texture, wgpu::BindGroup)>,
130 pub description: String,
132}
133
134impl Renderer {
135 pub fn offscreen(width: u32, height: u32) -> Result<Self, String> {
137 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: wgpu::Backends::all(), ..Default::default() });
138 Self::with_target(&instance, None, width, height)
139 }
140
141 pub unsafe fn on_window(
146 display: raw_window_handle::RawDisplayHandle,
147 window: raw_window_handle::RawWindowHandle,
148 width: u32,
149 height: u32,
150 ) -> Result<Self, String> {
151 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: wgpu::Backends::PRIMARY, ..Default::default() });
152 let surface = unsafe {
154 instance.create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle { raw_display_handle: display, raw_window_handle: window })
155 }
156 .map_err(|e| format!("surface: {e}"))?;
157 Self::with_target(&instance, Some(surface), width, height)
158 }
159
160 fn with_target(instance: &wgpu::Instance, surface: Option<wgpu::Surface<'static>>, width: u32, height: u32) -> Result<Self, String> {
161 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
162 power_preference: wgpu::PowerPreference::LowPower,
163 compatible_surface: surface.as_ref(),
164 force_fallback_adapter: false,
165 }))
166 .ok_or("no compatible GPU adapter")?;
167 let info = adapter.get_info();
168 let (device, queue) = pollster::block_on(adapter.request_device(
169 &wgpu::DeviceDescriptor {
170 label: Some("mouser"),
171 required_features: wgpu::Features::empty(),
172 required_limits: wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits()),
173 },
174 None,
175 ))
176 .map_err(|e| format!("device: {e}"))?;
177 let (format, alpha_mode) = match &surface {
178 Some(surface) => {
179 let caps = surface.get_capabilities(&adapter);
180 let format = *caps.formats.iter().find(|f| !f.is_srgb()).or(caps.formats.first()).ok_or("surface has no formats")?;
181 (format, caps.alpha_modes.first().copied().unwrap_or(wgpu::CompositeAlphaMode::Auto))
182 }
183 None => (wgpu::TextureFormat::Rgba8Unorm, wgpu::CompositeAlphaMode::Auto),
184 };
185 let config = wgpu::SurfaceConfiguration {
186 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
187 format,
188 width: width.max(1),
189 height: height.max(1),
190 present_mode: wgpu::PresentMode::Fifo,
191 alpha_mode,
192 view_formats: vec![],
193 desired_maximum_frame_latency: 2,
194 };
195 if let Some(surface) = &surface {
196 surface.configure(&device, &config);
197 }
198 let target = surface.is_none().then(|| offscreen_target(&device, &config));
199 let shader =
200 device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("mouser"), source: wgpu::ShaderSource::Wgsl(SHADER.into()) });
201 let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
202 label: None,
203 entries: &[
204 wgpu::BindGroupLayoutEntry {
205 binding: 0,
206 visibility: wgpu::ShaderStages::VERTEX,
207 ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None },
208 count: None,
209 },
210 wgpu::BindGroupLayoutEntry {
211 binding: 1,
212 visibility: wgpu::ShaderStages::FRAGMENT,
213 ty: wgpu::BindingType::Texture {
214 sample_type: wgpu::TextureSampleType::Float { filterable: true },
215 view_dimension: wgpu::TextureViewDimension::D2,
216 multisampled: false,
217 },
218 count: None,
219 },
220 wgpu::BindGroupLayoutEntry {
221 binding: 2,
222 visibility: wgpu::ShaderStages::FRAGMENT,
223 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
224 count: None,
225 },
226 ],
227 });
228 let pipeline_layout =
229 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[&layout], push_constant_ranges: &[] });
230 let make_pipeline = |vs: &str, fs: &str| {
231 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
232 label: Some("mouser quads"),
233 layout: Some(&pipeline_layout),
234 vertex: wgpu::VertexState {
235 module: &shader,
236 entry_point: vs,
237 compilation_options: Default::default(),
238 buffers: &[wgpu::VertexBufferLayout {
239 array_stride: (INSTANCE_FLOATS * 4) as u64,
240 step_mode: wgpu::VertexStepMode::Instance,
241 attributes: &wgpu::vertex_attr_array![0 => Float32x4, 1 => Float32x4, 2 => Float32x4],
242 }],
243 },
244 fragment: Some(wgpu::FragmentState {
245 module: &shader,
246 entry_point: fs,
247 compilation_options: Default::default(),
248 targets: &[Some(wgpu::ColorTargetState {
249 format,
250 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
251 write_mask: wgpu::ColorWrites::ALL,
252 })],
253 }),
254 primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleStrip, ..Default::default() },
255 depth_stencil: None,
256 multisample: wgpu::MultisampleState::default(),
257 multiview: None,
258 })
259 };
260 let pipeline = make_pipeline("vs", "fs");
261 let image_pipeline = make_pipeline("vs_img", "fs_img");
262 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
263 mag_filter: wgpu::FilterMode::Nearest,
264 min_filter: wgpu::FilterMode::Nearest,
265 ..Default::default()
266 });
267 let image_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
268 mag_filter: wgpu::FilterMode::Linear,
269 min_filter: wgpu::FilterMode::Linear,
270 ..Default::default()
271 });
272 let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
273 label: None,
274 size: 32,
275 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
276 mapped_at_creation: false,
277 });
278 let atlas = new_atlas(&device, &queue, ATLAS_START);
279 let bind_group = bind(&device, &layout, &uniforms, &atlas.texture, &sampler);
280 let capacity = 4096;
281 let instances = instance_buffer(&device, capacity);
282 let description = format!("{} ({:?}, {:?})", info.name, info.backend, format);
283 Ok(Self {
284 surface,
285 target,
286 device,
287 queue,
288 config,
289 pipeline,
290 image_pipeline,
291 layout,
292 sampler,
293 image_sampler,
294 uniforms,
295 bind_group,
296 atlas,
297 instances,
298 capacity,
299 srgb: format.is_srgb(),
300 textures: HashMap::new(),
301 description,
302 })
303 }
304
305 pub fn resize(&mut self, width: u32, height: u32) {
306 self.config.width = width.max(1);
307 self.config.height = height.max(1);
308 match &self.surface {
309 Some(s) => s.configure(&self.device, &self.config),
310 None => self.target = Some(offscreen_target(&self.device, &self.config)),
311 }
312 }
313
314 pub fn evict(&mut self, key: u64) {
316 self.textures.remove(&key);
317 self.atlas.slots.remove(&key);
318 }
319
320 pub fn read_pixels(&self) -> Option<Vec<u32>> {
322 Some(read_texture(&self.device, &self.queue, self.target.as_ref()?, self.config.width, self.config.height))
323 }
324
325 pub fn max_texture(&self) -> u32 {
326 self.device.limits().max_texture_dimension_2d
327 }
328
329 fn reset_atlas(&mut self, size: u32) {
330 self.atlas = new_atlas(&self.device, &self.queue, size);
331 self.bind_group = bind(&self.device, &self.layout, &self.uniforms, &self.atlas.texture, &self.sampler);
332 }
333
334 fn slot(&mut self, g: &MouserGlyph) -> Option<[u32; 4]> {
335 if let Some(s) = self.atlas.slots.get(&g.key) {
336 return Some(*s);
337 }
338 let (w, h) = (g.w, g.h);
339 let a = &mut self.atlas;
340 if w == 0 || h == 0 || g.coverage.is_null() {
341 a.slots.insert(g.key, [0, 0, 0, 0]);
342 return Some([0, 0, 0, 0]);
343 }
344 if a.x + w + 1 > a.size {
345 a.x = 0;
346 a.y += a.row_h + 1;
347 a.row_h = 0;
348 }
349 if a.y + h > a.size {
350 return None;
351 }
352 let data = unsafe { std::slice::from_raw_parts(g.coverage, (g.stride * h) as usize) };
354 let tight: Vec<u8> = data.chunks(g.stride as usize).flat_map(|r| &r[..w as usize]).copied().collect();
356 let slot = [a.x, a.y, w, h];
357 write_region(&self.queue, &a.texture, a.x, a.y, w, h, w, &tight);
358 a.x += w + 1;
359 a.row_h = a.row_h.max(h);
360 a.slots.insert(g.key, slot);
361 Some(slot)
362 }
363
364 fn color(&self, c: u32) -> [f32; 3] {
365 let ch = |s: u32| ((c >> s) & 0xFF) as f32 / 255.0;
366 let rgb = [ch(16), ch(8), ch(0)];
367 if self.srgb { rgb.map(srgb_to_linear) } else { rgb }
368 }
369
370 fn image_texture(&mut self, img: &MouserImage) {
371 if self.textures.contains_key(&img.key) {
372 return;
373 }
374 let format = if self.srgb { wgpu::TextureFormat::Rgba8UnormSrgb } else { wgpu::TextureFormat::Rgba8Unorm };
375 let size = wgpu::Extent3d { width: img.img_w, height: img.img_h, depth_or_array_layers: 1 };
376 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
377 label: Some("mouser image"),
378 size,
379 mip_level_count: 1,
380 sample_count: 1,
381 dimension: wgpu::TextureDimension::D2,
382 format,
383 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
384 view_formats: &[],
385 });
386 let rgba = unsafe { std::slice::from_raw_parts(img.rgba, (img.img_w * img.img_h * 4) as usize) };
388 self.queue.write_texture(
389 wgpu::ImageCopyTexture { texture: &texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All },
390 rgba,
391 wgpu::ImageDataLayout { offset: 0, bytes_per_row: Some(img.img_w * 4), rows_per_image: Some(img.img_h) },
392 size,
393 );
394 let bind = bind(&self.device, &self.layout, &self.uniforms, &texture, &self.image_sampler);
395 self.textures.insert(img.key, (texture, bind));
396 }
397
398 unsafe fn build(&mut self, frame: &MouserFrame) -> Option<(Vec<f32>, Vec<Segment>)> {
401 let (ops, rects, glyphs, images) = unsafe { frame.parts() };
403 let max = self.device.limits().max_texture_dimension_2d;
404 let mut data = Vec::with_capacity(ops.len() * INSTANCE_FLOATS);
405 let mut segs = Vec::new();
406 let mut start = 0usize;
407 for op in ops {
408 let i = op.index as usize;
409 if op.kind == OP_IMAGE {
410 let Some(img) = images.get(i) else { continue };
411 if img.img_w == 0 || img.img_h == 0 || img.img_w > max || img.img_h > max || img.rgba.is_null() {
412 continue;
413 }
414 self.image_texture(img);
415 let n = data.len() / INSTANCE_FLOATS;
416 if n > start {
417 segs.push(Segment::Quads(start as u32..n as u32));
418 }
419 let (iw, ih) = (img.img_w as f32, img.img_h as f32);
420 data.extend_from_slice(&[img.x as f32, img.y as f32, img.w as f32, img.h as f32]);
421 data.extend_from_slice(&[img.src_x as f32 / iw, img.src_y as f32 / ih, img.src_w as f32 / iw, img.src_h as f32 / ih]);
422 data.extend_from_slice(&[1.0, 1.0, 1.0, 1.0]);
423 segs.push(Segment::Image(img.key, n as u32));
424 start = n + 1;
425 continue;
426 }
427 let (rect, uv, color) = match op.kind {
428 OP_RECT => {
429 let Some(r) = rects.get(i) else { continue };
430 ([r.x as f32, r.y as f32, r.w as f32, r.h as f32], [0.5, 0.5, 0.0, 0.0], r.color)
431 }
432 OP_GLYPH => {
433 let Some(g) = glyphs.get(i) else { continue };
434 let s = self.slot(g)?;
435 if s[2] == 0 {
436 continue;
437 }
438 let f = s.map(|v| v as f32);
439 ([g.x as f32, g.y as f32, f[2], f[3]], f, g.color)
440 }
441 _ => continue,
442 };
443 let c = self.color(color);
444 data.extend_from_slice(&rect);
445 data.extend_from_slice(&uv);
446 data.extend_from_slice(&[c[0], c[1], c[2], opacity(color) as f32 / 255.0]);
447 }
448 let n = data.len() / INSTANCE_FLOATS;
449 if n > start {
450 segs.push(Segment::Quads(start as u32..n as u32));
451 }
452 Some((data, segs))
453 }
454
455 pub unsafe fn draw(&mut self, frame: &MouserFrame) -> c_int {
460 let built = match unsafe { self.build(frame) } {
462 Some(d) => Some(d),
463 None => {
464 let size = (self.atlas.size * 2).min(ATLAS_MAX).min(self.device.limits().max_texture_dimension_2d);
465 self.reset_atlas(size);
466 unsafe { self.build(frame) }
468 }
469 };
470 let Some((data, segs)) = built else { return DRAW_OK };
471 let (_, _, _, images) = unsafe { frame.parts() };
473 let used: std::collections::HashSet<u64> = images.iter().map(|i| i.key).collect();
474 self.textures.retain(|k, _| used.contains(k));
475 let count = data.len() / INSTANCE_FLOATS;
476 if count > self.capacity {
477 self.capacity = count.next_power_of_two();
478 self.instances = instance_buffer(&self.device, self.capacity);
479 }
480 let bytes: Vec<u8> = data.iter().flat_map(|f| f.to_ne_bytes()).collect();
481 if !bytes.is_empty() {
482 self.queue.write_buffer(&self.instances, 0, &bytes);
483 }
484 let u = [self.config.width as f32, self.config.height as f32, self.atlas.size as f32, self.atlas.size as f32, 0.0, 0.0, 0.0, 0.0];
485 let ub: Vec<u8> = u.iter().flat_map(|f| f.to_ne_bytes()).collect();
486 self.queue.write_buffer(&self.uniforms, 0, &ub);
487 let out = match &self.surface {
488 None => None,
489 Some(surface) => match surface.get_current_texture() {
490 Ok(f) => Some(f),
491 Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
492 surface.configure(&self.device, &self.config);
493 return DRAW_AGAIN;
494 }
495 Err(_) => return DRAW_AGAIN,
496 },
497 };
498 let texture = match (&out, &self.target) {
499 (Some(f), _) => &f.texture,
500 (None, Some(t)) => t,
501 (None, None) => return DRAW_AGAIN,
502 };
503 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
504 let bg = self.color(frame.clear);
505 let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
506 {
507 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
508 label: None,
509 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
510 view: &view,
511 resolve_target: None,
512 ops: wgpu::Operations {
513 load: wgpu::LoadOp::Clear(wgpu::Color { r: bg[0] as f64, g: bg[1] as f64, b: bg[2] as f64, a: 1.0 }),
514 store: wgpu::StoreOp::Store,
515 },
516 })],
517 depth_stencil_attachment: None,
518 timestamp_writes: None,
519 occlusion_query_set: None,
520 });
521 if count > 0 {
522 pass.set_vertex_buffer(0, self.instances.slice(..(count * INSTANCE_FLOATS * 4) as u64));
523 for seg in &segs {
524 match seg {
525 Segment::Quads(r) => {
526 pass.set_pipeline(&self.pipeline);
527 pass.set_bind_group(0, &self.bind_group, &[]);
528 pass.draw(0..4, r.clone());
529 }
530 Segment::Image(key, i) => {
531 if let Some((_, bind)) = self.textures.get(key) {
532 pass.set_pipeline(&self.image_pipeline);
533 pass.set_bind_group(0, bind, &[]);
534 pass.draw(0..4, *i..*i + 1);
535 }
536 }
537 }
538 }
539 }
540 }
541 self.queue.submit(Some(encoder.finish()));
542 if let Some(f) = out {
543 f.present();
544 }
545 DRAW_OK
546 }
547}
548
549fn bind(
550 device: &wgpu::Device,
551 layout: &wgpu::BindGroupLayout,
552 uniforms: &wgpu::Buffer,
553 texture: &wgpu::Texture,
554 sampler: &wgpu::Sampler,
555) -> wgpu::BindGroup {
556 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
557 device.create_bind_group(&wgpu::BindGroupDescriptor {
558 label: None,
559 layout,
560 entries: &[
561 wgpu::BindGroupEntry { binding: 0, resource: uniforms.as_entire_binding() },
562 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&view) },
563 wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(sampler) },
564 ],
565 })
566}
567
568fn instance_buffer(device: &wgpu::Device, capacity: usize) -> wgpu::Buffer {
569 device.create_buffer(&wgpu::BufferDescriptor {
570 label: Some("mouser instances"),
571 size: (capacity * INSTANCE_FLOATS * 4) as u64,
572 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
573 mapped_at_creation: false,
574 })
575}
576
577fn offscreen_target(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> wgpu::Texture {
578 device.create_texture(&wgpu::TextureDescriptor {
579 label: Some("mouser offscreen"),
580 size: wgpu::Extent3d { width: config.width, height: config.height, depth_or_array_layers: 1 },
581 mip_level_count: 1,
582 sample_count: 1,
583 dimension: wgpu::TextureDimension::D2,
584 format: config.format,
585 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
586 view_formats: &[],
587 })
588}
589
590fn read_texture(device: &wgpu::Device, queue: &wgpu::Queue, texture: &wgpu::Texture, w: u32, h: u32) -> Vec<u32> {
591 let row = (w * 4).div_ceil(256) * 256;
592 let buf = device.create_buffer(&wgpu::BufferDescriptor {
593 label: Some("mouser readback"),
594 size: (row * h) as u64,
595 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
596 mapped_at_creation: false,
597 });
598 let mut enc = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
599 enc.copy_texture_to_buffer(
600 wgpu::ImageCopyTexture { texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All },
601 wgpu::ImageCopyBuffer { buffer: &buf, layout: wgpu::ImageDataLayout { offset: 0, bytes_per_row: Some(row), rows_per_image: Some(h) } },
602 wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
603 );
604 queue.submit(Some(enc.finish()));
605 let slice = buf.slice(..);
606 slice.map_async(wgpu::MapMode::Read, |_| {});
607 device.poll(wgpu::Maintain::Wait);
608 let data = slice.get_mapped_range();
609 let mut out = Vec::with_capacity((w * h) as usize);
610 for y in 0..h as usize {
611 let r = &data[y * row as usize..][..w as usize * 4];
612 out.extend(r.as_chunks::<4>().0.iter().map(|p| (p[0] as u32) << 16 | (p[1] as u32) << 8 | p[2] as u32));
613 }
614 out
615}
616
617struct Engine {
621 r: Renderer,
622 pixels: Option<*mut MouserPixels>,
623}
624
625fn raw_handles(s: &MouserSurface) -> Result<(raw_window_handle::RawDisplayHandle, raw_window_handle::RawWindowHandle), String> {
626 use raw_window_handle::*;
627 use std::ptr::NonNull;
628 let nn = |p: *mut c_void, what: &str| NonNull::new(p).ok_or(format!("null {what}"));
629 Ok(match s.kind {
630 SURFACE_WAYLAND => (
631 RawDisplayHandle::Wayland(WaylandDisplayHandle::new(nn(s.display, "wl_display")?)),
632 RawWindowHandle::Wayland(WaylandWindowHandle::new(nn(s.window, "wl_surface")?)),
633 ),
634 SURFACE_XLIB => (
635 RawDisplayHandle::Xlib(XlibDisplayHandle::new(NonNull::new(s.display), 0)),
636 RawWindowHandle::Xlib(XlibWindowHandle::new(s.window as usize as _)),
637 ),
638 SURFACE_XCB => (
639 RawDisplayHandle::Xcb(XcbDisplayHandle::new(NonNull::new(s.display), 0)),
640 RawWindowHandle::Xcb(XcbWindowHandle::new(std::num::NonZeroU32::new(s.window as usize as u32).ok_or("null xcb window")?)),
641 ),
642 SURFACE_APPKIT => {
643 (RawDisplayHandle::AppKit(AppKitDisplayHandle::new()), RawWindowHandle::AppKit(AppKitWindowHandle::new(nn(s.window, "NSView")?)))
644 }
645 SURFACE_WIN32 => {
646 let mut w = Win32WindowHandle::new(std::num::NonZeroIsize::new(s.window as isize).ok_or("null HWND")?);
647 w.hinstance = std::num::NonZeroIsize::new(s.display as isize);
648 (RawDisplayHandle::Windows(WindowsDisplayHandle::new()), RawWindowHandle::Win32(w))
649 }
650 k => return Err(format!("unsupported surface kind {k}")),
651 })
652}
653
654unsafe extern "C" fn create(surface: *const MouserSurface, w: u32, h: u32, err: *mut c_char, err_len: usize) -> *mut c_void {
655 let fail = |m: String| {
656 unsafe { write_err(err, err_len, &m) };
658 std::ptr::null_mut()
659 };
660 let Some(s) = (unsafe { surface.as_ref() }) else { return fail("no surface".into()) };
662 let made = if s.kind == SURFACE_PIXELS {
663 Renderer::offscreen(w, h).map(|r| Engine { r, pixels: Some(s.window as *mut MouserPixels) })
664 } else {
665 match raw_handles(s) {
666 Ok((d, win)) => unsafe { Renderer::on_window(d, win, w, h) }.map(|r| Engine { r, pixels: None }),
668 Err(e) => Err(e),
669 }
670 };
671 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| made)) {
672 Ok(Ok(e)) => Box::into_raw(Box::new(e)) as *mut c_void,
673 Ok(Err(e)) => fail(e),
674 Err(_) => fail("wgpu panicked while starting".into()),
675 }
676}
677
678unsafe fn engine<'a>(p: *mut c_void) -> Option<&'a mut Engine> {
679 unsafe { (p as *mut Engine).as_mut() }
681}
682
683unsafe extern "C" fn resize(e: *mut c_void, w: u32, h: u32) {
684 if let Some(e) = unsafe { engine(e) } {
685 e.r.resize(w, h);
686 }
687}
688
689unsafe extern "C" fn draw(e: *mut c_void, frame: *const MouserFrame) -> c_int {
690 let (Some(e), Some(frame)) = (unsafe { engine(e) }, unsafe { frame.as_ref() }) else { return -1 };
691 let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
692 let r = unsafe { e.r.draw(frame) };
694 if let (Some(px), Some(out)) = (e.pixels, e.r.read_pixels()) {
695 if let Some(px) = unsafe { px.as_ref() } {
697 let (w, h) = (px.width.min(e.r.config.width) as usize, px.height.min(e.r.config.height) as usize);
698 for y in 0..h {
699 let dst = unsafe { std::slice::from_raw_parts_mut(px.data.add(y * px.stride as usize), w) };
701 dst.copy_from_slice(&out[y * e.r.config.width as usize..][..w]);
702 }
703 }
704 }
705 r
706 }));
707 r.unwrap_or(-2)
708}
709
710unsafe extern "C" fn destroy(e: *mut c_void) {
711 if !e.is_null() {
712 drop(unsafe { Box::from_raw(e as *mut Engine) });
714 }
715}
716
717unsafe extern "C" fn caps(e: *mut c_void, caps: *mut MouserCaps) {
718 if let (Some(e), Some(c)) = (unsafe { engine(e) }, unsafe { caps.as_mut() }) {
719 c.flags = CAP_DIRECT_SURFACE | CAP_PIXELS;
720 c.formats = FORMAT_XRGB8888;
721 c.max_texture = e.r.max_texture();
722 }
723}
724
725unsafe extern "C" fn evict(e: *mut c_void, key: u64) {
726 if let Some(e) = unsafe { engine(e) } {
727 e.r.evict(key);
728 }
729}
730
731static TABLE_V2: MouserEngineV2 = MouserEngineV2 {
732 base: MouserEngineV1 {
733 abi: MOUSER_ENGINE_ABI_V2,
734 size: std::mem::size_of::<MouserEngineV2>() as u32,
735 name: c"mouser".as_ptr(),
736 create,
737 resize,
738 draw,
739 destroy,
740 },
741 abi_min: MOUSER_ENGINE_ABI_V2,
742 abi_max: MOUSER_ENGINE_ABI_V2,
743 caps: Some(caps),
744 set_scale: None,
745 evict: Some(evict),
746 set_vsync: None,
747};
748
749#[unsafe(no_mangle)]
751pub extern "C" fn mouser_engine_v2() -> *const MouserEngineV2 {
752 &TABLE_V2
753}