#![allow(
clippy::cast_sign_loss,
clippy::cast_lossless,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::doc_markdown,
clippy::missing_panics_doc,
clippy::needless_range_loop,
clippy::pub_underscore_fields
)]
use bytemuck::Zeroable;
use wgpu::util::DeviceExt;
use crate::decompress::{gpu_mip_count, occ_words_per_column_for_mip, ChunkUpload};
use crate::grid::GridUpload;
pub const MAX_GPU_MIPS: usize = 6;
#[derive(Debug, Clone, Copy)]
pub struct MipLayout {
pub mip_count: u32,
pub occ_words_per_slot: u32,
pub offsets_words_per_slot: u32,
pub mip_occ_rel: [u32; MAX_GPU_MIPS],
pub mip_coff_rel: [u32; MAX_GPU_MIPS],
}
impl MipLayout {
#[must_use]
pub fn for_vsid(vsid: u32) -> Self {
let mip_count = gpu_mip_count(vsid);
let mut mip_occ_rel = [0u32; MAX_GPU_MIPS];
let mut mip_coff_rel = [0u32; MAX_GPU_MIPS];
let mut occ_acc = 0u32;
let mut coff_acc = 0u32;
for m in 0..mip_count {
mip_occ_rel[m as usize] = occ_acc;
mip_coff_rel[m as usize] = coff_acc;
let vsid_m = vsid >> m;
let cols = vsid_m * vsid_m;
occ_acc += 2 * cols * occ_words_per_column_for_mip(m);
coff_acc += cols + 1;
}
Self {
mip_count,
occ_words_per_slot: occ_acc,
offsets_words_per_slot: coff_acc,
mip_occ_rel,
mip_coff_rel,
}
}
}
pub const COLORS_PER_CHUNK_WORDS: u32 = 65536;
pub const MAX_OCC_PAGES: usize = 4;
#[derive(Debug, Clone, Copy)]
pub struct GridRuntimeTransform {
pub grid_origin_world: [f64; 3],
pub world_to_grid_rotation: [[f32; 3]; 3],
}
impl Default for GridRuntimeTransform {
fn default() -> Self {
Self {
grid_origin_world: [0.0, 0.0, 0.0],
world_to_grid_rotation: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
}
}
}
pub struct SceneUpload {
pub grids: Vec<GridUpload>,
}
impl SceneUpload {
#[must_use]
pub fn grid_count(&self) -> u32 {
u32::try_from(self.grids.len()).unwrap_or(u32::MAX)
}
}
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Debug)]
pub struct GridStaticMeta {
pub occupancy_offset: u32,
pub color_offsets_offset: u32,
pub colors_offset: u32,
pub chunk_colors_base_offset: u32,
pub chunk_occupancy_offset: u32,
pub slot_chunk_idx_offset: u32,
pub vsid: u32,
pub total_slots: u32,
pub pool_dims: [u32; 3],
pub _pad0: u32,
pub occ_words_per_slot: u32,
pub offsets_words_per_slot: u32,
pub mip_count: u32,
pub _pad1: u32,
pub mip_occ_rel: [u32; MAX_GPU_MIPS],
pub mip_coff_rel: [u32; MAX_GPU_MIPS],
pub aabb_min: [i32; 3],
pub _pad2: i32,
pub aabb_max: [i32; 3],
pub _pad3: i32,
}
pub const SLOT_EMPTY_SENTINEL: [i32; 3] = [i32::MIN, i32::MIN, i32::MIN];
pub struct GpuSceneResident {
pub grid_count: u32,
pub occupancy_pages: Vec<wgpu::Buffer>,
pub occupancy_page_words: u32,
pub occupancy_num_pages: u32,
pub all_color_offsets: wgpu::Buffer,
pub all_colors: wgpu::Buffer,
pub all_chunk_colors_base: wgpu::Buffer,
pub all_chunk_occupancy: wgpu::Buffer,
pub all_slot_chunk_idx: wgpu::Buffer,
pub grid_static_meta: wgpu::Buffer,
pub total_bytes: u64,
pub static_meta: Vec<GridStaticMeta>,
pub(crate) chunk_occupancy_shadow: Vec<Vec<u32>>,
pub(crate) slot_chunk_idx_shadow: Vec<Vec<[i32; 4]>>,
}
impl GpuSceneResident {
pub fn upload(device: &wgpu::Device, info: &SceneUpload) -> Self {
let grid_count = info.grid_count();
let mut all_occupancy: Vec<u32> = Vec::new();
let mut all_color_offsets: Vec<u32> = Vec::new();
let mut all_colors: Vec<u32> = Vec::new();
let mut all_chunk_colors_base: Vec<u32> = Vec::new();
let mut all_chunk_occupancy: Vec<u32> = Vec::new();
let mut all_slot_chunk_idx: Vec<i32> = Vec::new();
let mut static_meta: Vec<GridStaticMeta> = Vec::with_capacity(info.grids.len());
let mut chunk_occupancy_shadow: Vec<Vec<u32>> = Vec::with_capacity(info.grids.len());
let mut slot_chunk_idx_shadow: Vec<Vec<[i32; 4]>> = Vec::with_capacity(info.grids.len());
for grid in &info.grids {
let vsid = grid.vsid;
let layout = MipLayout::for_vsid(vsid);
let occ_words_per_slot = layout.occ_words_per_slot as usize;
let offsets_words_per_slot = layout.offsets_words_per_slot as usize;
let colors_stride = COLORS_PER_CHUNK_WORDS as usize;
assert!(
grid.pool_dims[0].is_power_of_two()
&& grid.pool_dims[1].is_power_of_two()
&& grid.pool_dims[2].is_power_of_two(),
"scene grid: pool_dims {:?} must all be powers of 2",
grid.pool_dims,
);
let pool_x = grid.pool_dims[0] as usize;
let pool_y = grid.pool_dims[1] as usize;
let pool_z = grid.pool_dims[2] as usize;
let total_slots = pool_x * pool_y * pool_z;
let mut grid_occupancy = vec![0u32; total_slots * occ_words_per_slot];
let mut grid_color_offsets = vec![0u32; total_slots * offsets_words_per_slot];
let mut grid_colors = vec![0u32; total_slots * colors_stride];
let mut grid_chunk_colors_base = vec![0u32; total_slots];
for i in 0..total_slots {
grid_chunk_colors_base[i] = (i * colors_stride) as u32;
}
let mut grid_chunk_occupancy = vec![0u32; total_slots.div_ceil(32)];
let mut grid_slot_chunk_idx: Vec<[i32; 4]> = Vec::with_capacity(total_slots);
for _ in 0..total_slots {
grid_slot_chunk_idx.push([
SLOT_EMPTY_SENTINEL[0],
SLOT_EMPTY_SENTINEL[1],
SLOT_EMPTY_SENTINEL[2],
0,
]);
}
let mask_x = (grid.pool_dims[0] - 1) as i32;
let mask_y = (grid.pool_dims[1] - 1) as i32;
let mask_z = (grid.pool_dims[2] - 1) as i32;
let chunks_per_layer = pool_x * pool_y;
for (chunk_idx, chunk) in &grid.chunks {
assert_eq!(chunk.vsid, vsid, "scene grid: chunk vsid mismatch");
let sx = (chunk_idx[0] & mask_x) as usize;
let sy = (chunk_idx[1] & mask_y) as usize;
let sz = (chunk_idx[2] & mask_z) as usize;
let slot_idx = sx + sy * pool_x + sz * chunks_per_layer;
let occ_start = slot_idx * occ_words_per_slot;
let off_start = slot_idx * offsets_words_per_slot;
let col_start = slot_idx * colors_stride;
let mut color_cursor = 0usize;
for (m, mip) in chunk.mips.iter().enumerate() {
let occ_dst = occ_start + layout.mip_occ_rel[m] as usize;
grid_occupancy[occ_dst..occ_dst + mip.occupancy.len()]
.copy_from_slice(&mip.occupancy);
let solid_dst = occ_dst + mip.occupancy.len();
grid_occupancy[solid_dst..solid_dst + mip.solid_occupancy.len()]
.copy_from_slice(&mip.solid_occupancy);
let coff_dst = off_start + layout.mip_coff_rel[m] as usize;
grid_color_offsets[coff_dst..coff_dst + mip.color_offsets.len()]
.copy_from_slice(&mip.color_offsets);
let remaining = colors_stride.saturating_sub(color_cursor);
let n = mip.colors.len().min(remaining);
if n < mip.colors.len() {
eprintln!(
"roxlap-gpu SceneUpload: scene grid chunk {chunk_idx:?} mip {m} \
colours overflow COLORS_PER_CHUNK_WORDS ({colors_stride}); \
truncating",
);
}
grid_colors[col_start + color_cursor..col_start + color_cursor + n]
.copy_from_slice(&mip.colors[..n]);
color_cursor += n;
}
if !chunk.mips[0].colors.is_empty() {
grid_chunk_occupancy[slot_idx >> 5] |= 1u32 << (slot_idx & 31);
}
grid_slot_chunk_idx[slot_idx] = [chunk_idx[0], chunk_idx[1], chunk_idx[2], 0];
}
let slot_chunk_idx_offset = u32::try_from(all_slot_chunk_idx.len()).expect("fits");
let (aabb_min, aabb_max) = aabb_of_slots(&grid_slot_chunk_idx);
let meta = GridStaticMeta {
occupancy_offset: u32::try_from(all_occupancy.len()).expect("fits"),
color_offsets_offset: u32::try_from(all_color_offsets.len()).expect("fits"),
colors_offset: u32::try_from(all_colors.len()).expect("fits"),
chunk_colors_base_offset: u32::try_from(all_chunk_colors_base.len()).expect("fits"),
chunk_occupancy_offset: u32::try_from(all_chunk_occupancy.len()).expect("fits"),
slot_chunk_idx_offset,
vsid,
total_slots: total_slots as u32,
pool_dims: grid.pool_dims,
_pad0: 0,
occ_words_per_slot: layout.occ_words_per_slot,
offsets_words_per_slot: layout.offsets_words_per_slot,
mip_count: layout.mip_count,
_pad1: 0,
mip_occ_rel: layout.mip_occ_rel,
mip_coff_rel: layout.mip_coff_rel,
aabb_min,
_pad2: 0,
aabb_max,
_pad3: 0,
};
chunk_occupancy_shadow.push(grid_chunk_occupancy.clone());
slot_chunk_idx_shadow.push(grid_slot_chunk_idx.clone());
all_occupancy.extend_from_slice(&grid_occupancy);
all_color_offsets.extend_from_slice(&grid_color_offsets);
all_colors.extend_from_slice(&grid_colors);
all_chunk_colors_base.extend_from_slice(&grid_chunk_colors_base);
all_chunk_occupancy.extend_from_slice(&grid_chunk_occupancy);
for entry in &grid_slot_chunk_idx {
all_slot_chunk_idx.extend_from_slice(entry);
}
static_meta.push(meta);
}
if all_occupancy.is_empty() {
all_occupancy.push(0);
}
if all_color_offsets.is_empty() {
all_color_offsets.push(0);
}
if all_colors.is_empty() {
all_colors.push(0);
}
if all_chunk_colors_base.is_empty() {
all_chunk_colors_base.push(0);
}
if all_chunk_occupancy.is_empty() {
all_chunk_occupancy.push(0);
}
if all_slot_chunk_idx.is_empty() {
all_slot_chunk_idx.extend_from_slice(&[0; 4]);
}
if static_meta.is_empty() {
static_meta.push(GridStaticMeta::zeroed());
}
let occupancy_bytes = (all_occupancy.len() * 4) as u64;
let color_offsets_bytes = (all_color_offsets.len() * 4) as u64;
let colors_bytes = (all_colors.len() * 4) as u64;
let chunk_colors_base_bytes = (all_chunk_colors_base.len() * 4) as u64;
let chunk_occupancy_bytes = (all_chunk_occupancy.len() * 4) as u64;
let slot_chunk_idx_bytes = (all_slot_chunk_idx.len() * 4) as u64;
let static_meta_bytes = (static_meta.len() * std::mem::size_of::<GridStaticMeta>()) as u64;
let total_bytes = occupancy_bytes
+ color_offsets_bytes
+ colors_bytes
+ chunk_colors_base_bytes
+ chunk_occupancy_bytes
+ slot_chunk_idx_bytes
+ static_meta_bytes;
let slot_align_words = info
.grids
.iter()
.map(|g| u64::from(MipLayout::for_vsid(g.vsid).occ_words_per_slot))
.max()
.unwrap_or(1)
.max(1);
let (occupancy_pages, occupancy_page_words, occupancy_num_pages) =
split_occupancy_pages(device, &all_occupancy, slot_align_words);
let all_color_offsets =
create_storage(device, "roxlap-gpu scene.color_offsets", &all_color_offsets);
let all_colors = create_storage(device, "roxlap-gpu scene.colors", &all_colors);
let all_chunk_colors_base = create_storage(
device,
"roxlap-gpu scene.chunk_colors_base",
&all_chunk_colors_base,
);
let all_chunk_occupancy = create_storage(
device,
"roxlap-gpu scene.chunk_occupancy",
&all_chunk_occupancy,
);
let all_slot_chunk_idx_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("roxlap-gpu scene.slot_chunk_idx"),
contents: bytemuck::cast_slice(&all_slot_chunk_idx),
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
});
let grid_static_meta = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("roxlap-gpu scene.grid_static_meta"),
contents: bytemuck::cast_slice(&static_meta),
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
});
Self {
grid_count,
occupancy_pages,
occupancy_page_words,
occupancy_num_pages,
all_color_offsets,
all_colors,
all_chunk_colors_base,
all_chunk_occupancy,
all_slot_chunk_idx: all_slot_chunk_idx_buf,
grid_static_meta,
total_bytes,
static_meta,
chunk_occupancy_shadow,
slot_chunk_idx_shadow,
}
}
pub fn resident_bytes(&self) -> u64 {
self.total_bytes
}
pub fn refresh_chunk(
&mut self,
queue: &wgpu::Queue,
scene_idx: usize,
chunk_idx: [i32; 3],
chunk: &ChunkUpload,
) -> RefreshOutcome {
let Some(meta) = self.static_meta.get(scene_idx).copied() else {
return RefreshOutcome::SceneIdxOob;
};
let slot_idx = modular_slot_idx(chunk_idx, meta.pool_dims);
let layout = MipLayout::for_vsid(meta.vsid);
let occ_words_per_slot = layout.occ_words_per_slot as usize;
let offsets_words_per_slot = layout.offsets_words_per_slot as usize;
let colors_stride = COLORS_PER_CHUNK_WORDS as usize;
assert_eq!(
chunk.mips.len() as u32,
layout.mip_count,
"refresh_chunk: mip count mismatch (chunk {} vs grid {})",
chunk.mips.len(),
layout.mip_count,
);
let slot_occ_base = meta.occupancy_offset as usize + slot_idx * occ_words_per_slot;
let page_words = self.occupancy_page_words as usize;
let page = slot_occ_base / page_words;
let slot_local_word = slot_occ_base % page_words;
debug_assert!(
slot_local_word + occ_words_per_slot <= page_words,
"occupancy slot straddles a page boundary — page size not slot-aligned",
);
let off_slot_base = meta.color_offsets_offset as usize + slot_idx * offsets_words_per_slot;
let col_slot_base = meta.colors_offset as usize + slot_idx * colors_stride;
let mut outcome = RefreshOutcome::Ok;
let mut color_cursor = 0usize;
for (m, mip) in chunk.mips.iter().enumerate() {
let local = slot_local_word + layout.mip_occ_rel[m] as usize;
queue.write_buffer(
&self.occupancy_pages[page],
(local * 4) as u64,
bytemuck::cast_slice(&mip.occupancy),
);
queue.write_buffer(
&self.occupancy_pages[page],
((local + mip.occupancy.len()) * 4) as u64,
bytemuck::cast_slice(&mip.solid_occupancy),
);
let coff = off_slot_base + layout.mip_coff_rel[m] as usize;
queue.write_buffer(
&self.all_color_offsets,
(coff * 4) as u64,
bytemuck::cast_slice(&mip.color_offsets),
);
let remaining = colors_stride.saturating_sub(color_cursor);
let n = mip.colors.len().min(remaining);
if n < mip.colors.len() {
eprintln!(
"roxlap-gpu refresh_chunk: scene_idx={scene_idx} chunk_idx={chunk_idx:?} \
mip {m} colours overflow stride {colors_stride}; truncating",
);
outcome = RefreshOutcome::ColorsTruncated;
}
if n > 0 {
queue.write_buffer(
&self.all_colors,
((col_slot_base + color_cursor) * 4) as u64,
bytemuck::cast_slice(&mip.colors[..n]),
);
}
color_cursor += n;
}
self.set_chunk_occupancy_bit(
queue,
scene_idx,
&meta,
slot_idx,
!chunk.mips[0].colors.is_empty(),
);
self.set_slot_chunk_idx(queue, scene_idx, &meta, slot_idx, chunk_idx);
self.sync_aabb(queue, scene_idx);
outcome
}
pub fn evict_chunk(
&mut self,
queue: &wgpu::Queue,
scene_idx: usize,
chunk_idx: [i32; 3],
) -> bool {
let Some(meta) = self.static_meta.get(scene_idx).copied() else {
return false;
};
let slot_idx = modular_slot_idx(chunk_idx, meta.pool_dims);
let shadow_entry = self.slot_chunk_idx_shadow[scene_idx][slot_idx];
if shadow_entry[0] != chunk_idx[0]
|| shadow_entry[1] != chunk_idx[1]
|| shadow_entry[2] != chunk_idx[2]
{
return true;
}
self.set_chunk_occupancy_bit(queue, scene_idx, &meta, slot_idx, false);
self.set_slot_chunk_idx(queue, scene_idx, &meta, slot_idx, SLOT_EMPTY_SENTINEL);
self.sync_aabb(queue, scene_idx);
true
}
fn set_chunk_occupancy_bit(
&mut self,
queue: &wgpu::Queue,
scene_idx: usize,
meta: &GridStaticMeta,
slot_idx: usize,
new_bit: bool,
) {
let word_idx = slot_idx >> 5;
let bit = slot_idx & 31;
let shadow = &mut self.chunk_occupancy_shadow[scene_idx][word_idx];
let was_bit = (*shadow >> bit) & 1 == 1;
if new_bit == was_bit {
return;
}
if new_bit {
*shadow |= 1u32 << bit;
} else {
*shadow &= !(1u32 << bit);
}
let global_word_idx = meta.chunk_occupancy_offset as usize + word_idx;
queue.write_buffer(
&self.all_chunk_occupancy,
(global_word_idx * 4) as u64,
bytemuck::bytes_of(shadow),
);
}
fn set_slot_chunk_idx(
&mut self,
queue: &wgpu::Queue,
scene_idx: usize,
meta: &GridStaticMeta,
slot_idx: usize,
chunk_idx: [i32; 3],
) {
let entry = [chunk_idx[0], chunk_idx[1], chunk_idx[2], 0];
self.slot_chunk_idx_shadow[scene_idx][slot_idx] = entry;
let global_word_idx = meta.slot_chunk_idx_offset as usize + slot_idx * 4;
queue.write_buffer(
&self.all_slot_chunk_idx,
(global_word_idx * 4) as u64,
bytemuck::cast_slice(&entry),
);
}
fn sync_aabb(&mut self, queue: &wgpu::Queue, scene_idx: usize) {
let (aabb_min, aabb_max) = aabb_of_slots(&self.slot_chunk_idx_shadow[scene_idx]);
let meta = &mut self.static_meta[scene_idx];
if meta.aabb_min == aabb_min && meta.aabb_max == aabb_max {
return;
}
meta.aabb_min = aabb_min;
meta.aabb_max = aabb_max;
let off = (scene_idx * std::mem::size_of::<GridStaticMeta>()) as u64;
queue.write_buffer(&self.grid_static_meta, off, bytemuck::bytes_of(meta));
}
}
fn aabb_of_slots(slots: &[[i32; 4]]) -> ([i32; 3], [i32; 3]) {
let mut min = [i32::MAX; 3];
let mut max = [i32::MIN; 3];
for e in slots {
if e[0] == SLOT_EMPTY_SENTINEL[0]
&& e[1] == SLOT_EMPTY_SENTINEL[1]
&& e[2] == SLOT_EMPTY_SENTINEL[2]
{
continue;
}
for k in 0..3 {
if e[k] < min[k] {
min[k] = e[k];
}
if e[k] > max[k] {
max[k] = e[k];
}
}
}
(min, max)
}
#[must_use]
pub fn modular_slot_idx(chunk_idx: [i32; 3], pool_dims: [u32; 3]) -> usize {
let mask_x = (pool_dims[0] - 1) as i32;
let mask_y = (pool_dims[1] - 1) as i32;
let mask_z = (pool_dims[2] - 1) as i32;
let sx = (chunk_idx[0] & mask_x) as usize;
let sy = (chunk_idx[1] & mask_y) as usize;
let sz = (chunk_idx[2] & mask_z) as usize;
sx + sy * (pool_dims[0] as usize) + sz * (pool_dims[0] as usize) * (pool_dims[1] as usize)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RefreshOutcome {
Ok,
ColorsTruncated,
ChunkOutOfBbox,
SceneIdxOob,
}
fn create_storage(device: &wgpu::Device, label: &str, data: &[u32]) -> wgpu::Buffer {
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(label),
contents: bytemuck::cast_slice(data),
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
})
}
fn split_occupancy_pages(
device: &wgpu::Device,
words: &[u32],
slot_align_words: u64,
) -> (Vec<wgpu::Buffer>, u32, u32) {
let total_words = words.len() as u64;
let limit_words = device.limits().max_storage_buffer_binding_size / 4;
let page_slots = (limit_words / slot_align_words).max(1);
let mut page_words = page_slots.saturating_mul(slot_align_words);
page_words = page_words.min(total_words.max(1));
let num_pages = total_words.div_ceil(page_words);
assert!(
num_pages as usize <= MAX_OCC_PAGES,
"occupancy needs {num_pages} pages (>{MAX_OCC_PAGES}) at this device's \
{limit_words}-word binding limit; shrink the streaming pool or raise MAX_OCC_PAGES",
);
let mut pages: Vec<wgpu::Buffer> = Vec::with_capacity(MAX_OCC_PAGES);
let page_words_usize = page_words as usize;
for p in 0..num_pages as usize {
let start = p * page_words_usize;
let end = ((p + 1) * page_words_usize).min(words.len());
pages.push(create_storage(
device,
&format!("roxlap-gpu scene.occupancy.page{p}"),
&words[start..end],
));
}
while pages.len() < MAX_OCC_PAGES {
pages.push(create_storage(
device,
"roxlap-gpu scene.occupancy.page_dummy",
&[0u32],
));
}
(
pages,
u32::try_from(page_words).expect("page_words fits u32"),
num_pages as u32,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn grid_static_meta_matches_wgsl_std430_size() {
assert_eq!(std::mem::size_of::<GridStaticMeta>(), 144);
assert_eq!(std::mem::align_of::<GridStaticMeta>(), 4);
}
#[test]
fn mip_layout_offsets_accumulate() {
let l = MipLayout::for_vsid(128);
assert_eq!(l.mip_count, 6);
assert_eq!(l.mip_occ_rel[0], 0);
assert_eq!(l.mip_coff_rel[0], 0);
let mut occ = 0u32;
let mut coff = 0u32;
for m in 0..6u32 {
assert_eq!(l.mip_occ_rel[m as usize], occ, "occ rel mip {m}");
assert_eq!(l.mip_coff_rel[m as usize], coff, "coff rel mip {m}");
let v = 128u32 >> m;
occ += 2 * v * v * occ_words_per_column_for_mip(m);
coff += v * v + 1;
}
assert_eq!(l.occ_words_per_slot, occ);
assert_eq!(l.offsets_words_per_slot, coff);
assert_eq!(l.mip_occ_rel[1], 2 * 128 * 128 * 8);
assert!(l.occ_words_per_slot < 2 * 128 * 128 * 8 * 5 / 4);
}
}