1use std::{
2 fmt, io, mem, ptr, slice, str,
3 sync::{Arc, Mutex},
4};
5
6#[repr(transparent)]
7#[derive(Clone, Copy, Debug, blade_macros::Flat)]
8struct TextureFormatWrap(blade_graphics::TextureFormat);
9
10#[derive(blade_macros::Flat)]
11struct CookedMip<'a> {
12 data: &'a [u8],
13}
14
15#[derive(blade_macros::Flat)]
16pub struct CookedImage<'a> {
17 name: &'a [u8],
18 extent: [u32; 3],
19 format: TextureFormatWrap,
20 mips: Vec<CookedMip<'a>>,
21}
22
23#[derive(Clone, Debug, PartialEq, Eq, Hash)]
24pub struct Meta {
25 pub format: blade_graphics::TextureFormat,
26 pub generate_mips: bool,
27 pub y_flip: bool,
28}
29
30impl fmt::Display for Meta {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 fmt::Debug::fmt(&self.format, f)
33 }
34}
35
36pub struct Texture {
37 pub object: blade_graphics::Texture,
38 pub view: blade_graphics::TextureView,
39 pub extent: blade_graphics::Extent,
40}
41
42struct Initialization {
43 dst: blade_graphics::Texture,
44}
45
46struct Transfer {
47 stage: blade_graphics::Buffer,
48 bytes_per_row: u32,
49 dst: blade_graphics::Texture,
50 extent: blade_graphics::Extent,
51 mip_level: u32,
52}
53
54#[derive(Default)]
56struct PendingOperations {
57 initializations: Vec<Initialization>,
58 transfers: Vec<Transfer>,
59}
60
61pub struct Baker {
62 gpu_context: Arc<blade_graphics::Context>,
63 pending_operations: Mutex<PendingOperations>,
64}
65
66impl Baker {
67 pub fn new(gpu_context: &Arc<blade_graphics::Context>) -> Self {
68 Self {
69 gpu_context: Arc::clone(gpu_context),
70 pending_operations: Mutex::new(PendingOperations::default()),
71 }
72 }
73
74 pub fn flush(
75 &self,
76 encoder: &mut blade_graphics::CommandEncoder,
77 temp_buffers: &mut Vec<blade_graphics::Buffer>,
78 ) {
79 let mut pending_ops = self.pending_operations.lock().unwrap();
80 for init in pending_ops.initializations.drain(..) {
81 encoder.init_texture(init.dst);
82 }
83 if !pending_ops.transfers.is_empty() {
84 let mut pass = encoder.transfer("init textures");
85 for transfer in pending_ops.transfers.drain(..) {
86 let dst = blade_graphics::TexturePiece {
87 texture: transfer.dst,
88 mip_level: transfer.mip_level,
89 array_layer: 0,
90 origin: [0; 3],
91 };
92 pass.copy_buffer_to_texture(
93 transfer.stage.into(),
94 transfer.bytes_per_row,
95 dst,
96 transfer.extent,
97 );
98 temp_buffers.push(transfer.stage);
99 }
100 }
101 }
102}
103
104impl blade_asset::Baker for Baker {
105 type Meta = Meta;
106 type Data<'a> = CookedImage<'a>;
107 type Output = Texture;
108 fn cook(
109 &self,
110 source: &[u8],
111 extension: &str,
112 meta: Meta,
113 cooker: Arc<blade_asset::Cooker<Self>>,
114 exe_context: &choir::ExecutionContext,
115 ) {
116 use blade_graphics::TextureFormat as Tf;
117
118 type LdrTexel = [u8; 4];
119 type HdrTexel = [f32; 3];
120 enum PlainData {
121 Ldr(Vec<LdrTexel>),
122 Hdr(Vec<HdrTexel>),
123 }
124 struct PlainImage {
125 width: usize,
126 height: usize,
127 data: PlainData,
128 }
129
130 let src: PlainImage = match extension {
131 #[cfg(feature = "asset")]
132 "png" => {
133 profiling::scope!("decode png");
134 let options =
135 zune_core::options::DecoderOptions::default().png_set_add_alpha_channel(true);
136 let mut decoder = zune_png::PngDecoder::new_with_options(source, options);
137 decoder.decode_headers().unwrap();
138 let info = decoder.get_info().unwrap().clone();
139 let mut data = vec![[0u8; 4]; info.width * info.height];
140 let count = data.len() * data[0].len();
141 assert_eq!(count, decoder.output_buffer_size().unwrap());
142 decoder
143 .decode_into(unsafe {
144 slice::from_raw_parts_mut(data.as_mut_ptr() as *mut u8, count)
145 })
146 .unwrap();
147 PlainImage {
148 width: info.width,
149 height: info.height,
150 data: PlainData::Ldr(data),
151 }
152 }
153 #[cfg(feature = "asset")]
154 "jpg" | "jpeg" => {
155 profiling::scope!("decode jpeg");
156 let options = zune_core::options::DecoderOptions::default()
157 .jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::RGBA);
158 let mut decoder = zune_jpeg::JpegDecoder::new_with_options(source, options);
159 decoder.decode_headers().unwrap();
160 let info = decoder.info().unwrap();
161 let mut data = vec![[0u8; 4]; info.width as usize * info.height as usize];
162 let count = data.len() * data[0].len();
163 assert_eq!(count, decoder.output_buffer_size().unwrap());
164 decoder
165 .decode_into(unsafe {
166 slice::from_raw_parts_mut(data.as_mut_ptr() as *mut u8, count)
167 })
168 .unwrap();
169 PlainImage {
170 width: info.width as usize,
171 height: info.height as usize,
172 data: PlainData::Ldr(data),
173 }
174 }
175 #[cfg(feature = "asset")]
176 "hdr" => {
177 profiling::scope!("decode hdr");
178 let options = zune_core::options::DecoderOptions::default();
179 let mut decoder = zune_hdr::HdrDecoder::new_with_options(source, options);
180 decoder.decode_headers().unwrap();
181 let (width, height) = decoder.get_dimensions().unwrap();
182 let colorspace = decoder.get_colorspace().unwrap();
183 assert_eq!(colorspace, zune_core::colorspace::ColorSpace::RGB);
184 let mut data = vec![[0f32; 3]; width * height];
185 let count = data.len() * data[0].len();
186 assert_eq!(count, decoder.output_buffer_size().unwrap());
187 decoder
188 .decode_into(unsafe {
189 slice::from_raw_parts_mut(data.as_mut_ptr() as *mut f32, count)
190 })
191 .unwrap();
192 PlainImage {
193 width,
194 height,
195 data: PlainData::Hdr(data),
196 }
197 }
198 #[cfg(feature = "asset")]
199 "exr" => {
200 use exr::prelude::{ReadChannels as _, ReadLayers as _};
201 profiling::scope!("decode exr");
202 struct RawImage {
203 width: usize,
204 data: Vec<HdrTexel>,
205 }
206 let image = exr::image::read::read()
207 .no_deep_data()
208 .largest_resolution_level()
209 .rgba_channels(
210 |size, _| RawImage {
211 width: size.width(),
212 data: vec![[0f32; 3]; size.width() * size.height()],
213 },
214 |image, position, (r, g, b, _): (f32, f32, f32, f32)| {
215 image.data[position.y() * image.width + position.x()] = [r, g, b];
216 },
217 )
218 .first_valid_layer()
219 .all_attributes()
220 .from_buffered(io::Cursor::new(source))
221 .unwrap();
222 PlainImage {
223 width: image.layer_data.size.width(),
224 height: image.layer_data.size.height(),
225 data: PlainData::Hdr(image.layer_data.channel_data.pixels.data),
226 }
227 }
228 other => panic!("Unknown texture extension: {}", other),
229 };
230
231 #[cfg(feature = "asset")]
232 match src.data {
233 PlainData::Ldr(mut data) => {
234 if meta.y_flip {
235 profiling::scope!("y-flip");
236 zune_imageprocs::flip::vertical_flip(&mut data, src.width);
237 }
238
239 let dst_format = match meta.format {
240 Tf::Bc1Unorm | Tf::Bc1UnormSrgb => texpresso::Format::Bc1,
241 Tf::Bc2Unorm | Tf::Bc2UnormSrgb => texpresso::Format::Bc2,
242 Tf::Bc3Unorm | Tf::Bc3UnormSrgb => texpresso::Format::Bc3,
243 Tf::Bc4Unorm | Tf::Bc4Snorm => texpresso::Format::Bc4,
244 Tf::Bc5Unorm | Tf::Bc5Snorm => texpresso::Format::Bc5,
245 other => panic!("Unsupported destination format {:?}", other),
246 };
247
248 let mut src_mips = vec![data];
249 let mut mips = {
250 let compressed_size = dst_format.compressed_size(src.width, src.height);
251 vec![vec![0u8; compressed_size]]
252 };
253 let base_extent = blade_graphics::Extent {
254 width: src.width as u32,
255 height: src.height as u32,
256 depth: 1,
257 };
258 if meta.generate_mips {
259 profiling::scope!("generate mipmap");
260 for i in 1..base_extent.max_mip_levels() {
261 let prev_extent = base_extent.at_mip_level(i - 1);
262 let cur_extent = base_extent.at_mip_level(i);
263 let prev_data = src_mips.last().unwrap();
264 let prev_raw = unsafe {
265 slice::from_raw_parts(
266 prev_data.as_ptr() as *const u8,
267 prev_data.len() * 4,
268 )
269 };
270 let mut cur_data =
271 vec![[0u8; 4]; cur_extent.width as usize * cur_extent.height as usize];
272 let cur_raw = unsafe {
273 slice::from_raw_parts_mut(
274 cur_data.as_mut_ptr() as *mut u8,
275 cur_data.len() * 4,
276 )
277 };
278 zune_imageprocs::resize::resize(
279 prev_raw,
280 cur_raw,
281 zune_imageprocs::resize::ResizeMethod::Bilinear,
282 prev_extent.width as _,
283 prev_extent.height as _,
284 cur_extent.width as _,
285 cur_extent.height as _,
286 );
287 src_mips.push(cur_data);
288 let compressed_size = dst_format
289 .compressed_size(cur_extent.width as _, cur_extent.height as _);
290 mips.push(vec![0u8; compressed_size]);
291 }
292 }
293
294 struct CompressTask {
295 src: Vec<LdrTexel>,
296 dst_ptr: *mut u8,
297 }
298 unsafe impl Send for CompressTask {}
299 unsafe impl Sync for CompressTask {}
300
301 let compress_task = exe_context
302 .fork("compress")
303 .init_iter(
304 src_mips
305 .into_iter()
306 .zip(mips.iter_mut())
307 .map(|(src, dst)| CompressTask {
308 src,
309 dst_ptr: dst.as_mut_ptr(),
310 })
311 .enumerate(),
312 move |_, (i, task)| {
313 let extent = base_extent.at_mip_level(i as u32);
314 let compressed_size =
315 dst_format.compressed_size(extent.width as _, extent.height as _);
316 let params = texpresso::Params {
317 algorithm: texpresso::Algorithm::RangeFit,
319 ..Default::default()
320 };
321 let dst =
322 unsafe { slice::from_raw_parts_mut(task.dst_ptr, compressed_size) };
323 let raw = unsafe {
324 slice::from_raw_parts(
325 task.src.as_ptr() as *const u8,
326 task.src.len() * 4,
327 )
328 };
329 dst_format.compress(
330 raw,
331 extent.width as _,
332 extent.height as _,
333 params,
334 dst,
335 );
336 },
337 )
338 .run();
339
340 exe_context
341 .fork("finish")
342 .init(move |_| {
343 cooker.finish(CookedImage {
344 name: &[],
345 extent: [base_extent.width, base_extent.height, base_extent.depth],
346 format: TextureFormatWrap(meta.format),
347 mips: mips.iter().map(|data| CookedMip { data }).collect(),
348 });
349 })
350 .depend_on(&compress_task);
351 }
352 PlainData::Hdr(data) => {
353 assert_eq!(meta.format, blade_graphics::TextureFormat::Rgba32Float);
356 let in_texel_elements = data[0].len();
357 let out_texel_size = 4 * mem::size_of::<f32>();
358 let mut buf = vec![0u8; data.len() * out_texel_size];
359 for (slice, texel) in buf.chunks_mut(out_texel_size).zip(data) {
360 unsafe {
361 ptr::copy_nonoverlapping(
362 texel.as_ptr(),
363 slice.as_mut_ptr() as *mut f32,
364 in_texel_elements,
365 )
366 }
367 }
368 cooker.finish(CookedImage {
369 name: &[],
370 extent: [src.width as u32, src.height as u32, 1],
371 format: TextureFormatWrap(meta.format),
372 mips: vec![CookedMip { data: &buf }],
373 });
374 }
375 }
376 }
377
378 fn serve(
379 &self,
380 image: CookedImage<'_>,
381 _exe_context: &choir::ExecutionContext,
382 ) -> Self::Output {
383 let name = str::from_utf8(image.name).unwrap();
384 let base_extent = blade_graphics::Extent {
385 width: image.extent[0],
386 height: image.extent[1],
387 depth: image.extent[2],
388 };
389 let texture = self
390 .gpu_context
391 .create_texture(blade_graphics::TextureDesc {
392 name,
393 format: image.format.0,
394 size: base_extent,
395 array_layer_count: 1,
396 mip_level_count: image.mips.len() as u32,
397 dimension: blade_graphics::TextureDimension::D2,
398 usage: blade_graphics::TextureUsage::COPY | blade_graphics::TextureUsage::RESOURCE,
399 sample_count: 1,
400 });
401 let view = self.gpu_context.create_texture_view(
402 texture,
403 blade_graphics::TextureViewDesc {
404 name,
405 format: image.format.0,
406 dimension: blade_graphics::ViewDimension::D2,
407 subresources: &Default::default(),
408 },
409 );
410 self.pending_operations
411 .lock()
412 .unwrap()
413 .initializations
414 .push(Initialization { dst: texture });
415
416 for (i, mip) in image.mips.iter().enumerate() {
417 let stage = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
418 name: &format!("{name}[{i}]/stage"),
419 size: mip.data.len() as u64,
420 memory: blade_graphics::Memory::Upload,
421 });
422 unsafe {
423 ptr::copy_nonoverlapping(mip.data.as_ptr(), stage.data(), mip.data.len());
424 }
425
426 let block_info = image.format.0.block_info();
427 let extent = base_extent.at_mip_level(i as u32);
428 let bytes_per_row = ((extent.width + block_info.dimensions.0 as u32 - 1)
429 / block_info.dimensions.0 as u32)
430 * block_info.size as u32;
431 let rows_per_image = (extent.height + block_info.dimensions.1 as u32 - 1)
432 / block_info.dimensions.1 as u32;
433 assert!(mip.data.len() >= rows_per_image as usize * bytes_per_row as usize,
434 "Image mip[{i}] data of size {} is insufficient for {bytes_per_row} bytes per {rows_per_image} rows",
435 mip.data.len());
436
437 let mut pending_ops = self.pending_operations.lock().unwrap();
438 pending_ops.transfers.push(Transfer {
439 stage,
440 bytes_per_row,
441 dst: texture,
442 extent,
443 mip_level: i as u32,
444 });
445 }
446
447 Texture {
448 object: texture,
449 view,
450 extent: base_extent,
451 }
452 }
453
454 fn delete(&self, texture: Self::Output) {
455 self.gpu_context.destroy_texture_view(texture.view);
456 self.gpu_context.destroy_texture(texture.object);
457 }
458}