use std::mem::size_of;
use imgui::{
DrawList,
Context,
DrawVert,
DrawIdx,
TextureId,
Textures,
Ui,
DrawCmd::Elements,
};
use wgpu::*;
pub type RendererResult<T> = Result<T, RendererError>;
#[derive(Clone, Debug)]
pub enum RendererError {
BadTexture(TextureId),
}
#[allow(dead_code)]
enum ShaderStage {
Vertex,
Fragment,
Compute,
}
struct Shaders;
impl Shaders {
fn compile_glsl(code: &str, stage: ShaderStage) -> Vec<u32> {
let ty = match stage {
ShaderStage::Vertex => glsl_to_spirv::ShaderType::Vertex,
ShaderStage::Fragment => glsl_to_spirv::ShaderType::Fragment,
ShaderStage::Compute => glsl_to_spirv::ShaderType::Compute,
};
read_spirv(glsl_to_spirv::compile(&code, ty).unwrap()).unwrap()
}
fn get_program_code() -> (&'static str, &'static str) {
(
include_str!("imgui.vert"),
include_str!("imgui.frag"),
)
}
}
struct Texture {
bind_group: BindGroup,
}
impl Texture {
fn new(texture: wgpu::Texture, layout: &BindGroupLayout, device: &Device) -> Self {
let view = texture.create_default_view();
let sampler = device.create_sampler(&SamplerDescriptor {
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::Linear,
lod_min_clamp: -100.0,
lod_max_clamp: 100.0,
compare_function: CompareFunction::Always,
});
let bind_group = device.create_bind_group(&BindGroupDescriptor {
layout,
bindings: &[
Binding {
binding: 0,
resource: BindingResource::TextureView(&view),
},
Binding {
binding: 1,
resource: BindingResource::Sampler(&sampler),
},
]
});
Texture {
bind_group,
}
}
}
#[allow(dead_code)]
pub struct Renderer {
pipeline: RenderPipeline,
uniform_buffer: Buffer,
uniform_bind_group: BindGroup,
textures: Textures<Texture>,
texture_layout: BindGroupLayout,
clear_color: Option<Color>,
}
impl Renderer {
pub fn new(
imgui: &mut Context,
device: &Device,
queue: &mut Queue,
format: TextureFormat,
clear_color: Option<Color>,
) -> Renderer {
let (vs_code, fs_code) = Shaders::get_program_code();
let vs_raw = Shaders::compile_glsl(vs_code, ShaderStage::Vertex);
let fs_raw = Shaders::compile_glsl(fs_code, ShaderStage::Fragment);
Self::new_impl(imgui, device, queue, format, clear_color, vs_raw, fs_raw)
}
pub fn new_static(
imgui: &mut Context,
device: &Device,
queue: &mut Queue,
format: TextureFormat,
clear_color: Option<Color>,
) -> Renderer {
let vs_bytes = include_bytes!("imgui.vert.spv");
let fs_bytes = include_bytes!("imgui.frag.spv");
fn compile(shader: &[u8]) -> Vec<u32> {
let mut words = vec![];
for bytes4 in shader.chunks(4) {
words.push(u32::from_le_bytes([
bytes4[0], bytes4[1], bytes4[2], bytes4[3],
]));
}
words
}
Self::new_impl(imgui, device, queue, format, clear_color, compile(vs_bytes), compile(fs_bytes))
}
fn new_impl(
imgui: &mut Context,
device: &Device,
queue: &mut Queue,
format: TextureFormat,
clear_color: Option<Color>,
vs_raw: Vec<u32>,
fs_raw: Vec<u32>,
) -> Renderer {
let vs_module = device.create_shader_module(&vs_raw);
let fs_module = device.create_shader_module(&fs_raw);
let size = 64;
let uniform_buffer = device.create_buffer(&BufferDescriptor {
size,
usage: BufferUsage::UNIFORM | BufferUsage::COPY_DST,
});
let uniform_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
bindings: &[
BindGroupLayoutBinding {
binding: 0,
visibility: wgpu::ShaderStage::VERTEX,
ty: BindingType::UniformBuffer {
dynamic: false,
},
},
]
});
let uniform_bind_group = device.create_bind_group(&BindGroupDescriptor {
layout: &uniform_layout,
bindings: &[
Binding {
binding: 0,
resource: BindingResource::Buffer {
buffer: &uniform_buffer,
range: 0..size,
},
},
]
});
let texture_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
bindings: &[
BindGroupLayoutBinding {
binding: 0,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: BindingType::SampledTexture {
multisampled: false,
dimension: TextureViewDimension::D2,
},
},
BindGroupLayoutBinding {
binding: 1,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: BindingType::Sampler,
},
]
});
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
bind_group_layouts: &[&uniform_layout, &texture_layout],
});
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
layout: &pipeline_layout,
vertex_stage: ProgrammableStageDescriptor {
module: &vs_module,
entry_point: "main",
},
fragment_stage: Some(ProgrammableStageDescriptor {
module: &fs_module,
entry_point: "main",
}),
rasterization_state: Some(RasterizationStateDescriptor {
front_face: FrontFace::Cw,
cull_mode: CullMode::None,
depth_bias: 0,
depth_bias_slope_scale: 0.0,
depth_bias_clamp: 0.0,
}),
primitive_topology: PrimitiveTopology::TriangleList,
color_states: &[
ColorStateDescriptor {
format,
color_blend: BlendDescriptor {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::OneMinusSrcAlpha,
operation: BlendOperation::Add,
},
alpha_blend: BlendDescriptor {
src_factor: BlendFactor::OneMinusDstAlpha,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
write_mask: ColorWrite::ALL,
},
],
depth_stencil_state: None,
index_format: IndexFormat::Uint16,
vertex_buffers: &[
VertexBufferDescriptor {
stride: size_of::<DrawVert>() as BufferAddress,
step_mode: InputStepMode::Vertex,
attributes: &[
VertexAttributeDescriptor {
format: VertexFormat::Float2,
shader_location: 0,
offset: 0,
},
VertexAttributeDescriptor {
format: VertexFormat::Float2,
shader_location: 1,
offset: 8,
},
VertexAttributeDescriptor {
format: VertexFormat::Uint,
shader_location: 2,
offset: 16,
},
]
},
],
sample_count: 1,
sample_mask: !0,
alpha_to_coverage_enabled: false,
});
let mut renderer = Renderer {
pipeline,
uniform_buffer,
uniform_bind_group,
textures: Textures::new(),
texture_layout,
clear_color,
};
renderer.reload_font_texture(imgui, device, queue);
renderer
}
pub fn render<'a>(
&mut self,
ui: Ui<'a>,
device: &Device,
encoder: &mut CommandEncoder,
view: &TextureView,
) -> RendererResult<()> {
let draw_data = ui.render();
let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0];
let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1];
if !(fb_width > 0.0 && fb_height > 0.0) {
return Ok(());
}
let width = draw_data.display_size[0];
let height = draw_data.display_size[1];
let matrix = [
[2.0 / width, 0.0, 0.0, 0.0],
[0.0, 2.0 / height as f32, 0.0, 0.0],
[0.0, 0.0, -1.0, 0.0],
[-1.0, -1.0, 0.0, 1.0],
];
self.update_uniform_buffer(device, encoder, &matrix);
let mut rpass = encoder.begin_render_pass(&RenderPassDescriptor {
color_attachments: &[RenderPassColorAttachmentDescriptor {
attachment: &view,
resolve_target: None,
load_op: match self.clear_color { Some(_) => LoadOp::Clear, _ => LoadOp::Load },
store_op: StoreOp::Store,
clear_color: self.clear_color.unwrap_or(Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }),
}],
depth_stencil_attachment: None,
});
rpass.set_pipeline(&self.pipeline);
rpass.set_bind_group(0, &self.uniform_bind_group, &[]);
for draw_list in draw_data.draw_lists() {
self.render_draw_list(
device,
&mut rpass,
&draw_list,
draw_data.display_pos,
draw_data.framebuffer_scale
)?;
}
Ok(())
}
fn render_draw_list<'render>(
&mut self,
device: &Device,
rpass: &mut RenderPass<'render>,
draw_list: &DrawList,
clip_off: [f32; 2],
clip_scale: [f32; 2]
) -> RendererResult<()> {
let mut start = 0;
let vertex_buffer = self.upload_vertex_buffer(device, draw_list.vtx_buffer());
let index_buffer = self.upload_index_buffer(device, draw_list.idx_buffer());
rpass.set_index_buffer(&index_buffer, 0);
rpass.set_vertex_buffers(0, &[(&vertex_buffer, 0)]);
for cmd in draw_list.commands() {
match cmd {
Elements { count, cmd_params, } => {
let clip_rect = [
(cmd_params.clip_rect[0] - clip_off[0]) * clip_scale[0],
(cmd_params.clip_rect[1] - clip_off[1]) * clip_scale[1],
(cmd_params.clip_rect[2] - clip_off[0]) * clip_scale[0],
(cmd_params.clip_rect[3] - clip_off[1]) * clip_scale[1],
];
let texture_id = cmd_params.texture_id.into();
let tex = self
.textures
.get(texture_id)
.ok_or_else(|| RendererError::BadTexture(texture_id))?;
rpass.set_bind_group(1, &tex.bind_group, &[]);
let scissors = (
clip_rect[0]
.max(0.0)
.floor() as u32,
clip_rect[1]
.max(0.0)
.floor() as u32,
(clip_rect[2] - clip_rect[0])
.abs()
.ceil() as u32,
(clip_rect[3] - clip_rect[1])
.abs()
.ceil() as u32,
);
rpass.set_scissor_rect(scissors.0, scissors.1, scissors.2, scissors.3);
let end = start + count as u32;
rpass.draw_indexed(start..end, 0, 0..1);
start = end;
},
_ => {},
}
}
Ok(())
}
fn update_uniform_buffer(&mut self, device: &Device, encoder: &mut CommandEncoder, matrix: &[[f32; 4]; 4]) {
let buffer = device.create_buffer_mapped(16, BufferUsage::COPY_SRC)
.fill_from_slice(matrix.iter().flatten().map(|f| *f).collect::<Vec<f32>>().as_slice());
encoder.copy_buffer_to_buffer(&buffer, 0, &self.uniform_buffer, 0, 64);
}
fn upload_vertex_buffer(&mut self, device: &Device, vertices: &[DrawVert]) -> Buffer {
device
.create_buffer_mapped(vertices.len(), BufferUsage::VERTEX)
.fill_from_slice(vertices)
}
fn upload_index_buffer(&mut self, device: &Device, indices: &[DrawIdx]) -> Buffer {
device
.create_buffer_mapped(indices.len(), BufferUsage::INDEX)
.fill_from_slice(indices)
}
pub fn reload_font_texture(&mut self, imgui: &mut Context, device: &Device, queue: &mut Queue) {
let mut atlas = imgui.fonts();
let handle = atlas.build_rgba32_texture();
let font_texture_id = self.upload_font_texture(device, queue, &handle.data, handle.width, handle.height);
atlas.tex_id = font_texture_id;
}
fn upload_font_texture(
&mut self,
device: &Device,
queue: &mut Queue,
data: &[u8],
width: u32,
height: u32
) -> TextureId {
let texture = device.create_texture(&TextureDescriptor {
size: Extent3d { width, height, depth: 1 },
array_layer_count: 1,
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba8Unorm,
usage: TextureUsage::SAMPLED | TextureUsage::COPY_DST,
});
let bytes = data.len();
let buffer = device.create_buffer_mapped(
bytes,
BufferUsage::COPY_SRC,
)
.fill_from_slice(data);
let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor {
todo: 0,
});
encoder.copy_buffer_to_texture(
BufferCopyView {
buffer: &buffer,
offset: 0,
row_pitch: bytes as u32 / height,
image_height: height,
},
TextureCopyView {
texture: &texture,
mip_level: 0,
array_layer: 0,
origin: Origin3d {
x: 0.0,
y: 0.0,
z: 0.0,
},
},
Extent3d {
width,
height,
depth: 1,
},
);
queue.submit(&[encoder.finish()]);
let texture = Texture::new(texture, &self.texture_layout, device);
self.textures.insert(texture)
}
}