use crate::blit::Blit;
use crate::buffer::GpuVecBuffer;
use crate::sprites::{EmoticonSprite, GameSprite, ParticleSprite, TeeSprite};
use crate::textures::Samplers;
use crate::{Camera, GpuCamera, LABEL};
use image::{GenericImageView, RgbaImage, SubImage};
use std::collections::HashMap;
use std::marker::PhantomData;
use std::num::{NonZeroU32, NonZeroU64};
use std::sync::Arc;
use vek::Vec2;
use wgpu::{
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
BindGroupLayoutEntry, BindingResource, BindingType, BufferBinding, Device, Queue, ShaderStages,
Texture, TextureSampleType, TextureView, TextureViewDescriptor, TextureViewDimension,
};
use super::ExtraSprite;
pub struct SpriteTextures {
textures: Vec<Texture>,
texture_views: Vec<TextureView>,
compatibility: bool,
sprite_bind_groups: Vec<BindGroup>,
bind_group_layout: BindGroupLayout,
blit: Blit,
samplers: Arc<Samplers>,
skins: HashMap<String, AtlasToken<TeeSprite>>,
invalid_texture: TextureToken,
blank_texture: TextureToken,
pub default_skin: AtlasToken<TeeSprite>,
pub ninja_skin: AtlasToken<TeeSprite>,
pub game_skin: AtlasToken<GameSprite>,
pub particles: AtlasToken<ParticleSprite>,
pub emoticons: AtlasToken<EmoticonSprite>,
pub extras: AtlasToken<ExtraSprite>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct AtlasToken<T: AtlasSegments>(u32, PhantomData<T>);
impl<T: AtlasSegments> AtlasToken<T> {
pub const INVALID: Self = Self(0, PhantomData);
pub fn select(self, segment: T) -> TextureToken {
match self.0 {
0 => TextureToken::INVALID,
_ => TextureToken(self.0 + segment.to_index() as u32),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C)]
pub struct TextureToken(u32);
impl TextureToken {
pub const INVALID: Self = Self(0);
}
impl SpriteTextures {
pub fn new(
device: &Device,
camera_buffer: BufferBinding,
samplers: &Arc<Samplers>,
queue: &Queue,
) -> Self {
let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: LABEL,
entries: &[
GpuCamera::bind_group_layout_entry(0, true),
Self::bind_group_layout_entry(3),
Samplers::bind_group_layout_entry(4),
],
});
let mut new = Self {
textures: Vec::new(),
texture_views: Vec::new(),
compatibility: true,
sprite_bind_groups: Vec::new(),
bind_group_layout,
blit: Blit::new(device),
samplers: samplers.clone(),
skins: HashMap::new(),
invalid_texture: TextureToken::INVALID,
blank_texture: TextureToken::INVALID,
default_skin: AtlasToken::INVALID,
ninja_skin: AtlasToken::INVALID,
game_skin: AtlasToken::INVALID,
particles: AtlasToken::INVALID,
emoticons: AtlasToken::INVALID,
extras: AtlasToken::INVALID,
};
let invalid_texture = RgbaImage::from_fn(2, 2, |x, y| {
image::Rgba([255, x as u8 * 255, y as u8 * 255, 255])
});
let blank_texture = RgbaImage::from_pixel(1, 1, image::Rgba([255; 4]));
new.invalid_texture =
new.register_image(&invalid_texture, camera_buffer.clone(), device, queue);
new.blank_texture = new.register_image(&blank_texture, camera_buffer, device, queue);
new
}
pub fn texture_count(&self) -> usize {
self.textures.len()
}
pub fn chunked_bind_groups(
&self,
layouts: &(BindGroupLayout, BindGroupLayout),
camera_buffer: BufferBinding,
texture_indices: &GpuVecBuffer<TextureToken>,
device: &Device,
) -> (BindGroup, BindGroup) {
let views: Vec<&TextureView> = self.texture_views.iter().collect();
(
device.create_bind_group(&BindGroupDescriptor {
label: Some("Chunked Camera"),
layout: &layouts.0,
entries: &[BindGroupEntry {
binding: 0,
resource: BindingResource::Buffer(camera_buffer),
}],
}),
device.create_bind_group(&BindGroupDescriptor {
label: Some("Chunked Texture Bind Array"),
layout: &layouts.1,
entries: &[
BindGroupEntry {
binding: 2,
resource: BindingResource::Buffer(texture_indices.binding(0, None)),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::TextureViewArray(views.as_slice()),
},
self.samplers.clamp_to_edge(4),
],
}),
)
}
fn chunked_group_layout_entry(&self, binding: u32) -> BindGroupLayoutEntry {
BindGroupLayoutEntry {
binding,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
multisampled: false,
},
count: Some(NonZeroU32::new(self.texture_count() as u32).unwrap()),
}
}
pub fn chunked_group_layouts(&self, device: &Device) -> (BindGroupLayout, BindGroupLayout) {
(
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("Texture Bind Array"),
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX,
ty: BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: Some(
NonZeroU64::new(std::mem::size_of::<Camera>() as u64).unwrap(),
),
},
count: None,
}],
}),
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("Texture Bind Array"),
entries: &[
BindGroupLayoutEntry {
binding: 2,
visibility: ShaderStages::VERTEX,
ty: BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: Some(NonZeroU64::new(4).unwrap()),
},
count: None,
},
self.chunked_group_layout_entry(3),
Samplers::bind_group_layout_entry(4),
],
}),
)
}
pub fn turn_off_compatibility(&mut self) {
self.compatibility = false;
}
pub fn blank_texture(&self) -> TextureToken {
self.blank_texture
}
pub fn bind_group_layout_entry(binding: u32) -> BindGroupLayoutEntry {
BindGroupLayoutEntry {
binding,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
multisampled: false,
},
count: None,
}
}
pub(crate) fn get_bind_group(&self, token: TextureToken) -> &BindGroup {
&self.sprite_bind_groups[token.0 as usize]
}
pub fn get_skin(&self, name: &str) -> AtlasToken<TeeSprite> {
*self.skins.get(name).unwrap_or(&self.default_skin)
}
pub fn register_image(
&mut self,
image: &RgbaImage,
camera_buffer: BufferBinding,
device: &Device,
queue: &Queue,
) -> TextureToken {
let texture = self.blit.upload_mipmapped(image, device, queue);
self.register_texture(texture, camera_buffer, device)
}
pub fn register_texture(
&mut self,
texture: Texture,
camera_buffer: BufferBinding<'_>,
device: &Device,
) -> TextureToken {
let view = texture.create_view(&TextureViewDescriptor::default());
if self.compatibility {
let bind_group = device.create_bind_group(&BindGroupDescriptor {
label: LABEL,
layout: &self.bind_group_layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::Buffer(camera_buffer),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::TextureView(&view),
},
self.samplers.clamp_to_edge(4),
],
});
self.sprite_bind_groups.push(bind_group);
}
self.textures.push(texture);
self.texture_views.push(view);
TextureToken(self.textures.len() as u32 - 1)
}
pub fn register_atlas_image<T: AtlasSegments>(
&mut self,
atlas: &RgbaImage,
camera_buffer: BufferBinding,
device: &Device,
queue: &Queue,
) -> AtlasToken<T> {
let textures = self.blit.upload_mipmapped_atlas::<T>(atlas, device, queue);
self.register_atlas_texture(textures, camera_buffer, device)
}
pub fn register_atlas_texture<T: AtlasSegments>(
&mut self,
textures: Vec<Texture>,
camera_buffer: BufferBinding,
device: &Device,
) -> AtlasToken<T> {
assert_eq!(textures.len(), T::ALL_ORDERED.len());
let token = AtlasToken(self.textures.len() as u32, PhantomData);
for texture in textures {
self.register_texture(texture, camera_buffer.clone(), device);
}
token
}
pub fn register_skin_image(
&mut self,
image: &RgbaImage,
name: String,
camera_buffer: BufferBinding,
device: &Device,
queue: &Queue,
) -> AtlasToken<TeeSprite> {
let texture = self
.blit
.upload_mipmapped_atlas::<TeeSprite>(image, device, queue);
self.register_skin_texture(texture, name, camera_buffer, device)
}
pub fn register_skin_texture(
&mut self,
texture: Vec<Texture>,
name: String,
camera_buffer: BufferBinding,
device: &Device,
) -> AtlasToken<TeeSprite> {
let token = self.register_atlas_texture(texture, camera_buffer, device);
self.skins.insert(name, token);
token
}
}
pub trait AtlasSegments: Copy + 'static {
fn to_index(self) -> usize;
const ALL_ORDERED: &'static [Self];
const TEXTURE_BLOCKS: Vec2<u8>;
fn blocks_offset(self) -> Vec2<u8>;
fn blocks_size(self) -> Vec2<u8>;
#[must_use]
fn check_image_dimensions(size: Vec2<u32>) -> bool {
let blocks = Self::TEXTURE_BLOCKS.az::<u32>();
if size.x % blocks.x != 0 || size.y % blocks.y != 0 {
return false;
}
size.y / blocks.y * blocks.x == size.x
}
fn sub_view(self, image: &RgbaImage) -> SubImage<&RgbaImage> {
let size = Vec2::new(image.width(), image.height());
assert!(Self::check_image_dimensions(size));
let pixels_per_block = size / Self::TEXTURE_BLOCKS.az::<u32>();
let offset = pixels_per_block * self.blocks_offset().az::<u32>();
let size = pixels_per_block * self.blocks_size().az::<u32>();
image.view(offset.x, offset.y, size.x, size.y)
}
}
impl AtlasSegments for TeeSprite {
fn to_index(self) -> usize {
self as usize
}
const ALL_ORDERED: &'static [Self] = &[
Self::Body,
Self::BodyOutline,
Self::EyeNormal,
Self::EyeAngry,
Self::EyePain,
Self::EyeHappy,
Self::EyeDead,
Self::EyeSurprise,
Self::Foot,
Self::FootOutline,
Self::Hand,
Self::HandOutline,
];
const TEXTURE_BLOCKS: Vec2<u8> = Vec2::new(8, 4);
fn blocks_offset(self) -> Vec2<u8> {
use TeeSprite::*;
match self {
Body => Vec2::new(0, 0),
BodyOutline => Vec2::new(3, 0),
EyeNormal => Vec2::new(2, 3),
EyeAngry => Vec2::new(3, 3),
EyePain => Vec2::new(4, 3),
EyeHappy => Vec2::new(5, 3),
EyeDead => Vec2::new(6, 3),
EyeSurprise => Vec2::new(7, 3),
Foot => Vec2::new(6, 1),
FootOutline => Vec2::new(6, 2),
Hand => Vec2::new(6, 0),
HandOutline => Vec2::new(7, 0),
}
}
fn blocks_size(self) -> Vec2<u8> {
use TeeSprite::*;
match self {
Body | BodyOutline => Vec2::new(3, 3),
EyeNormal | EyeAngry | EyePain | EyeHappy | EyeDead | EyeSurprise => Vec2::new(1, 1),
Foot | FootOutline => Vec2::new(2, 1),
Hand | HandOutline => Vec2::new(1, 1),
}
}
}
impl AtlasSegments for GameSprite {
fn to_index(self) -> usize {
self as usize
}
const ALL_ORDERED: &'static [Self] = &[
Self::Hook,
Self::HookTip,
Self::Hammer,
Self::Pistol,
Self::Shotgun,
Self::Grenade,
Self::Ninja,
Self::Laser,
Self::PistolProjectile,
Self::ShotgunProjectile,
Self::GrenadeProjectile,
Self::LaserProjectile,
Self::PistolMuzzle1,
Self::PistolMuzzle2,
Self::PistolMuzzle3,
Self::ShotgunMuzzle1,
Self::ShotgunMuzzle2,
Self::ShotgunMuzzle3,
Self::NinjaDash1,
Self::NinjaDash2,
Self::NinjaDash3,
Self::Heart,
Self::Shield,
Self::ShotgunShield,
Self::GrenadeShield,
Self::LaserShield,
Self::NinjaShield,
Self::BlueFlag,
Self::RedFlag,
];
const TEXTURE_BLOCKS: Vec2<u8> = Vec2::new(32, 16);
fn blocks_offset(self) -> Vec2<u8> {
use GameSprite::*;
match self {
Hook => Vec2::new(2, 0),
HookTip => Vec2::new(3, 0),
Hammer => Vec2::new(2, 1),
Pistol => Vec2::new(2, 4),
Shotgun => Vec2::new(2, 6),
Grenade => Vec2::new(2, 8),
Ninja => Vec2::new(2, 10),
Laser => Vec2::new(2, 12),
PistolProjectile => Vec2::new(6, 4),
ShotgunProjectile => Vec2::new(10, 6),
GrenadeProjectile => Vec2::new(10, 8),
LaserProjectile => Vec2::new(10, 12),
PistolMuzzle1 => Vec2::new(8, 4),
PistolMuzzle2 => Vec2::new(12, 4),
PistolMuzzle3 => Vec2::new(16, 4),
ShotgunMuzzle1 => Vec2::new(12, 6),
ShotgunMuzzle2 => Vec2::new(16, 6),
ShotgunMuzzle3 => Vec2::new(20, 6),
NinjaDash1 => Vec2::new(25, 0),
NinjaDash2 => Vec2::new(25, 4),
NinjaDash3 => Vec2::new(25, 8),
Heart => Vec2::new(10, 2),
Shield => Vec2::new(12, 2),
ShotgunShield => Vec2::new(15, 2),
GrenadeShield => Vec2::new(17, 2),
LaserShield => Vec2::new(19, 2),
NinjaShield => Vec2::new(10, 10),
BlueFlag => Vec2::new(12, 8),
RedFlag => Vec2::new(16, 8),
}
}
fn blocks_size(self) -> Vec2<u8> {
use GameSprite::*;
match self {
Hook => Vec2::new(1, 1),
HookTip => Vec2::new(2, 1),
Hammer => Vec2::new(4, 3),
Pistol => Vec2::new(4, 2),
Shotgun => Vec2::new(8, 2),
Grenade => Vec2::new(7, 2),
Ninja => Vec2::new(8, 2),
Laser => Vec2::new(7, 3),
PistolProjectile | ShotgunProjectile | GrenadeProjectile | LaserProjectile => {
Vec2::new(2, 2)
}
PistolMuzzle1 | PistolMuzzle2 | PistolMuzzle3 => Vec2::new(4, 2),
ShotgunMuzzle1 | ShotgunMuzzle2 | ShotgunMuzzle3 => Vec2::new(4, 2),
NinjaDash1 | NinjaDash2 | NinjaDash3 => Vec2::new(7, 4),
Heart | Shield | ShotgunShield | GrenadeShield | LaserShield | NinjaShield => {
Vec2::new(2, 2)
}
BlueFlag | RedFlag => Vec2::new(4, 8),
}
}
}
impl AtlasSegments for ParticleSprite {
fn to_index(self) -> usize {
self as usize
}
const ALL_ORDERED: &'static [Self] = &[
Self::Slice,
Self::Ball,
Self::Splat1,
Self::Splat2,
Self::Splat3,
Self::Smoke, Self::Shell,
Self::Explosion,
Self::AirJump,
Self::Hit,
];
const TEXTURE_BLOCKS: Vec2<u8> = Vec2::new(8, 8);
fn blocks_offset(self) -> Vec2<u8> {
match self {
ParticleSprite::Slice => Vec2::new(0, 0),
ParticleSprite::Ball => Vec2::new(1, 0),
ParticleSprite::Splat1 => Vec2::new(2, 0),
ParticleSprite::Splat2 => Vec2::new(3, 0),
ParticleSprite::Splat3 => Vec2::new(4, 0),
ParticleSprite::Smoke => Vec2::new(0, 1),
ParticleSprite::Shell => Vec2::new(0, 2),
ParticleSprite::Explosion => Vec2::new(0, 4),
ParticleSprite::AirJump => Vec2::new(2, 2),
ParticleSprite::Hit => Vec2::new(4, 1),
}
}
fn blocks_size(self) -> Vec2<u8> {
match self {
ParticleSprite::Slice => Vec2::new(1, 1),
ParticleSprite::Ball => Vec2::new(1, 1),
ParticleSprite::Splat1 => Vec2::new(1, 1),
ParticleSprite::Splat2 => Vec2::new(1, 1),
ParticleSprite::Splat3 => Vec2::new(1, 1),
ParticleSprite::Smoke => Vec2::new(1, 1),
ParticleSprite::Shell => Vec2::new(2, 2),
ParticleSprite::Explosion => Vec2::new(4, 4),
ParticleSprite::AirJump => Vec2::new(2, 2),
ParticleSprite::Hit => Vec2::new(2, 2),
}
}
}
impl AtlasSegments for twsnap::enums::Emoticon {
fn to_index(self) -> usize {
self as usize
}
const ALL_ORDERED: &'static [Self] = &[
Self::Oop,
Self::Exclamation,
Self::Hearts,
Self::Drop,
Self::Dotdot,
Self::Music,
Self::Sorry,
Self::Ghost,
Self::Sushi,
Self::Splattee,
Self::Deviltee,
Self::Zomg,
Self::Zzz,
Self::Wtf,
Self::Eyes,
Self::Question,
];
const TEXTURE_BLOCKS: Vec2<u8> = Vec2::new(4, 4);
fn blocks_offset(self) -> Vec2<u8> {
let index = self as u8;
Vec2::new(index % 4, index / 4)
}
fn blocks_size(self) -> Vec2<u8> {
Vec2::new(1, 1)
}
}
impl AtlasSegments for ExtraSprite {
fn to_index(self) -> usize {
self as usize
}
const ALL_ORDERED: &'static [Self] = &[Self::Snowflake];
const TEXTURE_BLOCKS: Vec2<u8> = Vec2::new(8, 8);
fn blocks_offset(self) -> Vec2<u8> {
match self {
Self::Snowflake => Vec2::new(0, 0),
}
}
fn blocks_size(self) -> Vec2<u8> {
match self {
Self::Snowflake => Vec2::new(1, 1),
}
}
}