use wgpu::*;
use wgpu::util::DeviceExt;
use winit::window::Window;
use cgmath::*;
use bytemuck::{Pod, Zeroable};
use crate::camera::Camera;
use std::path::PathBuf;
fn find_asset_path(relative_path: &str) -> PathBuf {
if let Ok(exe_path) = std::env::current_exe() {
if let Some(exe_dir) = exe_path.parent() {
let asset_path = exe_dir.join(relative_path);
if asset_path.exists() {
return asset_path;
}
}
}
PathBuf::from(relative_path)
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
struct Uniforms {
model: [[f32; 4]; 4],
view: [[f32; 4]; 4],
proj: [[f32; 4]; 4],
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
struct Vertex {
position: [f32; 3],
uv: [f32; 2],
}
pub struct Renderer {
window_id: winit::window::WindowId,
surface: wgpu::Surface<'static>,
device: Device,
queue: Queue,
config: SurfaceConfiguration,
pipeline: RenderPipeline,
vertex_buffer: Buffer,
index_buffer: Buffer,
uniform_buffer: Buffer,
uniform_bind_group: BindGroup,
texture_bind_group: BindGroup,
rotation: f32,
pub camera: Camera, }
impl Renderer {
pub async fn new(window: &Window) -> Self {
let window_id = window.id();
let size = window.inner_size();
let instance = Instance::new(InstanceDescriptor {
backends: Backends::PRIMARY,
..Default::default()
});
let surface_raw = instance.create_surface(window).unwrap();
let surface: wgpu::Surface<'static> = unsafe { std::mem::transmute(surface_raw) };
let adapter = instance
.request_adapter(&RequestAdapterOptions {
power_preference: PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.expect("Failed to find an appropriate adapter");
let (device, queue) = adapter
.request_device(
&DeviceDescriptor {
required_features: Features::empty(),
required_limits: Limits::default(),
label: None,
},
None,
)
.await
.expect("Failed to create device");
let surface_caps = surface.get_capabilities(&adapter);
let surface_format = surface_caps
.formats
.iter()
.copied()
.find(|f| f.is_srgb())
.unwrap_or(surface_caps.formats[0]);
let config = SurfaceConfiguration {
usage: TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width,
height: size.height,
present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &config);
let shader = device.create_shader_module(ShaderModuleDescriptor {
label: Some("Cube Shader"),
source: ShaderSource::Wgsl(include_str!("../shaders/cube.vert.wgsl").into()),
});
let fragment_shader = device.create_shader_module(ShaderModuleDescriptor {
label: Some("Cube Fragment Shader"),
source: ShaderSource::Wgsl(include_str!("../shaders/cube.frag.wgsl").into()),
});
let uniform_buffer = device.create_buffer(&BufferDescriptor {
label: Some("Uniform Buffer"),
size: std::mem::size_of::<Uniforms>() as u64,
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let uniform_bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
label: Some("Uniform Bind Group Layout"),
});
let uniform_bind_group = device.create_bind_group(&BindGroupDescriptor {
layout: &uniform_bind_group_layout,
entries: &[BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
}],
label: Some("Uniform Bind Group"),
});
let texture_bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
view_dimension: TextureViewDimension::D2,
sample_type: TextureSampleType::Float { filterable: true },
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Sampler(SamplerBindingType::Filtering),
count: None,
},
],
label: Some("Texture Bind Group Layout"),
});
use crate::texture::Texture;
let texture_path = find_asset_path("assets/textures/monkey.jxl");
let texture = Texture::from_jxl(&device, &queue, &texture_path)
.unwrap_or_else(|_| {
let width = 256;
let height = 256;
let mut rgba8_data = vec![0u8; (width * height * 4) as usize];
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 4) as usize;
let checker = ((x / 32) + (y / 32)) % 2;
rgba8_data[idx] = if checker == 0 { 255 } else { 128 };
rgba8_data[idx + 1] = if checker == 0 { 255 } else { 128 };
rgba8_data[idx + 2] = if checker == 0 { 255 } else { 128 };
rgba8_data[idx + 3] = 255;
}
}
let texture_size = Extent3d {
width,
height,
depth_or_array_layers: 1,
};
let texture = device.create_texture(&TextureDescriptor {
label: Some("Fallback Texture"),
size: texture_size,
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba8UnormSrgb,
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
ImageCopyTexture {
texture: &texture,
mip_level: 0,
origin: Origin3d::ZERO,
aspect: TextureAspect::All,
},
&rgba8_data,
ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * width),
rows_per_image: Some(height),
},
texture_size,
);
let view = texture.create_view(&TextureViewDescriptor::default());
let sampler = device.create_sampler(&SamplerDescriptor {
label: Some("Texture Sampler"),
address_mode_u: AddressMode::ClampToEdge,
address_mode_v: AddressMode::ClampToEdge,
address_mode_w: AddressMode::ClampToEdge,
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
mipmap_filter: FilterMode::Nearest,
..Default::default()
});
Texture {
texture,
view,
sampler,
width,
height,
}
});
let texture_bind_group = device.create_bind_group(&BindGroupDescriptor {
layout: &texture_bind_group_layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&texture.view),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::Sampler(&texture.sampler),
},
],
label: Some("Texture Bind Group"),
});
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[&uniform_bind_group_layout, &texture_bind_group_layout],
push_constant_ranges: &[],
});
let vertex_buffer = Self::create_cube_vertex_buffer(&device);
let index_buffer = Self::create_cube_index_buffer(&device);
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
label: Some("Render Pipeline"),
layout: Some(&pipeline_layout),
vertex: VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as u64,
step_mode: VertexStepMode::Vertex,
attributes: &[
VertexAttribute {
offset: 0,
shader_location: 0,
format: VertexFormat::Float32x3,
},
VertexAttribute {
offset: std::mem::size_of::<[f32; 3]>() as u64,
shader_location: 1,
format: VertexFormat::Float32x2,
},
],
}],
compilation_options: PipelineCompilationOptions::default(),
},
fragment: Some(FragmentState {
module: &fragment_shader,
entry_point: "fs_main",
targets: &[Some(ColorTargetState {
format: config.format,
blend: Some(BlendState::REPLACE),
write_mask: ColorWrites::ALL,
})],
compilation_options: PipelineCompilationOptions::default(),
}),
primitive: PrimitiveState {
topology: PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: FrontFace::Ccw,
cull_mode: Some(Face::Back),
polygon_mode: PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(DepthStencilState {
format: TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: CompareFunction::Less,
stencil: StencilState::default(),
bias: DepthBiasState::default(),
}),
multisample: MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
});
Self {
window_id,
surface,
device,
queue,
config,
pipeline,
vertex_buffer,
index_buffer,
uniform_buffer,
uniform_bind_group,
texture_bind_group,
rotation: 0.0,
camera: Camera::new(size.width, size.height),
}
}
fn create_cube_vertex_buffer(device: &Device) -> Buffer {
let vertices: [Vertex; 24] = [
Vertex { position: [-1.0, -1.0, -1.0], uv: [0.0, 1.0] },
Vertex { position: [1.0, -1.0, -1.0], uv: [1.0, 1.0] },
Vertex { position: [1.0, 1.0, -1.0], uv: [1.0, 0.0] },
Vertex { position: [-1.0, 1.0, -1.0], uv: [0.0, 0.0] },
Vertex { position: [1.0, -1.0, 1.0], uv: [0.0, 1.0] },
Vertex { position: [-1.0, -1.0, 1.0], uv: [1.0, 1.0] },
Vertex { position: [-1.0, 1.0, 1.0], uv: [1.0, 0.0] },
Vertex { position: [1.0, 1.0, 1.0], uv: [0.0, 0.0] },
Vertex { position: [-1.0, -1.0, 1.0], uv: [0.0, 1.0] },
Vertex { position: [-1.0, -1.0, -1.0], uv: [1.0, 1.0] },
Vertex { position: [-1.0, 1.0, -1.0], uv: [1.0, 0.0] },
Vertex { position: [-1.0, 1.0, 1.0], uv: [0.0, 0.0] },
Vertex { position: [1.0, -1.0, -1.0], uv: [0.0, 1.0] },
Vertex { position: [1.0, -1.0, 1.0], uv: [1.0, 1.0] },
Vertex { position: [1.0, 1.0, 1.0], uv: [1.0, 0.0] },
Vertex { position: [1.0, 1.0, -1.0], uv: [0.0, 0.0] },
Vertex { position: [-1.0, -1.0, 1.0], uv: [0.0, 1.0] },
Vertex { position: [1.0, -1.0, 1.0], uv: [1.0, 1.0] },
Vertex { position: [1.0, -1.0, -1.0], uv: [1.0, 0.0] },
Vertex { position: [-1.0, -1.0, -1.0], uv: [0.0, 0.0] },
Vertex { position: [-1.0, 1.0, -1.0], uv: [0.0, 1.0] },
Vertex { position: [1.0, 1.0, -1.0], uv: [1.0, 1.0] },
Vertex { position: [1.0, 1.0, 1.0], uv: [1.0, 0.0] },
Vertex { position: [-1.0, 1.0, 1.0], uv: [0.0, 0.0] },
];
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Cube Vertex Buffer"),
contents: bytemuck::cast_slice(&vertices),
usage: BufferUsages::VERTEX,
})
}
fn create_cube_index_buffer(device: &Device) -> Buffer {
let indices: [u16; 36] = [
0, 1, 2, 2, 3, 0,
4, 5, 6, 6, 7, 4,
8, 9, 10, 10, 11, 8,
12, 13, 14, 14, 15, 12,
16, 17, 18, 18, 19, 16,
20, 21, 22, 22, 23, 20,
];
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Cube Index Buffer"),
contents: bytemuck::cast_slice(&indices),
usage: BufferUsages::INDEX,
})
}
pub fn resize(&mut self, width: u32, height: u32) {
if width > 0 && height > 0 {
self.config.width = width;
self.config.height = height;
self.surface.configure(&self.device, &self.config);
}
}
pub fn update_rotation(&mut self, delta: f32) {
self.rotation += delta;
}
pub fn set_rotation(&mut self, rotation: f32) {
self.rotation = rotation;
}
pub fn window_id(&self) -> winit::window::WindowId {
self.window_id
}
pub fn render(&mut self) -> Result<(), SurfaceError> {
let output = self.surface.get_current_texture()?;
let texture_view = output.texture.create_view(&TextureViewDescriptor::default());
let depth_texture = self.device.create_texture(&TextureDescriptor {
size: Extent3d {
width: self.config.width,
height: self.config.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Depth32Float,
usage: TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
label: Some("Depth Texture"),
});
let depth_view = depth_texture.create_view(&TextureViewDescriptor::default());
let model = Matrix4::from_angle_y(Rad(self.rotation));
let view_matrix = self.camera.view_matrix();
let proj = self.camera.projection_matrix();
let uniforms = Uniforms {
model: model.into(),
view: view_matrix.into(),
proj: proj.into(),
};
self.queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
let mut encoder = self
.device
.create_command_encoder(&CommandEncoderDescriptor {
label: Some("Render Encoder"),
});
{
let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[Some(RenderPassColorAttachment {
view: &texture_view,
resolve_target: None,
ops: Operations {
load: LoadOp::Clear(Color {
r: 0.1,
g: 0.1,
b: 0.1,
a: 1.0,
}),
store: StoreOp::Store,
},
})],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &depth_view,
depth_ops: Some(Operations {
load: LoadOp::Clear(1.0),
store: StoreOp::Store,
}),
stencil_ops: None,
}),
occlusion_query_set: None,
timestamp_writes: None,
});
render_pass.set_pipeline(&self.pipeline);
render_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
render_pass.set_bind_group(1, &self.texture_bind_group, &[]);
render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
render_pass.set_index_buffer(self.index_buffer.slice(..), IndexFormat::Uint16);
render_pass.draw_indexed(0..36, 0, 0..1);
}
self.queue.submit(std::iter::once(encoder.finish()));
output.present();
Ok(())
}
}