Skip to content

Commit

Permalink
initial pixels integration
Browse files Browse the repository at this point in the history
  • Loading branch information
beetrootpaul committed Mar 10, 2023
1 parent 33f42af commit 1cf2a96
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .idea/dictionaries/nkoder.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
pixels = "0.11.0" # https://github.com/parasyte/pixels

# https://github.com/bevyengine/bevy
[dependencies.bevy]
Expand All @@ -19,4 +20,4 @@ version = "0.10.0"
default-features = false
features = [
"bevy_winit"
]
]
37 changes: 36 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use bevy::prelude::*;
use bevy::window::{close_on_esc, WindowResolution};

use crate::pixel_art::{PixelCanvas, PixelCanvasPlugin, PixelCanvasSystemSet};

mod pixel_art;

fn main() {
let mut app = App::new();

Expand All @@ -14,13 +18,19 @@ fn main() {
// TODO: extract game title as constant
title: "Bevy/pixels web game PoC".to_string(),
// TODO: extract window size as constants
resolution: WindowResolution::new(512., 512.).with_scale_factor_override(1.),
resolution: WindowResolution::new(512., 512.),
// TODO: any other props to set?
..default()
}),
..default()
}));

// TODO: extract canvas size as constants
app.add_plugin(PixelCanvasPlugin {
canvas_width: 16,
canvas_height: 16,
});

// TODO: ImagePlugin::default_nearest()
// comment: Prevent blurring of scaled up pixel art sprites

Expand All @@ -30,9 +40,34 @@ fn main() {
#[cfg(debug_assertions)]
app.add_system(close_on_esc);

// TODO: TMP
app.add_systems(
(draw_background, draw_pixel)
.chain()
.in_base_set(PixelCanvasSystemSet::Render),
);

app.run();
}

// TODO: TMP
fn draw_background(mut pixels_resource: ResMut<PixelCanvas>) {
// TODO: encapsulate frame access
let frame = pixels_resource.pixels.get_frame_mut();
frame.copy_from_slice(&[0x48, 0xb2, 0xe8, 0xff].repeat(frame.len() / 4));
}

// TODO: TMP
fn draw_pixel(mut pixels_resource: ResMut<PixelCanvas>) {
// TODO: encapsulate frame access
let frame = pixels_resource.pixels.get_frame_mut();
let pixel_index = 17;
frame[4 * pixel_index] = 0xff;
frame[4 * pixel_index + 1] = 0x00;
frame[4 * pixel_index + 2] = 0x55;
frame[4 * pixel_index + 3] = 0xff;
}

// TODO: anything left in https://github.com/bevyengine/bevy/tree/main/examples worth applying on this app?

// TODO: TESTS: https://chadnauseam.com/coding/gamedev/automated-testing-in-bevy
Expand Down
5 changes: 5 additions & 0 deletions src/pixel_art/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub use pixel_canvas::PixelCanvas;
pub use pixel_canvas::PixelCanvasPlugin;
pub use pixel_canvas::PixelCanvasSystemSet;

mod pixel_canvas;
248 changes: 248 additions & 0 deletions src/pixel_art/pixel_canvas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
use bevy::prelude::*;
use bevy::window::PrimaryWindow;
use bevy::winit::WinitWindows;
use pixels::{Pixels, SurfaceTexture};

pub struct PixelCanvasPlugin {
pub canvas_width: u32,
pub canvas_height: u32,
}

// TODO: check if there is a new release of bevy_pixels, adjusted for Bevy 0.10
impl Plugin for PixelCanvasPlugin {
fn build(&self, app: &mut App) {
let canvas_width = self.canvas_width;
let canvas_height = self.canvas_height;

app.configure_set(PixelCanvasSystemSet::Render.after(PixelCanvasSystemSet::Draw));
app.configure_set(PixelCanvasSystemSet::Draw.after(CoreSet::Update));

// TODO: move out to function. Probably will require to introduce a resource for canvas width/height
app.add_startup_system(
move |primary_window_query: Query<Entity, With<PrimaryWindow>>,
// TODO:: what does NonSend do?
winit_windows: NonSend<WinitWindows>,
mut commands: Commands| {
let primary_window = primary_window_query
.get_single()
.expect("should query single primary window");

let winit_window = winit_windows
.get_window(primary_window)
.expect("should get winit window for a given primary window");

let surface_texture = SurfaceTexture::new(
winit_window.inner_size().width,
winit_window.inner_size().height,
winit_window,
);

// TODO: WASM: Pixels::new_async(canvas_width, canvas_height, surface_texture).block_on()
let pixels = Pixels::new(canvas_width, canvas_height, surface_texture)
.expect("should create pixels");

commands.insert_resource(PixelCanvas { pixels })
},
);

app.add_system(render_canvas.in_base_set(PixelCanvasSystemSet::Render));
}
}

#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
#[system_set(base)]
pub enum PixelCanvasSystemSet {
Draw,
Render,
}

#[derive(Resource)]
pub struct PixelCanvas {
// TODO: make private, hide implementation details
pub pixels: Pixels,
}

pub fn render_canvas(resource: Res<PixelCanvas>) {
resource.pixels.render().expect("should render pixels");
}

// TODO: bevy_pixels for Bevy 0.9 . Anything else to be taken from here?
/*
pub mod prelude {
pub use crate::{PixelsPlugin, PixelsResource, PixelsStage};
}
pub use pixels;
use bevy::{
diagnostic::{Diagnostic, DiagnosticId, Diagnostics},
prelude::*,
window::{WindowBackendScaleFactorChanged, WindowId, WindowResized},
winit::WinitWindows,
};
use pixels::{Pixels, SurfaceTexture};
#[cfg(target_arch = "wasm32")]
use pollster::FutureExt as _;
#[cfg(not(target_arch = "wasm32"))]
use std::time::Instant;
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
pub enum PixelsStage {
Draw,
Render,
PostRender,
}
#[derive(Resource)]
pub struct PixelsResource {
pub pixels: Pixels,
pub window_id: WindowId,
}
// Internal configuration resource for use in `setup` system. Users should set values on
// `PixelsPlugin` instead of inserting this resource directly. Ideally we just read the plugin
// configuration directly within `setup` system, but this is not currently possible.
#[derive(Resource)]
pub struct PixelsOptions {
width: u32,
height: u32,
}
pub struct PixelsPlugin {
/// Width of the pixel buffer
pub width: u32,
/// Height of the pixel buffer
pub height: u32,
}
impl Default for PixelsPlugin {
fn default() -> Self {
PixelsPlugin {
width: 180,
height: 120,
}
}
}
impl Plugin for PixelsPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(PixelsOptions {
width: self.width,
height: self.height,
})
.add_stage_after(
CoreStage::PostUpdate,
PixelsStage::Draw,
SystemStage::parallel(),
)
.add_stage_after(
PixelsStage::Draw,
PixelsStage::Render,
SystemStage::parallel(),
)
.add_stage_after(
PixelsStage::Render,
PixelsStage::PostRender,
SystemStage::parallel(),
)
.add_startup_system_to_stage(StartupStage::PreStartup, Self::setup)
.add_system(Self::window_resize)
.add_system(Self::window_change)
.add_system_to_stage(PixelsStage::Render, Self::render);
}
}
impl PixelsPlugin {
pub const RENDER_TIME: DiagnosticId =
DiagnosticId::from_u128(1187582084072339577959028643519383692);
pub fn setup(
mut commands: Commands,
mut diagnostics: ResMut<Diagnostics>,
options: Res<PixelsOptions>,
windows: Res<Windows>,
winit_windows: NonSend<WinitWindows>,
) {
diagnostics.add(Diagnostic::new(Self::RENDER_TIME, "render_time", 20).with_suffix("s"));
let window_id = windows
.get_primary()
.expect("primary window not found")
.id();
let winit_window = winit_windows
.get_window(window_id)
.expect("failed to get primary winit window");
let window_size = winit_window.inner_size();
let surface_texture =
SurfaceTexture::new(window_size.width, window_size.height, winit_window);
let pixels = {
#[cfg(not(target_arch = "wasm32"))]
{
Pixels::new(options.width, options.height, surface_texture)
}
#[cfg(target_arch = "wasm32")]
{
// TODO: Find a way to asynchronously load pixels on web
Pixels::new_async(options.width, options.height, surface_texture).block_on()
}
}
.expect("failed to create pixels");
commands.insert_resource(PixelsResource { pixels, window_id });
}
pub fn window_resize(
mut window_resized_events: EventReader<WindowResized>,
mut resource: ResMut<PixelsResource>,
windows: Res<Windows>,
) {
for event in window_resized_events.iter() {
if event.id == resource.window_id {
Self::resize_surface_to_window(&mut resource, &windows);
}
}
}
pub fn window_change(
mut window_backend_scale_factor_changed_events: EventReader<
WindowBackendScaleFactorChanged,
>,
mut resource: ResMut<PixelsResource>,
windows: Res<Windows>,
) {
for event in window_backend_scale_factor_changed_events.iter() {
if event.id == resource.window_id {
Self::resize_surface_to_window(&mut resource, &windows);
}
}
}
fn resize_surface_to_window(resource: &mut ResMut<PixelsResource>, windows: &Res<Windows>) {
let window = windows.get(resource.window_id).unwrap();
let _ = resource
.pixels
.resize_surface(window.physical_width(), window.physical_height());
}
#[cfg(not(target_arch = "wasm32"))]
pub fn render(resource: Res<PixelsResource>, mut diagnostics: ResMut<Diagnostics>) {
let start = Instant::now();
resource.pixels.render().expect("failed to render pixels");
let end = Instant::now();
let render_time = end.duration_since(start);
diagnostics.add_measurement(Self::RENDER_TIME, || render_time.as_secs_f64());
}
#[cfg(target_arch = "wasm32")]
pub fn render(resource: Res<PixelsResource>) {
resource.pixels.render().expect("failed to render pixels");
}
}
*/

0 comments on commit 1cf2a96

Please sign in to comment.