Skip to content

Commit 2ab3ede

Browse files
authored
feat: atlas texture array (#121)
added Context::set_default_atlas_size added AtlasFrame to distinguish between image frames in the atlas and texture addressing added Atlas::add_images and Atlas::resize, removed ... added UiImage to renderling-ui to hold frames and textures added Ui::set_background_color and Ui::with_background_color published loading-bytes
1 parent 85d389a commit 2ab3ede

30 files changed

+1000
-792
lines changed

Cargo.lock

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

DEVLOG.md

+22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
# devlog
22

3+
## Mon June 3, 2024
4+
5+
### NLNet progress
6+
7+
[My first PR to add atomics to naga's SPIR-V frontend](https://github.com/gfx-rs/wgpu/pull/5702) was
8+
merged last week! I'm super stoked because I was worried it might be a bit beyond my pay grade, but
9+
I figured it out with the help of @jimblandy.
10+
11+
### Atlas improvements
12+
13+
Finally, the atlas in renderling is a true texture array, greatly increasing `renderling`'s texture
14+
capacity.
15+
16+
By default the atlas holds an array of 2048x2048x8 textures, but it's configurable so if you need
17+
more you can bump up the default size in `Context`, before you create the stage.
18+
19+
### renderling-ui
20+
21+
I've rebuilt and released a good portion of [`renderling-ui`](https://crates.io/renderling-ui).
22+
Partially because @jimsynz wanted to use `renderling` as a [`scenic`](hexdocs.pm/scenic/) driver,
23+
and partially because I still want to be able to write game and tools UI with `renderling`.
24+
325
## Sat May 25, 2024
426

527
### SPIR-V atomics update

README.md

+11-3
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Renderling takes a [forward+](https://takahiroharada.files.wordpress.com/2015/04
6464

6565
By default it uses a single uber-shader for rendering.
6666

67+
- [x] texture atlas
6768
- [ ] frustum culling
6869
- [ ] occlusion culling
6970
- [ ] light tiling
@@ -95,8 +96,15 @@ By default it uses a single uber-shader for rendering.
9596
- [x] textures, images, samplers
9697
- animation
9798
- [x] interpolation
98-
- [x] morph targets
99-
- [x] skinning
99+
- [x] skinning (still working on one [issue](https://github.com/schell/renderling/issues/120))
100+
- [ ] morph targets (requires rebuild)
101+
- 2d
102+
- [x] text
103+
- [x] stroked and filled paths
104+
- [x] cubic beziers
105+
- [x] quadratic beziers
106+
- [x] arbitrary polygons
107+
- [x] fill w/ image
100108

101109
## Definition
102110
**renderling** noun
@@ -188,7 +196,7 @@ Some of these solutions were then spun off into their own projects.
188196
- [`crabslab`](https://github.com/schell/crabslab)
189197
A slab allocator for working across CPU/GPU boundaries.
190198
- [`loading-bytes`](crates/loading-bytes)
191-
A cross-platform (including the web) way of loading files to bytes.
199+
A cross-platform (including the web) and comedically tiny way of loading files to bytes.
192200
- [`moongraph`](https://github.com/schell/moongraph)
193201
A DAG and resource graph runner.
194202
- Contributions to [`naga`](https://github.com/gfx-rs/wgpu/issues/4489)

crates/example/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ impl App {
164164
self.theta = std::f32::consts::FRAC_PI_4;
165165
self.left_mb_down = false;
166166
self.last_cursor_position = None;
167-
self.stage.set_images(std::iter::empty()).unwrap();
167+
self.stage.clear_images().unwrap();
168168
self.document = None;
169169
log::debug!("ticking stage to reclaim buffers");
170170
self.stage.tick();

crates/loading-bytes/Cargo.toml

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
[package]
22
name = "loading-bytes"
3-
version = "0.1.0"
3+
version = "0.1.1"
44
edition = "2021"
5+
description = "Load bytes from paths on native and WASM"
6+
repository = "https://github.com/schell/renderling"
7+
license = "MIT OR Apache-2.0"
8+
keywords = ["image", "font", "binary", "loading", "bytes"]
9+
categories = ["webassembly", "filesystem", "asynchronous"]
10+
readme = "README.md"
511

612
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
713

crates/loading-bytes/README.md

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# loading-bytes
2+
3+
Sometimes loading things really bites.
4+
5+
Let's say you just want to compile your program on native and web, but you've
6+
done a bit of loading in your call tree...
7+
8+
You know what I'm talking about, I mean reading from the filesystem. Easy stuff - right?
9+
10+
## WRONG!
11+
12+
Well, not really - it's still pretty easy - but it's a hassle, hoff!
13+
14+
## NOT ANYMORE.
15+
16+
That's right folks! Step right up and use this here little library to load things from
17+
the filesystem on native and through a web request on WASM!
18+
19+
What you get back totally bytes!
20+
21+
## BUT WAIT, THERE'S MORE!
22+
23+
Act now and I'll throw in some "nice" enumerated error handling.
24+
25+
OK. You got me. That's free!
26+
27+
In fact, the whole thing is free! Take it or leave it, folks, the choice is yours.
28+
29+
...
30+
31+
`loading-bytes` - from the same company that brought you `streaming-nibbles`🐟...
32+
33+
...Just kidding!
34+
35+
Happy hacking :) ☕☕☕

crates/loading-bytes/src/lib.rs

+50-47
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,74 @@
11
//! Abstraction over loading bytes on WASM or other.
22
use snafu::prelude::*;
33

4+
/// Enumeration of all errors this library may result in.
45
#[derive(Debug, Snafu)]
56
pub enum LoadingBytesError {
6-
#[snafu(display("loading by WASM error: {msg:#?}"))]
7+
#[snafu(display("loading '{path}' by WASM error: {msg:#?}"))]
78
Wasm {
89
path: String,
910
msg: wasm_bindgen::JsValue,
1011
},
11-
#[snafu(display("loading '{path}' from '{}' by filesystem error: {source}", cwd.display()))]
12+
#[snafu(display("loading '{path}' by filesystem from CWD '{}' error: {source}", cwd.display()))]
1213
Fs {
1314
path: String,
1415
cwd: std::path::PathBuf,
1516
source: std::io::Error,
1617
},
1718
}
1819

19-
/// Load the file at the given url and return it as a vector of bytes, if
20-
// possible.
21-
#[cfg(target_arch = "wasm32")]
20+
/// Load the file at the given url fragment or path and return it as a vector of bytes, if
21+
/// possible.
2222
pub async fn load(path: &str) -> Result<Vec<u8>, LoadingBytesError> {
23-
use wasm_bindgen::JsCast;
23+
#[cfg(target_arch = "wasm32")]
24+
{
25+
use wasm_bindgen::JsCast;
2426

25-
let path = path.to_string();
26-
let mut opts = web_sys::RequestInit::new();
27-
opts.method("GET");
28-
let request = web_sys::Request::new_with_str_and_init(&path, &opts).map_err(|msg| {
29-
LoadingBytesError::Wasm {
30-
path: path.clone(),
31-
msg,
32-
}
33-
})?;
34-
let window = web_sys::window().unwrap();
35-
let resp_value = wasm_bindgen_futures::JsFuture::from(window.fetch_with_request(&request))
36-
.await
37-
.map_err(|msg| LoadingBytesError::Wasm {
38-
path: path.clone(),
39-
msg,
27+
let path = path.to_string();
28+
let mut opts = web_sys::RequestInit::new();
29+
opts.method("GET");
30+
let request = web_sys::Request::new_with_str_and_init(&path, &opts).map_err(|msg| {
31+
LoadingBytesError::Wasm {
32+
path: path.clone(),
33+
msg,
34+
}
4035
})?;
41-
let resp: web_sys::Response = resp_value
42-
.dyn_into()
43-
.map_err(|msg| LoadingBytesError::Wasm {
36+
let window = web_sys::window().unwrap();
37+
let resp_value = wasm_bindgen_futures::JsFuture::from(window.fetch_with_request(&request))
38+
.await
39+
.map_err(|msg| LoadingBytesError::Wasm {
40+
path: path.clone(),
41+
msg,
42+
})?;
43+
let resp: web_sys::Response =
44+
resp_value
45+
.dyn_into()
46+
.map_err(|msg| LoadingBytesError::Wasm {
47+
path: path.clone(),
48+
msg,
49+
})?;
50+
let array_promise = resp.array_buffer().map_err(|msg| LoadingBytesError::Wasm {
4451
path: path.clone(),
4552
msg,
4653
})?;
47-
let array_promise = resp.array_buffer().map_err(|msg| LoadingBytesError::Wasm {
48-
path: path.clone(),
49-
msg,
50-
})?;
51-
let buffer = wasm_bindgen_futures::JsFuture::from(array_promise)
52-
.await
53-
.map_err(|msg| LoadingBytesError::Wasm {
54-
path: path.clone(),
55-
msg,
54+
let buffer = wasm_bindgen_futures::JsFuture::from(array_promise)
55+
.await
56+
.map_err(|msg| LoadingBytesError::Wasm {
57+
path: path.clone(),
58+
msg,
59+
})?;
60+
assert!(buffer.is_instance_of::<js_sys::ArrayBuffer>());
61+
let array: js_sys::Uint8Array = js_sys::Uint8Array::new(&buffer);
62+
let mut bytes: Vec<u8> = vec![0; array.length() as usize];
63+
array.copy_to(&mut bytes);
64+
Ok(bytes)
65+
}
66+
#[cfg(not(target_arch = "wasm32"))]
67+
{
68+
let bytes: Vec<u8> = async_fs::read(path).await.with_context(|_| FsSnafu {
69+
path: path.to_string(),
70+
cwd: std::env::current_dir().unwrap(),
5671
})?;
57-
assert!(buffer.is_instance_of::<js_sys::ArrayBuffer>());
58-
let array: js_sys::Uint8Array = js_sys::Uint8Array::new(&buffer);
59-
let mut bytes: Vec<u8> = vec![0; array.length() as usize];
60-
array.copy_to(&mut bytes);
61-
Ok(bytes)
62-
}
63-
64-
#[cfg(not(target_arch = "wasm32"))]
65-
pub async fn load(path: &str) -> Result<Vec<u8>, LoadingBytesError> {
66-
let bytes: Vec<u8> = async_fs::read(path).await.with_context(|_| FsSnafu {
67-
path: path.to_string(),
68-
cwd: std::env::current_dir().unwrap(),
69-
})?;
70-
Ok(bytes)
72+
Ok(bytes)
73+
}
7174
}

crates/renderling-ui/Cargo.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "renderling_ui"
3-
version = "0.3.1"
3+
version = "0.3.4"
44
edition = "2021"
55
description = "User-friendly real-time 2d rendering. 🍖"
66
repository = "https://github.com/schell/renderling"
@@ -13,10 +13,10 @@ readme = "README.md"
1313
crabslab = {workspace = true}
1414
glyph_brush = "0.7.8"
1515
image = {workspace=true}
16-
loading-bytes = {version = "0.1.0", path = "../loading-bytes"}
16+
loading-bytes = {version = "0.1.1", path = "../loading-bytes"}
1717
log = {workspace=true}
1818
lyon = "1.0.1"
19-
renderling = {version = "0.4.6", path = "../renderling"}
19+
renderling = {version = "0.4.7", path = "../renderling"}
2020
rustc-hash = {workspace = true}
2121
snafu = {workspace = true}
2222
wgpu = {workspace = true}

crates/renderling-ui/src/lib.rs

+33-8
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use std::sync::{Arc, RwLock};
3131
use crabslab::Id;
3232
use glyph_brush::ab_glyph;
3333
use renderling::{
34-
atlas::AtlasTexture,
34+
atlas::{AtlasFrame, AtlasTexture, TextureModes},
3535
camera::Camera,
3636
math::{Quat, UVec2, Vec2, Vec3Swizzles, Vec4},
3737
slab::{Hybrid, UpdatesSlab},
@@ -130,14 +130,20 @@ impl UiTransform {
130130
}
131131
}
132132

133+
#[derive(Clone)]
134+
struct UiImage {
135+
frame: Hybrid<AtlasFrame>,
136+
texture: Hybrid<AtlasTexture>,
137+
}
138+
133139
/// A 2d user interface renderer.
134140
///
135141
/// Clones of `Ui` all point to the same data.
136142
#[derive(Clone)]
137143
pub struct Ui {
138144
camera: Hybrid<Camera>,
139145
stage: Stage,
140-
images: Arc<RwLock<Vec<Hybrid<AtlasTexture>>>>,
146+
images: Arc<RwLock<Vec<UiImage>>>,
141147
fonts: Arc<RwLock<Vec<FontArc>>>,
142148
// We keep a list of transforms that we use to "manually" order renderlets.
143149
//
@@ -168,6 +174,16 @@ impl Ui {
168174
}
169175
}
170176

177+
pub fn set_background_color(&self, color: impl Into<Vec4>) -> &Self {
178+
self.stage.set_background_color(color);
179+
self
180+
}
181+
182+
pub fn with_background_color(self, color: impl Into<Vec4>) -> Self {
183+
self.set_background_color(color);
184+
self
185+
}
186+
171187
pub fn set_antialiasing(&self, antialiasing_is_on: bool) -> &Self {
172188
let sample_count = if antialiasing_is_on { 4 } else { 1 };
173189
self.stage.set_msaa_sample_count(sample_count);
@@ -248,17 +264,26 @@ impl Ui {
248264
image::ImageFormat::from_path(path_s).context(ImageSnafu)?,
249265
)
250266
.context(ImageSnafu)?;
251-
let mut texture = self.stage.add_image(img).context(StageSnafu)?;
252-
texture.modes.s = renderling::atlas::TextureAddressMode::Repeat;
253-
texture.modes.t = renderling::atlas::TextureAddressMode::Repeat;
254-
let hybrid = self.stage.new_value(texture);
267+
let frame = self
268+
.stage
269+
.add_images(Some(img))
270+
.context(StageSnafu)?
271+
.pop()
272+
.unwrap();
273+
let texture = self.stage.new_value(AtlasTexture {
274+
frame_id: frame.id(),
275+
modes: TextureModes {
276+
s: renderling::atlas::TextureAddressMode::Repeat,
277+
t: renderling::atlas::TextureAddressMode::Repeat,
278+
},
279+
});
255280
let mut guard = self.images.write().unwrap();
256281
let id = guard.len();
257-
guard.push(hybrid);
282+
guard.push(UiImage { frame, texture });
258283
Ok(ImageId(id))
259284
}
260285

261-
pub(crate) fn get_texture(&self, index: usize) -> Option<Hybrid<AtlasTexture>> {
286+
pub(crate) fn get_image(&self, index: usize) -> Option<UiImage> {
262287
self.images.read().unwrap().get(index).cloned()
263288
}
264289

0 commit comments

Comments
 (0)