grafix-toolbox 0.8.33

Personal collection of opengl and rust tools, also serving as an functional gui crate. See ./gui/elements for premade gui elements
Documentation
use super::*;

pub trait DrawablePrimitive<'r> {
	fn draw(self, _: u32, _: &Geom, _: &mut Renderer);
}
macro_rules! DRAWABLE {
	($t: ty, $draw_spec: ident) => {
		impl<'r> DrawablePrimitive<'r> for $t {
			fn draw(self, obj_n: u32, crop: &Geom, r: &mut Renderer) {
				use PrimImpl::$draw_spec as object;
				if obj_n < u32(r.cache.objs.len()) {
					let PrimCache { state, o } = r.cache.objs.at_mut(obj_n);
					if let object(l) = o {
						*state = self.compare(crop, l) | r.status;
						if !state.contains(State::MISMATCH) {
							if !state.is_empty() {
								r.flush |= *state;
								*o = self.obj(*crop).pipe(object);
							}
							return;
						}
					}

					r.cache.shrink(obj_n);
				}

				r.flush |= State::FULL;
				let p = PrimCache { state: State::MISMATCH, o: object(self.obj(*crop)) };
				r.cache.objs.push(p);
			}
		}
	};
}

#[derive(Default)]
pub struct RenderCache {
	pub batches: Vec<Batch>,
	pub objs: Vec<PrimCache>,
	pub first_transparent: usize,
}
impl RenderCache {
	pub fn b_box(&self, at: u32) -> Geom {
		self.objs.at(at).o.obj().base().bound_box()
	}
	pub fn shrink(&mut self, to: u32) {
		let Self { batches, objs, .. } = self;
		batches.retain_mut(|b| !b.shrink_and_empty(objs, to));

		objs.truncate(usize(to));
	}
	fn batch(&mut self) {
		let Self { batches, objs, first_transparent } = self;

		let overlaps = |o, z| {
			batches.iter().position(|b| b.contains(objs, (o, z))).is_some_and(|b| {
				let covered = || batches[..b].iter().any(|b| b.covered(objs, (o, z)));
				let covers = || batches[b + 1..].iter().any(|b| b.covers(objs, (o, z)));
				covered() || covers()
			})
		};

		let Some(first_invalid) = objs
			.iter()
			.enumerate()
			.find(|(n, PrimCache { state, o })| {
				let overlapping = || state.contains(State::XYZW) && o.obj().ordered() && overlaps(o, u32(n));
				state.contains(State::MISMATCH) || overlapping()
			})
			.map(|(n, _)| n)
		else {
			return;
		};

		batches.retain_mut(|b| !b.shrink_and_empty(objs, u32(first_invalid)));

		objs.iter().enumerate().skip(first_invalid).for_each(|(z, o)| {
			let (z, o) = (u32(z), &o.o);
			for b in batches.iter_mut().rev() {
				if b.try_add(objs, (o, z)) {
					return;
				}

				if b.interferes(objs, o) {
					break;
				}
			}

			if o.obj().ordered() {
				batches.push(Batch::new(z));
			} else {
				batches.insert(0, Batch::new(z));
			}
		});

		*first_transparent = batches.iter().position(|b| b.typ(objs).obj().ordered()).unwrap_or(batches.len());
	}
	pub fn flush(
		&mut self, aspect: Vec2, idxs: &mut Vec<u16>, xyzw: &mut Vec<f16>, rgba: &mut Vec<u8>, uv: &mut Vec<f16>,
	) -> (Option<usize>, Option<usize>, Option<usize>, Option<usize>) {
		self.batch();

		let Self { batches, objs, .. } = self;
		let MAXIDX = const { u16::MAX as usize };

		batches
			.iter_mut()
			.fold(((None, None, None, None), State::empty(), 0, 0), |(mut flush, mut state, idx_start, batch_start), b| {
				let (batch_size, s) = b.redraw(aspect, objs);
				state |= s;
				DEBUG!("Flushing state {state:?}");

				if state.contains(State::BATCH_RESIZED) {
					flush.0.get_or_insert(idx_start);
					let mut indices = b.typ(objs).obj().gen_idxs(vec2((batch_start, batch_size)));
					if idx_start + indices.len() > MAXIDX {
						WARN!("GUI batch too saturated with polygons, dropping some");
						let mut i = indices.into_vec();
						i.truncate(MAXIDX - idx_start);
						indices = i.into();
					}
					b.idx_range = vec2((idx_start, indices.len()));
					idxs.truncate(usize(idx_start));
					idxs.extend_from_slice(&indices);
				}

				fn update<T: Copy>(changed: bool, ordered: bool, dim: usize, v: &mut Vec<T>, at: usize, upd: &[T], flush: &mut Option<usize>) {
					if changed {
						flush.get_or_insert(at * dim);
						v.truncate(at * dim);
						if ordered {
							v.extend_from_slice(upd);
						} else {
							v.extend(upd.chunks(dim).rev().flatten());
						}
					}
				}

				let ordered = b.typ(objs).obj().ordered();
				update(state.contains(State::XYZW), ordered, 4, xyzw, batch_start, &b.xyzw, &mut flush.1);
				update(state.contains(State::RGBA), ordered, 4, rgba, batch_start, &b.rgba, &mut flush.2);
				update(state.contains(State::UV), ordered, 2, uv, batch_start, &b.uv, &mut flush.3);

				(flush, state, ulVec2(b.idx_range).fold(|l, r| l + r).min(MAXIDX), batch_start + usize(batch_size))
			})
			.pipe(|(flush, ..)| flush)
	}
	pub fn draw_opaque_batches(&self, v: &VaoBind<u16>) {
		let Self { batches, objs, first_transparent } = self;
		batches.iter().take(*first_transparent).for_each(|b| b.typ(objs).obj().batch_draw(v, b.idx_range));
	}
	pub fn draw_translucent_batches(&self, v: &VaoBind<u16>) {
		let Self { batches, objs, first_transparent } = self;
		batches.iter().skip(*first_transparent).for_each(|b| b.typ(objs).obj().batch_draw(v, b.idx_range));
	}
}

pub struct PrimCache {
	pub state: State,
	pub o: PrimImpl,
}
pub enum PrimImpl {
	Rect(RectImpl),
	ImgRGB(SpriteImpl<RGB>),
	ImgRGBA(SpriteImpl<RGBA>),
	Img9RGB(Sprite9Impl<RGB>),
	Img9RGBA(Sprite9Impl<RGBA>),
	Frame9(Frame9Impl),
	Text(TextImpl),
}
impl PrimImpl {
	pub fn obj(&self) -> &dyn Primitive {
		match self {
			Rect(r) => r,
			ImgRGB(r) => r,
			ImgRGBA(r) => r,
			Img9RGB(r) => r,
			Img9RGBA(r) => r,
			Frame9(r) => r,
			Text(r) => r,
		}
	}
	pub fn batchable(&self, r: &Self) -> bool {
		match (self, r) {
			(Rect(l), Rect(r)) => l.batchable(r),
			(ImgRGB(l), ImgRGB(r)) => l.batchable(r),
			(Img9RGB(l), Img9RGB(r)) => l.batchable(r),
			(ImgRGBA(l), ImgRGBA(r)) => l.batchable(r),
			(Img9RGBA(l), Img9RGBA(r)) => l.batchable(r),
			(Frame9(l), Frame9(r)) => l.batchable(r),
			(Text(l), Text(r)) => l.batchable(r),
			_ => false,
		}
	}
}
use PrimImpl::*;