- crates/engine: сборка мира (браши, displacement'ы, лайтмапы в вершинах, материалы через VMT/patch/include, кэш текстур, пропуск прозрачных материалов), загрузка скайбокса из skyname - crates/render: wgpu-рендерер мира (VB/IB, батчи, глубина), скайбокс-куб, камера-полёт, capture PNG с учётом BGRA, debug-режимы материалов - client: команда map (диск/VPK, pakfile карты, WASD+мышь, --pos/--angles/--screenshot) - тесты: gm_construct/gm_flatgrass собираются, 0 заглушек, 41 324 треугольника - docs: прогресс M3 и заметки (BGRA, pak-VMT, z=w клиппинг)
157 lines
5 KiB
Rust
157 lines
5 KiB
Rust
//! Загрузка текстур мира на GPU.
|
||
|
||
use engine::TextureData;
|
||
|
||
/// Создаёт 1x1 текстуру заданного цвета (debug-режим материалов).
|
||
pub fn upload_solid(
|
||
device: &wgpu::Device,
|
||
queue: &wgpu::Queue,
|
||
layout: &wgpu::BindGroupLayout,
|
||
sampler: &wgpu::Sampler,
|
||
color: [u8; 4],
|
||
) -> wgpu::BindGroup {
|
||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||
label: Some("debug material"),
|
||
size: wgpu::Extent3d {
|
||
width: 1,
|
||
height: 1,
|
||
depth_or_array_layers: 1,
|
||
},
|
||
mip_level_count: 1,
|
||
sample_count: 1,
|
||
dimension: wgpu::TextureDimension::D2,
|
||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||
view_formats: &[],
|
||
});
|
||
queue.write_texture(
|
||
wgpu::TexelCopyTextureInfo {
|
||
texture: &texture,
|
||
mip_level: 0,
|
||
origin: wgpu::Origin3d::ZERO,
|
||
aspect: wgpu::TextureAspect::All,
|
||
},
|
||
&color,
|
||
wgpu::TexelCopyBufferLayout {
|
||
offset: 0,
|
||
bytes_per_row: Some(4),
|
||
rows_per_image: Some(1),
|
||
},
|
||
wgpu::Extent3d {
|
||
width: 1,
|
||
height: 1,
|
||
depth_or_array_layers: 1,
|
||
},
|
||
);
|
||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||
label: Some("debug material bind group"),
|
||
layout,
|
||
entries: &[
|
||
wgpu::BindGroupEntry {
|
||
binding: 0,
|
||
resource: wgpu::BindingResource::TextureView(&view),
|
||
},
|
||
wgpu::BindGroupEntry {
|
||
binding: 1,
|
||
resource: wgpu::BindingResource::Sampler(sampler),
|
||
},
|
||
],
|
||
})
|
||
}
|
||
|
||
/// Цвет палитры для индекса материала (debug-режим).
|
||
pub fn debug_palette_color(index: usize) -> [u8; 4] {
|
||
let hue = (index as f32 * 0.618_034) % 1.0;
|
||
let (r, g, b) = hsv_to_rgb(hue, 0.85, 1.0);
|
||
[r, g, b, 255]
|
||
}
|
||
|
||
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (u8, u8, u8) {
|
||
let i = (h * 6.0).floor();
|
||
let f = h * 6.0 - i;
|
||
let p = v * (1.0 - s);
|
||
let q = v * (1.0 - f * s);
|
||
let t = v * (1.0 - (1.0 - f) * s);
|
||
let (r, g, b) = match (i as i32) % 6 {
|
||
0 => (v, t, p),
|
||
1 => (q, v, p),
|
||
2 => (p, v, t),
|
||
3 => (p, q, v),
|
||
4 => (t, p, v),
|
||
_ => (v, p, q),
|
||
};
|
||
((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8)
|
||
}
|
||
/// Загружает текстуру (с мипами) и создаёт bind group материала.
|
||
pub fn upload_texture(
|
||
device: &wgpu::Device,
|
||
queue: &wgpu::Queue,
|
||
layout: &wgpu::BindGroupLayout,
|
||
sampler: &wgpu::Sampler,
|
||
texture: &TextureData,
|
||
) -> wgpu::BindGroup {
|
||
let width = texture.width.max(1);
|
||
let height = texture.height.max(1);
|
||
let mip_levels = texture.mips.len().max(1) as u32;
|
||
|
||
let gpu_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||
label: Some(&texture.name),
|
||
size: wgpu::Extent3d {
|
||
width,
|
||
height,
|
||
depth_or_array_layers: 1,
|
||
},
|
||
mip_level_count: mip_levels,
|
||
sample_count: 1,
|
||
dimension: wgpu::TextureDimension::D2,
|
||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||
view_formats: &[],
|
||
});
|
||
|
||
for (level, data) in texture.mips.iter().enumerate() {
|
||
let level = level as u32;
|
||
let mip_width = (width >> level).max(1);
|
||
let mip_height = (height >> level).max(1);
|
||
let expected = (mip_width * mip_height * 4) as usize;
|
||
if data.len() < expected {
|
||
break;
|
||
}
|
||
queue.write_texture(
|
||
wgpu::TexelCopyTextureInfo {
|
||
texture: &gpu_texture,
|
||
mip_level: level,
|
||
origin: wgpu::Origin3d::ZERO,
|
||
aspect: wgpu::TextureAspect::All,
|
||
},
|
||
&data[..expected],
|
||
wgpu::TexelCopyBufferLayout {
|
||
offset: 0,
|
||
bytes_per_row: Some(mip_width * 4),
|
||
rows_per_image: Some(mip_height),
|
||
},
|
||
wgpu::Extent3d {
|
||
width: mip_width,
|
||
height: mip_height,
|
||
depth_or_array_layers: 1,
|
||
},
|
||
);
|
||
}
|
||
|
||
let view = gpu_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||
label: Some(&texture.name),
|
||
layout,
|
||
entries: &[
|
||
wgpu::BindGroupEntry {
|
||
binding: 0,
|
||
resource: wgpu::BindingResource::TextureView(&view),
|
||
},
|
||
wgpu::BindGroupEntry {
|
||
binding: 1,
|
||
resource: wgpu::BindingResource::Sampler(sampler),
|
||
},
|
||
],
|
||
})
|
||
}
|