Skip to content

Commit

Permalink
feature: unify AtlasFrame and AtlasTexture (#128)
Browse files Browse the repository at this point in the history
* using AtlasEntry

* merge AtlasFrame into AtlasTexture

* send_wrapper for JsValue in errors, devlog, readme, added Stage::get_camera
  • Loading branch information
schell authored Sep 8, 2024
1 parent b925658 commit e306b20
Show file tree
Hide file tree
Showing 17 changed files with 384 additions and 234 deletions.
2 changes: 1 addition & 1 deletion 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 @@ -30,6 +30,7 @@ pretty_assertions = "1.4.0"
proc-macro2 = { version = "1.0", features = ["span-locations"] }
rustc-hash = "1.1"
serde_json = "1.0.117"
send_wrapper = "0.6.0"
snafu = "0.7"
syn = { version = "2.0.49", features = ["full", "extra-traits", "parsing"] }
wasm-bindgen = "0.2"
Expand Down
23 changes: 23 additions & 0 deletions DEVLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@
title: devlog
---

## Wed Sep 4, 2024

### nlnet updates

I'm just waiting for a review on [my third PR, which adds parsing for a bunch of atomic
ops](https://github.com/gfx-rs/wgpu/pull/5824). The turn-around time has gotten pretty
long, though - this PR has been sitting for a month and I can't seem to get folks to
review it. I'm sure if I keep pinging them they'll pick it back up but I don't like
doing that. I know they're busy with day jobs just like I am, but my time on this grant
is running out, and I still have 6 milestones to hit.

### Atlas updates

I'm updating the atlas so that it automatically evicts frames/textures without any
references (stale or dead frames and textures). I think I'll just merge `AtlasFrame`
and `AtlasTexture`. The only reason they're separate is in case somebody wants to have
different wrapping parameters for the same texture data, but I don't think any graphics
APIs even support this.

...

It wasn't too hard!

## Mon Aug 26, 2024

I'm adding morph targets. I've done this once before so I expect it'll be pretty easy.
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ Renderling takes a [forward+](https://takahiroharada.files.wordpress.com/2015/04
By default it uses a single uber-shader for rendering.

- [x] texture atlas
- [x] automatic resource management (Arc/drop based)
- [x] GPU slab allocator
- [x] automatic resource management (Arc/drop based)
- [ ] frustum culling
- [ ] occlusion culling
- [ ] light tiling
Expand Down Expand Up @@ -102,9 +105,11 @@ By default it uses a single uber-shader for rendering.
- [x] interpolation
- [x] skinning (still working on one [issue](https://github.com/schell/renderling/issues/120))
- [ ] morph targets (requires rebuild)
- 2d
- 2d (renderling-ui)
- [x] text
- [x] stroked and filled paths
- [x] circles
- [x] rectangles
- [x] cubic beziers
- [x] quadratic beziers
- [x] arbitrary polygons
Expand Down
1 change: 1 addition & 0 deletions crates/loading-bytes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ readme = "README.md"
[dependencies]
async-fs = "1.6"
js-sys = "0.3"
send_wrapper = {workspace=true}
snafu = {workspace = true}
wasm-bindgen = {workspace = true}
wasm-bindgen-futures = {workspace = true}
Expand Down
12 changes: 6 additions & 6 deletions crates/loading-bytes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub enum LoadingBytesError {
#[snafu(display("loading '{path}' by WASM error: {msg:#?}"))]
Wasm {
path: String,
msg: wasm_bindgen::JsValue,
msg: send_wrapper::SendWrapper<wasm_bindgen::JsValue>,
},
#[snafu(display("loading '{path}' by filesystem from CWD '{}' error: {source}", cwd.display()))]
Fs {
Expand All @@ -30,32 +30,32 @@ pub async fn load(path: &str) -> Result<Vec<u8>, LoadingBytesError> {
let request = web_sys::Request::new_with_str_and_init(&path, &opts).map_err(|msg| {
LoadingBytesError::Wasm {
path: path.clone(),
msg,
msg: send_wrapper::SendWrapper::new(msg),
}
})?;
let window = web_sys::window().unwrap();
let resp_value = wasm_bindgen_futures::JsFuture::from(window.fetch_with_request(&request))
.await
.map_err(|msg| LoadingBytesError::Wasm {
path: path.clone(),
msg,
msg: send_wrapper::SendWrapper::new(msg),
})?;
let resp: web_sys::Response =
resp_value
.dyn_into()
.map_err(|msg| LoadingBytesError::Wasm {
path: path.clone(),
msg,
msg: send_wrapper::SendWrapper::new(msg),
})?;
let array_promise = resp.array_buffer().map_err(|msg| LoadingBytesError::Wasm {
path: path.clone(),
msg,
msg: send_wrapper::SendWrapper::new(msg),
})?;
let buffer = wasm_bindgen_futures::JsFuture::from(array_promise)
.await
.map_err(|msg| LoadingBytesError::Wasm {
path: path.clone(),
msg,
msg: send_wrapper::SendWrapper::new(msg),
})?;
assert!(buffer.is_instance_of::<js_sys::ArrayBuffer>());
let array: js_sys::Uint8Array = js_sys::Uint8Array::new(&buffer);
Expand Down
44 changes: 31 additions & 13 deletions crates/renderling-ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use std::sync::{Arc, RwLock};
use crabslab::Id;
use glyph_brush::ab_glyph;
use renderling::{
atlas::{AtlasFrame, AtlasTexture, TextureModes},
atlas::AtlasTexture,
camera::Camera,
math::{Quat, UVec2, Vec2, Vec3Swizzles, Vec4},
slab::{Hybrid, UpdatesSlab},
Expand Down Expand Up @@ -131,10 +131,8 @@ impl UiTransform {
}

#[derive(Clone)]
struct UiImage {
frame: Hybrid<AtlasFrame>,
texture: Hybrid<AtlasTexture>,
}
#[repr(transparent)]
struct UiImage(Hybrid<AtlasTexture>);

/// A 2d user interface renderer.
///
Expand Down Expand Up @@ -174,6 +172,24 @@ impl Ui {
}
}

pub fn set_clear_color_attachments(&self, should_clear: bool) {
self.stage.set_clear_color_attachments(should_clear);
}

pub fn with_clear_color_attachments(self, should_clear: bool) -> Self {
self.set_clear_color_attachments(should_clear);
self
}

pub fn set_clear_depth_attachments(&self, should_clear: bool) {
self.stage.set_clear_depth_attachments(should_clear);
}

pub fn with_clear_depth_attachments(self, should_clear: bool) -> Self {
self.set_clear_depth_attachments(should_clear);
self
}

pub fn set_background_color(&self, color: impl Into<Vec4>) -> &Self {
self.stage.set_background_color(color);
self
Expand Down Expand Up @@ -256,6 +272,10 @@ impl Ui {
self.fonts.read().unwrap().clone()
}

pub fn get_camera(&self) -> &Hybrid<Camera> {
&self.camera
}

pub async fn load_image(&self, path: impl AsRef<str>) -> Result<ImageId, UiError> {
let path_s = path.as_ref();
let bytes = loading_bytes::load(path_s).await.context(LoadingSnafu)?;
Expand All @@ -264,22 +284,19 @@ impl Ui {
image::ImageFormat::from_path(path_s).context(ImageSnafu)?,
)
.context(ImageSnafu)?;
let frame = self
let entry = self
.stage
.add_images(Some(img))
.context(StageSnafu)?
.pop()
.unwrap();
let texture = self.stage.new_value(AtlasTexture {
frame_id: frame.id(),
modes: TextureModes {
s: renderling::atlas::TextureAddressMode::Repeat,
t: renderling::atlas::TextureAddressMode::Repeat,
},
entry.modify(|t| {
t.modes.s = renderling::atlas::TextureAddressMode::Repeat;
t.modes.t = renderling::atlas::TextureAddressMode::Repeat;
});
let mut guard = self.images.write().unwrap();
let id = guard.len();
guard.push(UiImage { frame, texture });
guard.push(UiImage(entry));
Ok(ImageId(id))
}

Expand Down Expand Up @@ -321,6 +338,7 @@ impl Ui {
log::trace!("a ui transform changed, sorting the renderlets");
self.reorder_renderlets();
}
self.stage.tick();
self.stage.render(view);
}
}
Expand Down
20 changes: 10 additions & 10 deletions crates/renderling-ui/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,11 +353,11 @@ impl UiPathBuilder {
let mut size = Vec2::ONE;
let albedo_texture_id = if let Some(ImageId(index)) = options.image_id {
if let Some(image) = self.ui.get_image(index) {
let frame = image.frame.get();
log::debug!("size: {}", frame.size_px);
size.x = frame.size_px.x as f32;
size.y = frame.size_px.y as f32;
image.texture.id()
let tex = image.0.get();
log::debug!("size: {}", tex.size_px);
size.x = tex.size_px.x as f32;
size.y = tex.size_px.y as f32;
image.0.id()
} else {
Id::NONE
}
Expand Down Expand Up @@ -445,11 +445,11 @@ impl UiPathBuilder {
let mut size = Vec2::ONE;
let albedo_texture_id = if let Some(ImageId(index)) = image_id {
if let Some(image) = self.ui.get_image(index) {
let frame = image.frame.get();
log::debug!("size: {}", frame.size_px);
size.x = frame.size_px.x as f32;
size.y = frame.size_px.y as f32;
image.texture.id()
let tex = image.0.get();
log::debug!("size: {}", tex.size_px);
size.x = tex.size_px.x as f32;
size.y = tex.size_px.y as f32;
image.0.id()
} else {
Id::NONE
}
Expand Down
13 changes: 6 additions & 7 deletions crates/renderling-ui/src/text.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Text rendering capabilities for `Renderling`.
//!
//! This module is only enabled with the `text` cargo feature.
use std::{
borrow::Cow,
ops::{Deref, DerefMut},
Expand All @@ -23,6 +24,8 @@ use renderling::{

use crate::{Ui, UiTransform};

// TODO: make UiText able to be updated without fully destroying it

pub struct UiText {
pub cache: GlyphCache,
pub vertices: GpuArray<Vertex>,
Expand Down Expand Up @@ -102,12 +105,8 @@ impl UiTextBuilder {
));

// UNWRAP: panic on purpose
let frame = ui.stage.add_images(Some(img)).unwrap().pop().unwrap();
let texture = ui.stage.new_value(AtlasTexture {
frame_id: frame.id(),
..Default::default()
});
material.albedo_texture_id = texture.id();
let entry = ui.stage.add_images(Some(img)).unwrap().pop().unwrap();
material.albedo_texture_id = entry.id();

let vertices = ui.stage.new_array(mesh);
let material = ui.stage.new_value(material);
Expand All @@ -127,7 +126,7 @@ impl UiTextBuilder {
bounds,
vertices: vertices.into_gpu_only(),
transform,
texture,
texture: entry,
material,
renderlet,
}
Expand Down
1 change: 0 additions & 1 deletion crates/renderling/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ half = "2.3"
image = {workspace = true, features = ["hdr"]}
log = {workspace = true}
rustc-hash = {workspace = true}
send_wrapper = "0.6"
serde_json = {workspace = true, optional = true}
snafu = {workspace = true}
wgpu = { workspace = true, features = ["spirv"] }
Expand Down
27 changes: 9 additions & 18 deletions crates/renderling/src/atlas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! on web.
//!
//! `Atlas` is only available on CPU. Not available in shaders.
use crabslab::{Id, Slab, SlabItem};
use crabslab::SlabItem;
use glam::{UVec2, Vec2, Vec3};
#[cfg(target_arch = "spirv")]
use spirv_std::num_traits::*;
Expand All @@ -32,10 +32,10 @@ pub struct TextureModes {
pub t: TextureAddressMode,
}

/// A locatable frame within the atlas.
/// A texture inside the atlas.
#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]
#[derive(Clone, Copy, Default, PartialEq, SlabItem)]
pub struct AtlasFrame {
pub struct AtlasTexture {
/// The top left offset of texture in the atlas.
pub offset_px: UVec2,
/// The size of the texture in the atlas.
Expand All @@ -44,41 +44,32 @@ pub struct AtlasFrame {
pub layer_index: u32,
/// The index of this frame within the layer.
pub frame_index: u32,
}

/// A texture inside the atlas.
#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]
#[derive(Clone, Copy, Default, PartialEq, SlabItem)]
pub struct AtlasTexture {
/// Id of the texture's frame.
pub frame_id: Id<AtlasFrame>,
/// Various toggles of texture modes.
pub modes: TextureModes,
}

impl AtlasTexture {
/// Transform the given `uv` coordinates for this texture's address mode
/// and placement in the atlas of the given size.
pub fn uv(&self, slab: &[u32], mut uv: Vec2, atlas_size: UVec2) -> Vec3 {
let frame = slab.read(self.frame_id);
pub fn uv(&self, mut uv: Vec2, atlas_size: UVec2) -> Vec3 {
uv.x = self.modes.s.wrap(uv.x);
uv.y = self.modes.t.wrap(uv.y);

// get the pixel index of the uv coordinate in terms of the original image
let mut px_index_s = (uv.x * frame.size_px.x as f32) as u32;
let mut px_index_t = (uv.y * frame.size_px.y as f32) as u32;
let mut px_index_s = (uv.x * self.size_px.x as f32) as u32;
let mut px_index_t = (uv.y * self.size_px.y as f32) as u32;

// convert the pixel index from image to atlas space
px_index_s += frame.offset_px.x;
px_index_t += frame.offset_px.y;
px_index_s += self.offset_px.x;
px_index_t += self.offset_px.y;

let sx = atlas_size.x as f32;
let sy = atlas_size.y as f32;
// normalize the pixels by dividing by the atlas size
let uv_s = px_index_s as f32 / sx;
let uv_t = px_index_t as f32 / sy;

Vec2::new(uv_s, uv_t).extend(frame.layer_index as f32)
Vec2::new(uv_s, uv_t).extend(self.layer_index as f32)
}
}

Expand Down
Loading

0 comments on commit e306b20

Please sign in to comment.