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: 2 additions & 0 deletions .github/workflows/push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ jobs:
key: cargo-cache-${{ env.CARGO_GPU_COMMITSH }}-${{ runner.os }}
- uses: moonrepo/setup-rust@v1
- run: rustup default stable
- run: rustup component add --toolchain nightly rustfmt
- run: rustup update
- run: |
cargo install --git https://github.com/rust-gpu/cargo-gpu --rev $CARGO_GPU_COMMITSH cargo-gpu
Expand Down Expand Up @@ -83,6 +84,7 @@ jobs:
- run: cargo shaders
- run: cargo linkage
- run: cargo build -p renderling
- run: cargo +nightly fmt
- run: git diff --exit-code --no-ext-diff

# BAU clippy lints
Expand Down
26 changes: 24 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"crates/loading-bytes",
"crates/renderling",
"crates/renderling-build",
"crates/renderling-ui",
"crates/wire-types",
# "crates/sandbox",
"crates/xtask"
Expand Down
1 change: 1 addition & 0 deletions crates/example/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ lazy_static = "1.4.0"
loading-bytes = { path = "../loading-bytes" }
log = { workspace = true }
renderling = { path = "../renderling" }
renderling-ui = { path = "../renderling-ui", features = ["text", "path"] }
wasm-bindgen = { workspace = true }
wasm-bindgen-futures = "^0.4"
web-sys = { workspace = true, features = ["Performance", "Window"] }
Expand Down
102 changes: 53 additions & 49 deletions crates/example/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ use renderling::{
primitive::Primitive,
skybox::Skybox,
stage::Stage,
ui::{FontArc, Section, Text, Ui, UiPath, UiText},
};
use renderling_ui::{FontArc, Section, Text, UiRect, UiRenderer, UiText};

pub mod camera;
use camera::{
Expand Down Expand Up @@ -75,38 +75,45 @@ fn now() -> f64 {
}

struct AppUi {
ui: Ui,
ui: UiRenderer,
fps_text: UiText,
fps_counter: FPSCounter,
fps_background: UiPath,
fps_background: UiRect,
last_fps_display: f64,
}

impl AppUi {
fn make_fps_widget(fps_counter: &FPSCounter, ui: &Ui) -> (UiText, UiPath) {
let translation = Vec2::new(2.0, 2.0);
fn make_fps_widget(fps_counter: &FPSCounter, ui: &mut UiRenderer) -> (UiText, UiRect) {
let offset = Vec2::new(2.0, 2.0);
let text = format!("{}fps", fps_counter.current_fps_string());
let fps_text = ui
.text_builder()
.with_color(Vec3::ZERO.extend(1.0))
.with_section(Section::new().add_text(Text::new(&text).with_scale(32.0)))
.build();
fps_text.transform().set_translation(translation);
let fps_text = ui.add_text(
Section::default()
.add_text(
Text::new(&text)
.with_scale(32.0)
.with_color([0.0, 0.0, 0.0, 1.0]),
)
.with_screen_position((offset.x, offset.y)),
);
let (min, max) = fps_text.bounds();
let size = max - min;
let background = ui
.path_builder()
.add_rect()
.with_position(min)
.with_size(size)
.with_fill_color(Vec4::ONE)
.with_rectangle(fps_text.bounds().0, fps_text.bounds().1)
.fill();
background.transform.set_translation(translation);
background.transform.set_z(-0.9);
.with_z(-0.9);
(fps_text, background)
}

fn tick(&mut self) {
self.fps_counter.next_frame();
let now = now();
if now - self.last_fps_display >= 1.0 {
let (fps_text, background) = Self::make_fps_widget(&self.fps_counter, &self.ui);
// Remove old text and background before recreating.
self.ui.remove_text(&self.fps_text);
self.ui.remove_rect(&self.fps_background);
let (fps_text, background) = Self::make_fps_widget(&self.fps_counter, &mut self.ui);
self.fps_text = fps_text;
self.fps_background = background;
self.last_fps_display = now;
Expand Down Expand Up @@ -160,10 +167,10 @@ impl App {
})
.unwrap();

let ui = Ui::new(ctx).with_background_color(Vec4::ZERO);
let mut ui = UiRenderer::new(ctx);
let _ = ui.add_font(FontArc::try_from_slice(FONT_BYTES).unwrap());
let fps_counter = FPSCounter::default();
let (fps_text, fps_background) = AppUi::make_fps_widget(&fps_counter, &ui);
let (fps_text, fps_background) = AppUi::make_fps_widget(&fps_counter, &mut ui);

Self {
ui: AppUi {
Expand Down Expand Up @@ -199,7 +206,7 @@ impl App {
self.ui.tick();
}

pub fn render(&self, ctx: &Context) {
pub fn render(&mut self, ctx: &Context) {
log::info!("render");
let frame = ctx.get_next_frame().unwrap();
self.stage.render(&frame.view());
Expand All @@ -223,6 +230,24 @@ impl App {
self.stage.use_ibl(&ibl);
}

fn set_model(&mut self, model: Model) {
match std::mem::replace(&mut self.model, model) {
Model::Gltf(gltf_document) => {
// Remove all the things that was loaded by the document
for prim in gltf_document.primitives.values().flatten() {
self.stage.remove_primitive(prim);
}
for light in gltf_document.lights.iter() {
self.stage.remove_light(light);
}
}
Model::Default(primitive) => {
self.stage.remove_primitive(&primitive);
}
Model::None => {}
}
}

pub fn load_default_model(&mut self) {
log::info!("loading default model");
let mut min = Vec3::splat(f32::INFINITY);
Expand All @@ -249,7 +274,8 @@ impl App {
BoundingSphere::from((min, max))
});

self.model = Model::Default(primitive);
self.set_model(Model::Default(primitive));

self.camera_controller.reset(Aabb::new(min, max));
self.camera_controller
.update_camera(self.stage.get_size(), &self.camera);
Expand All @@ -260,7 +286,6 @@ impl App {
self.camera_controller
.reset(Aabb::new(Vec3::NEG_ONE, Vec3::ONE));
self.stage.clear_images().unwrap();
self.model = Model::None;
let doc = match self.stage.load_gltf_document_from_bytes(bytes) {
Err(e) => {
log::error!("gltf loading error: {e}");
Expand All @@ -286,29 +311,8 @@ impl App {

let scene = doc.default_scene.unwrap_or(0);
log::info!("Displaying scene {scene}");
fn get_children(doc: &GltfDocument, n: usize) -> Vec<usize> {
let mut children = vec![];
if let Some(parent) = doc.nodes.get(n) {
children.extend(parent.children.iter().copied());
let descendants = parent
.children
.iter()
.copied()
.flat_map(|n| get_children(doc, n));
children.extend(descendants);
}
children
}

let nodes = doc.nodes_in_scene(scene).flat_map(|n| {
let mut all_nodes = vec![n];
for child_index in get_children(&doc, n.index) {
if let Some(child_node) = doc.nodes.get(child_index) {
all_nodes.push(child_node);
}
}
all_nodes
});
let nodes = doc.recursive_nodes_in_scene(scene);
log::trace!(" nodes:");
for node in nodes {
let tfrm = Mat4::from(node.global_transform());
Expand Down Expand Up @@ -380,13 +384,13 @@ impl App {

// self.lighting
// .shadow_map
// .update(&self.lighting.lighting, doc.primitives.values().flatten());
// self.lighting.light = light.light.clone();
// self.lighting.light_details = dir.clone();
// }
// .update(&self.lighting.lighting,
// doc.primitives.values().flatten()); self.lighting.light =
// light.light.clone(); self.lighting.light_details =
// dir.clone(); }
// }

self.model = Model::Gltf(Box::new(doc));
self.set_model(Model::Gltf(Box::new(doc)));
}

pub fn tick_loads(&mut self) {
Expand Down
39 changes: 39 additions & 0 deletions crates/renderling-ui/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[package]
name = "renderling-ui"
version = "0.1.0"
edition = "2021"
description = "Lightweight 2D/UI renderer for renderling."
repository = "https://github.com/schell/renderling"
license = "MIT OR Apache-2.0"

[features]
default = ["text", "path"]
text = ["dep:glyph_brush", "dep:image", "dep:loading-bytes"]
path = ["dep:lyon"]
test-utils = ["renderling/test-utils"]

[dependencies]
bytemuck = { workspace = true }
craballoc = { workspace = true }
crabslab = { workspace = true, features = ["default"] }
glam = { workspace = true, features = ["std"] }
glyph_brush = { workspace = true, optional = true }
image = { workspace = true, optional = true }
loading-bytes = { workspace = true, optional = true }
log = { workspace = true }
lyon = { workspace = true, optional = true }
renderling = { path = "../renderling", default-features = false }
rustc-hash = { workspace = true }
snafu = { workspace = true }
wgpu = { workspace = true, features = ["spirv"] }

[dev-dependencies]
env_logger = { workspace = true }
futures-lite = { workspace = true }
img-diff = { path = "../img-diff" }
image = { workspace = true }
renderling = { path = "../renderling", features = ["test-utils"] }
renderling_build = { path = "../renderling-build" }

[lints]
workspace = true
59 changes: 59 additions & 0 deletions crates/renderling-ui/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//! Lightweight 2D/UI renderer for renderling.
//!
//! This crate provides a dedicated 2D rendering pipeline that is separate
//! from renderling's 3D PBR pipeline. It features:
//!
//! - SDF-based shape rendering (rectangles, rounded rectangles, circles,
//! ellipses) with anti-aliased edges
//! - Gradient fills (linear and radial)
//! - Texture/image rendering via the renderling atlas system
//! - Text rendering via `glyph_brush` (behind the `text` feature)
//! - Vector path rendering via `lyon` tessellation (behind the `path` feature)
//! - A lightweight vertex format (32 bytes vs ~160 bytes for 3D)
//! - Minimal GPU bindings (3 vs 13 for 3D)
//!
//! # Quick Start
//!
//! ```ignore
//! use renderling::context::Context;
//! use renderling_ui::UiRenderer;
//!
//! let ctx = futures_lite::future::block_on(Context::headless(800, 600));
//! let mut ui = UiRenderer::new(&ctx);
//!
//! // Add a rounded rectangle
//! let _rect = ui.add_rect()
//! .with_position(glam::Vec2::new(10.0, 10.0))
//! .with_size(glam::Vec2::new(200.0, 100.0))
//! .with_corner_radii(glam::Vec4::splat(8.0))
//! .with_fill_color(glam::Vec4::new(0.2, 0.3, 0.8, 1.0));
//!
//! let frame = ctx.get_next_frame().unwrap();
//! ui.render(&frame.view());
//! frame.present();
//! ```

mod renderer;
#[cfg(test)]
mod test;

// Re-export key types from renderling that users will need.
pub use renderling::{
atlas::{AtlasImage, AtlasTexture},
context::Context,
glam,
ui_slab::{
GradientDescriptor, GradientType, UiDrawCallDescriptor, UiElementType, UiVertex, UiViewport,
},
};

// Re-export our own types.
pub use renderer::{UiCircle, UiEllipse, UiImage, UiRect, UiRenderer};

// Re-export text types (behind "text" feature).
#[cfg(feature = "text")]
pub use renderer::{FontArc, FontId, Section, Text, UiText};

// Re-export path types (behind "path" feature).
#[cfg(feature = "path")]
pub use renderer::{StrokeConfig, UiPath, UiPathBuilder};
Loading
Loading