refactor project structure (start of redoing atomic branch without atomics)
This commit is contained in:
50
core/src/render/data.rs
Normal file
50
core/src/render/data.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use crate::{layout::UiRegion, util::Id};
|
||||
use wgpu::*;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
||||
pub struct WindowUniform {
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct PrimitiveInstance {
|
||||
pub region: UiRegion,
|
||||
pub binding: u32,
|
||||
pub idx: u32,
|
||||
pub mask_idx: MaskIdx,
|
||||
}
|
||||
|
||||
impl PrimitiveInstance {
|
||||
const ATTRIBS: [VertexAttribute; 7] = vertex_attr_array![
|
||||
0 => Float32x2,
|
||||
1 => Float32x2,
|
||||
2 => Float32x2,
|
||||
3 => Float32x2,
|
||||
4 => Uint32,
|
||||
5 => Uint32,
|
||||
6 => Uint32,
|
||||
];
|
||||
|
||||
pub fn desc() -> VertexBufferLayout<'static> {
|
||||
VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<Self>() as BufferAddress,
|
||||
step_mode: VertexStepMode::Instance,
|
||||
attributes: &Self::ATTRIBS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type MaskIdx = Id<u32>;
|
||||
|
||||
impl MaskIdx {
|
||||
pub const NONE: Self = Self::preset(u32::MAX);
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct Mask {
|
||||
pub region: UiRegion,
|
||||
}
|
||||
356
core/src/render/mod.rs
Normal file
356
core/src/render/mod.rs
Normal file
@@ -0,0 +1,356 @@
|
||||
use std::num::NonZero;
|
||||
|
||||
use crate::{
|
||||
layout::Ui,
|
||||
render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf},
|
||||
util::HashMap,
|
||||
};
|
||||
use data::WindowUniform;
|
||||
use wgpu::{
|
||||
util::{BufferInitDescriptor, DeviceExt},
|
||||
*,
|
||||
};
|
||||
use winit::dpi::PhysicalSize;
|
||||
|
||||
mod data;
|
||||
mod primitive;
|
||||
mod texture;
|
||||
mod util;
|
||||
|
||||
pub use data::{Mask, MaskIdx};
|
||||
pub use primitive::*;
|
||||
|
||||
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
|
||||
|
||||
pub struct UiRenderNode {
|
||||
uniform_group: BindGroup,
|
||||
primitive_layout: BindGroupLayout,
|
||||
rsc_layout: BindGroupLayout,
|
||||
rsc_group: BindGroup,
|
||||
|
||||
pipeline: RenderPipeline,
|
||||
|
||||
layers: HashMap<usize, RenderLayer>,
|
||||
active: Vec<usize>,
|
||||
window_buffer: Buffer,
|
||||
textures: GpuTextures,
|
||||
masks: ArrBuf<Mask>,
|
||||
}
|
||||
|
||||
struct RenderLayer {
|
||||
instance: ArrBuf<PrimitiveInstance>,
|
||||
primitives: PrimitiveBuffers,
|
||||
primitive_group: BindGroup,
|
||||
}
|
||||
|
||||
impl UiRenderNode {
|
||||
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
||||
pass.set_pipeline(&self.pipeline);
|
||||
pass.set_bind_group(0, &self.uniform_group, &[]);
|
||||
pass.set_bind_group(2, &self.rsc_group, &[]);
|
||||
for i in &self.active {
|
||||
let layer = &self.layers[i];
|
||||
if layer.instance.len() == 0 {
|
||||
continue;
|
||||
}
|
||||
pass.set_bind_group(1, &layer.primitive_group, &[]);
|
||||
pass.set_vertex_buffer(0, layer.instance.buffer.slice(..));
|
||||
pass.draw(0..4, 0..layer.instance.len() as u32);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &mut Ui) {
|
||||
self.active.clear();
|
||||
for (i, primitives) in ui.data.layers.iter_mut() {
|
||||
self.active.push(i);
|
||||
for change in primitives.apply_free() {
|
||||
if let Some(inst) = ui.data.active.get_mut(&change.id) {
|
||||
for h in &mut inst.primitives {
|
||||
if h.layer == i && h.inst_idx == change.old {
|
||||
h.inst_idx = change.new;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let rlayer = self.layers.entry(i).or_insert_with(|| {
|
||||
let primitives = PrimitiveBuffers::new(device);
|
||||
let primitive_group =
|
||||
Self::primitive_group(device, &self.primitive_layout, primitives.buffers());
|
||||
RenderLayer {
|
||||
instance: ArrBuf::new(
|
||||
device,
|
||||
BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
||||
"instance",
|
||||
),
|
||||
primitives,
|
||||
primitive_group,
|
||||
}
|
||||
});
|
||||
if primitives.updated {
|
||||
rlayer
|
||||
.instance
|
||||
.update(device, queue, primitives.instances());
|
||||
rlayer.primitives.update(device, queue, primitives.data());
|
||||
rlayer.primitive_group = Self::primitive_group(
|
||||
device,
|
||||
&self.primitive_layout,
|
||||
rlayer.primitives.buffers(),
|
||||
);
|
||||
primitives.updated = false;
|
||||
}
|
||||
}
|
||||
let mut changed = false;
|
||||
changed |= self.textures.update(&mut ui.data.textures);
|
||||
if ui.data.masks.changed {
|
||||
ui.data.masks.changed = false;
|
||||
self.masks.update(device, queue, &ui.data.masks[..]);
|
||||
changed = true;
|
||||
}
|
||||
if changed {
|
||||
self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures, &self.masks);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
device: &Device,
|
||||
queue: &Queue,
|
||||
config: &SurfaceConfiguration,
|
||||
limits: UiLimits,
|
||||
) -> Self {
|
||||
let shader = device.create_shader_module(ShaderModuleDescriptor {
|
||||
label: Some("UI Shape Shader"),
|
||||
source: ShaderSource::Wgsl(SHAPE_SHADER.into()),
|
||||
});
|
||||
|
||||
let window_uniform = WindowUniform::default();
|
||||
let window_buffer = device.create_buffer_init(&BufferInitDescriptor {
|
||||
label: Some("window"),
|
||||
contents: bytemuck::cast_slice(&[window_uniform]),
|
||||
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let uniform_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &[BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
label: Some("window"),
|
||||
});
|
||||
|
||||
let uniform_group = Self::bind_group_0(device, &uniform_layout, &window_buffer);
|
||||
|
||||
let primitive_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &core::array::from_fn::<_, { PrimitiveBuffers::LEN }, _>(|i| {
|
||||
BindGroupLayoutEntry {
|
||||
binding: i as u32,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}
|
||||
}),
|
||||
label: Some("primitive"),
|
||||
});
|
||||
|
||||
let tex_manager = GpuTextures::new(device, queue);
|
||||
let masks = ArrBuf::new(
|
||||
device,
|
||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||
"ui masks",
|
||||
);
|
||||
|
||||
let rsc_layout = Self::rsc_layout(device, &limits);
|
||||
let rsc_group = Self::rsc_group(device, &rsc_layout, &tex_manager, &masks);
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
|
||||
label: Some("UI Shape Pipeline Layout"),
|
||||
bind_group_layouts: &[&uniform_layout, &primitive_layout, &rsc_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 {
|
||||
uniform_group,
|
||||
primitive_layout,
|
||||
rsc_layout,
|
||||
rsc_group,
|
||||
pipeline,
|
||||
window_buffer,
|
||||
layers: HashMap::default(),
|
||||
active: Vec::new(),
|
||||
textures: tex_manager,
|
||||
masks,
|
||||
}
|
||||
}
|
||||
|
||||
fn bind_group_0(
|
||||
device: &Device,
|
||||
layout: &BindGroupLayout,
|
||||
window_buffer: &Buffer,
|
||||
) -> BindGroup {
|
||||
device.create_bind_group(&BindGroupDescriptor {
|
||||
layout,
|
||||
entries: &[BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: window_buffer.as_entire_binding(),
|
||||
}],
|
||||
label: Some("ui window"),
|
||||
})
|
||||
}
|
||||
|
||||
fn primitive_group(
|
||||
device: &Device,
|
||||
layout: &BindGroupLayout,
|
||||
buffers: [(u32, &Buffer); PrimitiveBuffers::LEN],
|
||||
) -> BindGroup {
|
||||
device.create_bind_group(&BindGroupDescriptor {
|
||||
layout,
|
||||
entries: &buffers.map(|(binding, buf)| BindGroupEntry {
|
||||
binding,
|
||||
resource: buf.as_entire_binding(),
|
||||
}),
|
||||
label: Some("ui primitives"),
|
||||
})
|
||||
}
|
||||
|
||||
fn rsc_layout(device: &Device, limits: &UiLimits) -> BindGroupLayout {
|
||||
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Texture {
|
||||
sample_type: TextureSampleType::Float { filterable: false },
|
||||
view_dimension: TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: Some(NonZero::new(limits.max_textures).unwrap()),
|
||||
},
|
||||
BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Sampler(SamplerBindingType::NonFiltering),
|
||||
count: Some(NonZero::new(limits.max_samplers).unwrap()),
|
||||
},
|
||||
BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: Some("ui rsc"),
|
||||
})
|
||||
}
|
||||
|
||||
fn rsc_group(
|
||||
device: &Device,
|
||||
layout: &BindGroupLayout,
|
||||
tex_manager: &GpuTextures,
|
||||
masks: &ArrBuf<Mask>,
|
||||
) -> BindGroup {
|
||||
device.create_bind_group(&BindGroupDescriptor {
|
||||
layout,
|
||||
entries: &[
|
||||
BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: BindingResource::TextureViewArray(&tex_manager.views()),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: BindingResource::SamplerArray(&tex_manager.samplers()),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: masks.buffer.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
label: Some("ui rsc"),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn view_count(&self) -> usize {
|
||||
self.textures.view_count()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UiLimits {
|
||||
max_textures: u32,
|
||||
max_samplers: u32,
|
||||
}
|
||||
|
||||
impl Default for UiLimits {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_textures: 100000,
|
||||
max_samplers: 1000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UiLimits {
|
||||
pub fn max_binding_array_elements_per_shader_stage(&self) -> u32 {
|
||||
self.max_textures + self.max_samplers
|
||||
}
|
||||
pub fn max_binding_array_sampler_elements_per_shader_stage(&self) -> u32 {
|
||||
self.max_samplers
|
||||
}
|
||||
}
|
||||
283
core/src/render/primitive.rs
Normal file
283
core/src/render/primitive.rs
Normal file
@@ -0,0 +1,283 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use crate::{
|
||||
layout::{Color, UiRegion},
|
||||
render::{
|
||||
ArrBuf,
|
||||
data::{MaskIdx, PrimitiveInstance},
|
||||
},
|
||||
util::Id,
|
||||
};
|
||||
use bytemuck::Pod;
|
||||
use wgpu::*;
|
||||
|
||||
pub struct Primitives {
|
||||
instances: Vec<PrimitiveInstance>,
|
||||
assoc: Vec<Id>,
|
||||
data: PrimitiveData,
|
||||
free: Vec<usize>,
|
||||
pub updated: bool,
|
||||
}
|
||||
|
||||
impl Default for Primitives {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
instances: Default::default(),
|
||||
assoc: Default::default(),
|
||||
data: Default::default(),
|
||||
free: Vec::new(),
|
||||
updated: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Primitive: Pod {
|
||||
const BINDING: u32;
|
||||
fn vec(data: &mut PrimitiveData) -> &mut PrimitiveVec<Self>;
|
||||
}
|
||||
|
||||
macro_rules! primitives {
|
||||
($($name:ident: $ty:ty => $binding:expr,)*) => {
|
||||
#[derive(Default)]
|
||||
pub struct PrimitiveData {
|
||||
$(pub(crate) $name: PrimitiveVec<$ty>,)*
|
||||
}
|
||||
|
||||
pub struct PrimitiveBuffers {
|
||||
$($name: ArrBuf<$ty>,)*
|
||||
}
|
||||
|
||||
impl PrimitiveBuffers {
|
||||
pub fn update(&mut self, device: &Device, queue: &Queue, data: &PrimitiveData) {
|
||||
$(self.$name.update(device, queue, &data.$name);)*
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimitiveBuffers {
|
||||
pub const LEN: usize = primitives!(@count $($name)*);
|
||||
pub fn buffers(&self) -> [(u32, &Buffer); Self::LEN] {
|
||||
[
|
||||
$((<$ty>::BINDING, &self.$name.buffer),)*
|
||||
]
|
||||
}
|
||||
pub fn new(device: &Device) -> Self {
|
||||
Self {
|
||||
$($name: ArrBuf::new(
|
||||
device,
|
||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||
stringify!($name),
|
||||
),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimitiveData {
|
||||
pub fn clear(&mut self) {
|
||||
$(self.$name.clear();)*
|
||||
}
|
||||
pub fn free(&mut self, binding: u32, idx: usize) {
|
||||
match binding {
|
||||
$(<$ty>::BINDING => self.$name.free(idx),)*
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
unsafe impl bytemuck::Pod for $ty {}
|
||||
unsafe impl bytemuck::Zeroable for $ty {}
|
||||
impl Primitive for $ty {
|
||||
const BINDING: u32 = $binding;
|
||||
fn vec(data: &mut PrimitiveData) -> &mut PrimitiveVec<Self> {
|
||||
&mut data.$name
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
(@count $t1:tt $($t:tt)+) => { 1 + primitives!(@count $($t),+) };
|
||||
(@count $t:tt) => { 1 };
|
||||
}
|
||||
|
||||
pub struct PrimitiveInst<P> {
|
||||
pub id: Id,
|
||||
pub primitive: P,
|
||||
pub region: UiRegion,
|
||||
pub mask_idx: MaskIdx,
|
||||
}
|
||||
|
||||
impl Primitives {
|
||||
pub fn write<P: Primitive>(
|
||||
&mut self,
|
||||
layer: usize,
|
||||
PrimitiveInst {
|
||||
id,
|
||||
primitive,
|
||||
region,
|
||||
mask_idx,
|
||||
}: PrimitiveInst<P>,
|
||||
) -> PrimitiveHandle {
|
||||
self.updated = true;
|
||||
let vec = P::vec(&mut self.data);
|
||||
let i = vec.add(primitive);
|
||||
let inst = PrimitiveInstance {
|
||||
region,
|
||||
idx: i as u32,
|
||||
mask_idx,
|
||||
binding: P::BINDING,
|
||||
};
|
||||
let inst_i = if let Some(i) = self.free.pop() {
|
||||
self.instances[i] = inst;
|
||||
self.assoc[i] = id;
|
||||
i
|
||||
} else {
|
||||
let i = self.instances.len();
|
||||
self.instances.push(inst);
|
||||
self.assoc.push(id);
|
||||
i
|
||||
};
|
||||
PrimitiveHandle::new::<P>(layer, inst_i, i)
|
||||
}
|
||||
|
||||
/// returns (old index, new index)
|
||||
pub fn apply_free(&mut self) -> impl Iterator<Item = PrimitiveChange> {
|
||||
self.free.sort_by(|a, b| b.cmp(a));
|
||||
self.free.drain(..).filter_map(|i| {
|
||||
self.instances.swap_remove(i);
|
||||
self.assoc.swap_remove(i);
|
||||
if i == self.instances.len() {
|
||||
return None;
|
||||
}
|
||||
let id = self.assoc[i];
|
||||
let old = self.instances.len();
|
||||
Some(PrimitiveChange { id, old, new: i })
|
||||
})
|
||||
}
|
||||
|
||||
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
||||
self.updated = true;
|
||||
self.data.free(h.binding, h.data_idx);
|
||||
self.free.push(h.inst_idx);
|
||||
self.instances[h.inst_idx].mask_idx
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &PrimitiveData {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn instances(&self) -> &Vec<PrimitiveInstance> {
|
||||
&self.instances
|
||||
}
|
||||
|
||||
pub fn region_mut(&mut self, h: &PrimitiveHandle) -> &mut UiRegion {
|
||||
self.updated = true;
|
||||
&mut self.instances[h.inst_idx].region
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PrimitiveChange {
|
||||
pub id: Id,
|
||||
pub old: usize,
|
||||
pub new: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PrimitiveHandle {
|
||||
pub layer: usize,
|
||||
pub inst_idx: usize,
|
||||
pub data_idx: usize,
|
||||
pub binding: u32,
|
||||
}
|
||||
|
||||
impl PrimitiveHandle {
|
||||
fn new<P: Primitive>(layer: usize, inst_idx: usize, data_idx: usize) -> Self {
|
||||
Self {
|
||||
layer,
|
||||
inst_idx,
|
||||
data_idx,
|
||||
binding: P::BINDING,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
primitives!(
|
||||
rects: RectPrimitive => 0,
|
||||
textures: TexturePrimitive => 1,
|
||||
);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct RectPrimitive {
|
||||
pub color: Color<u8>,
|
||||
pub radius: f32,
|
||||
pub thickness: f32,
|
||||
pub inner_radius: f32,
|
||||
}
|
||||
|
||||
impl RectPrimitive {
|
||||
pub fn color(color: Color<u8>) -> Self {
|
||||
Self {
|
||||
color,
|
||||
radius: 0.0,
|
||||
thickness: 0.0,
|
||||
inner_radius: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TexturePrimitive {
|
||||
pub view_idx: u32,
|
||||
pub sampler_idx: u32,
|
||||
}
|
||||
|
||||
pub struct PrimitiveVec<T> {
|
||||
vec: Vec<T>,
|
||||
free: Vec<usize>,
|
||||
}
|
||||
|
||||
impl<T> PrimitiveVec<T> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
vec: Vec::new(),
|
||||
free: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub fn add(&mut self, t: T) -> usize {
|
||||
if let Some(i) = self.free.pop() {
|
||||
self.vec[i] = t;
|
||||
i
|
||||
} else {
|
||||
let i = self.vec.len();
|
||||
self.vec.push(t);
|
||||
i
|
||||
}
|
||||
}
|
||||
pub fn free(&mut self, i: usize) {
|
||||
self.free.push(i);
|
||||
}
|
||||
pub fn clear(&mut self) {
|
||||
self.free.clear();
|
||||
self.vec.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for PrimitiveVec<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for PrimitiveVec<T> {
|
||||
type Target = Vec<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.vec
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for PrimitiveVec<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.vec
|
||||
}
|
||||
}
|
||||
178
core/src/render/shader.wgsl
Normal file
178
core/src/render/shader.wgsl
Normal file
@@ -0,0 +1,178 @@
|
||||
const RECT: u32 = 0u;
|
||||
const TEXTURE: u32 = 1u;
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> window: WindowUniform;
|
||||
@group(1) @binding(RECT)
|
||||
var<storage> rects: array<Rect>;
|
||||
@group(1) @binding(TEXTURE)
|
||||
var<storage> textures: array<TextureInfo>;
|
||||
|
||||
struct Rect {
|
||||
color: u32,
|
||||
radius: f32,
|
||||
thickness: f32,
|
||||
inner_radius: f32,
|
||||
}
|
||||
|
||||
struct TextureInfo {
|
||||
view_idx: u32,
|
||||
sampler_idx: u32,
|
||||
}
|
||||
|
||||
struct Mask {
|
||||
x: UiSpan,
|
||||
y: UiSpan,
|
||||
}
|
||||
|
||||
struct UiSpan {
|
||||
start: UiScalar,
|
||||
end: UiScalar,
|
||||
}
|
||||
|
||||
struct UiScalar {
|
||||
rel: f32,
|
||||
abs: f32,
|
||||
}
|
||||
|
||||
struct UiVec2 {
|
||||
rel: vec2<f32>,
|
||||
abs: vec2<f32>,
|
||||
}
|
||||
|
||||
@group(2) @binding(0)
|
||||
var views: binding_array<texture_2d<f32>>;
|
||||
@group(2) @binding(1)
|
||||
var samplers: binding_array<sampler>;
|
||||
@group(2) @binding(2)
|
||||
var<storage> masks: array<Mask>;
|
||||
|
||||
struct WindowUniform {
|
||||
dim: vec2<f32>,
|
||||
};
|
||||
|
||||
struct InstanceInput {
|
||||
@location(0) x_start: vec2<f32>,
|
||||
@location(1) x_end: vec2<f32>,
|
||||
@location(2) y_start: vec2<f32>,
|
||||
@location(3) y_end: vec2<f32>,
|
||||
@location(4) binding: u32,
|
||||
@location(5) idx: u32,
|
||||
@location(6) mask_idx: u32,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@location(0) top_left: vec2<f32>,
|
||||
@location(1) bot_right: vec2<f32>,
|
||||
@location(2) uv: vec2<f32>,
|
||||
@location(3) binding: u32,
|
||||
@location(4) idx: u32,
|
||||
@location(5) mask_idx: u32,
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
};
|
||||
|
||||
struct Region {
|
||||
pos: vec2<f32>,
|
||||
uv: 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_rel = vec2(in.x_start.x, in.y_start.x);
|
||||
let top_left_abs = vec2(in.x_start.y, in.y_start.y);
|
||||
let bot_right_rel = vec2(in.x_end.x, in.y_end.x);
|
||||
let bot_right_abs = vec2(in.x_end.y, in.y_end.y);
|
||||
|
||||
let top_left = floor(top_left_rel * window.dim) + floor(top_left_abs);
|
||||
let bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_abs);
|
||||
let size = bot_right - top_left;
|
||||
|
||||
let uv = vec2<f32>(
|
||||
f32(vi % 2u),
|
||||
f32(vi / 2u)
|
||||
);
|
||||
let pos = (top_left + uv * size) / window.dim * 2.0 - 1.0;
|
||||
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
|
||||
out.uv = uv;
|
||||
out.binding = in.binding;
|
||||
out.idx = in.idx;
|
||||
out.top_left = top_left;
|
||||
out.bot_right = bot_right;
|
||||
out.mask_idx = in.mask_idx;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(
|
||||
in: VertexOutput
|
||||
) -> @location(0) vec4<f32> {
|
||||
let pos = in.clip_position.xy;
|
||||
let region = Region(pos, in.uv, in.top_left, in.bot_right);
|
||||
let i = in.idx;
|
||||
var color: vec4<f32>;
|
||||
switch in.binding {
|
||||
case RECT: {
|
||||
color = draw_rounded_rect(region, rects[i]);
|
||||
}
|
||||
case TEXTURE: {
|
||||
color = draw_texture(region, textures[i]);
|
||||
}
|
||||
default: {
|
||||
color = vec4(1.0, 0.0, 1.0, 1.0);
|
||||
}
|
||||
}
|
||||
if in.mask_idx != 4294967295u {
|
||||
let mask = masks[in.mask_idx];
|
||||
let tl = UiVec2(vec2(mask.x.start.rel, mask.y.start.rel), vec2(mask.x.start.abs, mask.y.start.abs));
|
||||
let br = UiVec2(vec2(mask.x.end.rel, mask.y.end.rel), vec2(mask.x.end.abs, mask.y.end.abs));
|
||||
|
||||
let top_left = floor(tl.rel * window.dim) + floor(tl.abs);
|
||||
let bot_right = floor(br.rel * window.dim) + floor(br.abs);
|
||||
if pos.x < top_left.x || pos.x > bot_right.x || pos.y < top_left.y || pos.y > bot_right.y {
|
||||
color *= 0.0;
|
||||
}
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
// TODO: this seems really inefficient (per frag indexing)?
|
||||
fn draw_texture(region: Region, info: TextureInfo) -> vec4<f32> {
|
||||
return textureSample(views[info.view_idx], samplers[info.sampler_idx], region.uv);
|
||||
}
|
||||
|
||||
fn draw_rounded_rect(region: Region, rect: Rect) -> 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(0.0))) - radius;
|
||||
}
|
||||
|
||||
129
core/src/render/texture.rs
Normal file
129
core/src/render/texture.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
use image::{DynamicImage, EncodableLayout};
|
||||
use wgpu::{util::DeviceExt, *};
|
||||
|
||||
use crate::layout::{TextureUpdate, Textures};
|
||||
|
||||
pub struct GpuTextures {
|
||||
device: Device,
|
||||
queue: Queue,
|
||||
views: Vec<TextureView>,
|
||||
view_count: usize,
|
||||
samplers: Vec<Sampler>,
|
||||
null_view: TextureView,
|
||||
no_views: Vec<TextureView>,
|
||||
}
|
||||
|
||||
impl GpuTextures {
|
||||
pub fn update(&mut self, textures: &mut Textures) -> bool {
|
||||
let mut changed = false;
|
||||
for update in textures.updates() {
|
||||
changed = true;
|
||||
match update {
|
||||
TextureUpdate::Push(image) => self.push(image),
|
||||
TextureUpdate::Set(i, image) => self.set(i, image),
|
||||
TextureUpdate::SetFree => self.view_count += 1,
|
||||
TextureUpdate::Free(i) => self.free(i),
|
||||
TextureUpdate::PushFree => self.push_free(),
|
||||
}
|
||||
}
|
||||
changed
|
||||
}
|
||||
fn set(&mut self, i: u32, image: &DynamicImage) {
|
||||
self.view_count += 1;
|
||||
let view = self.create_view(image);
|
||||
self.views[i as usize] = view;
|
||||
}
|
||||
fn free(&mut self, i: u32) {
|
||||
self.view_count -= 1;
|
||||
self.views[i as usize] = self.null_view.clone();
|
||||
}
|
||||
fn push(&mut self, image: &DynamicImage) {
|
||||
self.view_count += 1;
|
||||
let view = self.create_view(image);
|
||||
self.views.push(view);
|
||||
}
|
||||
fn push_free(&mut self) {
|
||||
self.view_count += 1;
|
||||
self.views.push(self.null_view.clone());
|
||||
}
|
||||
|
||||
fn create_view(&self, image: &DynamicImage) -> TextureView {
|
||||
let image = image.to_rgba8();
|
||||
let (width, height) = image.dimensions();
|
||||
let texture = self.device.create_texture_with_data(
|
||||
&self.queue,
|
||||
&TextureDescriptor {
|
||||
label: None,
|
||||
size: Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
format: TextureFormat::Rgba8Unorm,
|
||||
usage: TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
},
|
||||
wgt::TextureDataOrder::MipMajor,
|
||||
image.as_bytes(),
|
||||
);
|
||||
texture.create_view(&TextureViewDescriptor::default())
|
||||
}
|
||||
|
||||
pub fn new(device: &Device, queue: &Queue) -> Self {
|
||||
let null_view = null_texture_view(device);
|
||||
Self {
|
||||
device: device.clone(),
|
||||
queue: queue.clone(),
|
||||
views: Vec::new(),
|
||||
samplers: vec![default_sampler(device)],
|
||||
no_views: vec![null_view.clone()],
|
||||
null_view,
|
||||
view_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn views(&self) -> Vec<&TextureView> {
|
||||
if self.views.is_empty() {
|
||||
&self.no_views
|
||||
} else {
|
||||
&self.views
|
||||
}
|
||||
.iter()
|
||||
.by_ref()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn samplers(&self) -> Vec<&Sampler> {
|
||||
self.samplers.iter().by_ref().collect()
|
||||
}
|
||||
|
||||
pub fn view_count(&self) -> usize {
|
||||
self.view_count
|
||||
}
|
||||
}
|
||||
|
||||
pub fn null_texture_view(device: &Device) -> TextureView {
|
||||
device
|
||||
.create_texture(&TextureDescriptor {
|
||||
label: Some("null"),
|
||||
size: Extent3d {
|
||||
width: 1,
|
||||
height: 1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
format: TextureFormat::Rgba8Unorm,
|
||||
usage: TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
})
|
||||
.create_view(&TextureViewDescriptor::default())
|
||||
}
|
||||
|
||||
pub fn default_sampler(device: &Device) -> Sampler {
|
||||
device.create_sampler(&SamplerDescriptor::default())
|
||||
}
|
||||
48
core/src/render/util/mod.rs
Normal file
48
core/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(std::mem::size_of::<T>() as u64);
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user