Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 34 additions & 6 deletions crates/client/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
//! [`MAX_RELAYS`]; it holds from the next lobby), and `/net relay clear` to drop them all
//!
//! A browser has no vsync or fps-max, and no relay-only: it paces the frames to the display
//! itself, and reaches the others through the relay only.
//! itself, and reaches the others through the relay only. Nor has it msaa: the heat shimmer and
//! the warpball's glow (`heat.rs`) read the depth buffer, which WebGL2 cannot when it is
//! multisampled.
//!
//! The last character driven is kept too, and is the one the client starts with, and the
//! keyboard's driving keys, which the pause menu's Controls change (`controls.rs`), and whether
Expand Down Expand Up @@ -122,6 +124,9 @@ impl Default for Config {
impl Config {
/// The multisampling of the cameras.
pub fn msaa(&self) -> Msaa {
if cfg!(target_arch = "wasm32") {
return Msaa::Off;
}
match self.msaa {
2 => Msaa::Sample2,
4 => Msaa::Sample4,
Expand Down Expand Up @@ -190,14 +195,31 @@ fn write(text: &str) -> Result<(), String> {
}
}

/// Moves the settings to where they are no longer read, nor overwritten by the next ones saved,
/// and says where.
#[cfg(not(target_arch = "wasm32"))]
fn set_aside() -> Result<String, String> {
let to = path().with_extension("json.broken");
std::fs::rename(path(), &to).map_err(|e| format!("Could not move {}: {e}", place()))?;
Ok(to.display().to_string())
}

#[cfg(target_arch = "wasm32")]
fn set_aside() -> Result<String, String> {
match crate::web::set_config_aside() {
None => Ok("ctr-rs.config.broken in the browser's storage".to_string()),
Some(e) => Err(format!("Could not move the settings: {e}")),
}
}

impl Config {
/// What is kept; the defaults if there is nothing or it cannot be read (it is told of).
pub fn load() -> Config {
let text = match read() {
Ok(Some(text)) => text,
Ok(None) => return Config::default(),
Err(why) => {
eprintln!("{why}");
tell!("{why}");
return Config::default();
}
};
Expand All @@ -213,12 +235,16 @@ impl Config {
}
config.relays = relays;
for cup in config.cups.take_invalid() {
eprintln!("{}: dropped the cup {cup:?}", place());
tell!("{}: dropped the cup {cup:?}", place());
}
config
}
Err(e) => {
eprintln!("{}: {e}", place());
tell!("{}: {e}", place());
match set_aside() {
Ok(to) => tell!("The defaults are used: what could not be read is in {to}"),
Err(why) => tell!("{why}"),
}
Config::default()
}
}
Expand All @@ -235,7 +261,7 @@ impl Config {
if self.character != Some(character) {
self.character = Some(character);
if let Err(why) = self.save() {
eprintln!("{why}");
tell!("{why}");
}
}
}
Expand Down Expand Up @@ -289,7 +315,6 @@ const USAGE: &str = "Usage: /config set nickname [name, up to 12 characters]\n\
/config set volume music|sfx|voice <0-100>";
#[cfg(target_arch = "wasm32")]
const USAGE: &str = "Usage: /config set nickname [name, up to 12 characters]\n\
/config set msaa off|2|4\n\
/config set shader-effects on|off\n\
/config set render-scale <0.25 to 1>\n\
/config set volume music|sfx|voice <0-100>";
Expand All @@ -303,6 +328,9 @@ impl Setting {
"vsync" | "fps-max" => {
return Err("In a browser, the browser sets the frame rate".into());
}
"msaa" => {
return Err("In a browser, anti-aliasing is off for the heat shimmer".into());
}
_ => {}
}
}
Expand Down
16 changes: 8 additions & 8 deletions crates/client/src/disc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ pub fn prepare() {
// go of the image (`web::release_disc`).
if cfg!(target_arch = "wasm32") {
match source.hold() {
Ok(size) => eprintln!("Took {} MiB from the disc", size >> 20),
Err(e) => eprintln!("Could not take the files of the disc: {e}"),
Ok(size) => tell!("Took {} MiB from the disc", size >> 20),
Err(e) => tell!("Could not take the files of the disc: {e}"),
}
return;
}
Expand All @@ -54,18 +54,18 @@ pub fn prepare() {
Check::Stale(why) => format!("making the files of the disc again: {why}"),
Check::Missing(needs) => format!("making {} more files of the disc", needs.len()),
};
eprintln!("{}...", why);
tell!("{}...", why);
let mut told = 0;
let made = source.prepare(|done, of| {
let tenth = if of == 0 { 10 } else { done * 10 / of };
if tenth > told {
told = tenth;
eprintln!(" {}%", tenth * 10);
tell!(" {}%", tenth * 10);
}
});
match made {
Ok(made) => eprintln!("Made {made} files in {}", paths::disc().display()),
Err(e) => eprintln!("Could not make the files of the disc: {e}"),
Ok(made) => tell!("Made {made} files in {}", paths::disc().display()),
Err(e) => tell!("Could not make the files of the disc: {e}"),
}
}

Expand All @@ -84,12 +84,12 @@ pub fn locate(config: &mut Config) {
let picked = ask()?;
config.iso = Some(picked.clone());
if let Err(why) = config.save() {
eprintln!("{why}");
tell!("{why}");
}
Some(picked)
});
let Some(path) = found else {
eprintln!(
tell!(
"No Crash Team Racing (USA) disc image. Set CTR_BIN to its path, or \"iso\" in {}\n\
(it is read once, to make the files of the game in {})",
crate::paths::config_file().display(),
Expand Down
11 changes: 10 additions & 1 deletion crates/client/src/heat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ impl Plugin for HeatPlugin {
}
}

/// The pass reads the depth buffer of the camera that has it.
/// The pass reads the depth buffer of the camera that has it. (WebGL2 could not read a
/// multisampled one: a browser has no msaa, see `config.rs`.)
fn readable_depth(add: On<Add<HeatWarp>>, mut cameras: Query<&mut Camera3d>) {
if let Ok(mut camera) = cameras.get_mut(add.entity) {
let usages = TextureUsages::from(camera.depth_texture_usages);
Expand Down Expand Up @@ -290,6 +291,8 @@ struct HeatPipeline {
/// With the depth buffer as it is without multisampling, and as it is with.
layouts: [BindGroupLayoutDescriptor; 2],
sampler: Sampler,
/// Nearest, for the depth buffer without multisampling (see `heat.wesl`).
depth_sampler: Sampler,
shader: Handle<Shader>,
vertex: FullscreenShader,
/// By the format of the picture and its samples.
Expand All @@ -312,6 +315,7 @@ fn init_pipeline(
sampler(SamplerBindingType::Filtering),
uniform_buffer::<HeatWarp>(true),
depth,
sampler(SamplerBindingType::NonFiltering),
),
),
)
Expand All @@ -330,6 +334,10 @@ fn init_pipeline(
),
],
sampler: render_device.create_sampler(&SamplerDescriptor::default()),
depth_sampler: render_device.create_sampler(&SamplerDescriptor {
label: Some("heat_warp_depth_sampler"),
..default()
}),
shader: asset_server.load(concat!("embedded://", env!("CARGO_PKG_NAME"), "/heat.wesl")),
vertex: fullscreen_shader.clone(),
pipelines: HashMap::new(),
Expand Down Expand Up @@ -430,6 +438,7 @@ fn pass(
&pipeline.sampler,
binding,
depth_view,
&pipeline.depth_sampler,
)),
);
let mut render_pass = ctx
Expand Down
20 changes: 9 additions & 11 deletions crates/client/src/heat.wesl
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ struct HeatWarp {
@if(MULTISAMPLED)
@group(0) @binding(3) var depth_texture: texture_depth_multisampled_2d;
// A float texture rather than a depth one (its depth in `r`), as WebGL2 cannot `textureLoad` a
// depth texture.
// depth texture. It is read through a sampler: WebGL2 reads a depth texture as 0 when its own
// filtering is linear, which it is (wgpu cannot set it), and the sampler's nearest overrides that.
@if(!MULTISAMPLED)
@group(0) @binding(3) var depth_texture: texture_2d<f32>;
@if(!MULTISAMPLED)
@group(0) @binding(4) var depth_sampler: sampler;

struct Input {
@builtin(position) position: vec4<f32>,
Expand All @@ -49,21 +52,16 @@ fn lens_depth(lens: u32) -> f32 {
return heat.points[lens * PER_LENS + 1u].w;
}

// The depth at the texel `at`: the float texture has it in `r`.
// The scene's depth at uv (the first sample, if it is multisampled).
@if(MULTISAMPLED)
fn load_depth(at: vec2<i32>) -> f32 {
fn depth_at(uv: vec2<f32>) -> f32 {
let size = vec2<i32>(textureDimensions(depth_texture));
let at = clamp(vec2<i32>(uv * vec2<f32>(size)), vec2<i32>(0), size - 1);
return textureLoad(depth_texture, at, 0);
}
@if(!MULTISAMPLED)
fn load_depth(at: vec2<i32>) -> f32 {
return textureLoad(depth_texture, at, 0).r;
}

// The scene's depth at uv (the first sample, if it is multisampled).
fn depth_at(uv: vec2<f32>) -> f32 {
let size = vec2<i32>(textureDimensions(depth_texture));
let at = clamp(vec2<i32>(uv * vec2<f32>(size)), vec2<i32>(0), size - 1);
return load_depth(at);
return textureSampleLevel(depth_texture, depth_sampler, uv, 0.0).r;
}

// Whether what is drawn at uv is in front of the lens, drawn after it in retail.
Expand Down
12 changes: 12 additions & 0 deletions crates/client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@
// Bevy systems take their parameters and `Query` filters as types, so both lints fire on them.
#![allow(clippy::too_many_arguments, clippy::type_complexity)]

/// Like `eprintln!`, which in a browser goes nowhere: there it goes to the console.
macro_rules! tell {
($($arg:tt)*) => {{
#[cfg(not(target_arch = "wasm32"))]
eprintln!($($arg)*);
#[cfg(target_arch = "wasm32")]
crate::web::console_warn(&format!($($arg)*));
}};
}

mod about;
mod accordion;
mod animation;
Expand Down Expand Up @@ -80,6 +90,7 @@ mod suggest;
mod toast;
mod track_picture;
mod tracker;
mod unlit;
mod version;
mod weapon;
mod weather;
Expand Down Expand Up @@ -247,6 +258,7 @@ fn main() {
.add_plugins(sky::SkyPlugin)
.add_plugins(drop_map::DropMapPlugin)
.add_plugins(MaterialPlugin::<SubtractMaterial>::default())
.add_plugins(unlit::UnlitPlugin)
.insert_resource(Time::<Fixed>::from_hz(GAME_TICK_HZ))
.add_systems(
FixedUpdate,
Expand Down
22 changes: 10 additions & 12 deletions crates/client/src/maps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub fn load() {
let (mut id, hash, map) = match read {
Ok(read) => read,
Err(e) => {
eprintln!("{}: {e}", path.display());
tell!("{}: {e}", path.display());
continue;
}
};
Expand All @@ -78,14 +78,14 @@ pub fn load() {
} else {
"a custom track cannot be named for one of the game's tracks"
};
eprintln!("{}: {why}", path.display());
tell!("{}: {why}", path.display());
continue;
}
if builtins {
match builtin::level_of(&map.name) {
Some(level) => id = level.0,
None => {
eprintln!("{}: not one of the game's tracks", path.display());
tell!("{}: not one of the game's tracks", path.display());
continue;
}
}
Expand All @@ -103,7 +103,7 @@ pub fn load() {
for bytes in crate::web::take_maps() {
match Map::from_bytes(&bytes).and_then(|m| Ok((m.id()?, m.hash()?, m))) {
Ok((_, _, map)) if builtin::is_builtin_name(&map.name) => {
eprintln!(
tell!(
"{}: a custom track cannot be named for one of the game's tracks",
map.name
);
Expand All @@ -114,7 +114,7 @@ pub fn load() {
file: File::Held(bytes),
map: Box::leak(Box::new(map)),
}),
Err(e) => eprintln!("A kept track: {e}"),
Err(e) => tell!("A kept track: {e}"),
}
}
maps.sort_by(|a, b| a.map.name.cmp(&b.map.name));
Expand Down Expand Up @@ -321,15 +321,13 @@ pub fn make_builtins() {
return;
}
let Some(image) = crate::disc::image() else {
eprintln!(
"The game's own tracks are missing, and there is no disc image to make them from"
);
tell!("The game's own tracks are missing, and there is no disc image to make them from");
return;
};
eprintln!("Making the game's own tracks from the disc (this is done once)...");
tell!("Making the game's own tracks from the disc (this is done once)...");
match map::builtin::export(&image, &dir) {
Ok(made) => eprintln!("Made {made} tracks in {}", dir.display()),
Err(e) => eprintln!("Could not make the game's own tracks: {e}"),
Ok(made) => tell!("Made {made} tracks in {}", dir.display()),
Err(e) => tell!("Could not make the game's own tracks: {e}"),
}
}

Expand All @@ -343,7 +341,7 @@ pub fn make_builtins() {
let made = match builtin::make_all(&image) {
Ok(made) => made,
Err(e) => {
eprintln!("Could not make the game's own tracks: {e}");
tell!("Could not make the game's own tracks: {e}");
return;
}
};
Expand Down
2 changes: 2 additions & 0 deletions crates/client/src/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,8 @@ pub(crate) const COMMANDS: &[Spec] = &[
"How much of the resolution to draw",
"<0.25 to 1>",
),
// (A browser has no msaa either, see `config.rs`.)
#[cfg(not(target_arch = "wasm32"))]
node(
"msaa",
"Anti-aliasing samples of the cameras",
Expand Down
Loading
Loading