use core::fmt::Debug;
use embedded_graphics_core::{
Pixel,
draw_target::DrawTarget,
pixelcolor::Rgb565,
prelude::{OriginDimensions, Point},
};
use super::bounds::{clamp_bounds_to_frame, primitive_bounds};
use super::post::{apply_post, tint_primitive};
use super::sky::draw_sky_background;
use super::{DirtyRegion, FrameCtx};
#[cfg(feature = "textured")]
use crate::pipeline::assemble::primitive::DrawPrimitive;
use crate::{
error::RenderError,
pipeline::command_buffer::{CommandBuffer, RenderCommand},
pipeline::rasterize::draw::state::RasterState,
pipeline::rasterize::draw::zbuffered::draw_zbuffered_with_state,
};
pub fn execute_commands<D, const MAX: usize>(
fb: &mut D,
frame: &mut FrameCtx<'_>,
cmd: &CommandBuffer<MAX>,
state: &RasterState<'_>,
) -> Result<Option<DirtyRegion>, RenderError>
where
D: DrawTarget<Color = Rgb565> + OriginDimensions,
D::Error: Debug,
{
frame.validate()?;
let mut dirty_bounds: Option<(i32, i32, i32, i32)> = None;
if let Some(sky_cfg) = state.sky {
draw_sky_background(
fb,
frame.width,
frame.height,
sky_cfg,
state.camera_dir,
state.screen_tint,
state.palette_mode,
)?;
dirty_bounds = Some((
0,
0,
frame.width.saturating_sub(1) as i32,
frame.height.saturating_sub(1) as i32,
));
}
for c in cmd.iter() {
match c {
RenderCommand::ClearColor(color) => {
let w = frame.width as i32;
let h = frame.height as i32;
let clear_color = apply_post(*color, state.screen_tint, state.palette_mode);
for y in 0..h {
for x in 0..w {
fb.draw_iter([Pixel(Point::new(x, y), clear_color)])
.map_err(|_| {
RenderError::InvalidInput("draw target rejected clear write")
})?;
}
}
}
RenderCommand::ClearDepth(value) => {
crate::clear_zbuffer(frame.zbuffer, *value);
}
RenderCommand::Draw(primitive) => {
let prim = tint_primitive(primitive, state.screen_tint, state.palette_mode);
draw_zbuffered_with_state(prim, fb, frame.zbuffer, state);
let (min_x, min_y, max_x, max_y) = primitive_bounds(primitive);
if let Some((min_x, min_y, max_x, max_y)) =
clamp_bounds_to_frame(min_x, min_y, max_x, max_y, frame.width, frame.height)
{
dirty_bounds = Some(match dirty_bounds {
Some((cx0, cy0, cx1, cy1)) => (
cx0.min(min_x),
cy0.min(min_y),
cx1.max(max_x),
cy1.max(max_y),
),
None => (min_x, min_y, max_x, max_y),
});
}
}
}
}
let region = dirty_bounds.and_then(|(x0, y0, x1, y1)| DirtyRegion::from_bounds(x0, y0, x1, y1));
Ok(region)
}
#[cfg(feature = "textured")]
pub fn execute_commands_textured<D, const MAX: usize, const N: usize>(
fb: &mut D,
frame: &mut FrameCtx<'_>,
cmd: &CommandBuffer<MAX>,
texture_manager: &crate::pipeline::rasterize::texture::TextureManager<N>,
state: &RasterState<'_>,
) -> Result<Option<DirtyRegion>, RenderError>
where
D: DrawTarget<Color = Rgb565> + OriginDimensions,
D::Error: Debug,
{
use crate::pipeline::rasterize::draw::textured::draw_zbuffered_with_textures_state;
use crate::pipeline::rasterize::draw::textured::lightmap::draw_zbuffered_lightmapped_mapped;
frame.validate()?;
let mut dirty_bounds: Option<(i32, i32, i32, i32)> = None;
if let Some(sky_cfg) = state.sky {
draw_sky_background(
fb,
frame.width,
frame.height,
sky_cfg,
state.camera_dir,
state.screen_tint,
state.palette_mode,
)?;
dirty_bounds = Some((
0,
0,
frame.width.saturating_sub(1) as i32,
frame.height.saturating_sub(1) as i32,
));
}
for c in cmd.iter() {
match c {
RenderCommand::ClearColor(color) => {
let w = frame.width as i32;
let h = frame.height as i32;
let clear_color = apply_post(*color, state.screen_tint, state.palette_mode);
for y in 0..h {
for x in 0..w {
fb.draw_iter([Pixel(Point::new(x, y), clear_color)])
.map_err(|_| {
RenderError::InvalidInput("draw target rejected clear write")
})?;
}
}
}
RenderCommand::ClearDepth(value) => {
crate::clear_zbuffer(frame.zbuffer, *value);
}
RenderCommand::Draw(primitive) => {
match primitive {
#[cfg(feature = "textured")]
DrawPrimitive::LightmappedTriangle {
points,
depths,
ws,
surface_uvs,
lm_uvs,
texture_id,
lightmap_id,
brightness,
dynamic_tint,
} => {
draw_zbuffered_lightmapped_mapped(
*points,
*depths,
*ws,
*surface_uvs,
*lm_uvs,
*texture_id,
*lightmap_id,
*brightness,
*dynamic_tint,
texture_manager,
fb,
frame.zbuffer,
state,
);
}
#[cfg(feature = "textured")]
DrawPrimitive::TexturedTriangle { .. }
| DrawPrimitive::TexturedTriangleWithDepth { .. }
| DrawPrimitive::TexturedGouraudTriangleWithDepth { .. } => {
draw_zbuffered_with_textures_state(
primitive.clone(),
fb,
frame.zbuffer,
texture_manager,
state,
);
}
_ => {
let prim = tint_primitive(primitive, state.screen_tint, state.palette_mode);
draw_zbuffered_with_state(prim, fb, frame.zbuffer, state);
}
}
let (min_x, min_y, max_x, max_y) = primitive_bounds(primitive);
if let Some((min_x, min_y, max_x, max_y)) =
clamp_bounds_to_frame(min_x, min_y, max_x, max_y, frame.width, frame.height)
{
dirty_bounds = Some(match dirty_bounds {
Some((cx0, cy0, cx1, cy1)) => (
cx0.min(min_x),
cy0.min(min_y),
cx1.max(max_x),
cy1.max(max_y),
),
None => (min_x, min_y, max_x, max_y),
});
}
}
}
}
let region = dirty_bounds.and_then(|(x0, y0, x1, y1)| DirtyRegion::from_bounds(x0, y0, x1, y1));
Ok(region)
}
pub fn execute_commands_tiled<D, const MAX: usize, const BIN_CAP: usize>(
fb: &mut D,
frame: &mut FrameCtx<'_>,
cmd: &CommandBuffer<MAX>,
tile: crate::pipeline::rasterize::tilebin::TileConfig,
state: &RasterState<'_>,
) -> Result<crate::pipeline::rasterize::tilebin::TileBinStats, RenderError>
where
D: DrawTarget<Color = Rgb565> + OriginDimensions,
D::Error: Debug,
{
frame.validate()?;
if let Some(sky_cfg) = state.sky {
draw_sky_background(
fb,
frame.width,
frame.height,
sky_cfg,
state.camera_dir,
state.screen_tint,
state.palette_mode,
)?;
}
let (bins, stats) = crate::pipeline::rasterize::tilebin::build_bins::<MAX, BIN_CAP>(
cmd,
frame.width,
frame.height,
tile,
)?;
let mut executed_draw = [false; MAX];
for command in cmd.iter() {
match command {
RenderCommand::ClearColor(color) => {
let w = frame.width as i32;
let h = frame.height as i32;
let clear_color = apply_post(*color, state.screen_tint, state.palette_mode);
for y in 0..h {
for x in 0..w {
fb.draw_iter([Pixel(Point::new(x, y), clear_color)])
.map_err(|_| {
RenderError::InvalidInput("draw target rejected clear write")
})?;
}
}
}
RenderCommand::ClearDepth(value) => crate::clear_zbuffer(frame.zbuffer, *value),
RenderCommand::Draw(_) => {}
}
}
for bin in bins.iter() {
for idx in bin.iter().copied() {
if idx >= MAX || executed_draw[idx] {
continue;
}
let Some(RenderCommand::Draw(primitive)) = cmd.get(idx) else {
continue;
};
let prim = tint_primitive(primitive, state.screen_tint, state.palette_mode);
draw_zbuffered_with_state(prim, fb, frame.zbuffer, state);
executed_draw[idx] = true;
}
}
Ok(stats)
}
#[cfg(feature = "aa")]
pub fn execute_commands_2xssaa<D, const MAX: usize>(
fb: &mut D,
frame: &mut FrameCtx<'_>,
cmd_buf: &CommandBuffer<MAX>,
) -> Result<(), RenderError>
where
D: DrawTarget<Color = Rgb565> + crate::pipeline::rasterize::raster::aa::ReadPixel,
<D as DrawTarget>::Error: Debug,
{
frame.validate()?;
for c in cmd_buf.iter() {
if let RenderCommand::Draw(primitive) = c {
crate::pipeline::rasterize::draw::aa::draw_zbuffered_2xssaa(
primitive.clone(),
fb,
frame.zbuffer,
frame.width,
);
}
}
Ok(())
}