use crate::context::WgpuClump;
use crate::engine_handle::Engine;
use crate::resource::{self, InProgressResource, LoadingOp, ResourceId, ResourceType};
use crate::vectors::Vec2;
use crate::{layouts, ERROR_TEXTURE_DATA};
use image::{GenericImageView, ImageError};
use std::fmt::Display;
use std::io::Error;
use std::path::Path;
pub struct Texture {
pub(crate) _view: wgpu::TextureView,
pub(crate) bind_group: wgpu::BindGroup,
pub(crate) size: Vec2<f32>,
}
impl Texture {
pub fn new<P>(engine: &mut Engine, path: P, loading_op: LoadingOp) -> ResourceId<Texture>
where
P: AsRef<Path>,
{
let typed_id = resource::generate_id::<Texture>();
let id = typed_id.get_id();
let path = path.as_ref();
let ip_resource = InProgressResource::new(
path,
id,
ResourceType::Image(
SamplerType::LinearInterpolation,
SamplerType::NearestNeighbor,
),
loading_op,
);
engine.loader.load(ip_resource, engine.get_proxy());
typed_id
}
pub fn new_with_sampler<P>(
engine: &mut Engine,
path: P,
sampler: SamplerType,
loading_op: LoadingOp,
) -> ResourceId<Texture>
where
P: AsRef<Path>,
{
let typed_id = resource::generate_id::<Texture>();
let id = typed_id.get_id();
let path = path.as_ref();
let ip_resource =
InProgressResource::new(path, id, ResourceType::Image(sampler, sampler), loading_op);
engine.loader.blocking_load(ip_resource, engine.get_proxy());
typed_id
}
pub fn new_with_mag_min_sampler<P>(
engine: &mut Engine,
path: P,
mag_sampler: SamplerType,
min_sampler: SamplerType,
loading_op: LoadingOp,
) -> ResourceId<Texture>
where
P: AsRef<Path>,
{
let typed_id = resource::generate_id::<Texture>();
let id = typed_id.get_id();
let path = path.as_ref();
let ip_resource = InProgressResource::new(
path,
id,
ResourceType::Image(mag_sampler, min_sampler),
loading_op,
);
engine.loader.blocking_load(ip_resource, engine.get_proxy());
typed_id
}
pub(crate) fn from_resource_data(
engine: &Engine,
label: Option<&str>,
data: Vec<u8>,
mag_sampler: SamplerType,
min_sampler: SamplerType,
) -> Result<Self, TextureError> {
let img = image::load_from_memory(&data)?;
Ok(Self::from_image(
engine,
img,
label,
mag_sampler,
min_sampler,
))
}
pub(crate) fn new_direct(
view: wgpu::TextureView,
bind_group: wgpu::BindGroup,
size: Vec2<f32>,
) -> Self {
Self {
_view: view,
bind_group,
size,
}
}
pub(crate) fn default(engine: &Engine) -> Self {
let image = image::load_from_memory(ERROR_TEXTURE_DATA).unwrap();
Self::from_image(
engine,
image,
Some("Error Texture"),
SamplerType::LinearInterpolation,
SamplerType::NearestNeighbor,
)
}
fn from_image(
engine: &Engine,
img: image::DynamicImage,
label: Option<&str>,
mag_filter: SamplerType,
min_filter: SamplerType,
) -> Self {
let wgpu = &engine.context.as_ref().expect("need graphic context").wgpu;
let diffuse_rgba = img.to_rgba8();
let (width, height) = img.dimensions();
let texture_size = wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
};
let texture = wgpu.device.create_texture(&wgpu::TextureDescriptor {
size: texture_size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
view_formats: &[],
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
label,
});
wgpu.queue.write_texture(
wgpu::ImageCopyTextureBase {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&diffuse_rgba,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * width),
rows_per_image: Some(height),
},
texture_size,
);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let bind_group_layout = layouts::create_texture_layout(&wgpu.device);
let texture_sampler = wgpu.device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: mag_filter.into(),
min_filter: min_filter.into(),
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let bind_group = wgpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&texture_sampler),
},
],
label: Some("diffuse_bind_group"),
});
let size = Vec2 {
x: width as f32,
y: height as f32,
};
Self {
_view: view,
bind_group,
size,
}
}
}
#[derive(Debug)]
pub(crate) enum TextureError {
IoError(Error),
ImageError(ImageError),
}
impl From<Error> for TextureError {
fn from(value: Error) -> TextureError {
Self::IoError(value)
}
}
impl From<ImageError> for TextureError {
fn from(value: ImageError) -> Self {
Self::ImageError(value)
}
}
pub struct UniformTexture {
inner_texture: Option<InnerTexture>,
size: Vec2<u32>,
mag_sampler: SamplerType,
min_sampler: SamplerType,
needs_update: bool,
}
impl UniformTexture {
pub fn new(engine: &Engine, size: Vec2<u32>) -> Self {
let inner_texture = engine.context.as_ref().map(|c| {
InnerTexture::from_wgpu(
size,
SamplerType::LinearInterpolation,
SamplerType::NearestNeighbor,
c.get_texture_format(),
&c.wgpu,
)
});
Self {
inner_texture,
size,
mag_sampler: SamplerType::LinearInterpolation,
min_sampler: SamplerType::NearestNeighbor,
needs_update: true,
}
}
pub fn new_with_sampler(
engine: &Engine,
size: Vec2<u32>,
mag_sampler: SamplerType,
min_sampler: SamplerType,
) -> Self {
let inner_texture = engine.context.as_ref().map(|c| {
InnerTexture::from_wgpu(
size,
mag_sampler,
min_sampler,
c.get_texture_format(),
&c.wgpu,
)
});
Self {
inner_texture,
size,
mag_sampler,
min_sampler,
needs_update: true,
}
}
pub(crate) fn resize(
&mut self,
new_size: Vec2<u32>,
wgpu: &WgpuClump,
format: wgpu::TextureFormat,
) {
if self.inner_texture.is_none() {
self.inner_texture = Some(InnerTexture::from_wgpu(
new_size,
self.mag_sampler,
self.min_sampler,
format,
wgpu,
));
self.size = new_size;
return;
}
self.inner_texture
.as_mut()
.unwrap()
.resize(new_size, wgpu, format);
self.needs_update = true;
}
pub fn get_size(&self) -> Vec2<u32> {
self.size
}
pub(crate) fn get_sampler(&self) -> &wgpu::Sampler {
&self.inner_texture.as_ref().unwrap().sampler
}
pub(crate) fn get_sampler_info(&self) -> (SamplerType, SamplerType) {
(self.mag_sampler, self.min_sampler)
}
pub(crate) fn make_render_view<'a>(
&'a mut self,
wgpu: &WgpuClump,
format: wgpu::TextureFormat,
) -> &'a wgpu::TextureView {
if self.inner_texture.is_none() {
self.inner_texture = Some(InnerTexture::from_wgpu(
self.size,
self.mag_sampler,
self.min_sampler,
format,
wgpu,
));
}
self.inner_texture.as_mut().unwrap().make_render_view()
}
pub(crate) fn make_view(
&mut self,
wgpu: &WgpuClump,
format: wgpu::TextureFormat,
) -> wgpu::TextureView {
if self.inner_texture.is_none() {
self.inner_texture = Some(InnerTexture::from_wgpu(
self.size,
self.mag_sampler,
self.min_sampler,
format,
wgpu,
));
}
self.inner_texture.as_ref().unwrap().make_view()
}
pub(crate) fn updated(&mut self) {
self.needs_update = false;
}
pub(crate) fn needs_update(&self) -> bool {
self.needs_update
}
}
struct InnerTexture {
inner_texture: wgpu::Texture,
view: wgpu::TextureView,
sampler: wgpu::Sampler,
}
impl InnerTexture {
fn from_wgpu(
size: Vec2<u32>,
mag_sampler: SamplerType,
min_sampler: SamplerType,
format: wgpu::TextureFormat,
wgpu: &WgpuClump,
) -> Self {
let sampler = wgpu.device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Uniform Texture Sampler"),
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: mag_sampler.into(),
min_filter: min_sampler.into(),
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let inner_texture = wgpu.device.create_texture(&wgpu::TextureDescriptor {
label: Some("Uniform Texture"),
size: wgpu::Extent3d {
width: size.x,
height: size.y,
depth_or_array_layers: 1,
},
dimension: wgpu::TextureDimension::D2,
mip_level_count: 1,
sample_count: 1,
format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let view = inner_texture.create_view(&wgpu::TextureViewDescriptor::default());
Self {
inner_texture,
view,
sampler,
}
}
fn resize(&mut self, new_size: Vec2<u32>, wgpu: &WgpuClump, format: wgpu::TextureFormat) {
let inner_texture = wgpu.device.create_texture(&wgpu::TextureDescriptor {
label: Some("Uniform Texture"),
size: wgpu::Extent3d {
width: new_size.x,
height: new_size.y,
depth_or_array_layers: 1,
},
dimension: wgpu::TextureDimension::D2,
mip_level_count: 1,
sample_count: 1,
format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let new_view = inner_texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("Uniform Texture View"),
..Default::default()
});
self.inner_texture = inner_texture;
self.view = new_view;
}
pub(crate) fn make_view(&self) -> wgpu::TextureView {
self.inner_texture
.create_view(&wgpu::TextureViewDescriptor {
label: Some("Uniform Texture View"),
..Default::default()
})
}
pub(crate) fn make_render_view(&mut self) -> &wgpu::TextureView {
self.view = self
.inner_texture
.create_view(&wgpu::TextureViewDescriptor {
label: Some("Uniform Texture View"),
..Default::default()
});
&self.view
}
}
impl std::error::Error for TextureError {}
impl Display for TextureError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::IoError(e) => write!(f, "{}", e),
Self::ImageError(e) => write!(f, "{}", e),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SamplerType {
NearestNeighbor,
LinearInterpolation,
}
impl From<SamplerType> for wgpu::FilterMode {
fn from(value: SamplerType) -> Self {
match value {
SamplerType::LinearInterpolation => wgpu::FilterMode::Linear,
SamplerType::NearestNeighbor => wgpu::FilterMode::Nearest,
}
}
}