Skip to content
Merged
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
21 changes: 12 additions & 9 deletions .github/workflows/push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
install-cargo-gpu:
strategy:
matrix:
os: [ubuntu-latest, ubuntu-24.04, macos-latest]
os: [ubuntu-24.04, macos-latest]
runs-on: ${{ matrix.os }}
defaults:
run:
Expand All @@ -35,8 +35,10 @@ jobs:
with:
path: ~/.cargo
# THIS KEY MUST MATCH BELOW
key: cargo-cache-${{ env.CARGO_GPU_COMMITSH }}-${{ matrix.os }}
key: cargo-cache-2-${{ env.CARGO_GPU_COMMITSH }}-${{ matrix.os }}
- uses: moonrepo/setup-rust@v1
with:
cache: false
- run: rustup default stable
- run: rustup update
- run: |
Expand All @@ -56,7 +58,7 @@ jobs:
matrix:
# temporarily skip windows, revisit after a fix for this error is found:
# https://github.com/rust-lang/cc-rs/issues/1331
os: [ubuntu-latest, ubuntu-24.04, macos-latest] #, windows-latest]
os: [ubuntu-24.04, macos-latest] #, windows-latest]
runs-on: ${{ matrix.os }}
defaults:
run:
Expand All @@ -65,22 +67,23 @@ jobs:
RUST_LOG: debug
steps:
- uses: actions/checkout@v2
# Run moonrepo/setup-rust BEFORE restoring ~/.cargo cache, because
# moonrepo restores its own ~/.cargo cache which would overwrite
# the cargo-gpu binary installed by the install-cargo-gpu job.
# Disable moonrepo's built-in cache so it doesn't overwrite ~/.cargo
# (which contains the cargo-gpu binary from the install-cargo-gpu job).
- uses: moonrepo/setup-rust@v1
with:
cache: false
- uses: actions/cache@v4
with:
path: ~/.cargo
# THIS KEY MUST MATCH ABOVE
key: cargo-cache-${{ env.CARGO_GPU_COMMITSH }}-${{ matrix.os }}
key: cargo-cache-2-${{ env.CARGO_GPU_COMMITSH }}-${{ matrix.os }}
- uses: actions/cache@v4
with:
path: |
${{ needs.install-cargo-gpu.outputs.cachepath-macOS }}
${{ needs.install-cargo-gpu.outputs.cachepath-Linux }}
${{ needs.install-cargo-gpu.outputs.cachepath-Windows }}
key: rust-gpu-cache-0-${{ matrix.os }}
key: rust-gpu-cache-1-${{ matrix.os }}
- run: rustup install nightly
- run: rustup component add --toolchain nightly rustfmt
- run: cargo gpu show commitsh
Expand All @@ -99,7 +102,7 @@ jobs:
with:
path: ~/.cargo
# THIS KEY MUST MATCH ABOVE
key: cargo-cache-${{ env.CARGO_GPU_COMMITSH }}-ubuntu-latest
key: renderling-fmt-${{ env.CARGO_GPU_COMMITSH }}-ubuntu-24.04
- run: mkdir -p $HOME/.cargo/bin
- uses: moonrepo/setup-rust@v1
- run: rustup install nightly
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
71 changes: 48 additions & 23 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 Down Expand Up @@ -365,7 +390,7 @@ impl App {
// 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