Compare commits

..

3 Commits

Author SHA1 Message Date
b88895e82f feat: separate dirty rendering with flag resource 2025-08-15 14:19:39 -05:00
2f0c734d13 feat: only present/render canvas when renderables change 2025-08-15 14:15:18 -05:00
e96b3159d7 fix: disable vsync 2025-08-15 13:46:57 -05:00
7 changed files with 43 additions and 25 deletions

View File

@@ -51,7 +51,6 @@ impl App {
window
.into_canvas()
.accelerated()
.present_vsync()
.build()
.map_err(|e| GameError::Sdl(e.to_string()))?,
));

View File

@@ -14,13 +14,13 @@ use crate::systems::{
collision::collision_system,
components::{
Collider, CollisionLayer, DeltaTime, DirectionalAnimated, EntityType, GlobalState, ItemBundle, ItemCollider,
PacmanCollider, PlayerBundle, PlayerControlled, Renderable, Score, ScoreResource,
PacmanCollider, PlayerBundle, PlayerControlled, RenderDirty, Renderable, Score, ScoreResource,
},
control::player_system,
input::input_system,
movement::movement_system,
profiling::{profile, SystemTimings},
render::{directional_render_system, render_system, BackbufferResource, MapTextureResource},
render::{directional_render_system, dirty_render_system, render_system, BackbufferResource, MapTextureResource},
};
use crate::texture::animated::AnimatedTexture;
use bevy_ecs::schedule::IntoScheduleConfigs;
@@ -100,7 +100,7 @@ impl Game {
// Render map to texture
canvas
.with_texture_canvas(&mut map_texture, |map_canvas| {
MapRenderer::render_map(map_canvas, &mut atlas, &mut map_tiles);
MapRenderer::render_map(map_canvas, &mut atlas, &map_tiles);
})
.map_err(|e| GameError::Sdl(e.to_string()))?;
@@ -175,6 +175,7 @@ impl Game {
world.insert_resource(SystemTimings::default());
world.insert_resource(Bindings::default());
world.insert_resource(DeltaTime(0f32));
world.insert_resource(RenderDirty::default());
world.add_observer(
|event: Trigger<GameEvent>, mut state: ResMut<GlobalState>, _score: ResMut<ScoreResource>| match *event {
@@ -196,10 +197,10 @@ impl Game {
profile("collision", collision_system),
profile("blinking", blinking_system),
profile("directional_render", directional_render_system),
profile("render", render_system),
)
.chain(),
);
schedule.add_systems((profile("dirty_render", dirty_render_system), profile("render", render_system)).chain());
// Spawn player
world.spawn(player);

View File

@@ -19,7 +19,7 @@ impl MapRenderer {
///
/// This function draws the static map texture to the screen at the correct
/// position and scale.
pub fn render_map<T: RenderTarget>(canvas: &mut Canvas<T>, atlas: &mut SpriteAtlas, map_tiles: &mut [AtlasTile]) {
pub fn render_map<T: RenderTarget>(canvas: &mut Canvas<T>, atlas: &mut SpriteAtlas, map_tiles: &[AtlasTile]) {
for (y, row) in TILE_MAP.iter().enumerate() {
for (x, &tile_index) in row.iter().enumerate() {
let mut tile = map_tiles[tile_index];

View File

@@ -109,3 +109,6 @@ pub struct ScoreResource(pub u32);
#[derive(Resource)]
pub struct DeltaTime(pub f32);
#[derive(Resource, Default)]
pub struct RenderDirty(pub bool);

View File

@@ -1,14 +1,25 @@
use crate::error::{GameError, TextureError};
use crate::map::builder::Map;
use crate::systems::components::{DeltaTime, DirectionalAnimated, Renderable};
use crate::systems::components::{DeltaTime, DirectionalAnimated, RenderDirty, Renderable};
use crate::systems::movement::{Movable, MovementState, Position};
use crate::texture::sprite::SpriteAtlas;
use bevy_ecs::entity::Entity;
use bevy_ecs::event::EventWriter;
use bevy_ecs::system::{NonSendMut, Query, Res};
use bevy_ecs::prelude::{Changed, Or, RemovedComponents};
use bevy_ecs::system::{NonSendMut, Query, Res, ResMut};
use sdl2::render::{Canvas, Texture};
use sdl2::video::Window;
pub fn dirty_render_system(
mut dirty: ResMut<RenderDirty>,
changed_renderables: Query<(), Or<(Changed<Renderable>, Changed<Position>)>>,
removed_renderables: RemovedComponents<Renderable>,
) {
if !changed_renderables.is_empty() || !removed_renderables.is_empty() {
dirty.0 = true;
}
}
/// Updates the directional animated texture of an entity.
///
/// This runs before the render system so it can update the sprite based on the current direction of travel, as well as whether the entity is moving.
@@ -31,7 +42,10 @@ pub fn directional_render_system(
if !stopped {
texture.tick(dt.0);
}
renderable.sprite = *texture.current_tile();
let new_tile = *texture.current_tile();
if renderable.sprite != new_tile {
renderable.sprite = new_tile;
}
} else {
errors.write(TextureError::RenderFailed(format!("Entity has no texture")).into());
continue;
@@ -51,9 +65,13 @@ pub fn render_system(
mut backbuffer: NonSendMut<BackbufferResource>,
mut atlas: NonSendMut<SpriteAtlas>,
map: Res<Map>,
renderables: Query<(Entity, &mut Renderable, &Position)>,
mut dirty: ResMut<RenderDirty>,
renderables: Query<(Entity, &Renderable, &Position)>,
mut errors: EventWriter<GameError>,
) {
if !dirty.0 {
return;
}
// Clear the main canvas first
canvas.set_draw_color(sdl2::pixels::Color::BLACK);
canvas.clear();
@@ -66,17 +84,12 @@ pub fn render_system(
backbuffer_canvas.clear();
// Copy the pre-rendered map texture to the backbuffer
backbuffer_canvas
.copy(&map_texture.0, None, None)
.err()
.map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
if let Err(e) = backbuffer_canvas.copy(&map_texture.0, None, None) {
errors.write(TextureError::RenderFailed(e.to_string()).into());
}
// Render all entities to the backbuffer
for (_, mut renderable, position) in renderables
// .iter_mut()
// .sort_by_key::<&mut Renderable, _, _>(|(renderable, renderable, renderable)| renderable.layer)
// .collect()
{
for (_, renderable, position) in renderables.iter() {
let pos = position.get_pixel_pos(&map.graph);
match pos {
Ok(pos) => {
@@ -107,4 +120,6 @@ pub fn render_system(
.map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
canvas.present();
dirty.0 = false;
}

View File

@@ -21,7 +21,7 @@ pub struct MapperFrame {
pub height: u16,
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct AtlasTile {
pub pos: U16Vec2,
pub size: U16Vec2,
@@ -30,7 +30,7 @@ pub struct AtlasTile {
impl AtlasTile {
pub fn render<C: RenderTarget>(
&mut self,
&self,
canvas: &mut Canvas<C>,
atlas: &mut SpriteAtlas,
dest: Rect,
@@ -41,7 +41,7 @@ impl AtlasTile {
}
pub fn render_with_color<C: RenderTarget>(
&mut self,
&self,
canvas: &mut Canvas<C>,
atlas: &mut SpriteAtlas,
dest: Rect,

View File

@@ -103,9 +103,9 @@ impl TextTexture {
&self.char_map
}
pub fn get_tile(&mut self, c: char, atlas: &mut SpriteAtlas) -> Result<Option<&mut AtlasTile>> {
pub fn get_tile(&mut self, c: char, atlas: &mut SpriteAtlas) -> Result<Option<&AtlasTile>> {
if self.char_map.contains_key(&c) {
return Ok(self.char_map.get_mut(&c));
return Ok(self.char_map.get(&c));
}
if let Some(tile_name) = char_to_tile_name(c) {
@@ -113,7 +113,7 @@ impl TextTexture {
.get_tile(&tile_name)
.ok_or(GameError::Texture(TextureError::AtlasTileNotFound(tile_name)))?;
self.char_map.insert(c, tile);
Ok(self.char_map.get_mut(&c))
Ok(self.char_map.get(&c))
} else {
Ok(None)
}