1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//! `GameScene` calls for shader packs; the pack logic lives in
//! `rendering::shader_pack`.
use bevy_ecs::prelude::World;
use crate::project_runner::{GameScene, ProjectFolder};
use crate::rendering::shader_pack::{
builtin_info, PackOptionValue, ShaderPackInfo, ShaderPackState, ShaderWorld,
};
use crate::runtime::{KeyCode, RuntimeInput};
/// The world's shader pack state, set up from the project on first use.
pub(crate) fn state(
world: &mut World,
) -> bevy_ecs::world::Mut<'_, ShaderPackState> {
let project = world.get_resource::<ProjectFolder>().map(|f| f.0.clone());
let mut state = world.get_resource_or_insert_with(ShaderPackState::default);
state.init(project.as_deref());
state
}
/// Once a frame before extraction: F3+T reloads the pack, and debug
/// builds reload it when one of its files changes.
pub(crate) fn sync(world: &mut World) {
let reload = world.get_resource::<RuntimeInput>().is_some_and(|input| {
input.key_held(KeyCode::F3) && input.key_just_pressed(KeyCode::KeyT)
});
let mut state = state(world);
if reload {
state.reload();
} else if cfg!(debug_assertions) {
state.poll();
}
}
impl GameScene<'_> {
/// Every shader pack the game can use: the built-in shading (id `""`)
/// first, then the game's packs under `assets/shaderpacks/` by id, then
/// the player's packs (`user/<folder>`) under the user data folder.
pub fn shader_packs(&mut self) -> Vec<ShaderPackInfo> {
state(self.world).list()
}
/// The active pack's info; the built-in entry when none is active.
pub fn active_shader_pack(&mut self) -> ShaderPackInfo {
let state = state(self.world);
state
.active
.as_ref()
.map_or_else(builtin_info, |active| active.pack.info.clone())
}
/// Switches to pack `id` (`""` for the built-in shading) at its default
/// preset. An unknown id, or a pack that does not parse or compile,
/// returns the error as `file:line: message` and keeps the current pack.
pub fn set_shader_pack(&mut self, id: &str) -> Result<(), String> {
state(self.world).set_pack(id)
}
/// The active preset: `Low`, `Medium`, `High`, `Ultra`, or `Custom`
/// after an option change no preset matches; empty with no pack.
pub fn shader_preset(&mut self) -> String {
state(self.world).preset.clone()
}
/// Applies one of the active pack's presets (`Low` to `Ultra`).
pub fn set_shader_preset(&mut self, preset: &str) -> Result<(), String> {
state(self.world).set_preset(preset)
}
/// Current value of option `id` of the active pack.
pub fn shader_option(&mut self, id: &str) -> Option<PackOptionValue> {
state(self.world)
.active
.as_ref()
.and_then(|active| active.values.get(id).cloned())
}
/// Sets option `id` of the active pack and rebuilds its shaders. The
/// preset becomes `Custom` unless the options now equal a preset.
pub fn set_shader_option(
&mut self,
id: &str,
value: PackOptionValue,
) -> Result<(), String> {
state(self.world).set_option(id, value)
}
/// Named numbers from the active pack's `values`, else from the
/// project's default pack (`project.json` `shader_pack`).
pub fn shader_pack_value(&mut self, name: &str) -> Option<Vec<f32>> {
state(self.world).value(name)
}
/// Grows by one on every pack switch, reload, preset or option change.
pub fn shader_pack_revision(&mut self) -> u64 {
state(self.world).revision
}
/// Reads the active pack's files again (F3+T does the same).
pub fn reload_shader_pack(&mut self) {
state(self.world).reload();
}
/// The last pack error as `file:line: message`, until a pack loads.
pub fn shader_pack_error(&mut self) -> Option<String> {
state(self.world).error.clone()
}
/// Hands shader packs the world state for the `frame` block: time of
/// day, sun and moon, weather and what the camera is in. Call it every
/// frame the values change; until the first call the sun comes from the
/// first directional light and the rest is 0.
pub fn set_shader_world(&mut self, world: ShaderWorld) {
state(self.world).world = Some(world);
}
}
/// Per frame: while a pack has failed, a red box at the top left names the
/// pack, the error and the fallback in use.
#[cfg(feature = "ui")]
pub(crate) fn draw_banner(
ui: bevy_ecs::system::Res<super::RuntimeUi>,
state: Option<bevy_ecs::system::Res<ShaderPackState>>,
) {
let Some(text) = state
.as_ref()
.and_then(|state| state.failed_banner.as_ref())
else {
return;
};
egui::Area::new(egui::Id::new("rusting.shader_pack_banner"))
.anchor(egui::Align2::LEFT_TOP, egui::vec2(8.0, 8.0))
.fade_in(false)
.interactable(false)
.order(egui::Order::Foreground)
.show(ui.context(), |ui| {
egui::Frame::new()
.fill(egui::Color32::from_rgb(170, 20, 20))
.inner_margin(egui::Margin::symmetric(8, 4))
.corner_radius(4.0)
.show(ui, |ui| {
ui.add(
egui::Label::new(
egui::RichText::new(text)
.color(egui::Color32::WHITE),
)
.selectable(false),
);
});
});
}