Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ glam = { version = "0.30", features = ["mint"] }
gltf = { version = "1.1", default-features = false }
log = "0.4"
mint = "0.5"
naga = { git = "https://github.com/gfx-rs/wgpu", rev = "78b4dfd0cb1ef5ed208fefe80ae2f855db3609bb", features = ["wgsl-in", "termcolor"] }
naga = { git = "https://github.com/gfx-rs/wgpu", rev = "b0a44f766e95a38f0aa45565ff005173cc96d46a", features = ["wgsl-in", "termcolor"] }
profiling = "1"
slab = "0.4"
strum = { version = "0.27", features = ["derive"] }
Expand Down
49 changes: 26 additions & 23 deletions blade-egui/shader.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,32 @@ struct VertexOutput {

struct Uniforms {
screen_size: vec2<f32>,
convert_to_linear: f32,
padding: f32,
padding: vec2<f32>,
};
var<uniform> r_uniforms: Uniforms;

//Note: avoiding `vec2<f32>` in order to keep the scalar alignment
struct Vertex {
pos_x: f32,
pos_y: f32,
tex_coord_x: f32,
tex_coord_y: f32,
color: u32,
struct VertexInput {
a_pos: vec2<f32>,
a_tex_coord: vec2<f32>,
a_color: u32,
}
var<storage, read> r_vertex_data: array<Vertex>;

fn linear_from_gamma(srgb: vec3<f32>) -> vec3<f32> {
let cutoff = srgb < vec3<f32>(0.04045);
let lower = srgb / vec3<f32>(12.92);
let higher = pow((srgb + vec3<f32>(0.055)) / vec3<f32>(1.055), vec3<f32>(2.4));
return select(higher, lower, cutoff);
let is_higher = step(vec3<f32>(0.04045), srgb);
return mix(lower, higher, is_higher);
}

@vertex
fn vs_main(
@builtin(vertex_index) v_index: u32,
) -> VertexOutput {
let input = r_vertex_data[v_index];
fn vs_main(input: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.tex_coord = vec2<f32>(input.tex_coord_x, input.tex_coord_y);
out.color = unpack4x8unorm(input.color);
out.tex_coord = input.a_tex_coord;
out.color = unpack4x8unorm(input.a_color);
out.position = vec4<f32>(
2.0 * input.pos_x / r_uniforms.screen_size.x - 1.0,
1.0 - 2.0 * input.pos_y / r_uniforms.screen_size.y,
2.0 * input.a_pos.x / r_uniforms.screen_size.x - 1.0,
1.0 - 2.0 * input.a_pos.y / r_uniforms.screen_size.y,
0.0,
1.0,
);
Expand All @@ -48,11 +41,21 @@ fn vs_main(
var r_texture: texture_2d<f32>;
var r_sampler: sampler;

// Egui blends in gamma space, see https://github.com/emilk/egui/pull/2071
fn blended_color(in: VertexOutput) -> vec4<f32> {
return in.color * textureSample(r_texture, r_sampler, in.tex_coord);
}

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
//Note: we always assume rendering to linear color space,
// but Egui wants to blend in gamma space, see
// https://github.com/emilk/egui/pull/2071
let blended = in.color * textureSample(r_texture, r_sampler, in.tex_coord);
// ColorSpace::Linear swapchain (Vulkan/Metal/EGL sRGB storage).
let blended = blended_color(in);
return vec4f(linear_from_gamma(blended.xyz), blended.a);
}

@fragment
fn fs_main_srgb(in: VertexOutput) -> @location(0) vec4<f32> {
// Plain UNORM canvas (WebGL blit to HTML canvas): output gamma-space directly.
let blended = blended_color(in);
return vec4f(blended.xyz, blended.a);
}
107 changes: 87 additions & 20 deletions blade-egui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ struct Globals {

#[derive(blade_macros::ShaderData)]
struct Locals {
r_vertex_data: blade_graphics::BufferPiece,
r_texture: blade_graphics::TextureView,
r_sampler: blade_graphics::Sampler,
}
Expand Down Expand Up @@ -136,18 +135,21 @@ impl GuiTexture {
pub struct GuiPainter {
pipeline: blade_graphics::RenderPipeline,
//TODO: find a better way to allocate temporary buffers.
belt: BufferBelt,
vertex_belt: BufferBelt,
index_belt: BufferBelt,
textures: HashMap<egui::TextureId, GuiTexture>,
//TODO: this could also look better
textures_dropped: Vec<GuiTexture>,
textures_to_free: Vec<egui::TextureId>,
textures_to_delete: Vec<(GuiTexture, blade_graphics::SyncPoint)>,
}

impl GuiPainter {
/// Destroy the contents of the painter.
pub fn destroy(&mut self, context: &blade_graphics::Context) {
context.destroy_render_pipeline(&mut self.pipeline);
self.belt.destroy(context);
self.vertex_belt.destroy(context);
self.index_belt.destroy(context);
for (_, gui_texture) in self.textures.drain() {
gui_texture.delete(context);
}
Expand All @@ -171,17 +173,55 @@ impl GuiPainter {
});
let globals_layout = <Globals as blade_graphics::ShaderData>::layout();
let locals_layout = <Locals as blade_graphics::ShaderData>::layout();
let egui_vertex_layout = blade_graphics::VertexLayout {
stride: 20, // egui::Vertex: pos(2xf32) + uv(2xf32) + color(u32) = 20
attributes: vec![
(
"a_pos",
blade_graphics::VertexAttribute {
offset: 0,
format: blade_graphics::VertexFormat::F32Vec2,
},
),
(
"a_tex_coord",
blade_graphics::VertexAttribute {
offset: 8,
format: blade_graphics::VertexFormat::F32Vec2,
},
),
(
"a_color",
blade_graphics::VertexAttribute {
offset: 16,
format: blade_graphics::VertexFormat::U32,
},
),
],
};
// Fragment entry contract: Linear/sRGB-capable swapchains (Vulkan, Metal, EGL) use
// `fs_main` (gamma→linear output). Plain UNORM surfaces (WebGL blit to HTML canvas)
// use `fs_main_srgb` (gamma passthrough). WebGL is the only backend that reports
// Rgba8Unorm here; embedders with other UNORM targets should pick the srgb entry likewise.
let fragment_entry = if matches!(info.format, blade_graphics::TextureFormat::Rgba8Unorm) {
"fs_main_srgb"
} else {
"fs_main"
};
let pipeline = context.create_render_pipeline(blade_graphics::RenderPipelineDesc {
name: "gui",
data_layouts: &[&globals_layout, &locals_layout],
vertex: shader.at("vs_main"),
vertex_fetches: &[],
vertex_fetches: &[blade_graphics::VertexFetchState {
layout: &egui_vertex_layout,
instanced: false,
}],
primitive: blade_graphics::PrimitiveState {
topology: blade_graphics::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: None, //TODO?
fragment: Some(shader.at("fs_main")),
fragment: Some(shader.at(fragment_entry)),
color_targets: &[blade_graphics::ColorTargetState {
format: info.format,
blend: Some(blade_graphics::BlendState {
Expand All @@ -201,17 +241,26 @@ impl GuiPainter {
multisample_state: Default::default(),
});

let belt = BufferBelt::new(BufferBeltDescriptor {
let vertex_belt = BufferBelt::new(BufferBeltDescriptor {
memory: blade_graphics::Memory::Shared,
min_chunk_size: 0x1000,
min_chunk_size: 0x40000,
alignment: blade_graphics::limits::STORAGE_BUFFER_ALIGNMENT,
name: "vertex",
});
let index_belt = BufferBelt::new(BufferBeltDescriptor {
memory: blade_graphics::Memory::Shared,
min_chunk_size: 0x40000,
alignment: blade_graphics::limits::STORAGE_BUFFER_ALIGNMENT,
name: "index",
});

Self {
pipeline,
belt,
vertex_belt,
index_belt,
textures: Default::default(),
textures_dropped: Vec::new(),
textures_to_free: Vec::new(),
textures_to_delete: Vec::new(),
}
}
Expand Down Expand Up @@ -247,7 +296,9 @@ impl GuiPainter {
let mut copies = Vec::new();
for &(texture_id, ref image_delta) in textures_delta.set.iter() {
let src = match image_delta.image {
egui::ImageData::Color(ref c) => self.belt.alloc_pod(c.pixels.as_slice(), context),
egui::ImageData::Color(ref c) => {
self.vertex_belt.alloc_pod(c.pixels.as_slice(), context)
}
};

let image_size = image_delta.image.size();
Expand Down Expand Up @@ -298,13 +349,12 @@ impl GuiPainter {
}
}

for texture_id in textures_delta.free.iter() {
let texture = self.textures.remove(texture_id).unwrap();
self.textures_dropped.push(texture);
}
self.textures_to_free
.extend(textures_delta.free.iter().copied());

self.triage_deletions(context);
self.belt.trim(4, context);
self.vertex_belt.trim(4, context);
self.index_belt.trim(4, context);
}

/// Render the set of clipped primitives into a render pass.
Expand Down Expand Up @@ -358,19 +408,22 @@ impl GuiPainter {
});

if let egui::epaint::Primitive::Mesh(ref mesh) = clipped_prim.primitive {
let texture = self.textures.get(&mesh.texture_id).unwrap();
let index_buf = self.belt.alloc_pod(&mesh.indices, context);
let vertex_buf = self.belt.alloc_pod(&mesh.vertices, context);
let Some(texture) = self.textures.get(&mesh.texture_id) else {
continue;
};
let index_buf = self.index_belt.alloc_pod(&mesh.indices, context);
let vertex_buf = self.vertex_belt.alloc_pod(&mesh.vertices, context);

pc.bind(
1,
&Locals {
r_vertex_data: vertex_buf,
r_texture: texture.view,
r_sampler: texture.sampler,
},
);

pc.bind_vertex(0, vertex_buf);

pc.draw_indexed(
index_buf,
blade_graphics::IndexType::U32,
Expand All @@ -383,14 +436,28 @@ impl GuiPainter {
}
}

pub fn sync(&mut self, context: &blade_graphics::Context) {
self.vertex_belt.sync(context);
self.index_belt.sync(context);
}

/// Call this after submitting work at the given `sync_point`.
#[profiling::function]
pub fn after_submit(&mut self, sync_point: &blade_graphics::SyncPoint) {
pub fn after_submit(
&mut self,
sync_point: &blade_graphics::SyncPoint,
) {
for texture_id in self.textures_to_free.drain(..) {
if let Some(texture) = self.textures.remove(&texture_id) {
self.textures_dropped.push(texture);
}
}
self.textures_to_delete.extend(
self.textures_dropped
.drain(..)
.map(|texture| (texture, sync_point.clone())),
);
self.belt.flush(sync_point);
self.vertex_belt.flush(sync_point);
self.index_belt.flush(sync_point);
}
}
Loading