From c3e3ccf1905e57578dcb023ee34175d20a186abb Mon Sep 17 00:00:00 2001 From: caelwarner Date: Fri, 7 Apr 2023 01:09:31 -0700 Subject: [PATCH] Added app config file - Config file can change window size, resizable, fullscreen, vsync, window scale and texture size - Embedded shader files in executable. This means the simulation can be shipped as just a single executable --- Cargo.toml | 5 +- assets/shaders/simulation.wgsl | 5 +- slime_simulation_config.toml | 11 ++++ src/main.rs | 107 +++++++++++++++++++++++++++------ src/pipeline/blur.rs | 22 +++---- src/pipeline/fade.rs | 18 ++---- src/pipeline/mod.rs | 33 +++++++--- src/pipeline/recolor.rs | 20 ++---- src/pipeline/simulation.rs | 28 ++++----- src/plugin.rs | 25 ++++---- 10 files changed, 169 insertions(+), 105 deletions(-) create mode 100644 slime_simulation_config.toml diff --git a/Cargo.toml b/Cargo.toml index 2bc0d8b..ec853a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,10 @@ edition = "2021" [dependencies] bevy = "0.8.1" -bytemuck = "1.12.2" +bevy_embedded_assets = "0.4.0" bevy-inspector-egui = "0.13.0" bevy-inspector-egui-derive = "0.13.0" +bytemuck = "1.12.2" rand = "0.8.5" +serde = { version = "1.0.159", features = ["derive"] } +toml = "0.7.3" diff --git a/assets/shaders/simulation.wgsl b/assets/shaders/simulation.wgsl index 669c36b..e6f8af7 100644 --- a/assets/shaders/simulation.wgsl +++ b/assets/shaders/simulation.wgsl @@ -9,9 +9,6 @@ struct Context { senseDistance: f32, turnSpeed: f32, turnRandomness: f32, - r: f32, - g: f32, - b: f32, } struct Agent { @@ -99,7 +96,7 @@ fn update(@builtin(global_invocation_id) id: vec3) { agents[id.x].position = newPosition; let location = vec2(agents[id.x].position); - let color = vec4(context.r, context.g, context.b, 1.0); + let color = vec4(1.0, 1.0, 1.0, 1.0); storageBarrier(); textureStore(textureOut, location, color); diff --git a/slime_simulation_config.toml b/slime_simulation_config.toml new file mode 100644 index 0000000..a36a17f --- /dev/null +++ b/slime_simulation_config.toml @@ -0,0 +1,11 @@ +[window] +width = 1280 +height = 720 +fullscreen = false +resizable = false +vsync = true +override_scale_factor = false + +[texture] +width = 2560 +height = 1440 diff --git a/src/main.rs b/src/main.rs index 3d9fae7..9b2ad48 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,13 @@ -extern crate core; +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +use std::fs; + +use bevy::asset::AssetPlugin; use bevy::DefaultPlugins; use bevy::prelude::*; -use bevy::window::WindowMode; +use bevy::window::{PresentMode, WindowMode, WindowResized}; +use bevy_embedded_assets::EmbeddedAssetPlugin; +use serde::{Deserialize, Serialize}; use crate::pipeline::PipelineImages; use crate::plugin::SlimeSimulationPlugin; @@ -10,37 +15,48 @@ use crate::plugin::SlimeSimulationPlugin; mod plugin; mod pipeline; -const SETTINGS: AppSettings = AppSettings { - window_size: (2560, 1440), - texture_size: (2560, 1440), -}; +const CONFIG_FILE_NAME: &str = "slime_simulation_config.toml"; fn main() { + let config: AppConfig = match fs::read_to_string(CONFIG_FILE_NAME) { + Ok(contents) => { + toml::from_str(contents.as_str()).unwrap() + }, + Err(..) => { + fs::write(CONFIG_FILE_NAME, toml::to_string(&AppConfig::default()).unwrap()).unwrap(); + AppConfig::default() + }, + }; + App::new() .insert_resource(ClearColor(Color::BLACK)) .insert_resource(WindowDescriptor { - title: "Slime Simulation".to_string(), - width: SETTINGS.window_size.0 as f32, - height: SETTINGS.window_size.1 as f32, - mode: WindowMode::Windowed, - scale_factor_override: Some(1.0), + title: String::from("Slime Simulation"), + width: config.window.width as f32, + height: config.window.height as f32, + resizable: config.window.resizable, + mode: if config.window.fullscreen { WindowMode::Fullscreen } else { WindowMode::Windowed }, + present_mode: if config.window.vsync { PresentMode::AutoVsync } else { PresentMode::AutoNoVsync }, + scale_factor_override: if config.window.override_scale_factor { Some(1.0) } else { None }, ..default() }) - .add_plugins(DefaultPlugins) + .insert_resource(config) + .add_plugins_with(DefaultPlugins, |group| group.add_before::(EmbeddedAssetPlugin)) .add_plugin(SlimeSimulationPlugin) .add_startup_system_to_stage( StartupStage::PostStartup, - setup + setup, ) + .add_system(on_window_resize) .run(); } -fn setup(mut commands: Commands, images: Res) { +fn setup(mut commands: Commands, images: Res, config: Res) { commands.spawn_bundle(SpriteBundle { sprite: Sprite { custom_size: Some(Vec2::new( - SETTINGS.window_size.0 as f32, - SETTINGS.window_size.1 as f32, + config.window.width as f32, + config.window.height as f32, )), ..default() }, @@ -51,8 +67,59 @@ fn setup(mut commands: Commands, images: Res) { commands.spawn_bundle(Camera2dBundle::default()); } -#[derive(Copy, Clone)] -struct AppSettings { - window_size: (u32, u32), - texture_size: (u32, u32), +fn on_window_resize( + mut resize_events: EventReader, + mut query: Query<&mut Sprite>, +) { + for event in resize_events.iter() { + let mut sprite = query.single_mut(); + sprite.custom_size = Some(Vec2::new( + event.width, + event.height, + )); + } +} + +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct AppConfig { + window: WindowConfig, + texture: TextureConfig, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct WindowConfig { + width: u32, + height: u32, + fullscreen: bool, + resizable: bool, + vsync: bool, + override_scale_factor: bool, +} + +impl Default for WindowConfig { + fn default() -> Self { + Self { + width: 1280, + height: 720, + fullscreen: false, + resizable: false, + vsync: true, + override_scale_factor: false, + } + } +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct TextureConfig { + width: u32, + height: u32, +} + +impl Default for TextureConfig { + fn default() -> Self { + Self { + width: 2560, + height: 1440, + } + } } diff --git a/src/pipeline/blur.rs b/src/pipeline/blur.rs index ad7edb6..4b6ca89 100644 --- a/src/pipeline/blur.rs +++ b/src/pipeline/blur.rs @@ -4,9 +4,9 @@ use bevy::render::render_asset::RenderAssets; use bevy::render::render_resource::*; use bevy::render::renderer::{RenderDevice, RenderQueue}; -use crate::pipeline::{get_compute_pipeline_id, PipelineData, SubShaderPipeline, WorkgroupSize}; -use crate::plugin::{PluginSettings, PluginTime}; -use crate::SETTINGS; +use crate::AppConfig; +use crate::pipeline::{get_compute_pipeline_id, PipelineData, SubShaderPipeline}; +use crate::plugin::{PluginTime, SimulationSettings}; pub struct BlurShaderPipeline { bind_group_layout: BindGroupLayout, @@ -39,7 +39,7 @@ impl BlurShaderPipeline { } impl SubShaderPipeline for BlurShaderPipeline { - fn init_data(&mut self, render_device: &RenderDevice, _settings: &PluginSettings) { + fn init_data(&mut self, render_device: &RenderDevice, _app_config: &AppConfig, _settings: &SimulationSettings) { self.context.buffer = Some(render_device .create_buffer( &BufferDescriptor { @@ -52,11 +52,11 @@ impl SubShaderPipeline for BlurShaderPipeline { ); } - fn prepare_data(&mut self, render_queue: &RenderQueue, settings: &PluginSettings, _time: &PluginTime) { + fn prepare_data(&mut self, render_queue: &RenderQueue, app_config: &AppConfig, settings: &SimulationSettings, _time: &PluginTime) { self.context.data = Some(BlurPipelineContext { pause: if settings.pause { 1 } else { 0 }, - width: SETTINGS.texture_size.0, - height: SETTINGS.texture_size.1, + width: app_config.texture.width, + height: app_config.texture.height, blur_radius: settings.blur_radius, }); @@ -113,14 +113,6 @@ impl SubShaderPipeline for BlurShaderPipeline { fn get_bind_group(&self) -> Option<&BindGroup> { self.bind_group.as_ref() } - - fn get_workgroup_size(&self, _settings: &PluginSettings) -> WorkgroupSize { - WorkgroupSize { - x: SETTINGS.texture_size.0 / 8, - y: SETTINGS.texture_size.1 / 8, - z: 1, - } - } } fn get_bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout { diff --git a/src/pipeline/fade.rs b/src/pipeline/fade.rs index 1eedce1..4e7158e 100644 --- a/src/pipeline/fade.rs +++ b/src/pipeline/fade.rs @@ -4,9 +4,9 @@ use bevy::render::render_asset::RenderAssets; use bevy::render::render_resource::*; use bevy::render::renderer::{RenderDevice, RenderQueue}; -use crate::pipeline::{get_compute_pipeline_id, PipelineData, SubShaderPipeline, WorkgroupSize}; -use crate::plugin::{PluginSettings, PluginTime}; -use crate::SETTINGS; +use crate::AppConfig; +use crate::pipeline::{get_compute_pipeline_id, PipelineData, SubShaderPipeline}; +use crate::plugin::{PluginTime, SimulationSettings}; pub struct FadeShaderPipeline { bind_group_layout: BindGroupLayout, @@ -39,7 +39,7 @@ impl FadeShaderPipeline { } impl SubShaderPipeline for FadeShaderPipeline { - fn init_data(&mut self, render_device: &RenderDevice, _settings: &PluginSettings) { + fn init_data(&mut self, render_device: &RenderDevice, _app_config: &AppConfig, _settings: &SimulationSettings) { self.context.buffer = Some(render_device .create_buffer( &BufferDescriptor { @@ -52,7 +52,7 @@ impl SubShaderPipeline for FadeShaderPipeline { ); } - fn prepare_data(&mut self, render_queue: &RenderQueue, settings: &PluginSettings, time: &PluginTime) { + fn prepare_data(&mut self, render_queue: &RenderQueue, _app_config: &AppConfig, settings: &SimulationSettings, time: &PluginTime) { self.context.data = Some(FadePipelineContext { pause: if settings.pause { 1 } else { 0 }, fade_rate: settings.fade_rate, @@ -107,14 +107,6 @@ impl SubShaderPipeline for FadeShaderPipeline { fn get_bind_group(&self) -> Option<&BindGroup> { self.bind_group.as_ref() } - - fn get_workgroup_size(&self, _settings: &PluginSettings) -> WorkgroupSize { - WorkgroupSize { - x: SETTINGS.texture_size.0 / 8, - y: SETTINGS.texture_size.1 / 8, - z: 1, - } - } } fn get_bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout { diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs index bcdca80..3354b99 100644 --- a/src/pipeline/mod.rs +++ b/src/pipeline/mod.rs @@ -7,11 +7,12 @@ use bevy::render::render_graph::{Node, NodeRunError, RenderGraphContext}; use bevy::render::render_resource::*; use bevy::render::renderer::{RenderContext, RenderDevice, RenderQueue}; +use crate::AppConfig; use crate::pipeline::blur::BlurShaderPipeline; use crate::pipeline::fade::FadeShaderPipeline; use crate::pipeline::recolor::RecolorShaderPipeline; use crate::pipeline::simulation::SimulationShaderPipeline; -use crate::plugin::{PluginSettings, PluginTime}; +use crate::plugin::{PluginTime, SimulationSettings}; pub mod blur; pub mod fade; @@ -33,21 +34,26 @@ impl FromWorld for MainShaderPipeline { ], }; - pipeline.init_data(world.resource::(), world.resource::()); + pipeline.init_data(world.resource::(), world.resource::(), world.resource::()); pipeline } } impl MainShaderPipeline { - pub fn init_data(&mut self, render_device: &RenderDevice, settings: &PluginSettings) { + pub fn init_data(&mut self, render_device: &RenderDevice, app_config: &AppConfig, settings: &SimulationSettings) { for sub_pipeline in &mut self.sub_pipelines { - sub_pipeline.init_data(render_device, settings); + sub_pipeline.init_data(render_device, app_config, settings); } } - pub fn prepare_data(&mut self, render_queue: Res, settings: Res, time: Res) { + pub fn prepare_data(&mut self, + render_queue: &RenderQueue, + app_config: &AppConfig, + settings: &SimulationSettings, + time: &PluginTime, + ) { for sub_pipeline in &mut self.sub_pipelines { - sub_pipeline.prepare_data(render_queue.as_ref(), settings.as_ref(), time.as_ref()); + sub_pipeline.prepare_data(render_queue, app_config, settings, time); } } @@ -75,7 +81,7 @@ impl MainShaderPipeline { pipeline_cache, sub_pipeline.get_pipeline(), sub_pipeline.get_bind_group(), - sub_pipeline.get_workgroup_size(world.resource::()), + sub_pipeline.get_workgroup_size(world.resource::(), world.resource::()), ) } } @@ -127,13 +133,20 @@ fn get_compute_pipeline_id( } pub trait SubShaderPipeline: Send + Sync { - fn init_data(&mut self, _render_device: &RenderDevice, _settings: &PluginSettings) {} - fn prepare_data(&mut self, _render_queue: &RenderQueue, _settings: &PluginSettings, _time: &PluginTime) {} + fn init_data(&mut self, _render_device: &RenderDevice, _app_config: &AppConfig, _settings: &SimulationSettings) {} + fn prepare_data(&mut self, _render_queue: &RenderQueue, _app_config: &AppConfig, _settings: &SimulationSettings, _time: &PluginTime) {} fn queue_bind_groups(&mut self, render_device: &RenderDevice, gpu_images: &RenderAssets, images: &Vec>); fn get_pipeline(&self) -> CachedComputePipelineId; fn get_bind_group(&self) -> Option<&BindGroup>; - fn get_workgroup_size(&self, settings: &PluginSettings) -> WorkgroupSize; + + fn get_workgroup_size(&self, app_config: &AppConfig, _settings: &SimulationSettings) -> WorkgroupSize { + WorkgroupSize { + x: app_config.texture.width / 8, + y: app_config.texture.height / 8, + z: 1, + } + } } pub struct PipelineData { diff --git a/src/pipeline/recolor.rs b/src/pipeline/recolor.rs index d99ddc0..566579f 100644 --- a/src/pipeline/recolor.rs +++ b/src/pipeline/recolor.rs @@ -1,13 +1,13 @@ use bevy::asset::Handle; use bevy::core::{Pod, Zeroable}; -use bevy::prelude::{AssetServer, Image, World}; +use bevy::prelude::*; use bevy::render::render_asset::RenderAssets; use bevy::render::render_resource::*; use bevy::render::renderer::{RenderDevice, RenderQueue}; -use crate::pipeline::{get_compute_pipeline_id, PipelineData, SubShaderPipeline, WorkgroupSize}; -use crate::plugin::{PluginSettings, PluginTime}; -use crate::SETTINGS; +use crate::AppConfig; +use crate::pipeline::{get_compute_pipeline_id, PipelineData, SubShaderPipeline}; +use crate::plugin::{PluginTime, SimulationSettings}; pub struct RecolorShaderPipeline { bind_group_layout: BindGroupLayout, @@ -40,7 +40,7 @@ impl RecolorShaderPipeline { } impl SubShaderPipeline for RecolorShaderPipeline { - fn init_data(&mut self, render_device: &RenderDevice, _settings: &PluginSettings) { + fn init_data(&mut self, render_device: &RenderDevice, _app_config: &AppConfig, _settings: &SimulationSettings) { self.context.buffer = Some(render_device .create_buffer( &BufferDescriptor { @@ -53,7 +53,7 @@ impl SubShaderPipeline for RecolorShaderPipeline { ); } - fn prepare_data(&mut self, render_queue: &RenderQueue, settings: &PluginSettings, _time: &PluginTime) { + fn prepare_data(&mut self, render_queue: &RenderQueue, _app_config: &AppConfig, settings: &SimulationSettings, _time: &PluginTime) { self.context.data = Some(RecolorPipelineContext { color: settings.color.as_rgba_f32(), }); @@ -100,14 +100,6 @@ impl SubShaderPipeline for RecolorShaderPipeline { fn get_bind_group(&self) -> Option<&BindGroup> { self.bind_group.as_ref() } - - fn get_workgroup_size(&self, _settings: &PluginSettings) -> WorkgroupSize { - WorkgroupSize { - x: SETTINGS.texture_size.0 / 8, - y: SETTINGS.texture_size.1 / 8, - z: 1, - } - } } fn get_bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout { diff --git a/src/pipeline/simulation.rs b/src/pipeline/simulation.rs index 352faae..d716ec4 100644 --- a/src/pipeline/simulation.rs +++ b/src/pipeline/simulation.rs @@ -8,9 +8,9 @@ use bevy::render::render_resource::*; use bevy::render::renderer::{RenderDevice, RenderQueue}; use rand::{Rng, thread_rng}; +use crate::AppConfig; use crate::pipeline::{get_compute_pipeline_id, PipelineData, SubShaderPipeline, WorkgroupSize}; -use crate::plugin::{PluginSettings, PluginTime}; -use crate::SETTINGS; +use crate::plugin::{PluginTime, SimulationSettings}; pub struct SimulationShaderPipeline { bind_group_layout: BindGroupLayout, @@ -24,7 +24,7 @@ impl SimulationShaderPipeline { pub fn new(world: &mut World) -> Self { let bind_group_layout = get_bind_group_layout( world.resource::(), - world.resource::(), + world.resource::(), ); let shader = world.resource::().load("shaders/simulation.wgsl"); @@ -46,7 +46,7 @@ impl SimulationShaderPipeline { } impl SubShaderPipeline for SimulationShaderPipeline { - fn init_data(&mut self, render_device: &RenderDevice, settings: &PluginSettings) { + fn init_data(&mut self, render_device: &RenderDevice, app_config: &AppConfig, settings: &SimulationSettings) { let mut rng = thread_rng(); self.context.buffer = Some(render_device @@ -68,8 +68,8 @@ impl SubShaderPipeline for SimulationShaderPipeline { Agent { position: [ - (SETTINGS.texture_size.0 as f32 / 2.0) + r * theta.cos(), - (SETTINGS.texture_size.1 as f32 / 2.0) + r * theta.sin(), + (app_config.texture.width as f32 / 2.0) + r * theta.cos(), + (app_config.texture.height as f32 / 2.0) + r * theta.sin(), ], angle: theta + PI, _padding: 0, @@ -89,11 +89,11 @@ impl SubShaderPipeline for SimulationShaderPipeline { )); } - fn prepare_data(&mut self, render_queue: &RenderQueue, settings: &PluginSettings, time: &PluginTime) { + fn prepare_data(&mut self, render_queue: &RenderQueue, app_config: &AppConfig, settings: &SimulationSettings, time: &PluginTime) { self.context.data = Some(SimulationPipelineContext { pause: if settings.pause { 1 } else { 0 }, - width: SETTINGS.texture_size.0, - height: SETTINGS.texture_size.1, + width: app_config.texture.width, + height: app_config.texture.height, speed: settings.agent_speed, delta_time: time.delta_time, time: time.time, @@ -101,9 +101,6 @@ impl SubShaderPipeline for SimulationShaderPipeline { sense_distance: settings.agent_sense_distance, turn_speed: settings.agent_turn_speed, turn_randomness: settings.agent_turn_randomness, - r: settings.color.r(), - g: settings.color.g(), - b: settings.color.b(), }); render_queue.write_buffer( @@ -166,7 +163,7 @@ impl SubShaderPipeline for SimulationShaderPipeline { self.bind_group.as_ref() } - fn get_workgroup_size(&self, settings: &PluginSettings) -> WorkgroupSize { + fn get_workgroup_size(&self, _app_config: &AppConfig, settings: &SimulationSettings) -> WorkgroupSize { WorkgroupSize { x: settings.num_agents / 16, y: 1, @@ -175,7 +172,7 @@ impl SubShaderPipeline for SimulationShaderPipeline { } } -fn get_bind_group_layout(render_device: &RenderDevice, settings: &PluginSettings) -> BindGroupLayout { +fn get_bind_group_layout(render_device: &RenderDevice, settings: &SimulationSettings) -> BindGroupLayout { render_device .create_bind_group_layout( &BindGroupLayoutDescriptor { @@ -242,9 +239,6 @@ struct SimulationPipelineContext { sense_distance: f32, turn_speed: f32, turn_randomness: f32, - r: f32, - g: f32, - b: f32, } diff --git a/src/plugin.rs b/src/plugin.rs index 6497a31..34647fc 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -8,8 +8,8 @@ use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat, T use bevy::render::renderer::{RenderDevice, RenderQueue}; use bevy_inspector_egui::{Inspectable, InspectorPlugin}; +use crate::AppConfig; use crate::pipeline::{MainShaderPipeline, PipelineImages, ShaderPipelineNode}; -use crate::SETTINGS; pub struct SlimeSimulationPlugin; @@ -17,15 +17,17 @@ impl Plugin for SlimeSimulationPlugin { fn build(&self, app: &mut App) { app .add_startup_system(create_images) - .add_plugin(InspectorPlugin::::new()) + .add_plugin(InspectorPlugin::::new()) .add_plugin(ExtractResourcePlugin::::default()) - .add_plugin(ExtractResourcePlugin::::default()) + .add_plugin(ExtractResourcePlugin::::default()) .add_plugin(ExtractResourcePlugin::::default()); + let app_config = app.world.get_resource::().cloned().unwrap(); let render_app = app.sub_app_mut(RenderApp); + render_app.insert_resource(app_config); render_app - .init_resource::() + .init_resource::() .init_resource::() .add_system_to_stage( RenderStage::Queue, @@ -48,14 +50,14 @@ impl Plugin for SlimeSimulationPlugin { } } -fn create_images(mut commands: Commands, mut images: ResMut>) { +fn create_images(mut commands: Commands, app_config: Res, mut images: ResMut>) { let mut pipeline_images: Vec> = Vec::new(); for _ in 0..2 { let mut image = Image::new_fill( Extent3d { - width: SETTINGS.texture_size.0, - height: SETTINGS.texture_size.1, + width: app_config.texture.width, + height: app_config.texture.height, depth_or_array_layers: 1, }, TextureDimension::D2, @@ -85,14 +87,15 @@ fn queue_bind_groups( fn prepare_data( mut pipeline: ResMut, render_queue: Res, - settings: Res, + app_config: Res, + settings: Res, time: Res, ) { - pipeline.prepare_data(render_queue, settings, time); + pipeline.prepare_data(render_queue.as_ref(), app_config.as_ref(), settings.as_ref(), time.as_ref()); } #[derive(Clone, Inspectable, ExtractResource)] -pub struct PluginSettings { +pub struct SimulationSettings { pub pause: bool, pub num_agents: u32, #[inspectable(min = 0.1, max = 5.0)] @@ -112,7 +115,7 @@ pub struct PluginSettings { pub blur_radius: u32, } -impl FromWorld for PluginSettings { +impl FromWorld for SimulationSettings { fn from_world(_world: &mut World) -> Self { Self { pause: true,