Skip to main content

ursus_core/vulkan/resources/texture/
mod.rs

1mod image_alloc;
2mod mipmap;
3mod staging;
4
5use crate::render::gfx::types::Format;
6use crate::vulkan::core::memory::destroy_image_resources;
7use crate::vulkan::core::{DeviceContext, SubmitContext};
8use ash::vk;
9use image_alloc::AllocatedTextureImage;
10use staging::StagingBuffer;
11
12pub struct GpuTexture {
13    pub image: vk::Image,
14    pub view: vk::ImageView,
15    pub memory: vk::DeviceMemory,
16    pub format: vk::Format,
17    pub width: u32,
18    pub height: u32,
19    pub name: String,
20    device: ash::Device,
21}
22
23pub struct TextureSource<'a> {
24    pub pixels: &'a [u8],
25    pub width: u32,
26    pub height: u32,
27    pub format: Format,
28    pub name: &'a str,
29}
30
31impl GpuTexture {
32    /// Loads a texture with a full mip chain (generated by blitting from mip 0).
33    pub fn upload(ctx: DeviceContext, submit: SubmitContext, source: TextureSource) -> anyhow::Result<Self> {
34        let mip_levels = compute_mip_levels(source.width, source.height);
35        let usage =
36            vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::TRANSFER_SRC | vk::ImageUsageFlags::SAMPLED;
37
38        Self::upload_impl(ctx, submit, source, mip_levels, usage, true)
39    }
40
41    /// Loads a texture without a mip chain (mip 0 only).
42    pub fn upload_no_mip(ctx: DeviceContext, submit: SubmitContext, source: TextureSource) -> anyhow::Result<Self> {
43        let usage = vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED;
44
45        Self::upload_impl(ctx, submit, source, 1, usage, false)
46    }
47
48    fn upload_impl(
49        ctx: DeviceContext,
50        submit: SubmitContext,
51        source: TextureSource,
52        mip_levels: u32,
53        usage: vk::ImageUsageFlags,
54        with_mipchain: bool,
55    ) -> anyhow::Result<Self> {
56        let name = source.name.to_string();
57
58        let staging = StagingBuffer::upload(ctx, source.pixels)?;
59        let alloc = AllocatedTextureImage::create(ctx, source.format, source.width, source.height, mip_levels, usage)?;
60
61        one_shot(ctx.device, submit, |cmd| {
62            if with_mipchain {
63                mipmap::upload_with_mipchain(
64                    ctx.device,
65                    cmd,
66                    alloc.image,
67                    staging.buffer,
68                    source.width,
69                    source.height,
70                    mip_levels,
71                );
72            } else {
73                mipmap::upload_single_level(ctx.device, cmd, alloc.image, staging.buffer, source.width, source.height);
74            }
75        })?;
76
77        let view = alloc.create_view(source.format, mip_levels)?;
78        let (image, memory) = alloc.into_raw();
79
80        log::debug!(
81            "GpuTexture '{}': {}x{} {:?} ({} mip levels)",
82            name,
83            source.width,
84            source.height,
85            source.format,
86            mip_levels
87        );
88
89        Ok(Self {
90            image,
91            view,
92            memory,
93            format: source.format.to_vk(),
94            width: source.width,
95            height: source.height,
96            name,
97            device: ctx.device.clone(),
98        })
99    }
100}
101
102impl Drop for GpuTexture {
103    fn drop(&mut self) {
104        unsafe { destroy_image_resources(&self.device, self.image, self.view, self.memory) }
105    }
106}
107
108/// Number of mip levels for a `width x height` texture. If either dimension is 0, returns 1 level.
109/// (An empty/invalid texture must not result in an invalid `log2`.)
110fn compute_mip_levels(width: u32, height: u32) -> u32 {
111    if width == 0 || height == 0 {
112        return 1;
113    }
114    (width.max(height) as f32).log2().floor() as u32 + 1
115}
116
117fn one_shot(device: &ash::Device, submit: SubmitContext, f: impl FnOnce(vk::CommandBuffer)) -> anyhow::Result<()> {
118    let alloc_info = vk::CommandBufferAllocateInfo::default()
119        .command_pool(submit.command_pool)
120        .level(vk::CommandBufferLevel::PRIMARY)
121        .command_buffer_count(1);
122    let cmd = unsafe { device.allocate_command_buffers(&alloc_info)?[0] };
123
124    unsafe {
125        device.begin_command_buffer(
126            cmd,
127            &vk::CommandBufferBeginInfo::default().flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
128        )?;
129    }
130
131    f(cmd);
132
133    unsafe {
134        device.end_command_buffer(cmd)?;
135        device.queue_submit(
136            submit.queue,
137            &[vk::SubmitInfo::default().command_buffers(std::slice::from_ref(&cmd))],
138            vk::Fence::null(),
139        )?;
140        device.queue_wait_idle(submit.queue)?;
141        device.free_command_buffers(submit.command_pool, &[cmd]);
142    }
143
144    Ok(())
145}