initial commit
This commit is contained in:
66
src/layout/mod.rs
Normal file
66
src/layout/mod.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
mod node;
|
||||
pub use node::*;
|
||||
|
||||
use crate::primitive::{Color, Painter};
|
||||
|
||||
pub type UIColor = Color<u8>;
|
||||
|
||||
pub trait UINode: 'static {
|
||||
fn draw(&self, painter: &mut Painter);
|
||||
}
|
||||
|
||||
pub struct UI {
|
||||
base: Box<dyn UINode>,
|
||||
}
|
||||
|
||||
impl UI {
|
||||
pub fn to_primitives(&self) -> Painter {
|
||||
let mut painter = Painter::default();
|
||||
self.base.draw(&mut painter);
|
||||
painter
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: UINode> From<N> for UI {
|
||||
fn from(node: N) -> Self {
|
||||
Self {
|
||||
base: Box::new(node),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UINode for Box<dyn UINode> {
|
||||
fn draw(&self, painter: &mut Painter) {
|
||||
self.as_ref().draw(painter);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NodeArray<const LEN: usize> {
|
||||
fn to_arr(self) -> [Box<dyn UINode>; LEN];
|
||||
}
|
||||
|
||||
// I hate this language it's so bad why do I even use it
|
||||
macro_rules! impl_node_arr {
|
||||
($n:expr;$($T:tt)+) => {
|
||||
impl<$($T: UINode,)*> NodeArray<$n> for ($($T,)*) {
|
||||
fn to_arr(self) -> [Box<dyn UINode>; $n] {
|
||||
#[allow(non_snake_case)]
|
||||
let ($($T,)*) = self;
|
||||
[$(Box::new($T),)*]
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_node_arr!(1;A);
|
||||
impl_node_arr!(2;A B);
|
||||
impl_node_arr!(3;A B C);
|
||||
impl_node_arr!(4;A B C D);
|
||||
impl_node_arr!(5;A B C D E);
|
||||
impl_node_arr!(6;A B C D E F);
|
||||
impl_node_arr!(7;A B C D E F G);
|
||||
impl_node_arr!(8;A B C D E F G H);
|
||||
impl_node_arr!(9;A B C D E F G H I);
|
||||
impl_node_arr!(10;A B C D E F G H I J);
|
||||
impl_node_arr!(11;A B C D E F G H I J K);
|
||||
impl_node_arr!(12;A B C D E F G H I J K L);
|
||||
164
src/layout/node.rs
Normal file
164
src/layout/node.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::{
|
||||
primitive::{Axis, Painter, RoundedRectData, UIRegion},
|
||||
NodeArray, UIColor, UINode,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct RoundedRect {
|
||||
pub color: UIColor,
|
||||
pub radius: f32,
|
||||
pub thickness: f32,
|
||||
pub inner_radius: f32,
|
||||
}
|
||||
|
||||
impl RoundedRect {
|
||||
pub fn color(mut self, color: UIColor) -> Self {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl UINode for RoundedRect {
|
||||
fn draw(&self, painter: &mut Painter) {
|
||||
painter.write(RoundedRectData {
|
||||
color: self.color,
|
||||
radius: self.radius,
|
||||
thickness: self.thickness,
|
||||
inner_radius: self.inner_radius,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Span {
|
||||
pub elements: Vec<(Range<f32>, Box<dyn UINode>)>,
|
||||
pub axis: Axis,
|
||||
}
|
||||
|
||||
impl UINode for Span {
|
||||
fn draw(&self, painter: &mut Painter) {
|
||||
for (span, child) in &self.elements {
|
||||
let mut sub_region = UIRegion::full();
|
||||
let view = sub_region.axis_mut(self.axis);
|
||||
*view.top_left.anchor = span.start;
|
||||
*view.bot_right.anchor = span.end;
|
||||
painter.draw_within(child, sub_region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Span {
|
||||
pub fn proportioned<const LEN: usize>(
|
||||
axis: Axis,
|
||||
ratios: [impl UINum; LEN],
|
||||
elements: impl NodeArray<LEN>,
|
||||
) -> Self {
|
||||
let ratios = ratios.map(|r| r.to_f32());
|
||||
let total: f32 = ratios.iter().sum();
|
||||
let mut start = 0.0;
|
||||
Self {
|
||||
elements: elements
|
||||
.to_arr()
|
||||
.into_iter()
|
||||
.zip(ratios)
|
||||
.map(|(e, r)| {
|
||||
let end = start + r / total;
|
||||
let res = (start..end, e);
|
||||
start = end;
|
||||
res
|
||||
})
|
||||
.collect(),
|
||||
axis,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Regioned<N: UINode> {
|
||||
region: UIRegion,
|
||||
inner: N,
|
||||
}
|
||||
|
||||
impl<N: UINode> UINode for Regioned<N> {
|
||||
fn draw(&self, painter: &mut Painter) {
|
||||
painter.region.select(&self.region);
|
||||
self.inner.draw(painter);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Padding {
|
||||
left: f32,
|
||||
right: f32,
|
||||
top: f32,
|
||||
bottom: f32,
|
||||
}
|
||||
|
||||
impl Padding {
|
||||
pub fn uniform(amt: f32) -> Self {
|
||||
Self {
|
||||
left: amt,
|
||||
right: amt,
|
||||
top: amt,
|
||||
bottom: amt,
|
||||
}
|
||||
}
|
||||
pub fn region(&self) -> UIRegion {
|
||||
let mut region = UIRegion::full();
|
||||
region.top_left.offset.x += self.left;
|
||||
region.top_left.offset.y += self.top;
|
||||
region.bot_right.offset.x -= self.right;
|
||||
region.bot_right.offset.y -= self.bottom;
|
||||
region
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UINum> From<T> for Padding {
|
||||
fn from(amt: T) -> Self {
|
||||
Self::uniform(amt.to_f32())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NodeUtil: Sized + UINode {
|
||||
fn pad(self, padding: impl Into<Padding>) -> Regioned<Self>;
|
||||
}
|
||||
|
||||
impl<T: UINode> NodeUtil for T {
|
||||
fn pad(self, padding: impl Into<Padding>) -> Regioned<Self> {
|
||||
Regioned {
|
||||
region: padding.into().region(),
|
||||
inner: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NodeArrayUtil<const LEN: usize> {
|
||||
fn proportioned(self, axis: Axis, ratios: [impl UINum; LEN]) -> Span;
|
||||
}
|
||||
|
||||
impl<T: NodeArray<LEN>, const LEN: usize> NodeArrayUtil<LEN> for T {
|
||||
fn proportioned(self, axis: Axis, ratios: [impl UINum; LEN]) -> Span {
|
||||
Span::proportioned(axis, ratios, self)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait UINum {
|
||||
fn to_f32(self) -> f32;
|
||||
}
|
||||
|
||||
impl UINum for f32 {
|
||||
fn to_f32(self) -> f32 {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl UINum for u32 {
|
||||
fn to_f32(self) -> f32 {
|
||||
self as f32
|
||||
}
|
||||
}
|
||||
|
||||
impl UINum for i32 {
|
||||
fn to_f32(self) -> f32 {
|
||||
self as f32
|
||||
}
|
||||
}
|
||||
10
src/lib.rs
Normal file
10
src/lib.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
#![feature(macro_metavar_expr_concat)]
|
||||
#![feature(const_ops)]
|
||||
#![feature(const_trait_impl)]
|
||||
#![feature(const_from)]
|
||||
|
||||
mod layout;
|
||||
mod render;
|
||||
|
||||
pub use layout::*;
|
||||
pub use render::*;
|
||||
35
src/main.rs
35
src/main.rs
@@ -1,36 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use app::App;
|
||||
use render::Renderer;
|
||||
use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window};
|
||||
|
||||
mod app;
|
||||
mod render;
|
||||
mod testing;
|
||||
|
||||
fn main() {
|
||||
App::run();
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
window: Arc<Window>,
|
||||
renderer: Renderer,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(window: Arc<Window>) -> Self {
|
||||
let renderer = Renderer::new(window.clone());
|
||||
Self {
|
||||
window,
|
||||
renderer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => event_loop.exit(),
|
||||
WindowEvent::RedrawRequested => self.renderer.draw(),
|
||||
WindowEvent::Resized(size) => self.renderer.resize(&size),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
testing::main();
|
||||
}
|
||||
|
||||
34
src/render/data.rs
Normal file
34
src/render/data.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use crate::primitive::UIRegion;
|
||||
use wgpu::VertexAttribute;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct PrimitiveInstance {
|
||||
pub region: UIRegion,
|
||||
pub ptr: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
||||
pub struct WindowUniform {
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
}
|
||||
|
||||
impl PrimitiveInstance {
|
||||
const ATTRIBS: [VertexAttribute; 5] = wgpu::vertex_attr_array![
|
||||
0 => Float32x2,
|
||||
1 => Float32x2,
|
||||
2 => Float32x2,
|
||||
3 => Float32x2,
|
||||
4 => Uint32,
|
||||
];
|
||||
|
||||
pub fn desc() -> wgpu::VertexBufferLayout<'static> {
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &Self::ATTRIBS,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,199 +1,184 @@
|
||||
use std::sync::{
|
||||
mpsc::{channel, Receiver},
|
||||
Arc,
|
||||
use crate::{
|
||||
render::{data::PrimitiveInstance, util::ArrBuf},
|
||||
UI,
|
||||
};
|
||||
|
||||
use notify::{
|
||||
event::{CreateKind, ModifyKind},
|
||||
EventKind, RecommendedWatcher, Watcher,
|
||||
use data::WindowUniform;
|
||||
use wgpu::{
|
||||
util::{BufferInitDescriptor, DeviceExt},
|
||||
*,
|
||||
};
|
||||
use pollster::FutureExt;
|
||||
use primitive::{RoundedRect, UIPos};
|
||||
use shape::ShapePipeline;
|
||||
use wgpu::util::StagingBelt;
|
||||
use winit::{dpi::PhysicalSize, window::Window};
|
||||
use winit::dpi::PhysicalSize;
|
||||
|
||||
mod data;
|
||||
pub mod primitive;
|
||||
mod shape;
|
||||
mod util;
|
||||
|
||||
pub const CLEAR_COLOR: wgpu::Color = wgpu::Color::BLACK;
|
||||
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
|
||||
|
||||
pub struct Renderer {
|
||||
surface: wgpu::Surface<'static>,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
config: wgpu::SurfaceConfiguration,
|
||||
adapter: wgpu::Adapter,
|
||||
encoder: wgpu::CommandEncoder,
|
||||
staging_belt: StagingBelt,
|
||||
shape_pipeline: ShapePipeline,
|
||||
recv: Receiver<Vec<RoundedRect>>,
|
||||
pub struct UIRenderNode {
|
||||
bind_group_layout: BindGroupLayout,
|
||||
bind_group: BindGroup,
|
||||
pipeline: RenderPipeline,
|
||||
|
||||
window_buffer: Buffer,
|
||||
instance: ArrBuf<PrimitiveInstance>,
|
||||
data: ArrBuf<u32>,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
pub fn new(window: Arc<Window>) -> Self {
|
||||
let size = window.inner_size();
|
||||
impl UIRenderNode {
|
||||
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
||||
pass.set_pipeline(&self.pipeline);
|
||||
pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
if self.instance.len() != 0 {
|
||||
pass.set_vertex_buffer(0, self.instance.buffer.slice(..));
|
||||
pass.draw(0..4, 0..self.instance.len() as u32);
|
||||
}
|
||||
}
|
||||
|
||||
let (send, recv) = channel();
|
||||
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &UI) {
|
||||
let primitives = ui.to_primitives();
|
||||
self.instance.update(device, queue, &primitives.instances);
|
||||
self.data.update(device, queue, &primitives.data);
|
||||
self.bind_group = Self::bind_group(
|
||||
device,
|
||||
&self.bind_group_layout,
|
||||
&self.window_buffer,
|
||||
&self.data.buffer,
|
||||
)
|
||||
}
|
||||
|
||||
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("data/test.rhai");
|
||||
let w2 = window.clone();
|
||||
std::thread::spawn(move || {
|
||||
let (tx, rx) = channel();
|
||||
pub fn resize(&mut self, size: &PhysicalSize<u32>, queue: &Queue) {
|
||||
let slice = &[WindowUniform {
|
||||
width: size.width as f32,
|
||||
height: size.height as f32,
|
||||
}];
|
||||
queue.write_buffer(&self.window_buffer, 0, bytemuck::cast_slice(slice));
|
||||
}
|
||||
|
||||
let mut watcher = RecommendedWatcher::new(tx, Default::default()).unwrap();
|
||||
watcher
|
||||
.watch("data".as_ref(), notify::RecursiveMode::Recursive)
|
||||
.unwrap();
|
||||
|
||||
for res in rx {
|
||||
let Ok(ev) = res else {
|
||||
continue;
|
||||
};
|
||||
if !ev.paths.contains(&path) {
|
||||
continue;
|
||||
}
|
||||
if !matches!(
|
||||
ev.kind,
|
||||
EventKind::Create(CreateKind::File) | EventKind::Modify(ModifyKind::Data(_))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
println!("reloaded {ev:?}");
|
||||
let mut engine = rhai::Engine::new();
|
||||
engine.build_type::<RoundedRect>();
|
||||
engine.register_type::<UIPos>();
|
||||
engine.register_fn("anchor_offset", UIPos::anchor_offset);
|
||||
engine.register_fn("rect", RoundedRect::default);
|
||||
let Ok(code) = std::fs::read_to_string(&path) else {
|
||||
continue;
|
||||
};
|
||||
let rects = engine
|
||||
.eval::<rhai::Array>(&code)
|
||||
.map_err(|e| println!("{e:?}"))
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter_map(|d| match rhai::Dynamic::try_cast(d.clone()) {
|
||||
Some(v) => Some(v),
|
||||
None => {
|
||||
println!("{d:?} is not a RoundedRect");
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<RoundedRect>>();
|
||||
send.send(rects).unwrap();
|
||||
w2.request_redraw();
|
||||
}
|
||||
pub fn new(device: &Device, config: &SurfaceConfiguration) -> Self {
|
||||
let shader = device.create_shader_module(ShaderModuleDescriptor {
|
||||
label: Some("UI Shape Shader"),
|
||||
source: ShaderSource::Wgsl(SHAPE_SHADER.into()),
|
||||
});
|
||||
|
||||
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
|
||||
backends: wgpu::Backends::PRIMARY,
|
||||
..Default::default()
|
||||
let window_uniform = WindowUniform::default();
|
||||
let window_buffer = device.create_buffer_init(&BufferInitDescriptor {
|
||||
label: Some("Camera Buffer"),
|
||||
contents: bytemuck::cast_slice(&[window_uniform]),
|
||||
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let surface = instance
|
||||
.create_surface(window.clone())
|
||||
.expect("Could not create window surface!");
|
||||
let instance = ArrBuf::new(
|
||||
device,
|
||||
BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
||||
"instance",
|
||||
);
|
||||
let data = ArrBuf::new(
|
||||
device,
|
||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||
"data",
|
||||
);
|
||||
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::default(),
|
||||
compatible_surface: Some(&surface),
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.block_on()
|
||||
.expect("Could not get adapter!");
|
||||
let 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,
|
||||
},
|
||||
BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: Some("camera_bind_group_layout"),
|
||||
});
|
||||
|
||||
let (device, queue) = adapter
|
||||
.request_device(&wgpu::DeviceDescriptor {
|
||||
required_features: wgpu::Features::empty(),
|
||||
required_limits: wgpu::Limits::default(),
|
||||
..Default::default()
|
||||
})
|
||||
.block_on()
|
||||
.expect("Could not get device!");
|
||||
let bind_group = Self::bind_group(device, &bind_group_layout, &window_buffer, &data.buffer);
|
||||
|
||||
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 = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::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],
|
||||
desired_maximum_frame_latency: 2,
|
||||
view_formats: vec![],
|
||||
};
|
||||
|
||||
surface.configure(&device, &config);
|
||||
|
||||
let staging_belt = StagingBelt::new(4096 * 4);
|
||||
let encoder = Self::create_encoder(&device);
|
||||
|
||||
let shape_pipeline = ShapePipeline::new(&device, &config);
|
||||
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
|
||||
label: Some("UI Shape Pipeline Layout"),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
|
||||
label: Some("UI Shape Pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[PrimitiveInstance::desc()],
|
||||
compilation_options: Default::default(),
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(ColorTargetState {
|
||||
format: config.format,
|
||||
blend: Some(BlendState::ALPHA_BLENDING),
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: Default::default(),
|
||||
}),
|
||||
primitive: PrimitiveState {
|
||||
topology: PrimitiveTopology::TriangleStrip,
|
||||
strip_index_format: None,
|
||||
front_face: FrontFace::Cw,
|
||||
cull_mode: Some(Face::Back),
|
||||
polygon_mode: PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
Self {
|
||||
surface,
|
||||
device,
|
||||
queue,
|
||||
config,
|
||||
adapter,
|
||||
encoder,
|
||||
staging_belt,
|
||||
shape_pipeline,
|
||||
recv,
|
||||
bind_group_layout,
|
||||
bind_group,
|
||||
pipeline,
|
||||
window_buffer,
|
||||
instance,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_encoder(device: &wgpu::Device) -> wgpu::CommandEncoder {
|
||||
device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Render Encoder"),
|
||||
pub fn bind_group(
|
||||
device: &Device,
|
||||
layout: &BindGroupLayout,
|
||||
window_buffer: &Buffer,
|
||||
data: &Buffer,
|
||||
) -> BindGroup {
|
||||
device.create_bind_group(&BindGroupDescriptor {
|
||||
layout,
|
||||
entries: &[
|
||||
BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: window_buffer.as_entire_binding(),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: data.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
label: Some("ui_bind_group"),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn draw(&mut self) {
|
||||
if let Some(rects) = self.recv.try_iter().last() {
|
||||
self.shape_pipeline
|
||||
.update(&self.device, &self.queue, &rects);
|
||||
}
|
||||
let output = self.surface.get_current_texture().unwrap();
|
||||
let view = output
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let mut encoder = std::mem::replace(&mut self.encoder, Self::create_encoder(&self.device));
|
||||
{
|
||||
let render_pass = &mut encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(CLEAR_COLOR),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
..Default::default()
|
||||
});
|
||||
self.shape_pipeline.draw(render_pass);
|
||||
}
|
||||
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
self.staging_belt.finish();
|
||||
output.present();
|
||||
self.staging_belt.recall();
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, size: &PhysicalSize<u32>) {
|
||||
self.config.width = size.width;
|
||||
self.config.height = size.height;
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
self.shape_pipeline.resize(size, &self.queue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
use glam::Vec2;
|
||||
use rhai::{CustomType, TypeBuilder};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, CustomType)]
|
||||
pub struct RoundedRect {
|
||||
pub top_left: UIPos,
|
||||
pub bottom_right: UIPos,
|
||||
pub colors: [[f32; 4]; 4],
|
||||
pub radius: f32,
|
||||
pub inner_radius: f32,
|
||||
pub thickness: f32,
|
||||
}
|
||||
|
||||
impl Default for RoundedRect {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
top_left: Default::default(),
|
||||
bottom_right: Default::default(),
|
||||
colors: [
|
||||
[1.0, 0.0, 0.0, 1.0],
|
||||
[0.0, 1.0, 0.0, 1.0],
|
||||
[0.0, 0.0, 1.0, 1.0],
|
||||
[1.0, 1.0, 1.0, 1.0],
|
||||
],
|
||||
radius: Default::default(),
|
||||
inner_radius: Default::default(),
|
||||
thickness: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub struct Text {
|
||||
pub content: String,
|
||||
pub align: Align,
|
||||
pub pos: Vec2,
|
||||
pub bounds: (f32, f32),
|
||||
}
|
||||
|
||||
impl Text {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
content: String::new(),
|
||||
align: Align::Left,
|
||||
pos: Vec2::default(),
|
||||
bounds: (0.0, 0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
||||
pub struct UIPos {
|
||||
pub anchor: Vec2,
|
||||
pub offset: Vec2,
|
||||
}
|
||||
|
||||
impl UIPos {
|
||||
pub fn anchor_offset(anchor_x: f32, anchor_y: f32, offset_x: f32, offset_y: f32) -> Self {
|
||||
Self {
|
||||
anchor: Vec2::new(anchor_x, anchor_y),
|
||||
offset: Vec2::new(offset_x, offset_y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum Align {
|
||||
Left,
|
||||
Right,
|
||||
Center,
|
||||
}
|
||||
47
src/render/primitive/color.rs
Normal file
47
src/render/primitive/color.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
#![allow(clippy::multiple_bound_locations)]
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, bytemuck::Zeroable)]
|
||||
pub struct Color<T: ColorNum> {
|
||||
r: T,
|
||||
g: T,
|
||||
b: T,
|
||||
a: T,
|
||||
}
|
||||
|
||||
impl<T: ColorNum> Color<T> {
|
||||
pub const BLACK: Self = Self::rgb(T::MIN, T::MIN, T::MIN);
|
||||
pub const WHITE: Self = Self::rgb(T::MAX, T::MAX, T::MAX);
|
||||
|
||||
pub const RED: Self = Self::rgb(T::MAX, T::MIN, T::MIN);
|
||||
pub const ORANGE: Self = Self::rgb(T::MAX, T::MID, T::MIN);
|
||||
pub const YELLOW: Self = Self::rgb(T::MAX, T::MAX, T::MIN);
|
||||
pub const LIME: Self = Self::rgb(T::MID, T::MAX, T::MIN);
|
||||
pub const GREEN: Self = Self::rgb(T::MIN, T::MAX, T::MIN);
|
||||
pub const CYAN: Self = Self::rgb(T::MIN, T::MAX, T::MAX);
|
||||
pub const BLUE: Self = Self::rgb(T::MIN, T::MIN, T::MAX);
|
||||
pub const MAGENTA: Self = Self::rgb(T::MAX, T::MIN, T::MAX);
|
||||
}
|
||||
|
||||
impl<T: ColorNum> Color<T> {
|
||||
pub const fn new(r: T, g: T, b: T, a: T) -> Self {
|
||||
Self { r, g, b, a }
|
||||
}
|
||||
pub const fn rgb(r: T, g: T, b: T) -> Self {
|
||||
Self { r, g, b, a: T::MAX }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ColorNum {
|
||||
const MIN: Self;
|
||||
const MID: Self;
|
||||
const MAX: Self;
|
||||
}
|
||||
|
||||
impl ColorNum for u8 {
|
||||
const MIN: Self = u8::MIN;
|
||||
const MID: Self = u8::MAX / 2;
|
||||
const MAX: Self = u8::MAX;
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Pod for Color<u8> {}
|
||||
14
src/render/primitive/def.rs
Normal file
14
src/render/primitive/def.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use crate::primitive::{Color, PrimitiveData};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct RoundedRectData {
|
||||
pub color: Color<u8>,
|
||||
pub radius: f32,
|
||||
pub thickness: f32,
|
||||
pub inner_radius: f32,
|
||||
}
|
||||
|
||||
impl PrimitiveData for RoundedRectData {
|
||||
const DISCRIM: u32 = 0;
|
||||
}
|
||||
97
src/render/primitive/format.rs
Normal file
97
src/render/primitive/format.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use crate::primitive::{point::point, Point};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
||||
pub struct UIPos {
|
||||
pub anchor: Point,
|
||||
pub offset: Point,
|
||||
}
|
||||
|
||||
impl UIPos {
|
||||
pub const fn anchor_offset(anchor_x: f32, anchor_y: f32, offset_x: f32, offset_y: f32) -> Self {
|
||||
Self {
|
||||
anchor: point(anchor_x, anchor_y),
|
||||
offset: point(offset_x, offset_y),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn top_left() -> Self {
|
||||
Self::anchor_offset(0.0, 0.0, 0.0, 0.0)
|
||||
}
|
||||
|
||||
pub const fn bottom_right() -> Self {
|
||||
Self::anchor_offset(1.0, 1.0, 0.0, 0.0)
|
||||
}
|
||||
|
||||
pub const fn within(&self, region: &UIRegion) -> UIPos {
|
||||
let range = region.bot_right.anchor - region.top_left.anchor;
|
||||
let region_offset = region
|
||||
.top_left
|
||||
.offset
|
||||
.lerp(region.bot_right.offset, self.anchor);
|
||||
UIPos {
|
||||
anchor: region.top_left.anchor + self.anchor * range,
|
||||
offset: self.offset + region_offset,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn axis_mut(&mut self, axis: Axis) -> UIPosAxisView<'_> {
|
||||
match axis {
|
||||
Axis::X => UIPosAxisView {
|
||||
anchor: &mut self.anchor.x,
|
||||
offset: &mut self.offset.x,
|
||||
},
|
||||
Axis::Y => UIPosAxisView {
|
||||
anchor: &mut self.anchor.y,
|
||||
offset: &mut self.offset.y,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UIPosAxisView<'a> {
|
||||
pub anchor: &'a mut f32,
|
||||
pub offset: &'a mut f32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct UIRegion {
|
||||
pub top_left: UIPos,
|
||||
pub bot_right: UIPos,
|
||||
}
|
||||
|
||||
impl UIRegion {
|
||||
pub const fn full() -> Self {
|
||||
Self {
|
||||
top_left: UIPos::top_left(),
|
||||
bot_right: UIPos::bottom_right(),
|
||||
}
|
||||
}
|
||||
pub fn within(&self, parent: &Self) -> Self {
|
||||
Self {
|
||||
top_left: self.top_left.within(parent),
|
||||
bot_right: self.bot_right.within(parent),
|
||||
}
|
||||
}
|
||||
pub fn select(&mut self, inner: &Self) {
|
||||
*self = inner.within(self);
|
||||
}
|
||||
pub fn axis_mut(&mut self, axis: Axis) -> UIRegionAxisView<'_> {
|
||||
UIRegionAxisView {
|
||||
top_left: self.top_left.axis_mut(axis),
|
||||
bot_right: self.bot_right.axis_mut(axis),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UIRegionAxisView<'a> {
|
||||
pub top_left: UIPosAxisView<'a>,
|
||||
pub bot_right: UIPosAxisView<'a>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Axis {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
50
src/render/primitive/mod.rs
Normal file
50
src/render/primitive/mod.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
mod color;
|
||||
mod def;
|
||||
mod format;
|
||||
mod point;
|
||||
|
||||
pub use color::*;
|
||||
pub use def::*;
|
||||
pub use format::*;
|
||||
pub use point::*;
|
||||
|
||||
use crate::{render::data::PrimitiveInstance, UINode};
|
||||
use bytemuck::Pod;
|
||||
|
||||
pub struct Painter {
|
||||
pub region: UIRegion,
|
||||
pub instances: Vec<PrimitiveInstance>,
|
||||
pub data: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Default for Painter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
region: UIRegion::full(),
|
||||
instances: Default::default(),
|
||||
data: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// NOTE: Self must have at least u32 alignment
|
||||
pub trait PrimitiveData: Pod {
|
||||
const DISCRIM: u32;
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
pub fn write<D: PrimitiveData>(&mut self, data: D) {
|
||||
let ptr = self.data.len() as u32;
|
||||
let region = self.region;
|
||||
self.instances.push(PrimitiveInstance { region, ptr });
|
||||
self.data.push(D::DISCRIM);
|
||||
self.data
|
||||
.extend_from_slice(bytemuck::cast_slice::<_, u32>(&[data]));
|
||||
}
|
||||
pub fn draw_within(&mut self, node: &impl UINode, region: UIRegion) {
|
||||
let old = self.region;
|
||||
self.region.select(®ion);
|
||||
node.draw(self);
|
||||
self.region = old;
|
||||
}
|
||||
}
|
||||
84
src/render/primitive/point.rs
Normal file
84
src/render/primitive/point.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use std::ops::*;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct Point {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
}
|
||||
|
||||
pub const fn point(x: f32, y: f32) -> Point {
|
||||
Point::new(x, y)
|
||||
}
|
||||
|
||||
impl Point {
|
||||
pub const fn new(x: f32, y: f32) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
pub const fn lerp(self, to: Self, amt: impl const Into<Self>) -> Self {
|
||||
let amt = amt.into();
|
||||
Self {
|
||||
x: lerp(self.x, to.x, amt.x),
|
||||
y: lerp(self.y, to.y, amt.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fn lerp(x: f32, y: f32, amt: f32) -> f32 {
|
||||
(1.0 - amt) * x + y * amt
|
||||
}
|
||||
|
||||
impl const From<f32> for Point {
|
||||
fn from(v: f32) -> Self {
|
||||
Self { x: v, y: v }
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_op_inner {
|
||||
($op:ident $fn:ident $opa:ident $fna:ident) => {
|
||||
impl const $op for Point {
|
||||
type Output = Self;
|
||||
|
||||
fn $fn(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
x: self.x.$fn(rhs.x),
|
||||
y: self.y.$fn(rhs.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl $opa for Point {
|
||||
fn $fna(&mut self, rhs: Self) {
|
||||
self.x.$fna(rhs.x);
|
||||
self.y.$fna(rhs.y);
|
||||
}
|
||||
}
|
||||
impl const $op<f32> for Point {
|
||||
type Output = Self;
|
||||
|
||||
fn $fn(self, rhs: f32) -> Self::Output {
|
||||
Self {
|
||||
x: self.x.$fn(rhs),
|
||||
y: self.y.$fn(rhs),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl $opa<f32> for Point {
|
||||
fn $fna(&mut self, rhs: f32) {
|
||||
self.x.$fna(rhs);
|
||||
self.y.$fna(rhs);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_op {
|
||||
($op:ident $fn:ident) => {
|
||||
impl_op_inner!($op $fn ${concat($op,Assign)} ${concat($fn,_assign)});
|
||||
};
|
||||
}
|
||||
|
||||
impl_op!(Add add);
|
||||
impl_op!(Sub sub);
|
||||
impl_op!(Mul mul);
|
||||
impl_op!(Div div);
|
||||
110
src/render/shader.wgsl
Normal file
110
src/render/shader.wgsl
Normal file
@@ -0,0 +1,110 @@
|
||||
@group(0) @binding(0)
|
||||
var<uniform> window: WindowUniform;
|
||||
@group(0) @binding(1)
|
||||
var<storage> data: array<u32>;
|
||||
|
||||
struct WindowUniform {
|
||||
dim: vec2<f32>,
|
||||
};
|
||||
|
||||
struct InstanceInput {
|
||||
@location(0) top_left_anchor: vec2<f32>,
|
||||
@location(1) top_left_offset: vec2<f32>,
|
||||
@location(2) bottom_right_anchor: vec2<f32>,
|
||||
@location(3) bottom_right_offset: vec2<f32>,
|
||||
@location(4) pointer: u32,
|
||||
}
|
||||
|
||||
struct RoundedRect {
|
||||
color: u32,
|
||||
radius: f32,
|
||||
thickness: f32,
|
||||
inner_radius: f32,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@location(0) pointer: u32,
|
||||
@location(1) top_left: vec2<f32>,
|
||||
@location(2) bot_right: vec2<f32>,
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
};
|
||||
|
||||
struct Region {
|
||||
pos: vec2<f32>,
|
||||
top_left: vec2<f32>,
|
||||
bot_right: vec2<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
@builtin(vertex_index) vi: u32,
|
||||
in: InstanceInput,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
let top_left = in.top_left_anchor * window.dim + in.top_left_offset;
|
||||
let bot_right = in.bottom_right_anchor * window.dim + in.bottom_right_offset;
|
||||
let size = bot_right - top_left;
|
||||
|
||||
var pos = top_left + vec2<f32>(
|
||||
f32(vi % 2u),
|
||||
f32(vi / 2u)
|
||||
) * size;
|
||||
pos = pos / window.dim * 2.0 - 1.0;
|
||||
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
|
||||
out.pointer = in.pointer;
|
||||
out.top_left = top_left;
|
||||
out.bot_right = bot_right;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(
|
||||
in: VertexOutput
|
||||
) -> @location(0) vec4<f32> {
|
||||
let pos = in.clip_position.xy;
|
||||
let ty = data[in.pointer];
|
||||
let dp = in.pointer + 1u;
|
||||
let region = Region(pos, in.top_left, in.bot_right);
|
||||
switch ty {
|
||||
case 0u: {
|
||||
return draw_rounded_rect(region, RoundedRect(
|
||||
data[dp + 0u],
|
||||
bitcast<f32>(data[dp + 1u]),
|
||||
bitcast<f32>(data[dp + 2u]),
|
||||
bitcast<f32>(data[dp + 3u]),
|
||||
));
|
||||
}
|
||||
default: {}
|
||||
}
|
||||
return vec4(1.0, 0.0, 1.0, 1.0);
|
||||
}
|
||||
|
||||
fn draw_rounded_rect(region: Region, rect: RoundedRect) -> vec4<f32> {
|
||||
var color = unpack4x8unorm(rect.color);
|
||||
|
||||
let edge = 0.5;
|
||||
|
||||
let size = region.bot_right - region.top_left;
|
||||
let corner = size / 2.0;
|
||||
let center = region.top_left + corner;
|
||||
|
||||
let dist = distance_from_rect(region.pos, center, corner, rect.radius);
|
||||
color.a *= 1.0 - smoothstep(-min(edge, rect.radius), edge, dist);
|
||||
|
||||
if rect.thickness > 0.0 {
|
||||
let dist2 = distance_from_rect(region.pos, center, corner - rect.thickness, rect.inner_radius);
|
||||
color.a *= smoothstep(-min(edge, rect.inner_radius), edge, dist2);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
fn distance_from_rect(pixel_pos: vec2<f32>, rect_center: vec2<f32>, rect_corner: vec2<f32>, radius: f32) -> f32 {
|
||||
// vec from center to pixel
|
||||
let p = pixel_pos - rect_center;
|
||||
// vec from inner rect corner to pixel
|
||||
let q = abs(p) - (rect_corner - radius);
|
||||
return length(max(q, vec2<f32>(0.0, 0.0))) - radius;
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
use wgpu::{RenderPass, VertexAttribute};
|
||||
|
||||
use crate::render::primitive::RoundedRect;
|
||||
|
||||
pub struct RoundedRectBuffer {
|
||||
buffer: wgpu::Buffer,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
||||
pub struct WindowUniform {
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
}
|
||||
|
||||
|
||||
impl RoundedRectBuffer {
|
||||
pub fn new(device: &wgpu::Device) -> Self {
|
||||
Self {
|
||||
buffer: Self::init_buf(device, 0),
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
pub fn update(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
rects: &[RoundedRect],
|
||||
) {
|
||||
if self.len != rects.len() {
|
||||
self.len = rects.len();
|
||||
self.buffer = Self::init_buf(device, std::mem::size_of_val(rects));
|
||||
}
|
||||
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(rects));
|
||||
}
|
||||
fn init_buf(device: &wgpu::Device, size: usize) -> wgpu::Buffer {
|
||||
device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Instance Buffer"),
|
||||
size: size as u64,
|
||||
mapped_at_creation: false,
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
})
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
pub fn set_in<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
||||
pass.set_vertex_buffer(0, self.buffer.slice(..));
|
||||
}
|
||||
}
|
||||
|
||||
impl RoundedRect {
|
||||
const ATTRIBS: [VertexAttribute; 11] = wgpu::vertex_attr_array![
|
||||
0 => Float32x2,
|
||||
1 => Float32x2,
|
||||
2 => Float32x2,
|
||||
3 => Float32x2,
|
||||
4 => Float32x4,
|
||||
5 => Float32x4,
|
||||
6 => Float32x4,
|
||||
7 => Float32x4,
|
||||
8 => Float32,
|
||||
9 => Float32,
|
||||
10 => Float32,
|
||||
];
|
||||
pub fn desc() -> wgpu::VertexBufferLayout<'static> {
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<RoundedRect>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &Self::ATTRIBS,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
use wgpu::{
|
||||
util::{BufferInitDescriptor, DeviceExt},
|
||||
BufferUsages,
|
||||
};
|
||||
|
||||
use crate::render::primitive::RoundedRect;
|
||||
|
||||
use super::{
|
||||
data::{RoundedRectBuffer, WindowUniform},
|
||||
ShapeBuffers, ShapePipeline, SHAPE_SHADER,
|
||||
};
|
||||
|
||||
impl ShapePipeline {
|
||||
pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> Self {
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("UI Shape Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(SHAPE_SHADER.into()),
|
||||
});
|
||||
|
||||
let window_uniform = WindowUniform::default();
|
||||
let window_buffer = device.create_buffer_init(&BufferInitDescriptor {
|
||||
label: Some("Camera Buffer"),
|
||||
contents: bytemuck::cast_slice(&[window_uniform]),
|
||||
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let instance_buffer = RoundedRectBuffer::new(device);
|
||||
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
label: Some("camera_bind_group_layout"),
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: window_buffer.as_entire_binding(),
|
||||
}],
|
||||
label: Some("camera_bind_group"),
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("UI Shape Pipeline Layout"),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("UI Shape Pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[RoundedRect::desc()],
|
||||
compilation_options: Default::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: config.format,
|
||||
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: Default::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleStrip,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Cw,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
let buffers = ShapeBuffers {
|
||||
window: window_buffer,
|
||||
instance: instance_buffer,
|
||||
};
|
||||
|
||||
Self {
|
||||
bind_group,
|
||||
pipeline,
|
||||
buffers,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
use crate::render::primitive::RoundedRect;
|
||||
use data::{RoundedRectBuffer, WindowUniform};
|
||||
use wgpu::{BindGroup, Buffer, RenderPass, RenderPipeline};
|
||||
use winit::dpi::PhysicalSize;
|
||||
|
||||
mod data;
|
||||
mod layout;
|
||||
|
||||
pub const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
|
||||
|
||||
pub struct ShapeBuffers {
|
||||
pub window: Buffer,
|
||||
pub instance: RoundedRectBuffer,
|
||||
}
|
||||
|
||||
pub struct ShapePipeline {
|
||||
pub bind_group: BindGroup,
|
||||
pub pipeline: RenderPipeline,
|
||||
|
||||
pub buffers: ShapeBuffers,
|
||||
}
|
||||
|
||||
impl ShapePipeline {
|
||||
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
||||
pass.set_pipeline(&self.pipeline);
|
||||
pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
if self.buffers.instance.len() != 0 {
|
||||
self.buffers.instance.set_in(pass);
|
||||
pass.draw(0..4, 0..self.buffers.instance.len() as u32);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, rects: &[RoundedRect]) {
|
||||
self.buffers.instance.update(device, queue, rects);
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, size: &PhysicalSize<u32>, queue: &wgpu::Queue) {
|
||||
let slice = &[WindowUniform {
|
||||
width: size.width as f32,
|
||||
height: size.height as f32,
|
||||
}];
|
||||
queue.write_buffer(&self.buffers.window, 0, bytemuck::cast_slice(slice));
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
|
||||
// vertex shader
|
||||
|
||||
struct VertexOutput {
|
||||
@location(0) color: vec4<f32>,
|
||||
@location(1) center: vec2<f32>,
|
||||
@location(2) corner: vec2<f32>,
|
||||
@location(3) radius: f32,
|
||||
@location(4) inner_radius: f32,
|
||||
@location(5) thickness: f32,
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
};
|
||||
|
||||
struct WindowUniform {
|
||||
dim: vec2<f32>,
|
||||
};
|
||||
|
||||
struct InstanceInput {
|
||||
@location(0) top_left_anchor: vec2<f32>,
|
||||
@location(1) top_left_offset: vec2<f32>,
|
||||
@location(2) bottom_right_anchor: vec2<f32>,
|
||||
@location(3) bottom_right_offset: vec2<f32>,
|
||||
@location(4) top_right_color: vec4<f32>,
|
||||
@location(5) top_left_color: vec4<f32>,
|
||||
@location(6) bottom_right_color: vec4<f32>,
|
||||
@location(7) bottom_left_color: vec4<f32>,
|
||||
@location(8) radius: f32,
|
||||
@location(9) inner_radius: f32,
|
||||
@location(10) thickness: f32,
|
||||
}
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> window: WindowUniform;
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
@builtin(vertex_index) vi: u32,
|
||||
in: InstanceInput,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
let top_left = in.top_left_anchor * window.dim + in.top_left_offset;
|
||||
let bottom_right = in.bottom_right_anchor * window.dim + in.bottom_right_offset;
|
||||
let size = bottom_right - top_left;
|
||||
|
||||
var pos = top_left + vec2<f32>(
|
||||
f32(vi % 2u),
|
||||
f32(vi / 2u)
|
||||
) * size;
|
||||
pos = pos / window.dim * 2.0 - 1.0;
|
||||
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
|
||||
|
||||
if vi == 0u {
|
||||
out.color = in.top_left_color;
|
||||
} else if vi == 1u {
|
||||
out.color = in.top_right_color;
|
||||
} else if vi == 2u {
|
||||
out.color = in.bottom_left_color;
|
||||
} else if vi == 3u {
|
||||
out.color = in.bottom_right_color;
|
||||
}
|
||||
|
||||
out.corner = size / 2.0;
|
||||
out.center = top_left + out.corner;
|
||||
out.radius = in.radius;
|
||||
out.inner_radius = in.inner_radius;
|
||||
out.thickness = in.thickness;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(
|
||||
in: VertexOutput
|
||||
) -> @location(0) vec4<f32> {
|
||||
var color = in.color;
|
||||
|
||||
let edge = 0.5;
|
||||
|
||||
let dist = distance_from_rect(in.clip_position.xy, in.center, in.corner, in.radius);
|
||||
color.a *= 1.0 - smoothstep(-min(edge, in.radius), edge, dist);
|
||||
|
||||
if in.thickness > 0.0 {
|
||||
let dist2 = distance_from_rect(in.clip_position.xy, in.center, in.corner - in.thickness, in.inner_radius);
|
||||
color.a *= smoothstep(-min(edge, in.inner_radius), edge, dist2);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
fn distance_from_rect(pixel_pos: vec2<f32>, rect_center: vec2<f32>, rect_corner: vec2<f32>, radius: f32) -> f32 {
|
||||
// vec from center to pixel
|
||||
let p = pixel_pos - rect_center;
|
||||
// vec from inner rect corner to pixel
|
||||
let q = abs(p) - (rect_corner - radius);
|
||||
return length(max(q, vec2<f32>(0.0, 0.0))) - radius;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod pipeline;
|
||||
@@ -1,127 +0,0 @@
|
||||
use glyphon::{
|
||||
Attrs, Buffer, Color, Family, FontSystem, Metrics, Resolution, Shaping, SwashCache, TextArea,
|
||||
TextAtlas, TextBounds, TextRenderer,
|
||||
};
|
||||
use wgpu::{MultisampleState, RenderPass};
|
||||
|
||||
use crate::{
|
||||
client::ui::element::Align,
|
||||
render::{primitive::TextPrimitive, surface::RenderSurface},
|
||||
};
|
||||
|
||||
pub struct TextPipeline {
|
||||
pub renderer: glyphon::TextRenderer,
|
||||
pub font_system: glyphon::FontSystem,
|
||||
pub atlas: glyphon::TextAtlas,
|
||||
pub cache: glyphon::SwashCache,
|
||||
pub text_buffers: Vec<glyphon::Buffer>,
|
||||
pub old_text: Vec<TextPrimitive>,
|
||||
}
|
||||
|
||||
impl TextPipeline {
|
||||
pub fn new(surface: &RenderSurface) -> Self {
|
||||
let RenderSurface {
|
||||
device,
|
||||
config,
|
||||
queue,
|
||||
..
|
||||
} = surface;
|
||||
|
||||
let font_system = FontSystem::new();
|
||||
let cache = SwashCache::new();
|
||||
let mut atlas = TextAtlas::new(&device, &queue, config.format);
|
||||
let renderer = TextRenderer::new(&mut atlas, &device, MultisampleState::default(), None);
|
||||
|
||||
Self {
|
||||
font_system,
|
||||
atlas,
|
||||
cache,
|
||||
renderer,
|
||||
text_buffers: Vec::new(),
|
||||
old_text: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
||||
self.renderer.render(&self.atlas, pass).unwrap();
|
||||
}
|
||||
|
||||
pub fn update(&mut self, surface: &RenderSurface, text: &[TextPrimitive]) {
|
||||
let buffers = &mut self.text_buffers;
|
||||
if buffers.len() < text.len() {
|
||||
self.old_text.resize(text.len(), TextPrimitive::empty());
|
||||
buffers.resize_with(text.len(), || {
|
||||
Buffer::new(&mut self.font_system, Metrics::new(20.0, 25.0))
|
||||
})
|
||||
}
|
||||
for ((buffer, text), old) in buffers.iter_mut().zip(text).zip(&mut self.old_text) {
|
||||
if text != old {
|
||||
*old = text.clone();
|
||||
buffer.set_size(&mut self.font_system, f32::MAX, f32::MAX);
|
||||
buffer.set_text(
|
||||
&mut self.font_system,
|
||||
&text.content,
|
||||
Attrs::new().family(Family::SansSerif),
|
||||
Shaping::Basic,
|
||||
);
|
||||
}
|
||||
}
|
||||
let color = Color::rgb(255, 255, 255);
|
||||
let areas = buffers.iter().zip(text).map(|(buffer, text)| {
|
||||
let width = measure(&buffer).0;
|
||||
let mut left = text.pos.x
|
||||
- match text.align {
|
||||
Align::Left => 0.0,
|
||||
Align::Center => width / 2.0,
|
||||
Align::Right => width,
|
||||
};
|
||||
let x = text.pos.x;
|
||||
let w = text.bounds.0;
|
||||
let x_bounds = match text.align {
|
||||
Align::Left => (x, x + w),
|
||||
Align::Center => (x - w / 2.0, x + w / 2.0),
|
||||
Align::Right => (x - w, x),
|
||||
};
|
||||
if left < x_bounds.0 {
|
||||
left = x_bounds.0;
|
||||
}
|
||||
TextArea {
|
||||
buffer: &buffer,
|
||||
left,
|
||||
top: text.pos.y,
|
||||
scale: 1.0,
|
||||
bounds: TextBounds {
|
||||
left: x_bounds.0 as i32,
|
||||
top: text.pos.y as i32,
|
||||
right: x_bounds.1 as i32,
|
||||
bottom: (text.pos.y + text.bounds.1) as i32,
|
||||
},
|
||||
default_color: color,
|
||||
}
|
||||
});
|
||||
self.renderer
|
||||
.prepare(
|
||||
&surface.device,
|
||||
&surface.queue,
|
||||
&mut self.font_system,
|
||||
&mut self.atlas,
|
||||
Resolution {
|
||||
width: surface.config.width,
|
||||
height: surface.config.height,
|
||||
},
|
||||
areas,
|
||||
&mut self.cache,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn measure(buffer: &glyphon::Buffer) -> (f32, f32) {
|
||||
let (width, total_lines) = buffer
|
||||
.layout_runs()
|
||||
.fold((0.0, 0usize), |(width, total_lines), run| {
|
||||
(run.line_w.max(width), total_lines + 1)
|
||||
});
|
||||
|
||||
(width, total_lines as f32 * buffer.metrics().line_height)
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
use wgpu::{
|
||||
util::DeviceExt,
|
||||
BindGroup, BindGroupLayout, Device, Queue,
|
||||
};
|
||||
|
||||
use crate::render::surface::RenderSurface;
|
||||
|
||||
use super::{
|
||||
pipeline::{TexturePipeline, TEXTURE_SHADER},
|
||||
vertex::{TextureVertex, TEXTURE_VERTICES}, texture::GameTexture,
|
||||
};
|
||||
|
||||
impl TexturePipeline {
|
||||
pub fn new(surface: &RenderSurface) -> Self {
|
||||
let RenderSurface {
|
||||
device,
|
||||
config,
|
||||
queue,
|
||||
..
|
||||
} = surface;
|
||||
|
||||
let (bind_group_layout, diffuse_bind_group) = Self::init_textures(device, queue);
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("UI Texture Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(TEXTURE_SHADER.into()),
|
||||
});
|
||||
|
||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Texture Vertex Buffer"),
|
||||
contents: bytemuck::cast_slice(TEXTURE_VERTICES),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("UI Texture Pipeline Layout"),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("UI Texture Pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[TextureVertex::desc()],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: config.format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleStrip,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
Self {
|
||||
pipeline,
|
||||
vertex_buffer,
|
||||
diffuse_bind_group,
|
||||
}
|
||||
}
|
||||
|
||||
fn init_textures(device: &Device, queue: &Queue) -> (BindGroupLayout, BindGroup) {
|
||||
let diffuse_bytes = include_bytes!("./textures/happy-tree.png");
|
||||
let diffuse_texture =
|
||||
GameTexture::from_bytes(&device, &queue, diffuse_bytes, "happy-tree.png").unwrap();
|
||||
|
||||
let texture_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
multisampled: false,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
// This should match the filterable field of the
|
||||
// corresponding Texture entry above.
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: Some("texture_bind_group_layout"),
|
||||
});
|
||||
|
||||
let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &texture_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
|
||||
},
|
||||
],
|
||||
label: Some("diffuse_bind_group"),
|
||||
});
|
||||
|
||||
(texture_bind_group_layout, diffuse_bind_group)
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
mod init;
|
||||
mod vertex;
|
||||
mod texture;
|
||||
|
||||
use wgpu::{RenderPass, RenderPipeline};
|
||||
|
||||
pub const TEXTURE_SHADER: &str = include_str!("./shader.wgsl");
|
||||
|
||||
pub struct TexturePipeline {
|
||||
pub pipeline: RenderPipeline,
|
||||
pub vertex_buffer: wgpu::Buffer,
|
||||
pub diffuse_bind_group: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
impl TexturePipeline {
|
||||
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
||||
pass.set_pipeline(&self.pipeline);
|
||||
pass.set_bind_group(0, &self.diffuse_bind_group, &[]);
|
||||
pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
|
||||
pass.draw(0..4, 0..1);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// Vertex shader
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2<f32>,
|
||||
@location(1) tex_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
model: VertexInput,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.tex_coords = model.tex_coords;
|
||||
out.clip_position = vec4<f32>(model.position, 0.0, 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Fragment shader
|
||||
|
||||
@group(0) @binding(0)
|
||||
var t_diffuse: texture_2d<f32>;
|
||||
@group(0)@binding(1)
|
||||
var s_diffuse: sampler;
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
use image::{GenericImageView, ImageResult};
|
||||
|
||||
pub struct GameTexture {
|
||||
pub texture: wgpu::Texture,
|
||||
pub view: wgpu::TextureView,
|
||||
pub sampler: wgpu::Sampler,
|
||||
}
|
||||
|
||||
impl GameTexture {
|
||||
pub fn from_bytes(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
bytes: &[u8],
|
||||
label: &str
|
||||
) -> ImageResult<Self> {
|
||||
let img = image::load_from_memory(bytes)?;
|
||||
Self::from_image(device, queue, &img, Some(label))
|
||||
}
|
||||
|
||||
pub fn from_image(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
img: &image::DynamicImage,
|
||||
label: Option<&str>
|
||||
) -> ImageResult<Self> {
|
||||
let rgba = img.to_rgba8();
|
||||
let dimensions = img.dimensions();
|
||||
|
||||
let size = wgpu::Extent3d {
|
||||
width: dimensions.0,
|
||||
height: dimensions.1,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
let texture = device.create_texture(
|
||||
&wgpu::TextureDescriptor {
|
||||
label,
|
||||
size,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
}
|
||||
);
|
||||
|
||||
queue.write_texture(
|
||||
wgpu::ImageCopyTexture {
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
},
|
||||
&rgba,
|
||||
wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(4 * dimensions.0),
|
||||
rows_per_image: Some(dimensions.1),
|
||||
},
|
||||
size,
|
||||
);
|
||||
|
||||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let sampler = device.create_sampler(
|
||||
&wgpu::SamplerDescriptor {
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
Ok(Self { texture, view, sampler })
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB |
@@ -1,35 +0,0 @@
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct TextureVertex {
|
||||
position: [f32; 2],
|
||||
tex_coords: [f32; 2],
|
||||
}
|
||||
|
||||
impl TextureVertex {
|
||||
const ATTRIBS: [wgpu::VertexAttribute; 2] =
|
||||
wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2];
|
||||
|
||||
pub fn desc() -> wgpu::VertexBufferLayout<'static> {
|
||||
use std::mem;
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: mem::size_of::<TextureVertex>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &Self::ATTRIBS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const TEXTURE_VERTICES: &[TextureVertex] = &[
|
||||
TextureVertex { position: [0.0, 0.0], tex_coords: [0.0, 1.0], },
|
||||
TextureVertex { position: [0.5, 0.0], tex_coords: [1.0, 1.0], },
|
||||
TextureVertex { position: [0.0, 0.5], tex_coords: [0.0, 0.0], },
|
||||
TextureVertex { position: [0.5, 0.5], tex_coords: [1.0, 0.0], },
|
||||
];
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct ShapeVertex {
|
||||
position: [f32; 2],
|
||||
tex_coords: [f32; 2],
|
||||
}
|
||||
|
||||
48
src/render/util/mod.rs
Normal file
48
src/render/util/mod.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use bytemuck::Pod;
|
||||
use wgpu::*;
|
||||
|
||||
pub struct ArrBuf<T: Pod> {
|
||||
label: &'static str,
|
||||
usage: BufferUsages,
|
||||
pub buffer: Buffer,
|
||||
len: usize,
|
||||
_pd: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Pod> ArrBuf<T> {
|
||||
pub fn new(device: &Device, usage: BufferUsages, label: &'static str) -> Self {
|
||||
Self {
|
||||
label,
|
||||
usage,
|
||||
buffer: Self::init_buf(device, 0, usage, label),
|
||||
len: 0,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
pub fn update(&mut self, device: &Device, queue: &Queue, data: &[T]) {
|
||||
if self.len != data.len() {
|
||||
self.len = data.len();
|
||||
self.buffer =
|
||||
Self::init_buf(device, std::mem::size_of_val(data), self.usage, self.label);
|
||||
}
|
||||
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(data));
|
||||
}
|
||||
fn init_buf(device: &Device, size: usize, usage: BufferUsages, label: &'static str) -> Buffer {
|
||||
let mut size = size as u64;
|
||||
if usage.contains(BufferUsages::STORAGE) {
|
||||
size = size.max(1);
|
||||
}
|
||||
device.create_buffer(&BufferDescriptor {
|
||||
label: Some(label),
|
||||
size,
|
||||
mapped_at_creation: false,
|
||||
usage,
|
||||
})
|
||||
}
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ impl ApplicationHandler for App {
|
||||
}
|
||||
}
|
||||
|
||||
fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
|
||||
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
|
||||
self.client.as_mut().unwrap().event(event, event_loop);
|
||||
}
|
||||
}
|
||||
58
src/testing/mod.rs
Normal file
58
src/testing/mod.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use app::App;
|
||||
use gui::{primitive::Axis, NodeArrayUtil, NodeUtil, RoundedRect, UIColor};
|
||||
use render::Renderer;
|
||||
use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window};
|
||||
|
||||
mod app;
|
||||
mod render;
|
||||
|
||||
pub fn main() {
|
||||
App::run();
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
renderer: Renderer,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(window: Arc<Window>) -> Self {
|
||||
let mut renderer = Renderer::new(window);
|
||||
let rect = RoundedRect {
|
||||
color: UIColor::WHITE,
|
||||
radius: 10.0,
|
||||
thickness: 0.0,
|
||||
inner_radius: 0.0,
|
||||
};
|
||||
let ui = (
|
||||
(
|
||||
rect.color(UIColor::BLUE),
|
||||
(
|
||||
rect.color(UIColor::RED),
|
||||
(rect.color(UIColor::ORANGE), rect.color(UIColor::LIME))
|
||||
.proportioned(Axis::Y, [1, 1]),
|
||||
rect.color(UIColor::YELLOW),
|
||||
)
|
||||
.proportioned(Axis::X, [2, 2, 1])
|
||||
.pad(10),
|
||||
)
|
||||
.proportioned(Axis::X, [1, 3]),
|
||||
rect.color(UIColor::GREEN),
|
||||
)
|
||||
.proportioned(Axis::Y, [3, 1])
|
||||
.pad(10)
|
||||
.into();
|
||||
renderer.update(&ui);
|
||||
Self { renderer }
|
||||
}
|
||||
|
||||
pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => event_loop.exit(),
|
||||
WindowEvent::RedrawRequested => self.renderer.draw(),
|
||||
WindowEvent::Resized(size) => self.renderer.resize(&size),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
132
src/testing/render/mod.rs
Normal file
132
src/testing/render/mod.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use gui::{UIRenderNode, UI};
|
||||
use pollster::FutureExt;
|
||||
use std::sync::Arc;
|
||||
use wgpu::util::StagingBelt;
|
||||
use winit::{dpi::PhysicalSize, window::Window};
|
||||
|
||||
pub const CLEAR_COLOR: wgpu::Color = wgpu::Color::BLACK;
|
||||
|
||||
pub struct Renderer {
|
||||
surface: wgpu::Surface<'static>,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
config: wgpu::SurfaceConfiguration,
|
||||
encoder: wgpu::CommandEncoder,
|
||||
staging_belt: StagingBelt,
|
||||
ui_node: UIRenderNode,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
pub fn update(&mut self, ui: &UI) {
|
||||
self.ui_node.update(&self.device, &self.queue, ui);
|
||||
}
|
||||
|
||||
pub fn draw(&mut self) {
|
||||
let output = self.surface.get_current_texture().unwrap();
|
||||
let view = output
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let mut encoder = std::mem::replace(&mut self.encoder, Self::create_encoder(&self.device));
|
||||
{
|
||||
let render_pass = &mut encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(CLEAR_COLOR),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
depth_slice: None,
|
||||
})],
|
||||
..Default::default()
|
||||
});
|
||||
self.ui_node.draw(render_pass);
|
||||
}
|
||||
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
self.staging_belt.finish();
|
||||
output.present();
|
||||
self.staging_belt.recall();
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, size: &PhysicalSize<u32>) {
|
||||
self.config.width = size.width;
|
||||
self.config.height = size.height;
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
self.ui_node.resize(size, &self.queue);
|
||||
}
|
||||
|
||||
fn create_encoder(device: &wgpu::Device) -> wgpu::CommandEncoder {
|
||||
device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Render Encoder"),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new(window: Arc<Window>) -> Self {
|
||||
let size = window.inner_size();
|
||||
|
||||
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
|
||||
backends: wgpu::Backends::PRIMARY,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let surface = instance
|
||||
.create_surface(window.clone())
|
||||
.expect("Could not create window surface!");
|
||||
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::default(),
|
||||
compatible_surface: Some(&surface),
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.block_on()
|
||||
.expect("Could not get adapter!");
|
||||
|
||||
let (device, queue) = adapter
|
||||
.request_device(&wgpu::DeviceDescriptor {
|
||||
required_features: wgpu::Features::empty(),
|
||||
required_limits: wgpu::Limits::default(),
|
||||
..Default::default()
|
||||
})
|
||||
.block_on()
|
||||
.expect("Could not get 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 = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: surface_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::AutoVsync,
|
||||
alpha_mode: surface_caps.alpha_modes[0],
|
||||
desired_maximum_frame_latency: 2,
|
||||
view_formats: vec![],
|
||||
};
|
||||
|
||||
surface.configure(&device, &config);
|
||||
|
||||
let staging_belt = StagingBelt::new(4096 * 4);
|
||||
let encoder = Self::create_encoder(&device);
|
||||
|
||||
let shape_pipeline = UIRenderNode::new(&device, &config);
|
||||
|
||||
Self {
|
||||
surface,
|
||||
device,
|
||||
queue,
|
||||
config,
|
||||
encoder,
|
||||
staging_belt,
|
||||
ui_node: shape_pipeline,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user