1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-02 14:50:03 -04:00

Add egui cpu renderer

This commit is contained in:
Lucas Meurer
2024-09-25 11:50:10 +02:00
parent 1603f05818
commit 8a33e66341
8 changed files with 813 additions and 14 deletions

View File

@@ -0,0 +1,23 @@
[package]
name = "egui_cpu"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
euc = { git = "https://github.com/zesterer/euc" }
egui = { workspace = true, features = ["bytemuck"] }
image = { workspace = true, default-features = false }
derive_more = { version = "1", features = ["add", "mul"] }
vek = "0.17.1"
bytemuck = "1"
[dev-dependencies]
egui = { workspace = true, features = ["default_fonts"] }
egui_extras = { workspace = true, features = ["all_loaders"] }
[lints]
workspace = true

View File

@@ -0,0 +1,67 @@
use egui::load::SizedTexture;
use egui::{include_image, ColorImage, ImageSource, Pos2, RawInput, Stroke, TextureId, Vec2};
fn main() {
let ctx = egui::Context::default();
let mut input = RawInput {
screen_rect: Some(egui::Rect::from_min_size(
Default::default(),
Vec2::new(400.0, 200.0),
)),
..Default::default()
};
input
.viewports
.get_mut(&input.viewport_id)
.unwrap()
.native_pixels_per_point = Some(2.0);
let output = ctx.run(input, |ctx| {
egui::CentralPanel::default().show(ctx, |ui| {
ui.group(|ui| {
ui.label("Hello World!");
ui.button("Click me!");
ui.checkbox(&mut true, "Check me!");
ui.heading("Heading");
});
// ui.image(SizedTexture::new(
// TextureId::default(),
// Vec2::new(2048.0, 128.0),
// ));
ui.image(include_image!("../../../media/rerun_io_logo.png"));
});
// ctx.debug_painter().rect_filled(
// egui::Rect::from_min_size(Pos2::new(0.0, 0.0), Vec2::new(100.0, 100.0)),
// 10.0,
// egui::Color32::from_rgba_premultiplied(255, 0, 0, 255),
// );
// ctx.debug_painter().rect_filled(
// egui::Rect::from_min_size(Pos2::new(100.0, 0.0), Vec2::new(100.0, 100.0)),
// 10.0,
// egui::Color32::from_rgba_premultiplied(0, 255, 0, 255),
// );
//
// ctx.debug_painter().rect_stroke(
// egui::Rect::from_min_size(Pos2::new(200.0, 0.0), Vec2::new(100.0, 100.0)),
// 10.0,
// Stroke::new(10.0, egui::Color32::from_rgba_premultiplied(0, 0, 255, 255)),
// );
});
let primitives = ctx.tessellate(output.shapes, ctx.pixels_per_point());
let mut cpu_renderer = egui_cpu::Renderer::default();
cpu_renderer.update_textures(output.textures_delta);
dbg!(ctx.screen_rect());
let image = cpu_renderer.render(
&primitives,
ctx.screen_rect().size(),
ctx.pixels_per_point(),
);
image.save("output.png").unwrap();
}

134
crates/egui_cpu/src/lib.rs Normal file
View File

@@ -0,0 +1,134 @@
mod pipeline;
use egui::ahash::HashMap;
use egui::epaint::Primitive;
use egui::{ClippedPrimitive, ColorImage, ImageData, TextureId, TexturesDelta, Vec2};
use euc::buffer::Buffer2d;
use euc::{IndexedVertices, Pipeline, Sampler, Texture};
use image::{DynamicImage, ImageBuffer, Pixel, RgbaImage};
use std::ops::Deref;
use vek::Rgba;
#[derive(Debug, Default)]
pub struct Renderer {
textures: HashMap<TextureId, Buffer2d<image::Rgba<u8>>>,
}
impl Renderer {
pub fn update_textures(&mut self, delta: TexturesDelta) {
for (id, delta) in delta.set {
dbg!(delta.image.size());
let image = match delta.image {
ImageData::Color(color) => RgbaImage::from_raw(
color.width() as u32,
color.height() as u32,
Vec::from(color.deref().as_raw()),
)
.unwrap(),
ImageData::Font(font) => {
let color_image = ColorImage {
size: font.size,
pixels: font.srgba_pixels(None).collect(),
};
RgbaImage::from_raw(
font.width() as u32,
font.height() as u32,
Vec::from(color_image.as_raw()),
)
.unwrap()
}
};
let buffer = Buffer2d::from_texture(&DynamicImage::from(image).to_rgba8());
self.textures.insert(id, buffer);
if delta.pos.is_some() {
unimplemented!()
}
}
}
pub fn render(
&self,
primitives: &[ClippedPrimitive],
resolution: Vec2,
dpi: f32,
) -> ImageBuffer<image::Rgba<u8>, Vec<u8>> {
let width = (resolution.x * dpi) as usize;
let height = (resolution.y * dpi) as usize;
dbg!(width, height);
let mut output = Buffer2d::fill([width, height], 0x000000);
let mut depth = Buffer2d::fill([width, height], 1.0);
for ClippedPrimitive {
primitive,
clip_rect,
} in primitives
{
match primitive {
Primitive::Mesh(mesh) => {
let texture = self.textures.get(&mesh.texture_id).unwrap();
let sampler = texture
.map(|pixel| Rgba::from(pixel.0).map(|e: u8| e as f32))
.linear();
// let sampler = DebugSampler {
// texture: Buffer2d::fill([1, 1], 0.0),
// };
// let sampler = |pos| Rgba::new(0, 0, 0, 0);
let mut pipeline = pipeline::EguiPipeline {
screen_size: vek::Vec2::new(width as f32, height as f32) / dpi,
scissor_rect: Default::default(),
sampler: &sampler,
};
pipeline.scissor_rect = vek::Rect::new(
clip_rect.min.x,
clip_rect.min.y,
clip_rect.width(),
clip_rect.height(),
);
pipeline.render(
mesh.indices.iter().map(|&i| mesh.vertices[i as usize]),
&mut output,
&mut depth,
);
}
Primitive::Callback(_) => {
println!("Callback not implemented");
}
}
}
let raw = output.raw();
let raw = Vec::from(bytemuck::cast_slice(&raw));
let image = RgbaImage::from_raw(width as u32, height as u32, raw).unwrap();
image
}
}
struct DebugSampler {
texture: Buffer2d<f32>,
}
impl Sampler<2> for DebugSampler {
type Index = f32;
type Sample = Rgba<f32>;
type Texture = Buffer2d<f32>;
fn raw_texture(&self) -> &Self::Texture {
&self.texture
}
fn sample(&self, index: [Self::Index; 2]) -> Self::Sample {
if index[0] != 0.0 || index[1] != 0.0 {
dbg!(index);
}
Rgba::new(0.0, 0.0, 0.0, 0.0)
}
}

View File

@@ -0,0 +1,117 @@
use derive_more::{Add, Mul};
use euc::primitives::PrimitiveKind;
use euc::rasterizer::{Rasterizer, Triangles};
use euc::{
AaMode, CoordinateMode, CullMode, DepthMode, Pipeline, PixelMode, Sampler, Texture,
TriangleList,
};
use vek::{Rgba, Vec2, Vec4};
pub(crate) struct EguiPipeline<S> {
pub screen_size: Vec2<f32>,
pub scissor_rect: vek::Rect<f32, f32>,
pub sampler: S,
}
#[derive(Debug, Clone, Add, Mul)]
pub(crate) struct VertexData {
text_coord: Vec2<f32>,
color: Rgba<f32>,
}
impl<'r, S: Sampler<2, Index = f32, Sample = Rgba<f32>>> Pipeline<'r> for EguiPipeline<S> {
type Vertex = egui::epaint::Vertex;
type VertexData = VertexData;
type Primitives = TriangleList;
type Fragment = Rgba<f32>;
type Pixel = u32;
fn coordinate_mode(&self) -> CoordinateMode {
CoordinateMode::VULKAN
}
fn pixel_mode(&self) -> PixelMode {
PixelMode::WRITE
}
fn vertex(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) {
let position =
position_from_screen(Vec2::new(vertex.pos.x, vertex.pos.y), self.screen_size);
let text_coord = Vec2::new(vertex.uv.x, vertex.uv.y);
let color = Rgba::new(
vertex.color.r() as f32 / 255.0,
vertex.color.g() as f32 / 255.0,
vertex.color.b() as f32 / 255.0,
vertex.color.a() as f32 / 255.0,
);
(position.into_array(), VertexData { text_coord, color })
}
fn fragment(&self, vs_out: Self::VertexData) -> Self::Fragment {
vs_out.color * self.sampler.sample(vs_out.text_coord.into_array())
}
fn blend(&self, old: Self::Pixel, new: Self::Fragment) -> Self::Pixel {
//Source over
let source = new;
let dest = Rgba::from(old.to_le_bytes()).map(|c: u8| c as f32);
let source_alpha = source.a / 255.0;
let inv_source_alpha = 1.0 - source_alpha;
let r = source.r + dest.r * inv_source_alpha;
let g = source.g + dest.g * inv_source_alpha;
let b = source.b + dest.b * inv_source_alpha;
let a = source.a + dest.a * inv_source_alpha;
u32::from_le_bytes(Rgba::new(r, g, b, a).map(|c| (c) as u8).into_array())
// u32::from_le_bytes(new.map(|c| c as u8).into_array())
}
fn rasterizer_config(
&self,
) -> <<Self::Primitives as PrimitiveKind<Self::VertexData>>::Rasterizer as Rasterizer>::Config
{
CullMode::None
}
fn aa_mode(&self) -> AaMode {
AaMode::None
}
}
// From wgsl shader:
// fn unpack_color(color: u32) -> vec4<f32> {
// return vec4<f32>(
// f32(color & 255u),
// f32((color >> 8u) & 255u),
// f32((color >> 16u) & 255u),
// f32((color >> 24u) & 255u),
// ) / 255.0;
// }
// fn unpack_color(color: [u8; 4]) -> Vec4<f32> {
// Vec4::new(
// (color & 255u32) as f32,
// ((color >> 8u32) & 255u32) as f32,
// ((color >> 16u32) & 255u32) as f32,
// ((color >> 24u32) & 255u32) as f32,
// ) / 255.0
// }
// From wgsl shader:
// fn position_from_screen(screen_pos: vec2<f32>) -> vec4<f32> {
// return vec4<f32>(
// 2.0 * screen_pos.x / r_locals.screen_size.x - 1.0,
// 1.0 - 2.0 * screen_pos.y / r_locals.screen_size.y,
// 0.0,
// 1.0,
// );
// }
fn position_from_screen(screen_pos: Vec2<f32>, screen_size: Vec2<f32>) -> Vec4<f32> {
Vec4::new(
2.0 * screen_pos.x / screen_size.x - 1.0,
1.0 - 2.0 * screen_pos.y / screen_size.y,
0.0,
1.0,
)
}