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:
23
crates/egui_cpu/Cargo.toml
Normal file
23
crates/egui_cpu/Cargo.toml
Normal 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
|
||||
67
crates/egui_cpu/examples/cpu_renderer.rs
Normal file
67
crates/egui_cpu/examples/cpu_renderer.rs
Normal 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
134
crates/egui_cpu/src/lib.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
117
crates/egui_cpu/src/pipeline.rs
Normal file
117
crates/egui_cpu/src/pipeline.rs
Normal 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,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user