mirror of
https://github.com/lisk77/comet.git
synced 2025-12-12 17:18:50 +00:00
Compare commits
No commits in common. "9312dc4444d0fbd848fa1a03e1b5422a8ca164c9" and "676b1dcb18c58dd81619ce7ff68e61b684b19871" have entirely different histories.
9312dc4444
...
676b1dcb18
9 changed files with 511 additions and 284 deletions
|
|
@ -1,12 +1,12 @@
|
|||
use crate::renderer::Renderer;
|
||||
use crate::{
|
||||
camera::CameraManager,
|
||||
render_context::RenderContext,
|
||||
render_pass::{universal_clear_execute, universal_load_execute, RenderPass},
|
||||
renderer::Renderer,
|
||||
};
|
||||
use comet_colors::Color;
|
||||
use comet_ecs::{Component, Render, Render2D, Transform2D};
|
||||
use comet_log::*;
|
||||
use comet_log::{debug, error, info};
|
||||
use comet_resources::{
|
||||
font::Font, graphic_resource_manager::GraphicResourceManager, texture_atlas::*, Texture, Vertex,
|
||||
};
|
||||
|
|
@ -541,28 +541,13 @@ impl<'a> Renderer2D<'a> {
|
|||
}
|
||||
|
||||
fn get_glyph_region(&self, glyph: char, font: String) -> &TextureRegion {
|
||||
let key = format!("{}::{}", font, glyph);
|
||||
|
||||
match self.resource_manager.font_atlas().textures().get(&key) {
|
||||
Some(region) => region,
|
||||
None => {
|
||||
warn!(
|
||||
"Missing glyph for character '{}' in font '{}', using fallback.",
|
||||
glyph, font
|
||||
);
|
||||
let fallback_key = format!("{}:: ", font);
|
||||
self.resource_manager
|
||||
.font_atlas()
|
||||
.textures()
|
||||
.get(&fallback_key)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"No fallback glyph available (space also missing) for font '{}'",
|
||||
font
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
let font_atlas = self
|
||||
.resource_manager
|
||||
.fonts()
|
||||
.iter()
|
||||
.find(|f| f.name() == font)
|
||||
.unwrap();
|
||||
font_atlas.get_glyph(glyph).unwrap()
|
||||
}
|
||||
|
||||
pub fn add_text_to_buffers(
|
||||
|
|
|
|||
|
|
@ -5,12 +5,11 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
comet_log = { path = "../comet_log" }
|
||||
wgpu = { version = "22.0", features = ["glsl", "wgsl", "naga-ir"] }
|
||||
wgpu = { version = "22.0", features = ["spirv"] }
|
||||
anyhow = "1.0"
|
||||
bytemuck = { version = "1.16", features = [ "derive" ] }
|
||||
ab_glyph = "0.2.29"
|
||||
chrono = "0.4.40"
|
||||
rect_packer = "0.2.1"
|
||||
|
||||
[dependencies.image]
|
||||
version = "0.24"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ use crate::{
|
|||
Texture,
|
||||
};
|
||||
use comet_log::info;
|
||||
use wgpu::{naga::ShaderStage, Device, Queue, ShaderModule};
|
||||
use wgpu::{
|
||||
naga::{self, ShaderStage},
|
||||
Device, Queue, ShaderModule,
|
||||
};
|
||||
|
||||
pub struct GraphicResourceManager {
|
||||
texture_atlas: TextureAtlas,
|
||||
|
|
@ -124,7 +127,7 @@ impl GraphicResourceManager {
|
|||
source: wgpu::ShaderSource::Glsl {
|
||||
shader: shader_source.into(),
|
||||
stage,
|
||||
defines: Default::default(),
|
||||
defines: naga::FastHashMap::default(),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use crate::font::*;
|
||||
use comet_log::*;
|
||||
use image::{DynamicImage, GenericImage, GenericImageView, RgbaImage};
|
||||
use rect_packer::{Config, Packer, Rect};
|
||||
use image::{DynamicImage, GenericImage, GenericImageView};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
|
|
@ -82,332 +81,335 @@ pub struct TextureAtlas {
|
|||
impl TextureAtlas {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
atlas: DynamicImage::new_rgba8(1, 1),
|
||||
atlas: DynamicImage::new(1, 1, image::ColorType::Rgb8),
|
||||
textures: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn texture_paths(&self) -> Vec<String> {
|
||||
self.textures.keys().cloned().collect()
|
||||
self.textures.keys().map(|k| k.to_string()).collect()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn next_power_of_two(mut x: u32) -> u32 {
|
||||
if x == 0 {
|
||||
return 1;
|
||||
fn calculate_atlas_width(textures: &Vec<DynamicImage>) -> u32 {
|
||||
let mut last_height: u32 = textures.get(0).unwrap().height();
|
||||
let mut widths: Vec<u32> = Vec::new();
|
||||
let mut current_width: u32 = 0;
|
||||
|
||||
for texture in textures {
|
||||
if last_height != texture.height() {
|
||||
widths.push(current_width);
|
||||
current_width = 0;
|
||||
last_height = texture.height();
|
||||
}
|
||||
x -= 1;
|
||||
x |= x >> 1;
|
||||
x |= x >> 2;
|
||||
x |= x >> 4;
|
||||
x |= x >> 8;
|
||||
x |= x >> 16;
|
||||
x + 1
|
||||
current_width += texture.width();
|
||||
}
|
||||
|
||||
fn pack_textures(
|
||||
textures: &[(&String, &DynamicImage)],
|
||||
padding: u32,
|
||||
) -> (u32, u32, HashMap<String, Rect>) {
|
||||
let mut atlas_size = 512;
|
||||
let max_size = 8192;
|
||||
widths.push(current_width);
|
||||
|
||||
let valid_textures: Vec<(String, DynamicImage)> = textures
|
||||
.iter()
|
||||
.map(|(name, tex)| {
|
||||
let (w, h) = (tex.width(), tex.height());
|
||||
if w == 0 || h == 0 {
|
||||
warn!(
|
||||
"Texture '{}' has invalid size {}x{}, replacing with 1x1 transparent dummy.",
|
||||
name, w, h
|
||||
);
|
||||
let mut img = RgbaImage::new(1, 1);
|
||||
img.put_pixel(0, 0, image::Rgba([0, 0, 0, 0]));
|
||||
(name.to_string(), DynamicImage::ImageRgba8(img))
|
||||
} else {
|
||||
((*name).clone(), (*tex).clone())
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if valid_textures.is_empty() {
|
||||
error!("No valid textures to pack!");
|
||||
return (0, 0, HashMap::new());
|
||||
*widths.iter().max().unwrap()
|
||||
}
|
||||
|
||||
loop {
|
||||
let config = Config {
|
||||
width: atlas_size as i32,
|
||||
height: atlas_size as i32,
|
||||
border_padding: padding as i32,
|
||||
rectangle_padding: padding as i32,
|
||||
};
|
||||
fn calculate_atlas_height(textures: &Vec<DynamicImage>) -> u32 {
|
||||
let last_height: u32 = textures.get(0).unwrap().height();
|
||||
let mut height: u32 = 0;
|
||||
height += last_height;
|
||||
|
||||
let mut packer = Packer::new(config);
|
||||
let mut placements = HashMap::new();
|
||||
let mut max_x = 0i32;
|
||||
let mut max_y = 0i32;
|
||||
let mut failed = false;
|
||||
|
||||
for (name, tex) in &valid_textures {
|
||||
let width = tex.width() as i32;
|
||||
let height = tex.height() as i32;
|
||||
|
||||
if width > atlas_size as i32 || height > atlas_size as i32 {
|
||||
error!(
|
||||
"Texture '{}' is too large ({width}x{height}) for current atlas size {atlas_size}x{atlas_size}",
|
||||
name
|
||||
);
|
||||
failed = true;
|
||||
break;
|
||||
for texture in textures {
|
||||
if last_height == texture.height() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(rect) = packer.pack(width, height, false) {
|
||||
max_x = max_x.max(rect.x + rect.width);
|
||||
max_y = max_y.max(rect.y + rect.height);
|
||||
placements.insert(name.clone(), rect);
|
||||
} else {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
height += texture.height();
|
||||
}
|
||||
|
||||
if failed {
|
||||
if atlas_size >= max_size {
|
||||
error!(
|
||||
"Failed to pack all textures even at max atlas size ({}x{}).",
|
||||
max_size, max_size
|
||||
);
|
||||
return (max_x as u32, max_y as u32, placements);
|
||||
height
|
||||
}
|
||||
|
||||
info!(
|
||||
"Atlas size {}x{} too small, doubling to {}x{}...",
|
||||
atlas_size,
|
||||
atlas_size,
|
||||
atlas_size * 2,
|
||||
atlas_size * 2
|
||||
);
|
||||
atlas_size *= 2;
|
||||
} else {
|
||||
info!(
|
||||
"Created texture atlas ({}x{}) with {} textures.",
|
||||
atlas_size,
|
||||
atlas_size,
|
||||
placements.len()
|
||||
);
|
||||
return (max_x as u32, max_y as u32, placements);
|
||||
fn insert_texture_at(base: &mut DynamicImage, texture: &DynamicImage, x_pos: u32, y_pos: u32) {
|
||||
for y in 0..texture.height() {
|
||||
for x in 0..texture.width() {
|
||||
let pixel = texture.get_pixel(x, y);
|
||||
base.put_pixel(x + x_pos, y + y_pos, pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_atlas(
|
||||
textures: &[(&String, &DynamicImage)],
|
||||
placements: &HashMap<String, Rect>,
|
||||
atlas_width: u32,
|
||||
atlas_height: u32,
|
||||
) -> (RgbaImage, HashMap<String, TextureRegion>) {
|
||||
let mut base = RgbaImage::new(atlas_width, atlas_height);
|
||||
let mut regions = HashMap::new();
|
||||
|
||||
for (name, tex) in textures {
|
||||
if let Some(rect) = placements.get(*name) {
|
||||
base.copy_from(&tex.to_rgba8(), rect.x as u32, rect.y as u32)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"Failed to blit texture '{}' into atlas at ({}, {})",
|
||||
name, rect.x, rect.y
|
||||
)
|
||||
});
|
||||
|
||||
let u0 = rect.x as f32 / atlas_width as f32;
|
||||
let v0 = rect.y as f32 / atlas_height as f32;
|
||||
let u1 = (rect.x + rect.width) as f32 / atlas_width as f32;
|
||||
let v1 = (rect.y + rect.height) as f32 / atlas_height as f32;
|
||||
|
||||
regions.insert(
|
||||
(*name).clone(),
|
||||
TextureRegion::new(
|
||||
u0,
|
||||
v0,
|
||||
u1,
|
||||
v1,
|
||||
(rect.width as u32, rect.height as u32),
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
(base, regions)
|
||||
}
|
||||
|
||||
pub fn from_texture_paths(paths: Vec<String>) -> Self {
|
||||
let mut textures = Vec::new();
|
||||
let mut textures: Vec<DynamicImage> = Vec::new();
|
||||
let mut regions: HashMap<String, TextureRegion> = HashMap::new();
|
||||
|
||||
info!("Loading textures...");
|
||||
|
||||
for path in &paths {
|
||||
let img = image::open(Path::new(path)).expect("Failed to load texture");
|
||||
textures.push((path, img));
|
||||
textures.push(image::open(&Path::new(path.as_str())).expect("Failed to load texture"));
|
||||
}
|
||||
|
||||
info!("Packing textures...");
|
||||
let tex_refs: Vec<(&String, &DynamicImage)> =
|
||||
textures.iter().map(|(p, i)| (*p, i)).collect();
|
||||
info!("Textures loaded!");
|
||||
info!("Sorting textures by height...");
|
||||
|
||||
let (atlas_w, atlas_h, placements) = Self::pack_textures(&tex_refs, 2);
|
||||
let mut texture_path_pairs: Vec<(&DynamicImage, &String)> =
|
||||
textures.iter().zip(paths.iter()).collect();
|
||||
texture_path_pairs.sort_by(|a, b| b.0.height().cmp(&a.0.height()));
|
||||
let (sorted_textures, sorted_paths): (Vec<&DynamicImage>, Vec<&String>) =
|
||||
texture_path_pairs.into_iter().unzip();
|
||||
let sorted_textures: Vec<DynamicImage> =
|
||||
sorted_textures.into_iter().map(|t| t.clone()).collect();
|
||||
let sorted_paths: Vec<String> = sorted_paths.into_iter().map(|s| s.to_string()).collect();
|
||||
|
||||
let atlas_w = Self::next_power_of_two(atlas_w);
|
||||
let atlas_h = Self::next_power_of_two(atlas_h);
|
||||
|
||||
let (base, regions) = Self::build_atlas(&tex_refs, &placements, atlas_w, atlas_h);
|
||||
|
||||
info!(
|
||||
"Created texture atlas ({}x{}) with {} textures.",
|
||||
atlas_w,
|
||||
atlas_h,
|
||||
regions.len()
|
||||
let (height, width) = (
|
||||
Self::calculate_atlas_height(&sorted_textures),
|
||||
Self::calculate_atlas_width(&sorted_textures),
|
||||
);
|
||||
let mut base = DynamicImage::new_rgba8(width, height);
|
||||
|
||||
let mut previous = sorted_textures.get(0).unwrap().height();
|
||||
let mut x_offset: u32 = 0;
|
||||
let mut y_offset: u32 = 0;
|
||||
|
||||
info!("Creating texture atlas...");
|
||||
|
||||
for (texture, path) in sorted_textures.iter().zip(sorted_paths.iter()) {
|
||||
if texture.height() != previous {
|
||||
y_offset += previous;
|
||||
x_offset = 0;
|
||||
previous = texture.height();
|
||||
}
|
||||
|
||||
Self::insert_texture_at(&mut base, &texture, x_offset, y_offset);
|
||||
let texel_w = 0.5 / width as f32;
|
||||
let texel_h = 0.5 / height as f32;
|
||||
|
||||
let u0 = (x_offset as f32 + texel_w) / width as f32;
|
||||
let v0 = (y_offset as f32 + texel_h) / height as f32;
|
||||
let u1 = ((x_offset + texture.width()) as f32 - texel_w) / width as f32;
|
||||
let v1 = ((y_offset + texture.height()) as f32 - texel_h) / height as f32;
|
||||
|
||||
regions.insert(
|
||||
path.to_string(),
|
||||
TextureRegion::new(u0, v0, u1, v1, texture.dimensions(), 0.0, 0.0, 0.0),
|
||||
);
|
||||
x_offset += texture.width();
|
||||
}
|
||||
|
||||
info!("Texture atlas created!");
|
||||
|
||||
TextureAtlas {
|
||||
atlas: DynamicImage::ImageRgba8(base),
|
||||
atlas: base,
|
||||
textures: regions,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_textures(names: Vec<String>, textures: Vec<DynamicImage>) -> Self {
|
||||
assert_eq!(
|
||||
names.len(),
|
||||
textures.len(),
|
||||
"Names and textures must have the same length."
|
||||
let mut regions: HashMap<String, TextureRegion> = HashMap::new();
|
||||
|
||||
info!("Sorting textures by height...");
|
||||
|
||||
let mut texture_path_pairs: Vec<(&DynamicImage, &String)> =
|
||||
textures.iter().zip(names.iter()).collect();
|
||||
texture_path_pairs.sort_by(|a, b| b.0.height().cmp(&a.0.height()));
|
||||
let (sorted_textures, sorted_paths): (Vec<&DynamicImage>, Vec<&String>) =
|
||||
texture_path_pairs.into_iter().unzip();
|
||||
let sorted_textures: Vec<DynamicImage> =
|
||||
sorted_textures.into_iter().map(|t| t.clone()).collect();
|
||||
let sorted_paths: Vec<String> = sorted_paths.into_iter().map(|s| s.to_string()).collect();
|
||||
|
||||
let (height, width) = (
|
||||
Self::calculate_atlas_height(&sorted_textures),
|
||||
Self::calculate_atlas_width(&sorted_textures),
|
||||
);
|
||||
let mut base = DynamicImage::new_rgba8(width, height);
|
||||
|
||||
let tex_refs: Vec<(&String, &DynamicImage)> = names.iter().zip(textures.iter()).collect();
|
||||
let mut previous = sorted_textures.get(0).unwrap().height();
|
||||
let mut x_offset: u32 = 0;
|
||||
let mut y_offset: u32 = 0;
|
||||
|
||||
let (atlas_w, atlas_h, placements) = Self::pack_textures(&tex_refs, 2);
|
||||
let atlas_w = Self::next_power_of_two(atlas_w);
|
||||
let atlas_h = Self::next_power_of_two(atlas_h);
|
||||
info!("Creating texture atlas...");
|
||||
|
||||
let (base, regions) = Self::build_atlas(&tex_refs, &placements, atlas_w, atlas_h);
|
||||
for (texture, name) in sorted_textures.iter().zip(sorted_paths.iter()) {
|
||||
if texture.height() != previous {
|
||||
y_offset += previous;
|
||||
x_offset = 0;
|
||||
previous = texture.height();
|
||||
}
|
||||
|
||||
Self::insert_texture_at(&mut base, &texture, x_offset, y_offset);
|
||||
regions.insert(
|
||||
name.to_string(),
|
||||
TextureRegion::new(
|
||||
x_offset as f32 / width as f32,
|
||||
y_offset as f32 / height as f32,
|
||||
(x_offset + texture.width()) as f32 / width as f32,
|
||||
(y_offset + texture.height()) as f32 / height as f32,
|
||||
texture.dimensions(),
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
),
|
||||
);
|
||||
x_offset += texture.width();
|
||||
}
|
||||
|
||||
info!("Texture atlas created!");
|
||||
|
||||
TextureAtlas {
|
||||
atlas: DynamicImage::ImageRgba8(base),
|
||||
atlas: base,
|
||||
textures: regions,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_glyphs(glyphs: Vec<GlyphData>) -> Self {
|
||||
let textures: Vec<(String, DynamicImage)> = glyphs
|
||||
.iter()
|
||||
.map(|g| (g.name.clone(), g.render.clone()))
|
||||
.collect();
|
||||
pub fn from_glyphs(mut glyphs: Vec<GlyphData>) -> Self {
|
||||
glyphs.sort_by(|a, b| b.render.height().cmp(&a.render.height()));
|
||||
|
||||
let tex_refs: Vec<(&String, &DynamicImage)> =
|
||||
textures.iter().map(|(n, i)| (n, i)).collect();
|
||||
let height = Self::calculate_atlas_height(
|
||||
&glyphs.iter().map(|g| g.render.clone()).collect::<Vec<_>>(),
|
||||
);
|
||||
let width = Self::calculate_atlas_width(
|
||||
&glyphs.iter().map(|g| g.render.clone()).collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let (atlas_w, atlas_h, placements) = Self::pack_textures(&tex_refs, 2);
|
||||
let atlas_w = Self::next_power_of_two(atlas_w);
|
||||
let atlas_h = Self::next_power_of_two(atlas_h);
|
||||
let padding = (glyphs.len() * 3) as u32;
|
||||
|
||||
let mut base = RgbaImage::new(atlas_w, atlas_h);
|
||||
let mut base = DynamicImage::new_rgba8(width + padding, height);
|
||||
let mut regions = HashMap::new();
|
||||
let mut current_row_height = glyphs[0].render.height();
|
||||
let mut x_offset: u32 = 0;
|
||||
let mut y_offset: u32 = 0;
|
||||
|
||||
for g in glyphs.iter() {
|
||||
if let Some(rect) = placements.get(&g.name) {
|
||||
base.copy_from(&g.render.to_rgba8(), rect.x as u32, rect.y as u32)
|
||||
.unwrap();
|
||||
let glyph_w = g.render.width();
|
||||
let glyph_h = g.render.height();
|
||||
|
||||
let u0 = rect.x as f32 / atlas_w as f32;
|
||||
let v0 = rect.y as f32 / atlas_h as f32;
|
||||
let u1 = (rect.x + rect.width) as f32 / atlas_w as f32;
|
||||
let v1 = (rect.y + rect.height) as f32 / atlas_h as f32;
|
||||
if glyph_h != current_row_height {
|
||||
y_offset += current_row_height + 3;
|
||||
x_offset = 0;
|
||||
current_row_height = glyph_h;
|
||||
}
|
||||
|
||||
Self::insert_texture_at(&mut base, &g.render, x_offset, y_offset);
|
||||
|
||||
let u0 = x_offset as f32 / (width + padding) as f32;
|
||||
let v0 = y_offset as f32 / height as f32;
|
||||
let u1 = (x_offset + glyph_w) as f32 / (width + padding) as f32;
|
||||
let v1 = (y_offset + glyph_h) as f32 / height as f32;
|
||||
|
||||
let region = TextureRegion::new(
|
||||
u0,
|
||||
v0,
|
||||
u1,
|
||||
v1,
|
||||
(rect.width as u32, rect.height as u32),
|
||||
(glyph_w, glyph_h),
|
||||
g.advance,
|
||||
g.offset_x,
|
||||
g.offset_y,
|
||||
);
|
||||
|
||||
regions.insert(g.name.clone(), region);
|
||||
}
|
||||
|
||||
x_offset += glyph_w + 3;
|
||||
}
|
||||
|
||||
TextureAtlas {
|
||||
atlas: DynamicImage::ImageRgba8(base),
|
||||
atlas: base,
|
||||
textures: regions,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_fonts(fonts: &[Font]) -> Self {
|
||||
pub fn from_fonts(fonts: &Vec<Font>) -> Self {
|
||||
if fonts.is_empty() {
|
||||
return Self::empty();
|
||||
}
|
||||
|
||||
let mut all_glyphs = Vec::new();
|
||||
let mut all_glyphs: Vec<(String, DynamicImage, TextureRegion)> = Vec::new();
|
||||
|
||||
for font in fonts {
|
||||
let mut font_indices: Vec<usize> = (0..fonts.len()).collect();
|
||||
font_indices.sort_by(|&a, &b| fonts[a].name().cmp(&fonts[b].name()));
|
||||
|
||||
for fi in font_indices {
|
||||
let font = &fonts[fi];
|
||||
let font_name = font.name();
|
||||
let src_atlas = font.glyphs().atlas();
|
||||
let atlas_width = src_atlas.width();
|
||||
let atlas_height = src_atlas.height();
|
||||
|
||||
for (glyph_name, region) in font.glyphs().textures() {
|
||||
let src_x = (region.u0() * atlas_width as f32) as u32;
|
||||
let src_y = (region.v0() * atlas_height as f32) as u32;
|
||||
let width = region.dimensions().0;
|
||||
let height = region.dimensions().1;
|
||||
let mut glyph_names: Vec<String> = font.glyphs().textures().keys().cloned().collect();
|
||||
glyph_names.sort();
|
||||
|
||||
let glyph_img = src_atlas.view(src_x, src_y, width, height).to_image();
|
||||
for glyph_name in glyph_names {
|
||||
let region = font.glyphs().textures().get(&glyph_name).unwrap();
|
||||
|
||||
let (u0, v0) = (region.u0(), region.v0());
|
||||
let (width, height) = region.dimensions();
|
||||
|
||||
let src_x = (u0 * font.glyphs().atlas().width() as f32) as u32;
|
||||
let src_y = (v0 * font.glyphs().atlas().height() as f32) as u32;
|
||||
|
||||
let glyph_img = DynamicImage::ImageRgba8(
|
||||
font.glyphs()
|
||||
.atlas()
|
||||
.view(src_x, src_y, width, height)
|
||||
.to_image(),
|
||||
);
|
||||
|
||||
let key = format!("{}::{}", font_name, glyph_name);
|
||||
all_glyphs.push((key, DynamicImage::ImageRgba8(glyph_img), region.clone()));
|
||||
|
||||
all_glyphs.push((key, glyph_img, region.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let tex_refs: Vec<(&String, &DynamicImage)> =
|
||||
all_glyphs.iter().map(|(n, i, _)| (n, i)).collect();
|
||||
let (atlas_w, atlas_h, placements) = Self::pack_textures(&tex_refs, 2);
|
||||
let atlas_w = Self::next_power_of_two(atlas_w);
|
||||
let atlas_h = Self::next_power_of_two(atlas_h);
|
||||
all_glyphs.sort_by(|a, b| {
|
||||
let ha = a.1.height();
|
||||
let hb = b.1.height();
|
||||
match hb.cmp(&ha) {
|
||||
std::cmp::Ordering::Equal => a.0.cmp(&b.0),
|
||||
other => other,
|
||||
}
|
||||
});
|
||||
|
||||
let mut base = RgbaImage::new(atlas_w, atlas_h);
|
||||
let textures: Vec<DynamicImage> =
|
||||
all_glyphs.iter().map(|(_, img, _)| img.clone()).collect();
|
||||
let atlas_height = Self::calculate_atlas_height(&textures);
|
||||
let atlas_width = Self::calculate_atlas_width(&textures);
|
||||
|
||||
let padding = (all_glyphs.len() * 3) as u32;
|
||||
let mut base = DynamicImage::new_rgba8(atlas_width + padding, atlas_height);
|
||||
let mut regions = HashMap::new();
|
||||
|
||||
let mut current_row_height = textures[0].height();
|
||||
let mut x_offset: u32 = 0;
|
||||
let mut y_offset: u32 = 0;
|
||||
|
||||
for (key, img, original_region) in all_glyphs {
|
||||
if let Some(rect) = placements.get(&key) {
|
||||
base.copy_from(&img.to_rgba8(), rect.x as u32, rect.y as u32)
|
||||
.unwrap();
|
||||
let w = img.width();
|
||||
let h = img.height();
|
||||
|
||||
let u0 = rect.x as f32 / atlas_w as f32;
|
||||
let v0 = rect.y as f32 / atlas_h as f32;
|
||||
let u1 = (rect.x + rect.width) as f32 / atlas_w as f32;
|
||||
let v1 = (rect.y + rect.height) as f32 / atlas_h as f32;
|
||||
if h != current_row_height {
|
||||
y_offset += current_row_height + 3;
|
||||
x_offset = 0;
|
||||
current_row_height = h;
|
||||
}
|
||||
|
||||
regions.insert(
|
||||
key,
|
||||
TextureRegion::new(
|
||||
Self::insert_texture_at(&mut base, &img, x_offset, y_offset);
|
||||
|
||||
let u0 = x_offset as f32 / (atlas_width + padding) as f32;
|
||||
let v0 = y_offset as f32 / atlas_height as f32;
|
||||
let u1 = (x_offset + w) as f32 / (atlas_width + padding) as f32;
|
||||
let v1 = (y_offset + h) as f32 / atlas_height as f32;
|
||||
|
||||
let region = TextureRegion::new(
|
||||
u0,
|
||||
v0,
|
||||
u1,
|
||||
v1,
|
||||
(rect.width as u32, rect.height as u32),
|
||||
(w, h),
|
||||
original_region.advance(),
|
||||
original_region.offset_x(),
|
||||
original_region.offset_y(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
regions.insert(key, region);
|
||||
|
||||
x_offset += w + 3;
|
||||
}
|
||||
|
||||
TextureAtlas {
|
||||
atlas: DynamicImage::ImageRgba8(base),
|
||||
atlas: base,
|
||||
textures: regions,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
42
res/shaders/base2d.wgsl
Normal file
42
res/shaders/base2d.wgsl
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Vertex shader
|
||||
struct CameraUniform {
|
||||
view_proj: mat4x4<f32>,
|
||||
};
|
||||
@group(1) @binding(0) // 1.
|
||||
var<uniform> camera: CameraUniform;
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) tex_coords: vec2<f32>,
|
||||
@location(2) color: vec4<f32>,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
model: VertexInput,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.tex_coords = model.tex_coords;
|
||||
out.color = model.color;
|
||||
out.clip_position = camera.view_proj * vec4<f32>(model.position, 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Fragment shader
|
||||
|
||||
@group(0) @binding(0)
|
||||
var t_diffuse: texture_2d<f32>;
|
||||
@group(0) @binding(1)
|
||||
var s_diffuse: sampler;
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let sample_color = textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
||||
return sample_color * in.color;
|
||||
}
|
||||
39
res/shaders/blacknwhite.wgsl
Normal file
39
res/shaders/blacknwhite.wgsl
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
struct CameraUniform {
|
||||
view_proj: mat4x4<f32>,
|
||||
};
|
||||
@group(1) @binding(0)
|
||||
var<uniform> camera: CameraUniform;
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) tex_coords: vec2<f32>,
|
||||
@location(2) color: vec4<f32>,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
model: VertexInput,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.tex_coords = model.tex_coords;
|
||||
out.color = model.color;
|
||||
out.clip_position = camera.view_proj * vec4<f32>(model.position, 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
@group(0) @binding(0)
|
||||
var t_diffuse: texture_2d<f32>;
|
||||
@group(0) @binding(1)
|
||||
var s_diffuse: sampler;
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let color = textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
||||
return vec4<f32>((color.r + color.g + color.b) / 3.0, (color.r + color.g + color.b) / 3.0, (color.r + color.g + color.b) / 3.0, color.a);
|
||||
}
|
||||
68
res/shaders/crt.wgsl
Normal file
68
res/shaders/crt.wgsl
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// Vertex shader
|
||||
struct CameraUniform {
|
||||
view_proj: mat4x4<f32>,
|
||||
};
|
||||
@group(1) @binding(0) // 1.
|
||||
var<uniform> camera: CameraUniform;
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) tex_coords: vec2<f32>,
|
||||
@location(2) color: vec4<f32>,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
model: VertexInput,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.tex_coords = model.tex_coords;
|
||||
out.color = model.color;
|
||||
out.clip_position = camera.view_proj * vec4<f32>(model.position, 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
@group(0) @binding(0)
|
||||
var t_diffuse: texture_2d<f32>; // Diffuse texture
|
||||
@group(0) @binding(1)
|
||||
var s_diffuse: sampler; // Sampler for the texture
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// Sample the texture using the input texture coordinates
|
||||
var texColor: vec4<f32> = textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
||||
|
||||
// Apply CRT curvature effect (distortion)
|
||||
let center = vec2<f32>(0.5, 0.5); // center of the screen
|
||||
let dist = distance(in.tex_coords, center); // Distance from the center
|
||||
let curvature = 0.1; // Adjust for more or less curvature
|
||||
var distorted_uv = in.tex_coords + (in.tex_coords - center) * curvature * dist;
|
||||
|
||||
// Apply chromatic aberration by slightly offsetting the UV coordinates
|
||||
let redOffset = 0.005;
|
||||
let greenOffset = 0.0;
|
||||
let blueOffset = -0.005;
|
||||
|
||||
let redColor = textureSample(t_diffuse, s_diffuse, distorted_uv + vec2<f32>(redOffset, 0.0));
|
||||
let greenColor = textureSample(t_diffuse, s_diffuse, distorted_uv + vec2<f32>(greenOffset, 0.0));
|
||||
let blueColor = textureSample(t_diffuse, s_diffuse, distorted_uv + vec2<f32>(blueOffset, 0.0));
|
||||
|
||||
// Combine chromatic aberration with the original color
|
||||
texColor.r = redColor.r;
|
||||
texColor.g = greenColor.g;
|
||||
texColor.b = blueColor.b;
|
||||
|
||||
// Apply scanline effect (darken even rows for scanlines)
|
||||
let scanlineEffect = 0.1 * (sin(in.clip_position.y * 0.3) + 1.0); // Horizontal scanlines
|
||||
texColor.r *= scanlineEffect; // Apply scanline effect to red channel
|
||||
texColor.g *= scanlineEffect; // Apply scanline effect to green channel
|
||||
texColor.b *= scanlineEffect; // Apply scanline effect to blue channel
|
||||
|
||||
return texColor;
|
||||
}
|
||||
89
res/shaders/glitch.wgsl
Normal file
89
res/shaders/glitch.wgsl
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
// Vertex shader
|
||||
struct CameraUniform {
|
||||
view_proj: mat4x4<f32>,
|
||||
};
|
||||
@group(1) @binding(0) // 1.
|
||||
var<uniform> camera: CameraUniform;
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) tex_coords: vec2<f32>,
|
||||
@location(2) color: vec4<f32>,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
model: VertexInput,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.tex_coords = model.tex_coords;
|
||||
out.color = model.color;
|
||||
out.clip_position = camera.view_proj * vec4<f32>(model.position, 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
@group(0) @binding(0)
|
||||
var t_diffuse: texture_2d<f32>; // Diffuse texture
|
||||
@group(0) @binding(1)
|
||||
var s_diffuse: sampler; // Sampler for the texture
|
||||
|
||||
// A simple pseudo-random number generator (using fragment coordinates)
|
||||
fn rand2D(p: vec2<f32>) -> f32 {
|
||||
let s = sin(dot(p, vec2<f32>(12.9898, 78.233))); // Pseudorandom calculation
|
||||
return fract(s * 43758.5453); // Return value between 0 and 1
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// Sample the texture using the input texture coordinates
|
||||
var texColor: vec4<f32> = textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
||||
|
||||
// Random horizontal displacement for glitching effect
|
||||
let glitchStrength = 0.03; // How far the texture will "glitch"
|
||||
let glitchAmount = rand2D(in.tex_coords * 100.0); // Use a different scale for more variation
|
||||
var displacedUV = in.tex_coords + vec2<f32>(glitchAmount * glitchStrength, 0.0);
|
||||
|
||||
// Sample the texture at the displaced position
|
||||
texColor = textureSample(t_diffuse, s_diffuse, displacedUV);
|
||||
|
||||
// Apply random color flickering
|
||||
let colorFlickerAmount = rand2D(in.tex_coords * 50.0); // More frequency for faster flickering
|
||||
texColor.r *= mix(0.7, 1.3, colorFlickerAmount); // Randomly adjust red channel brightness
|
||||
texColor.g *= mix(0.7, 1.3, colorFlickerAmount); // Randomly adjust green channel brightness
|
||||
texColor.b *= mix(0.7, 1.3, colorFlickerAmount); // Randomly adjust blue channel brightness
|
||||
|
||||
// Occasionally "flicker" the texture to simulate complete signal loss
|
||||
let flickerChance = rand2D(in.tex_coords * 200.0); // Different scale for randomness
|
||||
if (flickerChance < 0.05) { // 5% chance to completely "flicker" out
|
||||
texColor.r = 0.0; // Turn red channel to black
|
||||
texColor.g = 0.0; // Turn green channel to black
|
||||
texColor.b = 0.0; // Turn blue channel to black
|
||||
}
|
||||
|
||||
// Apply random horizontal offset to simulate screen tearing
|
||||
let tearEffect = rand2D(in.tex_coords * 25.0); // Vary this value for different effects
|
||||
if (tearEffect < 0.15) {
|
||||
texColor.r = 1.0; // Simulate red "tear"
|
||||
texColor.g = 0.0; // No green in the tear
|
||||
texColor.b = 0.0; // No blue in the tear
|
||||
} else if (tearEffect < 0.3) {
|
||||
texColor.r = 0.0; // No red in the tear
|
||||
texColor.g = 1.0; // Simulate green "tear"
|
||||
texColor.b = 0.0; // No blue in the tear
|
||||
}
|
||||
|
||||
// Optionally, you can add a "flickering noise" layer for additional effect
|
||||
let noiseAmount = rand2D(in.tex_coords * 500.0); // Highly random noise for flickering
|
||||
texColor.r += noiseAmount * 0.1; // Add small amount of random noise to red channel
|
||||
texColor.g += noiseAmount * 0.1; // Add small amount of random noise to green channel
|
||||
texColor.b += noiseAmount * 0.1; // Add small amount of random noise to blue channel
|
||||
|
||||
// Return the altered texture color
|
||||
return texColor;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue