use super::Material;
use crate::{Ctx, Texture, Vec3};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C)] pub struct Compute {
pub(crate) id: u32,
}
impl Compute {
#[doc(hidden)]
#[must_use]
pub fn id(self) -> u32 {
self.id
}
#[doc(hidden)]
#[must_use]
pub fn from_id(id: u32) -> Self {
Self { id }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ComputePass {
pub compute: Compute,
pub target: Texture,
pub buffer: Option<GpuBuffer>,
pub material: Option<Material>,
}
impl ComputePass {
pub fn for_buffer(compute: Compute, buffer: GpuBuffer) -> Self {
Self {
compute,
target: Texture::new(0, 0, 0),
buffer: Some(buffer),
material: None,
}
}
#[must_use]
pub fn new(compute: Compute, target: Texture) -> Self {
Self {
compute,
target,
buffer: None,
material: None,
}
}
#[must_use]
pub fn with_material(self, material: Material) -> Self {
Self {
material: Some(material),
..self
}
}
}
impl Ctx {
pub fn load_compute(&mut self, path: &str) -> Compute {
self.try_load_compute(path).unwrap_or_else(|e| panic!("ponko: {e}"))
}
#[doc(hidden)]
pub fn try_load_compute(&mut self, path: &str) -> Result<Compute, String> {
let file = crate::assets::resolve(path)?;
if let Some(&compute) = self.assets3d.loaded_computes.get(&file) {
return Ok(compute);
}
let source = crate::assets::read_to_string(&file)?;
let compute = self.try_create_compute(path, &source)?;
self.assets3d.loaded_computes.insert(file.clone(), compute);
self.watcher
.watch(path, file, crate::hot_reload::Asset::Compute(compute.id));
Ok(compute)
}
pub fn create_compute(&mut self, name: &str, source: &str) -> Compute {
self.try_create_compute(name, source)
.unwrap_or_else(|e| panic!("ponko: {e}"))
}
#[doc(hidden)]
pub fn try_create_compute(&mut self, name: &str, source: &str) -> Result<Compute, String> {
#[cfg(feature = "window")]
crate::render::check_shader(name, &crate::render::compose_compute(source))?;
let id = self.assets3d.computes;
if let Some(renderer) = self.renderer.as_mut()
&& !self.headless
{
let gpu_id = renderer.create_compute(name, source)?;
debug_assert_eq!(gpu_id, id);
}
self.assets3d.computes += 1;
Ok(Compute { id })
}
pub(crate) fn reload_compute(&mut self, id: u32, name: &str, source: &str) -> Result<(), String> {
#[cfg(feature = "window")]
crate::render::check_shader(name, &crate::render::compose_compute(source))?;
if let Some(renderer) = self.renderer.as_mut()
&& !self.headless
{
renderer.replace_compute(id, name, source)?;
}
Ok(())
}
#[doc(hidden)]
#[must_use]
pub fn has_compute(&self, compute: Compute) -> bool {
compute.id < self.assets3d.computes
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C)]
pub struct GpuBuffer {
pub(crate) id: u32,
}
impl GpuBuffer {
#[doc(hidden)]
pub fn id(self) -> u32 {
self.id
}
#[doc(hidden)]
pub fn from_id(id: u32) -> Self {
Self { id }
}
}
impl Ctx {
pub fn create_gpu_buffer(&mut self, len: u32, data: &[crate::Vec4]) -> GpuBuffer {
assert!(
len > 0 && (data.is_empty() || data.len() == len as usize),
"invalid GPU buffer length"
);
let id = match self.assets3d.free_gpu_buffers.pop() {
Some(id) => {
self.assets3d.gpu_buffers[id as usize] = len;
id
}
None => {
self.assets3d.gpu_buffers.push(len);
self.assets3d.gpu_buffers.len() as u32 - 1
}
};
if let Some(r) = self.renderer.as_mut()
&& !self.headless
{
r.create_gpu_buffer(id, len, data);
}
GpuBuffer { id }
}
pub fn gpu_buffer_len(&self, b: GpuBuffer) -> Option<u32> {
self.assets3d
.gpu_buffers
.get(b.id as usize)
.copied()
.filter(|&len| len > 0)
}
pub fn free_gpu_buffer(&mut self, b: GpuBuffer) {
let Some(len) = self.assets3d.gpu_buffers.get_mut(b.id as usize) else {
return;
};
if *len == 0 {
return;
}
*len = 0;
self.assets3d.free_gpu_buffers.push(b.id);
self.compute_passes.retain(|p| p.buffer != Some(b));
if let Some(r) = self.renderer.as_mut()
&& !self.headless
{
r.free_gpu_buffer(b.id);
}
}
pub fn write_gpu_buffer(&mut self, b: GpuBuffer, offset: u32, data: &[crate::Vec4]) {
let len = self.gpu_buffer_len(b).expect("invalid GPU buffer");
assert!(
offset <= len && data.len() <= (len - offset) as usize,
"GPU buffer write out of bounds"
);
if !data.is_empty()
&& let Some(r) = self.renderer.as_mut()
&& !self.headless
{
r.write_gpu_buffer(b.id, offset, data);
}
}
pub fn set_mesh_gpu_buffer(&mut self, mesh: super::Mesh, b: GpuBuffer, count: u32) {
assert!(self.is_mesh_live(mesh), "invalid mesh");
assert!(
count <= self.gpu_buffer_len(b).expect("invalid GPU buffer"),
"GPU instance count out of bounds"
);
if let Some(r) = self.renderer.as_mut()
&& !self.headless
{
r.set_mesh_gpu_buffer(mesh.id, b.id, count);
}
}
pub fn set_mesh_gpu_bounds(&mut self, mesh: super::Mesh, center: Vec3, radius: f32) {
assert!(
radius >= 0.0 && radius.is_finite() && center.is_finite(),
"invalid bounds"
);
assert!(self.is_mesh_live(mesh), "invalid mesh");
let own = *self
.assets3d
.gpu_mesh_bounds
.entry(mesh.id)
.or_insert_with(|| self.assets3d.bounds[mesh.id as usize]);
self.assets3d.bounds[mesh.id as usize] = union_spheres(own, (center, radius));
}
}
fn union_spheres(a: (Vec3, f32), b: (Vec3, f32)) -> (Vec3, f32) {
let d = b.0 - a.0;
let distance = d.length();
if distance + b.1 <= a.1 {
return a;
}
if distance + a.1 <= b.1 {
return b;
}
let radius = (distance + a.1 + b.1) * 0.5;
(a.0 + d * ((radius - a.1) / distance), radius)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vec3;
#[test]
fn gpu_bounds_grow_the_culling_sphere_of_a_buffer_mesh() {
crate::Game::new("bounds", 1, 1).run_headless(0, |ctx| {
let material = ctx.default_material();
let mesh = ctx.create_mesh(&crate::MeshData::cube(0.5), material);
let own = ctx.assets3d.bounds[mesh.id as usize];
ctx.set_mesh_gpu_bounds(mesh, Vec3::Y * 10.0, 2.0);
let grown = ctx.assets3d.bounds[mesh.id as usize];
assert!(grown.1 > own.1 + 5.0);
ctx.set_mesh_gpu_bounds(mesh, Vec3::Y * 10.0, 2.0);
assert_eq!(ctx.assets3d.bounds[mesh.id as usize], grown);
ctx.free_mesh(mesh);
let again = ctx.create_mesh(&crate::MeshData::cube(0.5), material);
assert_eq!((again, ctx.assets3d.bounds[again.id as usize]), (mesh, own));
});
}
#[test]
fn union_holds_both_spheres() {
let (a, b) = ((vec3(0.0, 0.0, 0.0), 1.0), (vec3(10.0, 0.0, 0.0), 2.0));
let (c, r) = union_spheres(a, b);
assert!(c.distance(a.0) + a.1 <= r + 1e-4 && c.distance(b.0) + b.1 <= r + 1e-4);
assert!((r - 6.5).abs() < 1e-4);
assert_eq!(union_spheres(a, (vec3(0.5, 0.0, 0.0), 0.1)), a);
assert_eq!(union_spheres((vec3(0.5, 0.0, 0.0), 0.1), a), a);
}
}