use crate::ecs::{Camera, Rotation, Scale, Shear, SpriteTexture, Translation};
use crate::rendering::mipmap::MipGenerator;
use crate::rendering::renderer::RenderInitRes::{Initialized, NotReadyYet};
use crate::rendering::texture;
use crate::rendering::ui::UiRenderable;
use bevy_ecs::schedule::Schedule;
use bevy_ecs::world::World;
use egui::ViewportId;
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use std::sync::Arc;
use tracing::info;
use web_time::Instant;
use wgpu::SurfaceConfiguration;
use wgpu::{
include_wgsl, Adapter, Backends, BindGroupDescriptor, BindGroupEntry, BindingResource, Buffer,
BufferDescriptor, BufferUsages, Color, ColorTargetState, CommandEncoderDescriptor, Device,
DeviceDescriptor, Features, FragmentState, Instance, InstanceDescriptor, Limits, LoadOp,
Operations, Queue, RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline,
RenderPipelineDescriptor, RequestAdapterOptions, ShaderModule, StoreOp, Surface,
TextureViewDescriptor, VertexState,
};
use winit::dpi::{LogicalSize, PhysicalSize};
use winit::window::Window;
#[allow(unused_attributes, dead_code)]
pub struct Renderer<T: UiRenderable> {
pub last_frame_time: Instant,
pub surface: Surface<'static>,
pub surface_configuration: SurfaceConfiguration,
pub device: Device,
pub queue: Queue,
pub adapter: Adapter,
pub sprite_shader_module: ShaderModule,
pub sprite_pipeline: RenderPipeline,
pub world: World,
pub update_schedule: Schedule,
pub gui_ctx: egui::Context,
pub gui_winit: egui_winit::State,
pub gui_renderer: egui_wgpu::Renderer,
pub gui_renderable: T,
pub textures: HashMap<String, texture::Texture>,
pub mip_generator: MipGenerator,
pub frame_uniform: Buffer,
pub local_uniform: Buffer,
pub scale_factor: f64,
pub window: Arc<Window>,
pub size: PhysicalSize<u32>,
pub logical_size: LogicalSize<u32>,
}
#[allow(clippy::large_enum_variant)]
pub enum RenderInitRes<T: UiRenderable> {
Initialized(Renderer<T>),
NotReadyYet(World, Schedule, T),
}
impl<T: UiRenderable> Debug for RenderInitRes<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Initialized(..) => write!(f, "[initialized renderer]"),
NotReadyYet(..) => write!(f, "[pending initialization]"),
}
}
}
impl<T: UiRenderable> Renderer<T> {
pub async fn try_init(
window: Arc<Window>,
world: World,
update_schedule: Schedule,
gui_renderable: T,
) -> RenderInitRes<T> {
let size = window.inner_size();
if size.width == 0 || size.height == 0 {
return NotReadyYet(world, update_schedule, gui_renderable);
}
// First, create an instance. This is our handle to wgpu, and is the equivalent of navigator.gpu in WebGPU
let instance = Instance::new(InstanceDescriptor {
backends: Backends::all(), // Select the appropriate backend for the HAL to use. Defined in the platform module, as it's platform-specific
..Default::default() // Other fields aren't relevant here
});
// Next, get our render surface
let surface = instance.create_surface(window.clone()).unwrap();
// Next, request out adapter
let adapter = instance
.request_adapter(&RequestAdapterOptions {
power_preference: Default::default(), // Don't care
force_fallback_adapter: false, // We want a real GPU
compatible_surface: Some(&surface), // Find an adapter that is able to render to our window
})
.await
.unwrap();
let mut limits = Limits::downlevel_webgl2_defaults();
limits.max_texture_dimension_1d = 8192;
limits.max_texture_dimension_2d = 8192;
limits.max_texture_dimension_3d = 2048;
let (device, queue) = adapter
.request_device(
&DeviceDescriptor {
label: Some("Basic render device"),
required_features: Features::default(),
required_limits: limits,
memory_hints: Default::default(),
},
None,
)
.await
.unwrap();
let format = surface
.get_default_config(&adapter, size.width, size.height)
.unwrap();
surface.configure(&device, &format);
let sprite_shader_module =
device.create_shader_module(include_wgsl!("../shaders/sprite.wgsl"));
let sprite_pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
label: Some("Sprite pipeline"),
layout: None,
vertex: VertexState {
module: &sprite_shader_module,
entry_point: Some("vs"),
compilation_options: Default::default(),
buffers: &[],
},
primitive: Default::default(),
depth_stencil: None,
multisample: Default::default(),
fragment: Some(FragmentState {
module: &sprite_shader_module,
entry_point: Some("fs"),
compilation_options: Default::default(),
targets: &[Some(ColorTargetState {
format: format.format,
blend: None,
write_mask: Default::default(),
})],
}),
multiview: None,
cache: None,
});
let gui_context = egui::Context::default();
let gui_winit = egui_winit::State::new(
gui_context.clone(),
ViewportId::ROOT,
&window,
Some(window.scale_factor() as f32),
None,
Some(device.limits().max_texture_dimension_2d as usize),
);
let gui_renderer = egui_wgpu::Renderer::new(&device, format.format, None, 1, false);
let frame_uniform = device.create_buffer(&BufferDescriptor {
label: Some("frame uniforms"),
size: 48 + 16, // mat3x3f, vec2f
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let local_uniform = device.create_buffer(&BufferDescriptor {
label: Some("local uniforms"),
size: 48, // mat3x3f
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
mapped_at_creation: false,
});
Initialized(Self {
mip_generator: MipGenerator::new(&device),
textures: HashMap::new(),
last_frame_time: Instant::now(),
surface,
device,
queue,
adapter,
sprite_shader_module,
sprite_pipeline,
world,
update_schedule,
gui_ctx: gui_context,
gui_winit,
gui_renderer,
gui_renderable,
logical_size: LogicalSize::from_physical(size, window.scale_factor()),
scale_factor: window.scale_factor(),
window,
frame_uniform,
local_uniform,
size,
surface_configuration: format,
})
}
pub fn render(&mut self) {
// update the world
self.update_schedule.run(&mut self.world);
// update the UI
let egui_output = self
.gui_ctx
.run(self.gui_winit.take_egui_input(&self.window), |ctx| {
self.gui_renderable.render(ctx, &mut self.world)
});
self.gui_winit
.handle_platform_output(&self.window, egui_output.platform_output);
let output = self.surface.get_current_texture().unwrap();
let view = output
.texture
.create_view(&TextureViewDescriptor::default());
let render_pass_descriptor = RenderPassDescriptor {
label: Some("basic render pass"),
color_attachments: &[Some(RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: Operations {
load: LoadOp::Clear(Color {
r: 0.3,
g: 0.3,
b: 0.3,
a: 1.0,
}),
store: StoreOp::Store,
},
})],
..Default::default()
};
let mut encoder = self
.device
.create_command_encoder(&CommandEncoderDescriptor {
label: Some("command encoder"),
});
let mut sprites_to_render: Vec<(Translation, Shear, Scale, SpriteTexture, Rotation)> = vec![];
let mut things_to_render = self.world.query::<(&Translation, &Shear, &Scale, &SpriteTexture, &Rotation)>();
for thing in things_to_render.iter_mut(&mut self.world) {
sprites_to_render.push((*thing.0, *thing.1, *thing.2, thing.3.clone(), *thing.4));
}
let cam = self.world.resource::<Camera>();
let mut frame_uniform = vec![];
let frame_uniform_values = [
cam.zoom, cam.shear_y,0.0, 0.0,
cam.shear_x,cam.zoom, 0.0, 0.0,
cam.x, cam.y, 1.0, 0.0,
self.logical_size.width as f32, self.logical_size.height as f32, 0.0, 0.0];
for i in frame_uniform_values {
let mut bytes = i.to_ne_bytes().to_vec();
frame_uniform.append(&mut bytes);
}
self.queue.write_buffer(&self.frame_uniform, 0, &frame_uniform);
for (pos, shear, scale, tex, rot) in sprites_to_render {
let tex = self.textures.entry(tex.texture.clone()).or_insert_with(|| {
info!("loading texture {}", &tex.texture);
let b: &[u8] = match tex.texture.as_str() {
"f" => include_bytes!("../textures/f.png"),
"happy-tree" => include_bytes!("../textures/happy-tree.png"),
"uv" => include_bytes!("../textures/uv.png"),
u => panic!("unknown texture {u}, has it been added in rendering::renderer::<impl Renderer>::render()?")
};
texture::Texture::new(
b,
&tex.texture,
&self.device,
&self.queue,
&mut self.mip_generator,
)
});
let xy_matrix = pos.as_matrix();
let shear_matrix = shear.as_matrix();
let rot_matrix = rot.as_matrix();
let scale_matrix = scale.as_matrix();
let transform_matrix = scale_matrix * shear_matrix * rot_matrix * xy_matrix;
let mut local_buffer = vec![];
let local_buffer_data = [
transform_matrix.m11, transform_matrix.m12, transform_matrix.m13, 0.0,
transform_matrix.m21, transform_matrix.m22, transform_matrix.m23, 0.0,
transform_matrix.m31, transform_matrix.m32, transform_matrix.m33, 0.0,
];
for i in local_buffer_data {
let mut bytes = i.to_ne_bytes().to_vec();
local_buffer.append(&mut bytes);
}
self.queue
.write_buffer(&self.local_uniform, 0, &local_buffer);
let bind_group = self.device.create_bind_group(&BindGroupDescriptor {
label: Some("test_bind_group"),
layout: &self.sprite_pipeline.get_bind_group_layout(0),
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::Sampler(&tex.sampler),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::TextureView(
&tex.texture.create_view(&TextureViewDescriptor::default()),
),
},
BindGroupEntry {
binding: 2,
resource: BindingResource::Buffer(
self.frame_uniform.as_entire_buffer_binding(),
),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::Buffer(
self.local_uniform.as_entire_buffer_binding(),
),
},
],
});
let mut pass = encoder.begin_render_pass(&render_pass_descriptor);
pass.set_pipeline(&self.sprite_pipeline);
pass.set_bind_group(0, Some(&bind_group), &[]);
pass.draw(0..6, 0..1);
}
// main game rendering done
// next up: egui UI rendering
for (id, image_delta) in &egui_output.textures_delta.set {
self.gui_renderer
.update_texture(&self.device, &self.queue, *id, image_delta);
}
{
let paint_jobs = self
.gui_ctx
.tessellate(egui_output.shapes, self.scale_factor as f32);
let screen_descriptor = egui_wgpu::ScreenDescriptor {
size_in_pixels: [self.size.width, self.size.height],
pixels_per_point: self.scale_factor as f32,
};
self.gui_renderer.update_buffers(
&self.device,
&self.queue,
&mut encoder,
&paint_jobs,
&screen_descriptor,
);
let render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
label: Some("ui render pass"),
color_attachments: &[Some(RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: Operations {
load: LoadOp::Load,
store: StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
let mut forgotten_render_pass = render_pass.forget_lifetime();
self.gui_renderer
.render(&mut forgotten_render_pass, &paint_jobs, &screen_descriptor);
for id in egui_output.textures_delta.free {
self.gui_renderer.free_texture(&id);
}
}
let buffer = encoder.finish();
self.queue.submit(std::iter::once(buffer));
output.present();
}
}