You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为D2Array上传尺寸小于4x4的BC7多级纹理(Mipmaps)?

问题:BC7压缩纹理在多层D2Array小尺寸Mipmap时的wgpu写入错误

我正在使用intel_tex_2 crate压缩图像,通过wgpu crate上传,纹理格式为Bc7RgbaUnorm。当D2Array层数大于1且mipmap尺寸小于4x4时出现问题:write_texture会尝试读取远超4x4块的数据。例如2x2x2纹理会报错:

Copy of 0..80 would end up overrunning the bounds of the Source buffer of size 32

每增加一层会多占用48字节。我尝试过多种填充方式,但显示均不正确。以下是最小复现示例:

Cargo.toml

[package]
name = "test_bed"
version = "0.1.0"
edition = "2021"

[dependencies]
pollster = "0.3"

winit = "0.30.3"
wgpu = "0.20.1"
intel_tex_2 = "0.4.0"

main.rs

use winit::{application::ApplicationHandler, event_loop::{ActiveEventLoop, EventLoop}, window::Window}; // 0.30.3
use wgpu::{InstanceDescriptor, InstanceFlags, Dx12Compiler, Gles3MinorVersion, Origin3d, Features}; // 0.20.1
use intel_tex_2::bc7;   // 0.4.0

pub fn compression_test(device: &wgpu::Device, queue: &wgpu::Queue) {
    assert!(device.features().contains(Features::TEXTURE_COMPRESSION_BC));
    
    // 3 mipmap levels
    let mut rgbas = vec![
        Vec::new(),
        Vec::new(),
        Vec::new(),
    ];

    // 4x4 top level
    let size = wgpu::Extent3d {
        width: 4,
        height: 4,
        depth_or_array_layers: 2,
    };
    
    for _ in 0..size.depth_or_array_layers {
        rgbas[0].extend_from_slice(&BLUE4X4);
        rgbas[1].extend_from_slice(&GREEN2X2);
        rgbas[2].extend_from_slice(&RED1X1);
    }

    let texture = device.create_texture(&wgpu::TextureDescriptor {
        label: None,
        size,
        mip_level_count: rgbas.len() as u32,
        sample_count: 1,
        dimension: wgpu::TextureDimension::D2,
        format: wgpu::TextureFormat::Bc7RgbaUnormSrgb,
        usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
        view_formats: &[],
    });

    for (mip_level, rgba) in rgbas.into_iter().enumerate() {
        let mip_size = size.mip_level_size(mip_level as u32, wgpu::TextureDimension::D2);
        let layer_bytes = (mip_size.width * mip_size.height * 4) as usize;
        let compressed_layer_bytes = bc7::calc_output_size(mip_size.width, mip_size.height).max(16);
        let compressed_layer_stride = layer_bytes.max(compressed_layer_bytes);

        let mut compressed = vec![0u8; rgba.len().max(compressed_layer_stride * mip_size.depth_or_array_layers as usize)];
        let data_iter = rgba.chunks(layer_bytes);
        let compressed_iter = compressed.chunks_mut(compressed_layer_stride);
        for (data, compressed) in data_iter.zip(compressed_iter) {
            let surface = intel_tex_2::RgbaSurface {
                width: mip_size.width,
                height: mip_size.height,
                stride: mip_size.width * 4,
                data,
            };

            bc7::compress_blocks_into(&bc7::opaque_ultra_fast_settings(), &surface, &mut compressed[..compressed_layer_bytes]);
        }

        let physical_size = mip_size.physical_size(wgpu::TextureFormat::Bc7RgbaUnormSrgb);

        println!("write: {} bytes, size {:?} padded to {:?}", compressed.len(), mip_size, physical_size);
        queue.write_texture(
            wgpu::ImageCopyTexture {
                aspect: wgpu::TextureAspect::All,
                texture: &texture,
                mip_level: mip_level as u32,
                origin: Origin3d::ZERO,
            },
            &compressed,
            wgpu::ImageDataLayout {
                offset: 0,
                bytes_per_row: Some(4 * physical_size.width),
                rows_per_image: Some(physical_size.height),
            },
            physical_size,
        );
    }
}

fn main() {
    let event_loop: EventLoop<()> = EventLoop::with_user_event().build().unwrap();
    let _ = event_loop.run_app(&mut AppHandler);
}

struct AppHandler;

impl ApplicationHandler<()> for AppHandler {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        let window = window(event_loop);
        let (device, queue) = pollster::block_on(wgpu(window)); // 0.3.0

        compression_test(&device, &queue);

        event_loop.exit();
    }

    fn window_event(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop, _window_id: winit::window::WindowId, _event: winit::event::WindowEvent) {}
}

pub fn window(event_loop: &ActiveEventLoop) -> Window {
    let window_attributes = Window::default_attributes();

    event_loop.create_window(window_attributes).unwrap()
}

pub async fn wgpu(window: Window) -> (wgpu::Device, wgpu::Queue) {
    let window_size = window.inner_size();

    // WGPU
    let instance = wgpu::Instance::new(InstanceDescriptor {
        backends: wgpu::Backends::DX12 | wgpu::Backends::METAL | wgpu::Backends::VULKAN | wgpu::Backends::GL,
        flags: InstanceFlags::default(),
        dx12_shader_compiler: Dx12Compiler::default(),
        gles_minor_version: Gles3MinorVersion::Automatic,
    });
    let surface = instance.create_surface(window).unwrap();
    let adapter = instance
        .request_adapter(&wgpu::RequestAdapterOptions {
            power_preference: wgpu::PowerPreference::HighPerformance,
            compatible_surface: Some(&surface),
            force_fallback_adapter: false,
        }).await.unwrap();

    let required_features = wgpu::Features::default()
        | wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING
        | wgpu::Features::TEXTURE_BINDING_ARRAY
        | wgpu::Features::BUFFER_BINDING_ARRAY
        | wgpu::Features::DEPTH_CLIP_CONTROL
        | wgpu::Features::ADDRESS_MODE_CLAMP_TO_ZERO
        | wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER
        | wgpu::Features::TEXTURE_COMPRESSION_BC;
    
    let (device, queue) = adapter.request_device(
        &wgpu::DeviceDescriptor {
            label: None,
            required_features,
            required_limits: adapter.limits(),
        },
        None,
    ).await.unwrap();

    let surface_capabilities = surface.get_capabilities(&adapter);
    let surface_format = surface_capabilities.formats.iter()
        .copied()
        .filter(|f| f.is_srgb())
        .next()
        .unwrap_or(surface_capabilities.formats[0]);
    let config = wgpu::SurfaceConfiguration {
        usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
        format: surface_format,
        width: window_size.width,
        height: window_size.height,
        present_mode: wgpu::PresentMode::Fifo,
        desired_maximum_frame_latency: 2,
        alpha_mode: surface_capabilities.alpha_modes[0],
        view_formats: vec![],
    };
    surface.configure(&device, &config);

    (device, queue)
}

pub const BLUE4X4: [u8; 4 * 4 * 4] = [
    0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255,
    0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255,
    0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255,
    0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255,
];

pub const GREEN2X2: [u8; 2 * 2 * 4] = [
    0, 255, 0, 255, 0, 255, 0, 255,
    0, 255, 0, 255, 0, 255, 0, 255,
];

pub const RED1X1: [u8; 1 * 1 * 4] = [
    255, 0, 0, 255
];

内容的提问来源于stack exchange,提问作者VrydayVrything

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 20:57:04