Skip to content

Commit 569260c

Browse files
committed
support fast path when no image transforms, add mod+plugin
1 parent 86f0729 commit 569260c

File tree

4 files changed

+508
-340
lines changed

4 files changed

+508
-340
lines changed

crates/bevy_winit/src/cursor.rs

Lines changed: 15 additions & 337 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ use crate::{
66
};
77
#[cfg(feature = "custom_cursor")]
88
use crate::{
9+
custom_cursor::{
10+
calculate_effective_rect, extract_and_transform_rgba_pixels, extract_rgba_pixels,
11+
CustomCursorPlugin,
12+
},
913
state::{CustomCursorCache, CustomCursorCacheKey},
1014
WinitCustomCursor,
1115
};
@@ -27,21 +31,19 @@ use bevy_ecs::{
2731
#[cfg(feature = "custom_cursor")]
2832
use bevy_image::{Image, TextureAtlas, TextureAtlasLayout};
2933
#[cfg(feature = "custom_cursor")]
30-
use bevy_math::{Rect, URect, Vec2};
34+
use bevy_math::URect;
3135
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
3236
use bevy_utils::HashSet;
3337
use bevy_window::{SystemCursorIcon, Window};
3438
#[cfg(feature = "custom_cursor")]
3539
use tracing::warn;
36-
#[cfg(feature = "custom_cursor")]
37-
use wgpu_types::TextureFormat;
3840

3941
pub(crate) struct CursorPlugin;
4042

4143
impl Plugin for CursorPlugin {
4244
fn build(&self, app: &mut App) {
4345
#[cfg(feature = "custom_cursor")]
44-
app.init_resource::<CustomCursorCache>();
46+
app.add_plugins(CustomCursorPlugin);
4547

4648
app.register_type::<CursorIcon>()
4749
.add_systems(Last, update_cursors);
@@ -161,36 +163,22 @@ fn update_cursors(
161163
continue;
162164
};
163165

164-
let atlas_rect = texture_atlas
165-
.as_ref()
166-
.and_then(|s| s.texture_rect(&texture_atlases))
167-
.map(|r| r.as_rect());
166+
let (rect, needs_sub_image) =
167+
calculate_effective_rect(&texture_atlases, image, texture_atlas, rect);
168168

169-
let rect = match (atlas_rect, rect) {
170-
(None, None) => Rect {
171-
min: Vec2::ZERO,
172-
max: Vec2::new(
173-
image.texture_descriptor.size.width as f32,
174-
image.texture_descriptor.size.height as f32,
175-
),
176-
},
177-
(None, Some(image_rect)) => image_rect.as_rect(),
178-
(Some(atlas_rect), None) => atlas_rect,
179-
(Some(atlas_rect), Some(image_rect)) => {
180-
let mut image_rect = image_rect.as_rect();
181-
image_rect.min += atlas_rect.min;
182-
image_rect.max += atlas_rect.min;
183-
image_rect
184-
}
169+
let maybe_rgba = if *flip_x || *flip_y || needs_sub_image {
170+
extract_and_transform_rgba_pixels(image, *flip_x, *flip_y, rect)
171+
} else {
172+
extract_rgba_pixels(image)
185173
};
186174

187-
let Some(rgba) = image_to_rgba_pixels(image, *flip_x, *flip_y, rect) else {
175+
let Some(rgba) = maybe_rgba else {
188176
warn!("Cursor image {handle:?} not accepted because it's not rgba8 or rgba32float format");
189177
continue;
190178
};
191179

192-
let width = (rect.max.x - rect.min.x) as u16;
193-
let height = (rect.max.y - rect.min.y) as u16;
180+
let width = rect.width() as u16;
181+
let height = rect.height() as u16;
194182
let source = match WinitCustomCursor::from_rgba(
195183
rgba, width, height, hotspot.0, hotspot.1,
196184
) {
@@ -240,313 +228,3 @@ fn on_remove_cursor_icon(trigger: Trigger<OnRemove, CursorIcon>, mut commands: C
240228
convert_system_cursor_icon(SystemCursorIcon::Default),
241229
))));
242230
}
243-
244-
#[cfg(feature = "custom_cursor")]
245-
/// Returns the `image` data as a `Vec<u8>` for the specified sub-region.
246-
///
247-
/// The image is flipped along the x and y axes if `flip_x` and `flip_y` are
248-
/// `true`, respectively.
249-
///
250-
/// Only supports rgba8 and rgba32float formats.
251-
fn image_to_rgba_pixels(image: &Image, flip_x: bool, flip_y: bool, rect: Rect) -> Option<Vec<u8>> {
252-
let image_data_as_u8s: Vec<u8>;
253-
254-
let image_data = match image.texture_descriptor.format {
255-
TextureFormat::Rgba8Unorm
256-
| TextureFormat::Rgba8UnormSrgb
257-
| TextureFormat::Rgba8Snorm
258-
| TextureFormat::Rgba8Uint
259-
| TextureFormat::Rgba8Sint => Some(&image.data),
260-
TextureFormat::Rgba32Float => {
261-
image_data_as_u8s = image
262-
.data
263-
.chunks(4)
264-
.map(|chunk| {
265-
let chunk = chunk.try_into().unwrap();
266-
let num = bytemuck::cast_ref::<[u8; 4], f32>(chunk);
267-
(num * 255.0) as u8
268-
})
269-
.collect::<Vec<u8>>();
270-
271-
Some(&image_data_as_u8s)
272-
}
273-
_ => None,
274-
};
275-
276-
let image_data = image_data?;
277-
278-
let width = (rect.max.x - rect.min.x) as usize;
279-
let height = (rect.max.y - rect.min.y) as usize;
280-
let mut sub_image_data = Vec::with_capacity(width * height * 4); // assuming 4 bytes per pixel (RGBA8)
281-
282-
for y in 0..height {
283-
for x in 0..width {
284-
let src_x = if flip_x { width - 1 - x } else { x };
285-
let src_y = if flip_y { height - 1 - y } else { y };
286-
let index = ((rect.min.y as usize + src_y)
287-
* image.texture_descriptor.size.width as usize
288-
+ (rect.min.x as usize + src_x))
289-
* 4;
290-
sub_image_data.extend_from_slice(&image_data[index..index + 4]);
291-
}
292-
}
293-
294-
Some(sub_image_data)
295-
}
296-
297-
#[cfg(feature = "custom_cursor")]
298-
#[cfg(test)]
299-
mod tests {
300-
use bevy_asset::RenderAssetUsages;
301-
use bevy_image::Image;
302-
use bevy_math::Rect;
303-
use bevy_math::Vec2;
304-
use wgpu_types::{Extent3d, TextureDimension};
305-
306-
use super::*;
307-
308-
fn create_image_rgba8(data: &[u8]) -> Image {
309-
Image::new(
310-
Extent3d {
311-
width: 3,
312-
height: 3,
313-
depth_or_array_layers: 1,
314-
},
315-
TextureDimension::D2,
316-
data.to_vec(),
317-
TextureFormat::Rgba8UnormSrgb,
318-
RenderAssetUsages::default(),
319-
)
320-
}
321-
322-
fn create_image_rgba32float(data: &[u8]) -> Image {
323-
let float_data: Vec<f32> = data
324-
.chunks(4)
325-
.flat_map(|chunk| {
326-
chunk
327-
.iter()
328-
.map(|&x| x as f32 / 255.0) // convert each channel to f32
329-
.collect::<Vec<f32>>()
330-
})
331-
.collect();
332-
333-
Image::new(
334-
Extent3d {
335-
width: 3,
336-
height: 3,
337-
depth_or_array_layers: 1,
338-
},
339-
TextureDimension::D2,
340-
bytemuck::cast_slice(&float_data).to_vec(),
341-
TextureFormat::Rgba32Float,
342-
RenderAssetUsages::default(),
343-
)
344-
}
345-
346-
macro_rules! test_image_to_rgba_pixels {
347-
($name:ident, $flip_x:expr, $flip_y:expr, $rect:expr, $expected:expr) => {
348-
#[test]
349-
fn $name() {
350-
let image_data: &[u8] = &[
351-
// Row 1: Red, Green, Blue
352-
255, 0, 0, 255, // Red
353-
0, 255, 0, 255, // Green
354-
0, 0, 255, 255, // Blue
355-
// Row 2: Yellow, Cyan, Magenta
356-
255, 255, 0, 255, // Yellow
357-
0, 255, 255, 255, // Cyan
358-
255, 0, 255, 255, // Magenta
359-
// Row 3: White, Gray, Black
360-
255, 255, 255, 255, // White
361-
128, 128, 128, 255, // Gray
362-
0, 0, 0, 255, // Black
363-
];
364-
365-
// RGBA8 test
366-
{
367-
let image = create_image_rgba8(image_data);
368-
let rect = $rect;
369-
let result = image_to_rgba_pixels(&image, $flip_x, $flip_y, rect);
370-
assert_eq!(result, Some($expected.to_vec()));
371-
}
372-
373-
// RGBA32Float test
374-
{
375-
let image = create_image_rgba32float(image_data);
376-
let rect = $rect;
377-
let result = image_to_rgba_pixels(&image, $flip_x, $flip_y, rect);
378-
assert_eq!(result, Some($expected.to_vec()));
379-
}
380-
}
381-
};
382-
}
383-
384-
test_image_to_rgba_pixels!(
385-
no_flip_full_image,
386-
false,
387-
false,
388-
Rect {
389-
min: Vec2::ZERO,
390-
max: Vec2::new(3.0, 3.0)
391-
},
392-
[
393-
// Row 1: Red, Green, Blue
394-
255, 0, 0, 255, // Red
395-
0, 255, 0, 255, // Green
396-
0, 0, 255, 255, // Blue
397-
// Row 2: Yellow, Cyan, Magenta
398-
255, 255, 0, 255, // Yellow
399-
0, 255, 255, 255, // Cyan
400-
255, 0, 255, 255, // Magenta
401-
// Row 3: White, Gray, Black
402-
255, 255, 255, 255, // White
403-
128, 128, 128, 255, // Gray
404-
0, 0, 0, 255, // Black
405-
]
406-
);
407-
408-
test_image_to_rgba_pixels!(
409-
flip_x_full_image,
410-
true,
411-
false,
412-
Rect {
413-
min: Vec2::ZERO,
414-
max: Vec2::new(3.0, 3.0)
415-
},
416-
[
417-
// Row 1 flipped: Blue, Green, Red
418-
0, 0, 255, 255, // Blue
419-
0, 255, 0, 255, // Green
420-
255, 0, 0, 255, // Red
421-
// Row 2 flipped: Magenta, Cyan, Yellow
422-
255, 0, 255, 255, // Magenta
423-
0, 255, 255, 255, // Cyan
424-
255, 255, 0, 255, // Yellow
425-
// Row 3 flipped: Black, Gray, White
426-
0, 0, 0, 255, // Black
427-
128, 128, 128, 255, // Gray
428-
255, 255, 255, 255, // White
429-
]
430-
);
431-
432-
test_image_to_rgba_pixels!(
433-
flip_y_full_image,
434-
false,
435-
true,
436-
Rect {
437-
min: Vec2::ZERO,
438-
max: Vec2::new(3.0, 3.0)
439-
},
440-
[
441-
// Row 3: White, Gray, Black
442-
255, 255, 255, 255, // White
443-
128, 128, 128, 255, // Gray
444-
0, 0, 0, 255, // Black
445-
// Row 2: Yellow, Cyan, Magenta
446-
255, 255, 0, 255, // Yellow
447-
0, 255, 255, 255, // Cyan
448-
255, 0, 255, 255, // Magenta
449-
// Row 1: Red, Green, Blue
450-
255, 0, 0, 255, // Red
451-
0, 255, 0, 255, // Green
452-
0, 0, 255, 255, // Blue
453-
]
454-
);
455-
456-
test_image_to_rgba_pixels!(
457-
flip_both_full_image,
458-
true,
459-
true,
460-
Rect {
461-
min: Vec2::ZERO,
462-
max: Vec2::new(3.0, 3.0)
463-
},
464-
[
465-
// Row 3 flipped: Black, Gray, White
466-
0, 0, 0, 255, // Black
467-
128, 128, 128, 255, // Gray
468-
255, 255, 255, 255, // White
469-
// Row 2 flipped: Magenta, Cyan, Yellow
470-
255, 0, 255, 255, // Magenta
471-
0, 255, 255, 255, // Cyan
472-
255, 255, 0, 255, // Yellow
473-
// Row 1 flipped: Blue, Green, Red
474-
0, 0, 255, 255, // Blue
475-
0, 255, 0, 255, // Green
476-
255, 0, 0, 255, // Red
477-
]
478-
);
479-
480-
test_image_to_rgba_pixels!(
481-
no_flip_rect,
482-
false,
483-
false,
484-
Rect {
485-
min: Vec2::new(1.0, 1.0),
486-
max: Vec2::new(3.0, 3.0)
487-
},
488-
[
489-
// Only includes part of the original image (sub-rectangle)
490-
// Row 2, columns 2-3: Cyan, Magenta
491-
0, 255, 255, 255, // Cyan
492-
255, 0, 255, 255, // Magenta
493-
// Row 3, columns 2-3: Gray, Black
494-
128, 128, 128, 255, // Gray
495-
0, 0, 0, 255, // Black
496-
]
497-
);
498-
499-
test_image_to_rgba_pixels!(
500-
flip_x_rect,
501-
true,
502-
false,
503-
Rect {
504-
min: Vec2::new(1.0, 1.0),
505-
max: Vec2::new(3.0, 3.0)
506-
},
507-
[
508-
// Row 2 flipped: Magenta, Cyan
509-
255, 0, 255, 255, // Magenta
510-
0, 255, 255, 255, // Cyan
511-
// Row 3 flipped: Black, Gray
512-
0, 0, 0, 255, // Black
513-
128, 128, 128, 255, // Gray
514-
]
515-
);
516-
517-
test_image_to_rgba_pixels!(
518-
flip_y_rect,
519-
false,
520-
true,
521-
Rect {
522-
min: Vec2::new(1.0, 1.0),
523-
max: Vec2::new(3.0, 3.0)
524-
},
525-
[
526-
// Row 3 first: Gray, Black
527-
128, 128, 128, 255, // Gray
528-
0, 0, 0, 255, // Black
529-
// Row 2 second: Cyan, Magenta
530-
0, 255, 255, 255, // Cyan
531-
255, 0, 255, 255, // Magenta
532-
]
533-
);
534-
535-
test_image_to_rgba_pixels!(
536-
flip_both_rect,
537-
true,
538-
true,
539-
Rect {
540-
min: Vec2::new(1.0, 1.0),
541-
max: Vec2::new(3.0, 3.0)
542-
},
543-
[
544-
// Row 3 flipped: Black, Gray
545-
0, 0, 0, 255, // Black
546-
128, 128, 128, 255, // Gray
547-
// Row 2 flipped: Magenta, Cyan
548-
255, 0, 255, 255, // Magenta
549-
0, 255, 255, 255, // Cyan
550-
]
551-
);
552-
}

0 commit comments

Comments
 (0)