1use alloc::string::String;
4use alloc::vec::Vec;
5use spin::Mutex;
6
7use super::cache::{read_cache, write_cache};
8
9pub(super) struct ImageLoadQueue {
14 pub pending: Vec<ImageFetchReq>,
16 pub results: Vec<(String, super::DecodedImage)>,
18 pub active_workers: usize,
26}
27
28#[derive(Clone)]
29pub(super) struct ImageFetchReq {
30 pub src_key: String, pub is_https: bool,
32 pub host: String,
33 pub path: String,
34 pub is_local: bool, pub owner: u64, }
37
38pub(super) static IMAGE_QUEUE: Mutex<ImageLoadQueue> = Mutex::new(ImageLoadQueue {
39 pending: Vec::new(),
40 results: Vec::new(),
41 active_workers: 0,
42});
43
44const MAX_IMAGE_WORKERS: usize = 4;
49
50fn svg_target_size(data: &[u8]) -> (u32, u32) {
52 let (w, h) = crate::os_lib::svg::natural_size(data);
53 let cap = 512.0f32;
54 let scale = (cap / w.max(1.0)).min(cap / h.max(1.0)).clamp(0.01, 1.0);
55 let tw = (w * if w > cap || h > cap { scale } else { 1.0 }).max(1.0) as u32;
57 let th = (h * if w > cap || h > cap { scale } else { 1.0 }).max(1.0) as u32;
58 (tw.max(1), th.max(1))
59}
60
61pub(super) fn decode_image(data: &[u8]) -> Option<super::DecodedImage> {
62 crate::debug!("[GPU] TRACE: decode_image start. len={}", data.len());
63 {
65 let head_len = data.len().min(512);
66 if let Ok(head) = core::str::from_utf8(&data[..head_len]) {
67 let ht = head.trim_start();
68 if ht.starts_with("<svg")
69 || ht.starts_with("<?xml")
70 || ht.starts_with("<!--") && head.contains("<svg")
71 {
72 crate::debug!("[GPU] TRACE: decode_image SVG target size parsing...");
73 let (tw, th) = svg_target_size(data);
74 crate::debug!("[GPU] TRACE: decode_image SVG decoding...");
75 if let Some((w, h, rgba)) = crate::os_lib::svg::decode_svg_image(data, tw, th) {
76 crate::debug!("[GPU] TRACE: decode_image SVG success");
77 return Some(super::DecodedImage {
78 width: w,
79 height: h,
80 rgba,
81 });
82 }
83 crate::debug!("[GPU] TRACE: decode_image SVG failed");
84 return None;
85 }
86 }
87 }
88 if data.len() > 8 && &data[0..8] == b"\x89PNG\r\n\x1a\n" {
89 crate::debug!("[GPU] TRACE: decode_image trying PNG...");
90 let mut decoder = zune_png::PngDecoder::new(data);
91 if let Ok(pixels) = decoder.decode_raw() {
92 if let Some(info) = decoder.get_info() {
93 let width = info.width as u32;
94 let height = info.height as u32;
95 let bpp = pixels.len() / (width as usize * height as usize).max(1);
96 const MAX_DIM: u32 = 1024;
97 let (out_w, out_h) = if width > MAX_DIM || height > MAX_DIM {
98 if width * MAX_DIM > height * MAX_DIM {
99 (MAX_DIM, (height * MAX_DIM / width).max(1))
100 } else {
101 ((width * MAX_DIM / height).max(1), MAX_DIM)
102 }
103 } else {
104 (width, height)
105 };
106 let mut rgba = alloc::vec::Vec::with_capacity((out_w * out_h * 4) as usize);
107 for row in 0..out_h {
108 let src_row = (row * height) / out_h;
109 for col in 0..out_w {
110 let src_col = (col * width) / out_w;
111 let i = (src_row * width + src_col) as usize;
112 let idx = i * bpp;
113 if idx + bpp <= pixels.len() {
114 let (r, g, b, a) = match bpp {
115 1 => (pixels[idx], pixels[idx], pixels[idx], 255),
116 2 => (pixels[idx], pixels[idx], pixels[idx], pixels[idx + 1]),
117 3 => (pixels[idx], pixels[idx + 1], pixels[idx + 2], 255),
118 4 => (
119 pixels[idx],
120 pixels[idx + 1],
121 pixels[idx + 2],
122 pixels[idx + 3],
123 ),
124 _ => (0, 0, 0, 255),
125 };
126 rgba.push(r);
127 rgba.push(g);
128 rgba.push(b);
129 rgba.push(a);
130 }
131 }
132 }
133 crate::debug!(
134 "[GPU] TRACE: decode_image PNG success {}x{} -> {}x{}",
135 width,
136 height,
137 out_w,
138 out_h
139 );
140 return Some(super::DecodedImage {
141 width: out_w,
142 height: out_h,
143 rgba,
144 });
145 }
146 }
147 crate::debug!("[GPU] TRACE: decode_image PNG failed");
148 } else if data.len() > 2 && data[0] == 0xFF && data[1] == 0xD8 {
149 crate::debug!("[GPU] TRACE: decode_image trying JPEG...");
150 const MAX_JPEG_DIM: u32 = 8192;
160 {
161 let mut i = 2usize;
162 let mut skip = false;
163 while i + 3 < data.len() {
164 if data[i] != 0xFF {
165 break;
166 }
167 let marker = data[i + 1];
168 let is_sof =
169 matches!(marker, 0xC0..=0xC3 | 0xC5..=0xC7 | 0xC9..=0xCB | 0xCD..=0xCF);
170 if is_sof && i + 8 < data.len() {
171 let h = u16::from_be_bytes([data[i + 5], data[i + 6]]) as u32;
172 let w = u16::from_be_bytes([data[i + 7], data[i + 8]]) as u32;
173 if w > MAX_JPEG_DIM || h > MAX_JPEG_DIM {
174 crate::debug!(
175 "[GPU] TRACE: decode_image JPEG too large {}x{}, skipping (max {})",
176 w,
177 h,
178 MAX_JPEG_DIM
179 );
180 skip = true;
181 }
182 break;
183 }
184 if i + 3 >= data.len() {
185 break;
186 }
187 let seg_len = u16::from_be_bytes([data[i + 2], data[i + 3]]) as usize;
188 if seg_len < 2 {
189 break;
190 }
191 i += 2 + seg_len;
192 }
193 if skip {
194 return None;
195 }
196 }
197 let mut decoder = zune_jpeg::JpegDecoder::new(data);
198 let t0 = crate::kernel::timer::get_ticks();
201 let decoded = decoder.decode();
202 crate::warn!(
203 "[IMGDIAG] jpeg decode ok={} bytes={} elapsed_ticks={}",
204 decoded.is_ok(),
205 data.len(),
206 crate::kernel::timer::get_ticks().wrapping_sub(t0)
207 );
208 if let Ok(pixels) = decoded {
209 if let Some(info) = decoder.info() {
210 let width = info.width as u32;
211 let height = info.height as u32;
212 let bpp = pixels.len() / (width as usize * height as usize).max(1);
213 const MAX_DIM: u32 = 1024;
214 let (out_w, out_h) = if width > MAX_DIM || height > MAX_DIM {
215 if width * MAX_DIM > height * MAX_DIM {
216 (MAX_DIM, (height * MAX_DIM / width).max(1))
217 } else {
218 ((width * MAX_DIM / height).max(1), MAX_DIM)
219 }
220 } else {
221 (width, height)
222 };
223 let mut rgba = alloc::vec::Vec::with_capacity((out_w * out_h * 4) as usize);
224 for row in 0..out_h {
225 let src_row = (row * height) / out_h;
226 for col in 0..out_w {
227 let src_col = (col * width) / out_w;
228 let i = (src_row * width + src_col) as usize;
229 let idx = i * bpp;
230 if idx + bpp <= pixels.len() {
231 let (r, g, b) = match bpp {
232 1 => (pixels[idx], pixels[idx], pixels[idx]),
233 3 => (pixels[idx], pixels[idx + 1], pixels[idx + 2]),
234 _ => (0, 0, 0),
235 };
236 rgba.push(r);
237 rgba.push(g);
238 rgba.push(b);
239 rgba.push(255);
240 }
241 }
242 }
243 crate::debug!(
244 "[GPU] TRACE: decode_image JPEG success {}x{} -> {}x{}",
245 width,
246 height,
247 out_w,
248 out_h
249 );
250 return Some(super::DecodedImage {
251 width: out_w,
252 height: out_h,
253 rgba,
254 });
255 }
256 }
257 crate::debug!("[GPU] TRACE: decode_image JPEG failed");
258 } else if data.len() > 6 && &data[0..3] == b"GIF" {
259 crate::debug!("[GPU] TRACE: decode_image trying GIF...");
260 if let Some(img) = decode_gif(data) {
261 crate::debug!("[GPU] TRACE: decode_image GIF success");
262 return Some(img);
263 }
264 crate::debug!("[GPU] TRACE: decode_image GIF failed");
265 } else if data.len() > 12 && &data[0..4] == b"RIFF" && &data[8..12] == b"WEBP" {
266 crate::debug!("[GPU] TRACE: decode_image trying WEBP...");
267 if let Some((w, h, rgba)) = crate::os_lib::webp::decode_webp(data) {
268 crate::debug!("[GPU] TRACE: decode_image WEBP success");
269 return Some(super::DecodedImage {
270 width: w,
271 height: h,
272 rgba,
273 });
274 }
275 crate::debug!("[GPU] TRACE: decode_image WEBP failed");
276 } else if data.len() >= 54 && &data[0..2] == b"BM" {
277 crate::debug!("[GPU] TRACE: decode_image trying BMP...");
278 let off_bits = u32::from_le_bytes(data[10..14].try_into().unwrap_or([0; 4])) as usize;
279 let width = i32::from_le_bytes(data[18..22].try_into().unwrap_or([0; 4]));
280 let height = i32::from_le_bytes(data[22..26].try_into().unwrap_or([0; 4]));
281 let bit_count = u16::from_le_bytes(data[28..30].try_into().unwrap_or([0; 2]));
282 let compression = u32::from_le_bytes(data[30..34].try_into().unwrap_or([0; 4]));
283 if compression == 0 && (bit_count == 24 || bit_count == 32) {
284 let abs_h = height.unsigned_abs();
285 let bpp = (bit_count / 8) as usize;
286 let row_stride = (width.unsigned_abs() as usize * bpp).div_ceil(4) * 4;
287 let mut rgba =
288 alloc::vec::Vec::with_capacity((width.unsigned_abs() * abs_h * 4) as usize);
289 for row in 0..abs_h {
290 let src_row = if height > 0 { abs_h - 1 - row } else { row };
291 let row_off = off_bits + (src_row as usize * row_stride);
292 for col in 0..width.unsigned_abs() {
293 let pix_off = row_off + (col as usize * bpp);
294 if pix_off + bpp <= data.len() {
295 let b = data[pix_off];
296 let g = data[pix_off + 1];
297 let r = data[pix_off + 2];
298 let a = if bpp == 4 { data[pix_off + 3] } else { 255 };
299 rgba.push(r);
300 rgba.push(g);
301 rgba.push(b);
302 rgba.push(a);
303 } else {
304 rgba.push(0);
305 rgba.push(0);
306 rgba.push(0);
307 rgba.push(255);
308 }
309 }
310 }
311 crate::debug!("[GPU] TRACE: decode_image BMP success");
312 return Some(super::DecodedImage {
313 width: width.unsigned_abs(),
314 height: abs_h,
315 rgba,
316 });
317 }
318 crate::debug!("[GPU] TRACE: decode_image BMP failed");
319 }
320 crate::debug!("[GPU] TRACE: decode_image unsupported/unknown");
321 None
322}
323
324fn decode_gif(data: &[u8]) -> Option<super::DecodedImage> {
328 let mut p = 6usize; if data.len() < 13 {
330 return None;
331 }
332
333 let screen_w = u16::from_le_bytes([data[p], data[p + 1]]) as u32;
334 let screen_h = u16::from_le_bytes([data[p + 2], data[p + 3]]) as u32;
335 let packed = data[6 + 4];
336 p = 13;
337
338 let gct_flag = (packed & 0x80) != 0;
340 let gct_size = 2usize << (packed & 0x07); let mut global_palette: Vec<[u8; 3]> = Vec::new();
342 if gct_flag {
343 for _ in 0..gct_size {
344 if p + 3 > data.len() {
345 return None;
346 }
347 global_palette.push([data[p], data[p + 1], data[p + 2]]);
348 p += 3;
349 }
350 }
351
352 let mut transparent_idx: Option<u8> = None;
353
354 while p < data.len() {
356 match data[p] {
357 0x21 => {
358 p += 1;
360 if p >= data.len() {
361 return None;
362 }
363 let label = data[p];
364 p += 1;
365 if label == 0xF9 {
366 if p < data.len() {
368 let blk_size = data[p] as usize; if p + 1 + blk_size <= data.len() && blk_size >= 4 {
370 let gce_packed = data[p + 1];
371 if gce_packed & 0x01 != 0 {
372 transparent_idx = Some(data[p + 4]);
373 }
374 }
375 }
376 }
377 p = skip_gif_subblocks(data, p)?;
379 }
380 0x2C => {
381 if p + 10 > data.len() {
383 return None;
384 }
385 let img_w = u16::from_le_bytes([data[p + 5], data[p + 6]]) as u32;
386 let img_h = u16::from_le_bytes([data[p + 7], data[p + 8]]) as u32;
387 let ipacked = data[p + 9];
388 let lct_flag = (ipacked & 0x80) != 0;
389 let interlace = (ipacked & 0x40) != 0;
390 let lct_size = 2usize << (ipacked & 0x07);
391 p += 10;
392
393 let palette = if lct_flag {
395 let mut lp: Vec<[u8; 3]> = Vec::new();
396 for _ in 0..lct_size {
397 if p + 3 > data.len() {
398 return None;
399 }
400 lp.push([data[p], data[p + 1], data[p + 2]]);
401 p += 3;
402 }
403 lp
404 } else {
405 global_palette.clone()
406 };
407 if palette.is_empty() {
408 return None;
409 }
410 if img_w == 0 || img_h == 0 || img_w > 4096 || img_h > 4096 {
411 return None;
412 }
413
414 if p >= data.len() {
416 return None;
417 }
418 let min_code_size = data[p];
419 p += 1;
420
421 let mut lzw_data: Vec<u8> = Vec::new();
423 loop {
424 if p >= data.len() {
425 return None;
426 }
427 let bs = data[p] as usize;
428 p += 1;
429 if bs == 0 {
430 break;
431 }
432 if p + bs > data.len() {
433 return None;
434 }
435 lzw_data.extend_from_slice(&data[p..p + bs]);
436 p += bs;
437 }
438
439 let indices = gif_lzw_decode(&lzw_data, min_code_size, (img_w * img_h) as usize)?;
440
441 let total = (img_w * img_h) as usize;
443 let mut rgba = alloc::vec![0u8; total * 4];
444 let row_order: Vec<u32> = if interlace {
445 gif_interlace_rows(img_h)
446 } else {
447 (0..img_h).collect()
448 };
449 let mut src = 0usize;
450 for &dst_row in row_order.iter() {
451 for col in 0..img_w {
452 if src >= indices.len() {
453 break;
454 }
455 let ci = indices[src];
456 src += 1;
457 let dst = ((dst_row * img_w + col) as usize) * 4;
458 if Some(ci) == transparent_idx {
459 rgba[dst + 3] = 0; } else {
461 let c = palette.get(ci as usize).copied().unwrap_or([0, 0, 0]);
462 rgba[dst] = c[0];
463 rgba[dst + 1] = c[1];
464 rgba[dst + 2] = c[2];
465 rgba[dst + 3] = 255;
466 }
467 }
468 }
469 let _ = (screen_w, screen_h);
470 return Some(super::DecodedImage {
471 width: img_w,
472 height: img_h,
473 rgba,
474 });
475 }
476 0x3B => break, _ => {
478 p += 1;
479 }
480 }
481 }
482 None
483}
484
485fn skip_gif_subblocks(data: &[u8], mut p: usize) -> Option<usize> {
487 loop {
488 if p >= data.len() {
489 return None;
490 }
491 let bs = data[p] as usize;
492 p += 1;
493 if bs == 0 {
494 return Some(p);
495 }
496 p += bs;
497 }
498}
499
500fn gif_interlace_rows(h: u32) -> Vec<u32> {
502 let mut rows = Vec::with_capacity(h as usize);
503 let mut y = 0;
504 while y < h {
505 rows.push(y);
506 y += 8;
507 }
508 y = 4;
509 while y < h {
510 rows.push(y);
511 y += 8;
512 }
513 y = 2;
514 while y < h {
515 rows.push(y);
516 y += 4;
517 }
518 y = 1;
519 while y < h {
520 rows.push(y);
521 y += 2;
522 }
523 rows
524}
525
526fn gif_lzw_decode(data: &[u8], min_code_size: u8, expected: usize) -> Option<Vec<u8>> {
528 let clear_code = 1u16 << min_code_size;
529 let end_code = clear_code + 1;
530 let mut code_size = min_code_size + 1;
531
532 let mut prefix: Vec<i32> = Vec::with_capacity(4096);
534 let mut suffix: Vec<u8> = Vec::with_capacity(4096);
535 let reset_dict = |prefix: &mut Vec<i32>, suffix: &mut Vec<u8>| {
536 prefix.clear();
537 suffix.clear();
538 for i in 0..clear_code {
539 prefix.push(-1);
540 suffix.push(i as u8);
541 }
542 prefix.push(-1);
543 suffix.push(0); prefix.push(-1);
545 suffix.push(0); };
547 reset_dict(&mut prefix, &mut suffix);
548
549 let mut out: Vec<u8> = Vec::with_capacity(expected);
550 let mut bit_pos = 0usize;
551 let total_bits = data.len() * 8;
552 let mut prev_code: i32 = -1;
553 let mut stack: Vec<u8> = Vec::with_capacity(4096);
554
555 let read_code = |bit_pos: &mut usize, code_size: u8| -> Option<u16> {
556 if *bit_pos + code_size as usize > total_bits {
557 return None;
558 }
559 let mut val = 0u32;
560 for i in 0..code_size as usize {
561 let byte = data[(*bit_pos + i) >> 3];
562 let bit = (byte >> ((*bit_pos + i) & 7)) & 1;
563 val |= (bit as u32) << i;
564 }
565 *bit_pos += code_size as usize;
566 Some(val as u16)
567 };
568
569 while let Some(code) = read_code(&mut bit_pos, code_size) {
570 if code == clear_code {
571 reset_dict(&mut prefix, &mut suffix);
572 code_size = min_code_size + 1;
573 prev_code = -1;
574 continue;
575 }
576 if code == end_code {
577 break;
578 }
579
580 stack.clear();
582 let mut cur = code as i32;
583 let first_byte;
584 if (cur as usize) < suffix.len() {
585 while cur >= 0 && (cur as usize) < suffix.len() {
587 stack.push(suffix[cur as usize]);
588 cur = prefix[cur as usize];
589 }
590 first_byte = *stack.last()?;
591 } else {
592 if prev_code < 0 {
594 return None;
595 }
596 let mut c2 = prev_code;
597 while c2 >= 0 && (c2 as usize) < suffix.len() {
598 stack.push(suffix[c2 as usize]);
599 c2 = prefix[c2 as usize];
600 }
601 first_byte = *stack.last()?;
602 stack.insert(0, first_byte); }
604
605 for &b in stack.iter().rev() {
607 out.push(b);
608 }
609
610 if prev_code >= 0 && suffix.len() < 4096 {
612 prefix.push(prev_code);
613 suffix.push(first_byte);
614 if suffix.len() == (1usize << code_size) && code_size < 12 {
616 code_size += 1;
617 }
618 }
619 prev_code = code as i32;
620
621 if out.len() >= expected {
622 break;
623 }
624 }
625
626 Some(out)
627}
628
629pub(super) fn pump() {
632 let Some(mut q) = IMAGE_QUEUE.try_lock() else {
636 return;
637 };
638 let dead = super::fetch_limit::take_image_deaths();
640 if dead > 0 {
641 crate::warn!("[IMG] パニックで死んだワーカー {} 本を回収", dead);
642 q.active_workers = q.active_workers.saturating_sub(dead);
643 store_worker_count(q.active_workers);
644 }
645 if q.pending.is_empty() {
646 return;
647 }
648 let higher = super::font_queue::pending_higher_than(
650 super::fetch_limit::Kind::Image.priority(),
651 );
652 if q.active_workers < MAX_IMAGE_WORKERS
653 && super::fetch_limit::may_spawn_lower(
654 super::fetch_limit::main_load_in_flight(),
655 super::fetch_limit::active(),
656 q.pending.len(),
657 higher,
658 )
659 && super::fetch_limit::try_acquire(q.pending.len())
660 {
661 q.active_workers += 1;
662 store_worker_count(q.active_workers);
663 let pid = crate::kernel::scheduler::spawn(
668 image_loader_thread,
669 crate::kernel::scheduler::Priority::Low,
670 "img_load",
671 );
672 if pid == 0 {
673 q.active_workers = q.active_workers.saturating_sub(1);
674 store_worker_count(q.active_workers);
675 super::fetch_limit::release();
676 }
677 }
678}
679
680fn image_loader_thread() {
681 let my_pid = crate::kernel::scheduler::get_current_process_id().unwrap_or(0);
683 super::fetch_limit::claim(my_pid, super::fetch_limit::KIND_IMAGE);
684 loop {
685 let req = {
688 let mut q = IMAGE_QUEUE.lock();
689 let popped = q.pending.pop();
690 IMG_PENDING.store(q.pending.len(), core::sync::atomic::Ordering::Release);
691 match popped {
692 Some(r) => r,
693 None => {
694 q.active_workers = q.active_workers.saturating_sub(1);
695 store_worker_count(q.active_workers);
696 drop(q);
697 super::fetch_limit::unclaim(my_pid);
698 super::fetch_limit::release();
699 crate::kernel::scheduler::exit();
704 }
705 }
706 };
707
708 let t_fetch_start = crate::kernel::timer::get_ticks();
713 let polls_before =
714 crate::kernel::net::tcp::RECV_POLLS.load(core::sync::atomic::Ordering::Relaxed);
715 let ec_before =
716 crate::kernel::usb::ETH_POLL_CALLS.load(core::sync::atomic::Ordering::Relaxed);
717 let et_before =
718 crate::kernel::usb::ETH_POLL_US.load(core::sync::atomic::Ordering::Relaxed);
719 let em_before =
720 crate::kernel::usb::ETH_POLL_LOCK_MISS.load(core::sync::atomic::Ordering::Relaxed);
721 let hc_before =
722 crate::kernel::usb::HID_POLL_CALLS.load(core::sync::atomic::Ordering::Relaxed);
723 let ht_before =
724 crate::kernel::usb::HID_POLL_US.load(core::sync::atomic::Ordering::Relaxed);
725 let he_before =
726 crate::kernel::usb::HID_ETH_US.load(core::sync::atomic::Ordering::Relaxed);
727 let frames_before =
728 crate::kernel::usb::RX_FRAME_COUNT.load(core::sync::atomic::Ordering::Relaxed);
729 let mut from_cache = false;
730 let raw: Option<Vec<u8>> = if req.is_local {
731 let _fs_guard = crate::kernel::fs::FS_LOCK.lock();
736 let fs = crate::kernel::fs::get_fs();
737 fs.read_file(&req.path).map(|(_m, d)| d)
738 } else {
739 if let Some(cached) = read_cache(&req.host, &req.path) {
740 from_cache = true;
741 Some(cached)
742 } else {
743 let stack = crate::kernel::net_stack::TcpIpStack::new();
744 match stack.web_get_binary(req.is_https, &req.host, &req.path) {
745 Ok(d) => {
746 write_cache(&req.host, &req.path, &d);
747 Some(d)
748 }
749 Err(e) => {
750 crate::warn!(
751 "IMAGE_LOADER: fetch failed for {}{}: {}",
752 req.host,
753 req.path,
754 e.message
755 );
756 None
757 }
758 }
759 }
760 };
761
762 if let Some(bytes) = raw {
763 let t_fetch_end = crate::kernel::timer::get_ticks();
769 let _t_dec = super::perf::start();
770 let decoded_result = decode_image(&bytes);
771 super::perf::add(super::perf::Slot::ImageDecode, _t_dec);
772 crate::warn!(
773 "[IMGPERF] path={} bytes={} cache={} fetch={}tick decode={}tick polls={} eth_calls={} eth_us={} eth_lockmiss={} frames={} hid_calls={} hid_us={} hid_eth_us={}",
774 req.path,
775 bytes.len(),
776 from_cache,
777 t_fetch_end.wrapping_sub(t_fetch_start),
778 crate::kernel::timer::get_ticks().wrapping_sub(t_fetch_end),
779 crate::kernel::net::tcp::RECV_POLLS
780 .load(core::sync::atomic::Ordering::Relaxed)
781 .wrapping_sub(polls_before),
782 crate::kernel::usb::ETH_POLL_CALLS
783 .load(core::sync::atomic::Ordering::Relaxed)
784 .wrapping_sub(ec_before),
785 crate::kernel::usb::ETH_POLL_US
786 .load(core::sync::atomic::Ordering::Relaxed)
787 .wrapping_sub(et_before),
788 crate::kernel::usb::ETH_POLL_LOCK_MISS
789 .load(core::sync::atomic::Ordering::Relaxed)
790 .wrapping_sub(em_before),
791 crate::kernel::usb::RX_FRAME_COUNT
792 .load(core::sync::atomic::Ordering::Relaxed)
793 .wrapping_sub(frames_before),
794 crate::kernel::usb::HID_POLL_CALLS
795 .load(core::sync::atomic::Ordering::Relaxed)
796 .wrapping_sub(hc_before),
797 crate::kernel::usb::HID_POLL_US
798 .load(core::sync::atomic::Ordering::Relaxed)
799 .wrapping_sub(ht_before),
800 crate::kernel::usb::HID_ETH_US
801 .load(core::sync::atomic::Ordering::Relaxed)
802 .wrapping_sub(he_before)
803 );
804 match decoded_result {
805 Some(decoded) => {
806 crate::warn!(
812 "[IMG] 取得・デコード完了 host={} path={} bytes={} {}x{}",
813 req.host,
814 req.path,
815 bytes.len(),
816 decoded.width,
817 decoded.height
818 );
819 let mut q = IMAGE_QUEUE.lock();
820 q.results.push((req.src_key, decoded));
821 store_results_len(q.results.len());
822 }
823 None => {
824 let fmt =
826 if bytes.len() >= 12 && &bytes[0..4] == b"RIFF" && &bytes[8..12] == b"WEBP"
827 {
828 "WebP (unsupported)"
829 } else if bytes.len() >= 6
830 && (&bytes[0..6] == b"GIF89a" || &bytes[0..6] == b"GIF87a")
831 {
832 "GIF (unsupported)"
833 } else if bytes.len() >= 5 && &bytes[0..5] == b"<?xml"
834 || (bytes.len() >= 4 && &bytes[0..4] == b"<svg")
835 {
836 "SVG (unsupported)"
837 } else {
838 "unknown/corrupt"
839 };
840 crate::warn!(
841 "IMAGE_LOADER: decode failed for {} ({} bytes, format={})",
842 req.src_key,
843 bytes.len(),
844 fmt
845 );
846 }
847 }
848 }
849 }
850}
851
852pub fn image_results_ready() -> bool {
857 IMG_RESULTS.load(core::sync::atomic::Ordering::Acquire) > 0
858}
859
860static IMG_RESULTS: core::sync::atomic::AtomicUsize =
876 core::sync::atomic::AtomicUsize::new(0);
877
878pub(super) fn store_results_len(n: usize) {
880 IMG_RESULTS.store(n, core::sync::atomic::Ordering::Release);
881}
882
883static IMG_PENDING: core::sync::atomic::AtomicUsize =
896 core::sync::atomic::AtomicUsize::new(0);
897
898pub(super) fn pending_count() -> usize {
899 IMG_PENDING.load(core::sync::atomic::Ordering::Acquire)
900}
901
902static IMG_WORKERS: core::sync::atomic::AtomicUsize =
904 core::sync::atomic::AtomicUsize::new(0);
905
906pub(super) fn worker_count() -> usize {
907 IMG_WORKERS.load(core::sync::atomic::Ordering::Acquire)
908}
909
910pub(super) fn store_worker_count(n: usize) {
912 IMG_WORKERS.store(n, core::sync::atomic::Ordering::Release);
913}
914
915pub(super) fn enqueue_image(req: ImageFetchReq) {
916 let mut q = IMAGE_QUEUE.lock();
917 if q.pending.iter().any(|r| r.src_key == req.src_key) {
919 return;
920 }
921 q.pending.push(req);
922 IMG_PENDING.store(q.pending.len(), core::sync::atomic::Ordering::Release);
923 if q.active_workers < MAX_IMAGE_WORKERS
926 && super::fetch_limit::try_acquire(q.pending.len())
927 {
928 q.active_workers += 1;
929 store_worker_count(q.active_workers);
930 let pid = crate::kernel::scheduler::spawn(
935 image_loader_thread,
936 crate::kernel::scheduler::Priority::Normal,
937 "img_load",
938 );
939 if pid == 0 {
940 q.active_workers = q.active_workers.saturating_sub(1);
941 store_worker_count(q.active_workers);
942 super::fetch_limit::release();
943 }
944 }
945}
946
947static SHARED_IMAGES: spin::Mutex<
961 alloc::collections::BTreeMap<alloc::string::String, alloc::sync::Arc<super::DecodedImage>>,
962> = spin::Mutex::new(alloc::collections::BTreeMap::new());
963
964pub fn share_image(key: &str, img: alloc::sync::Arc<super::DecodedImage>) {
966 SHARED_IMAGES.lock().insert(alloc::string::String::from(key), img);
967}
968
969pub fn shared_image(key: &str) -> Option<alloc::sync::Arc<super::DecodedImage>> {
971 SHARED_IMAGES.lock().get(key).cloned()
972}
973
974pub fn clear_shared_images() {
976 SHARED_IMAGES.lock().clear();
977}