zengine_graphic/
texture.rs

1use crate::{Device, Image, Queue, TextureBindGroupLayout};
2use std::num::NonZeroU32;
3use zengine_asset::{AssetEvent, Assets, Handle};
4use zengine_ecs::system::{EventStream, Res, ResMut};
5use zengine_macro::Asset;
6
7#[derive(Debug)]
8pub(crate) struct GpuImage {
9    pub texture: wgpu::Texture,
10    pub _view: wgpu::TextureView,
11    pub _sampler: wgpu::Sampler,
12    pub diffuse_bind_group: wgpu::BindGroup,
13}
14
15impl Drop for GpuImage {
16    fn drop(&mut self) {
17        self.texture.destroy();
18    }
19}
20
21/// [Asset](zengine_asset::Asset) that rappresent a simple Texture
22#[derive(Asset, Debug)]
23pub struct Texture {
24    image: Handle<Image>,
25    pub(crate) gpu_image: Option<GpuImage>,
26}
27
28impl Texture {
29    fn new(image: Handle<Image>) -> Self {
30        Self {
31            image,
32            gpu_image: None,
33        }
34    }
35
36    pub(crate) fn convert_to_gpu_image(
37        &mut self,
38        device: &Device,
39        queue: &Queue,
40        texture_bind_group_layout: &TextureBindGroupLayout,
41        images: &Assets<Image>,
42    ) {
43        if let Some(image) = images.get(&self.image) {
44            let size = wgpu::Extent3d {
45                width: image.width,
46                height: image.height,
47                depth_or_array_layers: 1,
48            };
49            let texture = device.create_texture(&wgpu::TextureDescriptor {
50                size,
51                mip_level_count: 1,
52                sample_count: 1,
53                dimension: wgpu::TextureDimension::D2,
54                format: wgpu::TextureFormat::Rgba8UnormSrgb,
55                usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
56                label: Some("diffuse_texture"),
57            });
58
59            queue.write_texture(
60                wgpu::ImageCopyTexture {
61                    aspect: wgpu::TextureAspect::All,
62                    texture: &texture,
63                    mip_level: 0,
64                    origin: wgpu::Origin3d::ZERO,
65                },
66                &image.data,
67                wgpu::ImageDataLayout {
68                    offset: 0,
69                    bytes_per_row: NonZeroU32::new(4 * image.width),
70                    rows_per_image: NonZeroU32::new(image.height),
71                },
72                size,
73            );
74
75            let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
76            let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
77                address_mode_u: wgpu::AddressMode::ClampToEdge,
78                address_mode_v: wgpu::AddressMode::ClampToEdge,
79                address_mode_w: wgpu::AddressMode::ClampToEdge,
80                mag_filter: wgpu::FilterMode::Nearest,
81                min_filter: wgpu::FilterMode::Nearest,
82                mipmap_filter: wgpu::FilterMode::Nearest,
83                ..Default::default()
84            });
85
86            let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
87                layout: texture_bind_group_layout,
88                entries: &[
89                    wgpu::BindGroupEntry {
90                        binding: 0,
91                        resource: wgpu::BindingResource::TextureView(&view),
92                    },
93                    wgpu::BindGroupEntry {
94                        binding: 1,
95                        resource: wgpu::BindingResource::Sampler(&sampler),
96                    },
97                ],
98                label: Some("diffuse_bind_group"),
99            });
100
101            self.image = self.image.as_weak();
102            self.gpu_image = Some(GpuImage {
103                texture,
104                _view: view,
105                _sampler: sampler,
106                diffuse_bind_group,
107            })
108        }
109    }
110}
111
112/// Add functionalities to create a [Texture] to the [Assets<Texture>] storage
113pub trait TextureAssets {
114    /// Creates a [Texture] asset returning a strong [Handle] to it with the given Image handle
115    fn create_texture(&mut self, image: &Handle<Image>) -> Handle<Texture>;
116}
117
118impl TextureAssets for Assets<Texture> {
119    fn create_texture(&mut self, image_handle: &Handle<Image>) -> Handle<Texture> {
120        let handle = Handle::weak(image_handle.get_id().clone_with_different_type::<Texture>());
121        self.set(handle, Texture::new(image_handle.clone()))
122    }
123}
124
125pub(crate) fn prepare_texture_asset(
126    texture_bind_group_layout: Option<Res<TextureBindGroupLayout>>,
127    device: Option<Res<Device>>,
128    queue: Option<Res<Queue>>,
129    textures: Option<ResMut<Assets<Texture>>>,
130    images: Option<Res<Assets<Image>>>,
131    images_asset_event: EventStream<AssetEvent<Image>>,
132) {
133    let events = images_asset_event.read();
134    if let (
135        Some(mut textures),
136        Some(images),
137        Some(device),
138        Some(queue),
139        Some(texture_bind_group_layout),
140    ) = (textures, images, device, queue, texture_bind_group_layout)
141    {
142        for e in events {
143            if let AssetEvent::Loaded(handle) = e {
144                let handle = Handle::weak(handle.get_id().clone_with_different_type::<Texture>());
145                if let Some(texture) = textures.get_mut(&handle) {
146                    texture.convert_to_gpu_image(
147                        &device,
148                        &queue,
149                        &texture_bind_group_layout,
150                        &images,
151                    )
152                }
153            }
154        }
155    }
156}