#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RenderLayer {
Background,
Foreground,
Lighting,
Depth,
Final,
}
impl RenderLayer {
#[must_use]
pub fn is_foreground(&self) -> bool {
matches!(self, RenderLayer::Foreground | RenderLayer::Final)
}
#[must_use]
pub fn is_support_layer(&self) -> bool {
matches!(self, RenderLayer::Lighting | RenderLayer::Depth)
}
}
impl std::fmt::Display for RenderLayer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RenderLayer::Background => write!(f, "Background"),
RenderLayer::Foreground => write!(f, "Foreground"),
RenderLayer::Lighting => write!(f, "Lighting"),
RenderLayer::Depth => write!(f, "Depth"),
RenderLayer::Final => write!(f, "Final"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RenderOutputStatus {
Initializing,
Active,
Suspended,
Shutdown,
}
impl std::fmt::Display for RenderOutputStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RenderOutputStatus::Initializing => write!(f, "Initializing"),
RenderOutputStatus::Active => write!(f, "Active"),
RenderOutputStatus::Suspended => write!(f, "Suspended"),
RenderOutputStatus::Shutdown => write!(f, "Shutdown"),
}
}
}
#[derive(Debug, Clone)]
pub struct RenderOutputConfig {
pub width: u32,
pub height: u32,
pub fps: f32,
pub hdr_enabled: bool,
pub bit_depth: u8,
}
impl Default for RenderOutputConfig {
fn default() -> Self {
Self {
width: 1920,
height: 1080,
fps: 60.0,
hdr_enabled: false,
bit_depth: 8,
}
}
}
impl RenderOutputConfig {
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn total_pixels(&self) -> u64 {
u64::from(self.width) * u64::from(self.height)
}
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn data_rate_bps(&self) -> f64 {
let pixels_per_frame = self.total_pixels();
let bytes_per_pixel = (u64::from(self.bit_depth) * 3).div_ceil(8); pixels_per_frame as f64 * bytes_per_pixel as f64 * f64::from(self.fps)
}
}
#[derive(Debug, Clone)]
pub struct RenderOutput {
pub name: String,
pub layer: RenderLayer,
pub config: RenderOutputConfig,
pub status: RenderOutputStatus,
pub frames_rendered: u64,
}
impl RenderOutput {
pub fn new(name: impl Into<String>, layer: RenderLayer, config: RenderOutputConfig) -> Self {
Self {
name: name.into(),
layer,
config,
status: RenderOutputStatus::Initializing,
frames_rendered: 0,
}
}
pub fn activate(&mut self) {
if self.status == RenderOutputStatus::Initializing
|| self.status == RenderOutputStatus::Suspended
{
self.status = RenderOutputStatus::Active;
}
}
pub fn suspend(&mut self) {
if self.status == RenderOutputStatus::Active {
self.status = RenderOutputStatus::Suspended;
}
}
pub fn shutdown(&mut self) {
self.status = RenderOutputStatus::Shutdown;
}
pub fn render_frame(&mut self) -> bool {
if self.status == RenderOutputStatus::Active {
self.frames_rendered += 1;
return true;
}
false
}
#[must_use]
pub fn is_active(&self) -> bool {
self.status == RenderOutputStatus::Active
}
}
#[derive(Debug, Default)]
pub struct RenderOutputManager {
outputs: Vec<RenderOutput>,
}
impl RenderOutputManager {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_output(&mut self, output: RenderOutput) {
self.outputs.push(output);
}
#[must_use]
pub fn outputs(&self) -> &[RenderOutput] {
&self.outputs
}
#[must_use]
pub fn find_by_layer(&self, layer: RenderLayer) -> Option<&RenderOutput> {
self.outputs.iter().find(|o| o.layer == layer)
}
#[must_use]
pub fn find_by_name(&self, name: &str) -> Option<&RenderOutput> {
self.outputs.iter().find(|o| o.name == name)
}
pub fn find_by_name_mut(&mut self, name: &str) -> Option<&mut RenderOutput> {
self.outputs.iter_mut().find(|o| o.name == name)
}
pub fn activate_all(&mut self) {
for output in &mut self.outputs {
output.activate();
}
}
#[must_use]
pub fn active_count(&self) -> usize {
self.outputs.iter().filter(|o| o.is_active()).count()
}
#[must_use]
pub fn total_frames_rendered(&self) -> u64 {
self.outputs.iter().map(|o| o.frames_rendered).sum()
}
pub fn remove_shutdown(&mut self) {
self.outputs
.retain(|o| o.status != RenderOutputStatus::Shutdown);
}
#[must_use]
pub fn output_count(&self) -> usize {
self.outputs.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_output(name: &str, layer: RenderLayer) -> RenderOutput {
RenderOutput::new(name, layer, RenderOutputConfig::default())
}
#[test]
fn test_render_layer_is_foreground() {
assert!(RenderLayer::Foreground.is_foreground());
assert!(RenderLayer::Final.is_foreground());
assert!(!RenderLayer::Background.is_foreground());
assert!(!RenderLayer::Lighting.is_foreground());
}
#[test]
fn test_render_layer_is_support() {
assert!(RenderLayer::Lighting.is_support_layer());
assert!(RenderLayer::Depth.is_support_layer());
assert!(!RenderLayer::Final.is_support_layer());
}
#[test]
fn test_render_layer_display() {
assert_eq!(RenderLayer::Background.to_string(), "Background");
assert_eq!(RenderLayer::Final.to_string(), "Final");
assert_eq!(RenderLayer::Depth.to_string(), "Depth");
}
#[test]
fn test_render_output_status_display() {
assert_eq!(RenderOutputStatus::Active.to_string(), "Active");
assert_eq!(RenderOutputStatus::Suspended.to_string(), "Suspended");
assert_eq!(RenderOutputStatus::Shutdown.to_string(), "Shutdown");
}
#[test]
fn test_output_config_total_pixels() {
let cfg = RenderOutputConfig {
width: 1920,
height: 1080,
..Default::default()
};
assert_eq!(cfg.total_pixels(), 1920 * 1080);
}
#[test]
fn test_output_config_data_rate() {
let cfg = RenderOutputConfig::default(); let rate = cfg.data_rate_bps();
assert!(rate > 0.0);
}
#[test]
fn test_render_output_activate() {
let mut o = make_output("bg", RenderLayer::Background);
assert_eq!(o.status, RenderOutputStatus::Initializing);
o.activate();
assert_eq!(o.status, RenderOutputStatus::Active);
assert!(o.is_active());
}
#[test]
fn test_render_output_suspend_resume() {
let mut o = make_output("fg", RenderLayer::Foreground);
o.activate();
o.suspend();
assert_eq!(o.status, RenderOutputStatus::Suspended);
o.activate(); assert_eq!(o.status, RenderOutputStatus::Active);
}
#[test]
fn test_render_output_shutdown() {
let mut o = make_output("final", RenderLayer::Final);
o.activate();
o.shutdown();
assert_eq!(o.status, RenderOutputStatus::Shutdown);
assert!(!o.is_active());
}
#[test]
fn test_render_frame_increments_only_when_active() {
let mut o = make_output("depth", RenderLayer::Depth);
assert!(!o.render_frame()); assert_eq!(o.frames_rendered, 0);
o.activate();
assert!(o.render_frame());
assert!(o.render_frame());
assert_eq!(o.frames_rendered, 2);
}
#[test]
fn test_manager_activate_all() {
let mut mgr = RenderOutputManager::new();
mgr.add_output(make_output("bg", RenderLayer::Background));
mgr.add_output(make_output("fg", RenderLayer::Foreground));
mgr.activate_all();
assert_eq!(mgr.active_count(), 2);
}
#[test]
fn test_manager_find_by_layer() {
let mut mgr = RenderOutputManager::new();
mgr.add_output(make_output("lighting", RenderLayer::Lighting));
assert!(mgr.find_by_layer(RenderLayer::Lighting).is_some());
assert!(mgr.find_by_layer(RenderLayer::Final).is_none());
}
#[test]
fn test_manager_find_by_name_mut() {
let mut mgr = RenderOutputManager::new();
mgr.add_output(make_output("myout", RenderLayer::Final));
let out = mgr
.find_by_name_mut("myout")
.expect("should succeed in test");
out.activate();
assert!(mgr
.find_by_name("myout")
.expect("should succeed in test")
.is_active());
}
#[test]
fn test_manager_total_frames_rendered() {
let mut mgr = RenderOutputManager::new();
mgr.add_output(make_output("a", RenderLayer::Background));
mgr.add_output(make_output("b", RenderLayer::Foreground));
mgr.activate_all();
{
let a = mgr.find_by_name_mut("a").expect("should succeed in test");
a.render_frame();
a.render_frame();
}
{
let b = mgr.find_by_name_mut("b").expect("should succeed in test");
b.render_frame();
}
assert_eq!(mgr.total_frames_rendered(), 3);
}
#[test]
fn test_manager_remove_shutdown() {
let mut mgr = RenderOutputManager::new();
mgr.add_output(make_output("keep", RenderLayer::Background));
mgr.add_output(make_output("drop", RenderLayer::Depth));
{
let drop = mgr
.find_by_name_mut("drop")
.expect("should succeed in test");
drop.shutdown();
}
mgr.remove_shutdown();
assert_eq!(mgr.output_count(), 1);
assert!(mgr.find_by_name("keep").is_some());
}
#[test]
fn test_manager_output_count() {
let mut mgr = RenderOutputManager::new();
assert_eq!(mgr.output_count(), 0);
mgr.add_output(make_output("x", RenderLayer::Final));
assert_eq!(mgr.output_count(), 1);
}
}