rendy_texture/
texture.rs

1//! Module for creating a `Texture` from an image
2use {
3    crate::{
4        core::{cast_cow, cast_slice},
5        factory::{Factory, ImageState, UploadError},
6        memory::Data,
7        pixel::AsPixel,
8        resource::{
9            Escape, Handle, Image, ImageCreationError, ImageInfo, ImageView,
10            ImageViewCreationError, ImageViewInfo, Sampler,
11        },
12    },
13    rendy_core::hal::{
14        format::{Component, Format, Swizzle},
15        image, Backend,
16    },
17    std::num::NonZeroU8,
18    thread_profiler::profile_scope,
19};
20
21/// Static image.
22/// Can be loaded from various of formats.
23#[derive(Debug)]
24pub struct Texture<B: Backend> {
25    image: Handle<Image<B>>,
26    view: Escape<ImageView<B>>,
27    sampler: Handle<Sampler<B>>,
28    premultiplied: bool,
29}
30
31impl<B> Texture<B>
32where
33    B: Backend,
34{
35    /// Get image handle.
36    pub fn image(&self) -> &Handle<Image<B>> {
37        &self.image
38    }
39
40    /// Get sampler handle.
41    pub fn sampler(&self) -> &Handle<Sampler<B>> {
42        &self.sampler
43    }
44
45    /// Get reference to image view.
46    pub fn view(&self) -> &ImageView<B> {
47        &self.view
48    }
49
50    /// Get mutable reference to image view.
51    pub fn view_mut(&mut self) -> &mut ImageView<B> {
52        &mut self.view
53    }
54
55    /// Get whether texture has premultiplied alpha
56    pub fn premultiplied_alpha(&self) -> bool {
57        self.premultiplied
58    }
59}
60
61/// Number of mip levels
62#[derive(Clone, Copy, Debug)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64pub enum MipLevels {
65    /// Generate mip levels automaticaly from image size, each mip level
66    /// decreasing in resolution by half until 1x1
67    GenerateAuto,
68    /// Generate mip levels up to a certain level, each mip level
69    /// decreasing in resolution by half
70    GenerateLevels(NonZeroU8),
71    /// Create the image with raw mip levels but without blitting the main
72    /// texture data into them
73    Levels(NonZeroU8),
74}
75
76/// Calculate the number of mip levels for a 2D image with given dimensions
77pub fn mip_levels_from_dims(width: u32, height: u32) -> u8 {
78    ((32 - width.max(height).leading_zeros()).max(1) as u8).min(rendy_core::hal::image::MAX_LEVEL)
79}
80
81#[derive(Debug)]
82pub enum BuildError {
83    Format(Format),
84    Image(ImageCreationError),
85    Upload(UploadError),
86    ImageView(ImageViewCreationError),
87    Mipmap(rendy_core::hal::device::OutOfMemory),
88    Sampler(rendy_core::hal::device::AllocationError),
89}
90
91/// Generics-free texture builder.
92#[derive(Clone)]
93#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
94/// Struct for staging data in preparation of building a `Texture`
95pub struct TextureBuilder<'a> {
96    kind: image::Kind,
97    view_kind: image::ViewKind,
98    format: Format,
99    data: std::borrow::Cow<'a, [u8]>,
100    data_width: u32,
101    data_height: u32,
102    sampler_info: rendy_core::hal::image::SamplerDesc,
103    swizzle: Swizzle,
104    mip_levels: MipLevels,
105    premultiplied: bool,
106}
107
108impl<'a> std::fmt::Debug for TextureBuilder<'a> {
109    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        fmt.debug_struct("TextureBuilder")
111            .field("kind", &self.kind)
112            .field("view_kind", &self.view_kind)
113            .field("format", &self.format)
114            .field("data", &"<raw-data>")
115            .field("data_width", &self.data_width)
116            .field("data_height", &self.data_height)
117            .field("sampler_info", &self.sampler_info)
118            .field("swizzle", &self.swizzle)
119            .field("mip_levels", &self.mip_levels)
120            .field("premultiplied", &self.premultiplied)
121            .finish()
122    }
123}
124
125impl<'a> TextureBuilder<'a> {
126    /// New empty `TextureBuilder`
127    pub fn new() -> Self {
128        TextureBuilder {
129            kind: image::Kind::D1(0, 0),
130            view_kind: image::ViewKind::D1,
131            format: Format::Rgba8Unorm,
132            data: std::borrow::Cow::Borrowed(&[]),
133            data_width: 0,
134            data_height: 0,
135            sampler_info: rendy_core::hal::image::SamplerDesc::new(
136                rendy_core::hal::image::Filter::Linear,
137                rendy_core::hal::image::WrapMode::Clamp,
138            ),
139            swizzle: Swizzle::NO,
140            mip_levels: MipLevels::Levels(NonZeroU8::new(1).unwrap()),
141            premultiplied: false,
142        }
143    }
144
145    /// Set whether the image has premultiplied alpha
146    pub fn set_premultiplied_alpha(&mut self, premultiplied: bool) -> &mut Self {
147        self.premultiplied = premultiplied;
148        self
149    }
150
151    /// Set whether the image has premultiplied alpha
152    pub fn with_premultiplied_alpha(mut self, premultiplied: bool) -> Self {
153        self.set_premultiplied_alpha(premultiplied);
154        self
155    }
156
157    /// Set pixel data.
158    pub fn with_data<P: AsPixel>(mut self, data: impl Into<std::borrow::Cow<'a, [P]>>) -> Self {
159        self.set_data(data);
160        self
161    }
162
163    /// Set pixel data.
164    pub fn set_data<P: AsPixel>(
165        &mut self,
166        data: impl Into<std::borrow::Cow<'a, [P]>>,
167    ) -> &mut Self {
168        self.data = cast_cow(data.into());
169        self.format = P::FORMAT;
170        self
171    }
172
173    /// Set pixel data with manual format definition.
174    pub fn with_raw_data(
175        mut self,
176        data: impl Into<std::borrow::Cow<'a, [u8]>>,
177        format: Format,
178    ) -> Self {
179        self.set_raw_data(data, format);
180        self
181    }
182
183    /// Set pixel data with manual format definition.
184    pub fn set_raw_data(
185        &mut self,
186        data: impl Into<std::borrow::Cow<'a, [u8]>>,
187        format: Format,
188    ) -> &mut Self {
189        self.data = data.into();
190        self.format = format;
191        self
192    }
193
194    /// Set pixel data width.
195    pub fn with_data_width(mut self, data_width: u32) -> Self {
196        self.set_data_width(data_width);
197        self
198    }
199
200    /// Set pixel data width.
201    pub fn set_data_width(&mut self, data_width: u32) -> &mut Self {
202        self.data_width = data_width;
203        self
204    }
205
206    /// Set pixel data height.
207    pub fn with_data_height(mut self, data_height: u32) -> Self {
208        self.set_data_height(data_height);
209        self
210    }
211
212    /// Set pixel data height.
213    pub fn set_data_height(&mut self, data_height: u32) -> &mut Self {
214        self.data_height = data_height;
215        self
216    }
217
218    /// Set number of generated or raw mip levels
219    pub fn with_mip_levels(mut self, mip_levels: MipLevels) -> Self {
220        self.set_mip_levels(mip_levels);
221        self
222    }
223
224    /// Set number of generated or raw mip levels
225    pub fn set_mip_levels(&mut self, mip_levels: MipLevels) -> &mut Self {
226        self.mip_levels = mip_levels;
227        self
228    }
229
230    /// Set image extent.
231    pub fn with_kind(mut self, kind: image::Kind) -> Self {
232        self.set_kind(kind);
233        self
234    }
235
236    /// Set image kind.
237    pub fn set_kind(&mut self, kind: image::Kind) -> &mut Self {
238        self.kind = kind;
239        self
240    }
241
242    /// With image view kind.
243    pub fn with_view_kind(mut self, view_kind: image::ViewKind) -> Self {
244        self.set_view_kind(view_kind);
245        self
246    }
247
248    /// Set image view kind.
249    pub fn set_view_kind(&mut self, view_kind: image::ViewKind) -> &mut Self {
250        self.view_kind = view_kind;
251        self
252    }
253
254    /// With image sampler info.
255    pub fn with_sampler_info(mut self, sampler_info: rendy_core::hal::image::SamplerDesc) -> Self {
256        self.set_sampler_info(sampler_info);
257        self
258    }
259
260    /// Set image sampler info.
261    pub fn set_sampler_info(
262        &mut self,
263        sampler_info: rendy_core::hal::image::SamplerDesc,
264    ) -> &mut Self {
265        self.sampler_info = sampler_info;
266        self
267    }
268
269    /// With swizzle.
270    pub fn with_swizzle(mut self, swizzle: Swizzle) -> Self {
271        self.set_swizzle(swizzle);
272        self
273    }
274
275    /// Set swizzle.
276    pub fn set_swizzle(&mut self, swizzle: Swizzle) -> &mut Self {
277        self.swizzle = swizzle;
278        self
279    }
280
281    /// Build texture.
282    ///
283    /// ## Parameters
284    /// * `next_state`: The next state that this texture will be used in.
285    ///     It will get transitioned to this state after uploading.
286    /// * `factory`: Factory to use to build the texture
287    pub fn build<B>(
288        &self,
289        next_state: ImageState,
290        factory: &'a mut Factory<B>,
291    ) -> Result<Texture<B>, BuildError>
292    where
293        B: Backend,
294    {
295        profile_scope!("build");
296
297        let view_caps = match self.view_kind {
298            rendy_core::hal::image::ViewKind::D2Array => {
299                rendy_core::hal::image::ViewCapabilities::KIND_2D_ARRAY
300            }
301            rendy_core::hal::image::ViewKind::Cube
302            | rendy_core::hal::image::ViewKind::CubeArray => {
303                rendy_core::hal::image::ViewCapabilities::KIND_CUBE
304            }
305            _ => rendy_core::hal::image::ViewCapabilities::empty(),
306        };
307
308        let (mip_levels, generate_mips) = match self.mip_levels {
309            MipLevels::GenerateLevels(val) => (val.get(), true),
310            MipLevels::Levels(val) => (val.get(), false),
311            MipLevels::GenerateAuto => match self.kind {
312                rendy_core::hal::image::Kind::D1(_, _) => (1, false),
313                rendy_core::hal::image::Kind::D2(w, h, _, _) => (mip_levels_from_dims(w, h), true),
314                rendy_core::hal::image::Kind::D3(_, _, _) => (1, false),
315            },
316        };
317
318        let (info, transform, transform_swizzle) = find_compatible_format(
319            factory,
320            ImageInfo {
321                kind: self.kind,
322                levels: mip_levels,
323                format: self.format,
324                tiling: rendy_core::hal::image::Tiling::Optimal,
325                view_caps,
326                usage: rendy_core::hal::image::Usage::SAMPLED
327                    | rendy_core::hal::image::Usage::TRANSFER_DST
328                    | rendy_core::hal::image::Usage::TRANSFER_SRC,
329            },
330        )
331        .ok_or(BuildError::Format(self.format))?;
332
333        let image: Handle<Image<B>> = factory
334            .create_image(info, Data)
335            .map_err(BuildError::Image)?
336            .into();
337
338        let mut transformed_vec: Vec<u8>;
339
340        let buffer: &[u8] = match transform {
341            BufferTransform::Intact => &self.data,
342            BufferTransform::AddPadding { stride, padding } => {
343                profile_scope!("add_padding");
344                let new_stride = stride + padding.len();
345                let data_len = self.data.len() / stride * new_stride;
346
347                transformed_vec = vec![0; data_len];
348                let dst_slice: &mut [u8] = &mut transformed_vec;
349                // optimize most common cases
350                match (stride, padding) {
351                    (2, &[0u8, std::u8::MAX]) => {
352                        buf_add_padding(&self.data, dst_slice, stride, padding)
353                    }
354                    (3, &[std::u8::MAX]) => buf_add_padding(&self.data, dst_slice, stride, padding),
355                    _ => buf_add_padding(&self.data, dst_slice, stride, padding),
356                }
357                &transformed_vec
358            }
359        };
360
361        let mip_state = ImageState {
362            queue: next_state.queue,
363            stage: rendy_core::hal::pso::PipelineStage::TRANSFER,
364            access: image::Access::TRANSFER_READ,
365            layout: image::Layout::TransferSrcOptimal,
366        };
367
368        let undef_state = ImageState {
369            queue: next_state.queue,
370            stage: rendy_core::hal::pso::PipelineStage::TOP_OF_PIPE,
371            access: image::Access::empty(),
372            layout: image::Layout::Undefined,
373        };
374
375        // The reason that factory.upload_image is unsafe is that the image being uploaded
376        // must have been created by the same factory and that it is not in use; we guarantee
377        // that here because we just created the image on the same factory right before.
378        unsafe {
379            profile_scope!("upload_image");
380
381            factory
382                .upload_image(
383                    image.clone(),
384                    self.data_width,
385                    self.data_height,
386                    image::SubresourceLayers {
387                        aspects: info.format.surface_desc().aspects,
388                        level: 0,
389                        layers: 0..info.kind.num_layers(),
390                    },
391                    image::Offset::ZERO,
392                    info.kind.extent(),
393                    buffer,
394                    image::Layout::Undefined,
395                    if !generate_mips || mip_levels == 1 {
396                        next_state
397                    } else {
398                        mip_state
399                    },
400                )
401                .map_err(BuildError::Upload)?;
402        }
403
404        if mip_levels > 1 && generate_mips {
405            profile_scope!("fill_mips");
406            unsafe {
407                factory
408                    .blitter()
409                    .fill_mips(
410                        factory.device(),
411                        image.clone(),
412                        image::Filter::Linear,
413                        std::iter::once(mip_state).chain(std::iter::repeat(undef_state)),
414                        std::iter::repeat(next_state),
415                    )
416                    .map_err(BuildError::Mipmap)?;
417            }
418        } else if mip_levels > 1 && !generate_mips {
419            unsafe {
420                factory.transition_image(
421                    image.clone(),
422                    image::SubresourceRange {
423                        aspects: info.format.surface_desc().aspects,
424                        levels: 1..mip_levels,
425                        layers: 0..info.kind.num_layers(),
426                    },
427                    image::Layout::Undefined,
428                    next_state,
429                );
430            }
431        }
432
433        let view = {
434            profile_scope!("create_image_view");
435            factory
436                .create_image_view(
437                    image.clone(),
438                    ImageViewInfo {
439                        view_kind: self.view_kind,
440                        format: info.format,
441                        swizzle: double_swizzle(self.swizzle, transform_swizzle),
442                        range: image::SubresourceRange {
443                            aspects: info.format.surface_desc().aspects,
444                            levels: 0..info.levels,
445                            layers: 0..info.kind.num_layers(),
446                        },
447                    },
448                )
449                .map_err(BuildError::ImageView)?
450        };
451
452        let sampler = factory
453            .get_sampler(self.sampler_info.clone())
454            .map_err(BuildError::Sampler)?;
455
456        Ok(Texture {
457            image,
458            view,
459            sampler,
460            premultiplied: self.premultiplied,
461        })
462    }
463}
464
465enum BufferTransform {
466    Intact,
467    AddPadding {
468        stride: usize,
469        padding: &'static [u8],
470    },
471}
472
473fn double_swizzle(src: Swizzle, overlay: Swizzle) -> Swizzle {
474    fn pick_component(src: Swizzle, component: Component) -> Component {
475        let Swizzle(r, g, b, a) = src;
476        match component {
477            Component::R => r,
478            Component::G => g,
479            Component::B => b,
480            Component::A => a,
481            Component::Zero => Component::Zero,
482            Component::One => Component::One,
483        }
484    }
485    Swizzle(
486        pick_component(src, overlay.0),
487        pick_component(src, overlay.1),
488        pick_component(src, overlay.2),
489        pick_component(src, overlay.3),
490    )
491}
492
493fn find_compatible_format<B: Backend>(
494    factory: &Factory<B>,
495    info: ImageInfo,
496) -> Option<(ImageInfo, BufferTransform, Swizzle)> {
497    profile_scope!("find_compatible_format");
498
499    if let Some(info) = image_format_supported(factory, info) {
500        return Some((info, BufferTransform::Intact, Swizzle::NO));
501    }
502    if let Some((format, transform, swizzle)) = expand_format_channels(info.format) {
503        let mut new_info = info.clone();
504        new_info.format = format;
505        if let Some(new_info) = image_format_supported(factory, new_info) {
506            log::trace!("Converting image from {:?} to {:?}", info, new_info);
507            return Some((new_info, transform, swizzle));
508        }
509    }
510
511    None
512}
513
514fn expand_format_channels(format: Format) -> Option<(Format, BufferTransform, Swizzle)> {
515    const ONE_F16: u16 = 15360u16;
516
517    let t2_u8 = BufferTransform::AddPadding {
518        stride: 2,
519        padding: &[0u8, std::u8::MAX],
520    };
521
522    let t2_u16 = BufferTransform::AddPadding {
523        stride: 4,
524        padding: cast_slice(&[0u16, std::u16::MAX]),
525    };
526
527    let t2_f16 = BufferTransform::AddPadding {
528        stride: 4,
529        padding: cast_slice(&[0u16, ONE_F16]),
530    };
531
532    let t2_u32 = BufferTransform::AddPadding {
533        stride: 8,
534        padding: cast_slice(&[0u32, std::u32::MAX]),
535    };
536
537    let t2_f32 = BufferTransform::AddPadding {
538        stride: 8,
539        padding: cast_slice(&[0.0f32, 1.0f32]),
540    };
541
542    let t3_u8 = BufferTransform::AddPadding {
543        stride: 3,
544        padding: &[std::u8::MAX],
545    };
546
547    let t3_u16 = BufferTransform::AddPadding {
548        stride: 6,
549        padding: cast_slice(&[std::u16::MAX]),
550    };
551
552    let t3_f16 = BufferTransform::AddPadding {
553        stride: 6,
554        padding: cast_slice(&[ONE_F16]),
555    };
556
557    let t3_u32 = BufferTransform::AddPadding {
558        stride: 12,
559        padding: cast_slice(&[std::u32::MAX]),
560    };
561
562    let t3_f32 = BufferTransform::AddPadding {
563        stride: 12,
564        padding: cast_slice(&[1.0f32]),
565    };
566
567    let intact = BufferTransform::Intact;
568
569    let rgba = Swizzle(Component::R, Component::G, Component::B, Component::A);
570    let bgra = Swizzle(Component::B, Component::G, Component::R, Component::A);
571
572    Some(match format {
573        // Destination formats chosen according to this table
574        // https://vulkan.gpuinfo.org/listformats.php
575        Format::Rg8Unorm => (Format::Rgba8Unorm, t2_u8, rgba),
576        Format::Rg8Snorm => (Format::Rgba8Snorm, t2_u8, rgba),
577        Format::Rg8Uscaled => (Format::Rgba8Uscaled, t2_u8, rgba),
578        Format::Rg8Sscaled => (Format::Rgba8Sscaled, t2_u8, rgba),
579        Format::Rg8Uint => (Format::Rgba8Uint, t2_u8, rgba),
580        Format::Rg8Sint => (Format::Rgba8Sint, t2_u8, rgba),
581        Format::Rg8Srgb => (Format::Rgba8Srgb, t2_u8, rgba),
582
583        Format::Rgb8Unorm => (Format::Rgba8Unorm, t3_u8, rgba),
584        Format::Rgb8Snorm => (Format::Rgba8Snorm, t3_u8, rgba),
585        Format::Rgb8Uscaled => (Format::Rgba8Uscaled, t3_u8, rgba),
586        Format::Rgb8Sscaled => (Format::Rgba8Sscaled, t3_u8, rgba),
587        Format::Rgb8Uint => (Format::Rgba8Uint, t3_u8, rgba),
588        Format::Rgb8Sint => (Format::Rgba8Sint, t3_u8, rgba),
589        Format::Rgb8Srgb => (Format::Rgba8Srgb, t3_u8, rgba),
590
591        Format::Bgr8Unorm => (Format::Rgba8Unorm, t3_u8, bgra),
592        Format::Bgr8Snorm => (Format::Rgba8Snorm, t3_u8, bgra),
593        Format::Bgr8Uscaled => (Format::Rgba8Uscaled, t3_u8, bgra),
594        Format::Bgr8Sscaled => (Format::Rgba8Sscaled, t3_u8, bgra),
595        Format::Bgr8Uint => (Format::Rgba8Uint, t3_u8, bgra),
596        Format::Bgr8Sint => (Format::Rgba8Sint, t3_u8, bgra),
597        Format::Bgr8Srgb => (Format::Rgba8Srgb, t3_u8, bgra),
598
599        Format::Bgra8Unorm => (Format::Rgba8Unorm, intact, bgra),
600        Format::Bgra8Snorm => (Format::Rgba8Snorm, intact, bgra),
601        Format::Bgra8Uscaled => (Format::Rgba8Uscaled, intact, bgra),
602        Format::Bgra8Sscaled => (Format::Rgba8Sscaled, intact, bgra),
603        Format::Bgra8Uint => (Format::Rgba8Uint, intact, bgra),
604        Format::Bgra8Sint => (Format::Rgba8Sint, intact, bgra),
605        Format::Bgra8Srgb => (Format::Rgba8Srgb, intact, bgra),
606
607        Format::Rg16Unorm => (Format::Rgba16Unorm, t2_u16, rgba),
608        Format::Rg16Snorm => (Format::Rgba16Snorm, t2_u16, rgba),
609        Format::Rg16Uscaled => (Format::Rgba16Uscaled, t2_u16, rgba),
610        Format::Rg16Sscaled => (Format::Rgba16Sscaled, t2_u16, rgba),
611        Format::Rg16Uint => (Format::Rgba16Uint, t2_u16, rgba),
612        Format::Rg16Sint => (Format::Rgba16Sint, t2_u16, rgba),
613        Format::Rg16Sfloat => (Format::Rgba16Sfloat, t2_f16, rgba),
614
615        Format::Rgb16Unorm => (Format::Rgba16Unorm, t3_u16, rgba),
616        Format::Rgb16Snorm => (Format::Rgba16Snorm, t3_u16, rgba),
617        Format::Rgb16Uscaled => (Format::Rgba16Uscaled, t3_u16, rgba),
618        Format::Rgb16Sscaled => (Format::Rgba16Sscaled, t3_u16, rgba),
619        Format::Rgb16Uint => (Format::Rgba16Uint, t3_u16, rgba),
620        Format::Rgb16Sint => (Format::Rgba16Sint, t3_u16, rgba),
621        Format::Rgb16Sfloat => (Format::Rgba16Sfloat, t3_f16, rgba),
622
623        Format::Rg32Uint => (Format::Rgba32Uint, t2_u32, rgba),
624        Format::Rg32Sint => (Format::Rgba32Sint, t2_u32, rgba),
625        Format::Rg32Sfloat => (Format::Rgba32Sfloat, t2_f32, rgba),
626
627        Format::Rgb32Uint => (Format::Rgba32Uint, t3_u32, rgba),
628        Format::Rgb32Sint => (Format::Rgba32Sint, t3_u32, rgba),
629        Format::Rgb32Sfloat => (Format::Rgba32Sfloat, t3_f32, rgba),
630        // TODO: add more conversions
631        _ => return None,
632    })
633}
634
635fn image_format_supported<B: Backend>(
636    factory: &Factory<B>,
637    mut info: ImageInfo,
638) -> Option<ImageInfo> {
639    factory
640        .image_format_properties(info)
641        .filter(|props| {
642            props.max_layers >= info.kind.num_layers()
643                && props.max_extent.width >= info.kind.extent().width
644                && props.max_extent.height >= info.kind.extent().height
645                && props.max_extent.depth >= info.kind.extent().depth
646        })
647        .map(|props| {
648            match &mut info.kind {
649                image::Kind::D2(_, _, _, s) if *s & props.sample_count_mask != *s => {
650                    let mut new_samples = *s >> 1;
651                    while new_samples > 1 && new_samples & props.sample_count_mask != new_samples {
652                        new_samples = new_samples >> 1;
653                    }
654                    *s = new_samples;
655                }
656                _ => {}
657            };
658            info
659        })
660}
661
662#[inline(always)]
663fn buf_add_padding(buffer: &[u8], dst_slice: &mut [u8], stride: usize, padding: &'static [u8]) {
664    let lad_len = padding.len();
665    for (chunk, dst_chunk) in buffer
666        .chunks_exact(stride)
667        .zip(dst_slice.chunks_exact_mut(stride + lad_len))
668    {
669        // those loops gets unrolled in special-cased scenarios
670        for i in 0..stride {
671            dst_chunk[i] = chunk[i];
672        }
673        for i in 0..lad_len {
674            dst_chunk[stride + i] = padding[i];
675        }
676    }
677}