use super::*;
fn texture_write_u32(value: &JsValue) -> Option<u32> {
value
.as_f64()
.filter(|number: &f64| {
number.is_finite()
&& *number >= 0.0
&& *number <= f64::from(u32::MAX)
&& number.fract() == 0.0
})
.map(|number: f64| number as u32)
}
pub(crate) fn texture_write_extent(descriptor: &TextureWriteDescriptor) -> Option<[u32; 3]> {
let texture: JsValue = descriptor.get_texture();
let mip_level: u32 = descriptor.get_mip_level();
let mip_level_count: u32 = texture_write_u32(
&Reflect::get(
&texture,
&JsValue::from_str(WEBGPU_PROPERTY_MIP_LEVEL_COUNT),
)
.ok()?,
)?;
if mip_level >= mip_level_count {
return None;
}
let dimension: String = Reflect::get(&texture, &JsValue::from_str(WEBGPU_PROPERTY_DIMENSION))
.ok()?
.as_string()?;
if ![
WEBGPU_TEXTURE_DIMENSION_1D,
WEBGPU_TEXTURE_DIMENSION_2D,
WEBGPU_TEXTURE_DIMENSION_3D,
]
.contains(&dimension.as_str())
{
return None;
}
let origin: JsValue = descriptor.get_origin().unwrap_or(JsValue::UNDEFINED);
let mut extent: [u32; 3] = [0; 3];
for (axis, (size_key, origin_key)) in [
(WEBGPU_PROPERTY_WIDTH, WEBGPU_PROPERTY_X),
(WEBGPU_PROPERTY_HEIGHT, WEBGPU_PROPERTY_Y),
(WEBGPU_PROPERTY_DEPTH_OR_1, WEBGPU_PROPERTY_Z),
]
.iter()
.enumerate()
{
let size: u32 =
texture_write_u32(&Reflect::get(&texture, &JsValue::from_str(size_key)).ok()?)?;
if size == 0 {
return None;
}
let mip_size: u32 = if dimension == WEBGPU_TEXTURE_DIMENSION_1D && axis != 0 {
1
} else if axis == 2 && dimension != WEBGPU_TEXTURE_DIMENSION_3D {
size
} else {
size.checked_shr(mip_level).unwrap_or(0).max(1)
};
let coordinate: JsValue = if origin.is_undefined() || origin.is_null() {
JsValue::UNDEFINED
} else if Array::is_array(&origin) {
Reflect::get(&origin, &JsValue::from_f64(axis as f64)).ok()?
} else {
Reflect::get(&origin, &JsValue::from_str(origin_key)).ok()?
};
let offset: u32 = if coordinate.is_undefined() {
0
} else {
texture_write_u32(&coordinate)?
};
extent[axis] = mip_size.checked_sub(offset)?;
if extent[axis] == 0 {
return None;
}
}
Some(extent)
}
pub(crate) fn js_error_to_string(value: &JsValue) -> String {
if let Some(s) = value.as_string() {
s
} else if value.is_undefined() {
WEBGPU_JS_ERROR_UNDEFINED_LABEL.to_string()
} else if value.is_null() {
WEBGPU_JS_ERROR_NULL_LABEL.to_string()
} else {
format!("{:?}", value)
}
}
pub(crate) fn pick_depth_format(high_precision: bool, with_stencil: bool) -> &'static str {
if with_stencil {
WEBGPU_DEPTH_FORMAT_DEPTH24_PLUS_STENCIL8
} else if high_precision {
WEBGPU_DEPTH_FORMAT_DEPTH32_FLOAT
} else if cfg!(target_arch = "wasm32") {
WEBGPU_DEPTH_FORMAT_DEPTH24_PLUS
} else {
WEBGPU_DEPTH_FORMAT_DEPTH16_UNORM
}
}
pub(crate) fn map_mode_for(read: bool, write: bool) -> u32 {
let mut mode: u32 = 0;
if read {
mode |= WEBGPU_MAP_MODE_READ as u32;
}
if write {
mode |= WEBGPU_MAP_MODE_WRITE as u32;
}
mode
}
pub(crate) fn texture_usage(
render_target: bool,
copy_src: bool,
copy_dst: bool,
sampled: bool,
storage: bool,
) -> u32 {
let mut usage: u32 = 0;
if render_target {
usage |= WEBGPU_TEXTURE_USAGE_RENDER_ATTACHMENT as u32;
}
if copy_src {
usage |= WEBGPU_TEXTURE_USAGE_COPY_SRC as u32;
}
if copy_dst {
usage |= WEBGPU_TEXTURE_USAGE_COPY_DST as u32;
}
if sampled {
usage |= WEBGPU_TEXTURE_USAGE_TEXTURE_BINDING as u32;
}
if storage {
usage |= WEBGPU_TEXTURE_USAGE_STORAGE_BINDING as u32;
}
usage
}
pub(crate) fn cached_method_name(name: &'static str) -> JsValue {
thread_local! {
static CACHE: RefCell<Option<HashMap<&'static str, JsValue>>> =
const { RefCell::new(None) };
}
CACHE.with(|slot: &RefCell<Option<HashMap<&'static str, JsValue>>>| {
let cached: Option<JsValue> = {
let mut borrow: RefMut<'_, Option<HashMap<&'static str, JsValue>>> = slot.borrow_mut();
borrow.get_or_insert_with(HashMap::new).get(name).cloned()
};
if let Some(value) = cached {
return value;
}
let value: JsValue = JsValue::from_str(name);
let mut borrow: RefMut<'_, Option<HashMap<&'static str, JsValue>>> = slot.borrow_mut();
borrow
.get_or_insert_with(HashMap::new)
.insert(name, value.clone());
value
})
}
pub(crate) fn cached_method(
class: GpuReceiverClass,
obj: &JsValue,
method_name: &'static str,
) -> Result<Function, JsValue> {
thread_local! {
static FUNCTION_CACHE: RefCell<
Option<HashMap<(GpuReceiverClass, &'static str), Function>>,
> = const { RefCell::new(None) };
}
let key: (GpuReceiverClass, &'static str) = (class, method_name);
FUNCTION_CACHE.with(
|slot: &RefCell<Option<HashMap<(GpuReceiverClass, &'static str), Function>>>| {
let mut borrow: RefMut<
'_,
Option<HashMap<(GpuReceiverClass, &'static str), Function>>,
> = slot.borrow_mut();
let map: &mut HashMap<(GpuReceiverClass, &'static str), Function> =
borrow.get_or_insert_with(HashMap::new);
if let Some(func) = map.get(&key) {
return Ok(func.clone());
}
let value: Result<JsValue, JsValue> =
Reflect::get(obj, &cached_method_name(method_name));
let value: JsValue = match value {
Ok(v) => v,
Err(e) => return Err(e),
};
let func: Function = value.unchecked_into();
map.insert(key, func.clone());
Ok(func)
},
)
}
pub(crate) fn cached_method_call(
class: GpuReceiverClass,
obj: &JsValue,
method_name: &'static str,
arg: &JsValue,
) -> Result<JsValue, JsValue> {
let function: Function = cached_method(class, obj, method_name)?;
function.call1(obj, arg)
}