Compare commits
10 commits
b15437b87f
...
01ab64704b
Author | SHA1 | Date | |
---|---|---|---|
01ab64704b | |||
|
4590abae08 | ||
|
65825aa0d4 | ||
|
3425efd522 | ||
|
bfb07147cd | ||
|
7f9afac93e | ||
|
1bb46e41ff | ||
|
c2469de817 | ||
|
ce394f9d53 | ||
|
ca5f82e5e9 |
6 changed files with 154 additions and 104 deletions
10
Cargo.toml
10
Cargo.toml
|
@ -1,18 +1,18 @@
|
||||||
[package]
|
[package]
|
||||||
name = "glyphon"
|
name = "glyphon"
|
||||||
description = "Fast, simple 2D text rendering for wgpu"
|
description = "Fast, simple 2D text rendering for wgpu"
|
||||||
version = "0.3.0"
|
version = "0.5.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
homepage = "https://github.com/grovesNL/glyphon.git"
|
homepage = "https://github.com/grovesNL/glyphon.git"
|
||||||
repository = "https://github.com/grovesNL/glyphon"
|
repository = "https://github.com/grovesNL/glyphon"
|
||||||
license = "MIT OR Apache-2.0 OR Zlib"
|
license = "MIT OR Apache-2.0 OR Zlib"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wgpu = "0.18"
|
wgpu = "0.19"
|
||||||
etagere = "0.2.10"
|
etagere = "0.2.10"
|
||||||
cosmic-text = "0.10"
|
lru = "0.12.1"
|
||||||
lru = "0.11"
|
cosmic-text = "0.11.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
winit = "0.28.7"
|
winit = { version = "0.29.10", features = ["rwh_05"] }
|
||||||
pollster = "0.3.0"
|
pollster = "0.3.0"
|
||||||
|
|
|
@ -11,10 +11,12 @@ use wgpu::{
|
||||||
use winit::{
|
use winit::{
|
||||||
dpi::LogicalSize,
|
dpi::LogicalSize,
|
||||||
event::{Event, WindowEvent},
|
event::{Event, WindowEvent},
|
||||||
event_loop::{ControlFlow, EventLoop},
|
event_loop::EventLoop,
|
||||||
window::WindowBuilder,
|
window::WindowBuilder,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
pollster::block_on(run());
|
pollster::block_on(run());
|
||||||
}
|
}
|
||||||
|
@ -22,12 +24,12 @@ fn main() {
|
||||||
async fn run() {
|
async fn run() {
|
||||||
// Set up window
|
// Set up window
|
||||||
let (width, height) = (800, 600);
|
let (width, height) = (800, 600);
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::new().unwrap();
|
||||||
let window = WindowBuilder::new()
|
let window = Arc::new(WindowBuilder::new()
|
||||||
.with_inner_size(LogicalSize::new(width as f64, height as f64))
|
.with_inner_size(LogicalSize::new(width as f64, height as f64))
|
||||||
.with_title("glyphon hello world")
|
.with_title("glyphon hello world")
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap());
|
||||||
let size = window.inner_size();
|
let size = window.inner_size();
|
||||||
let scale_factor = window.scale_factor();
|
let scale_factor = window.scale_factor();
|
||||||
|
|
||||||
|
@ -41,14 +43,15 @@ async fn run() {
|
||||||
.request_device(
|
.request_device(
|
||||||
&DeviceDescriptor {
|
&DeviceDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
features: Features::empty(),
|
required_features: Features::empty(),
|
||||||
limits: Limits::downlevel_defaults(),
|
required_limits: Limits::downlevel_defaults(),
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let surface = unsafe { instance.create_surface(&window) }.expect("Create surface");
|
|
||||||
|
let surface = instance.create_surface(window.clone()).expect("Create surface");
|
||||||
let swapchain_format = TextureFormat::Bgra8UnormSrgb;
|
let swapchain_format = TextureFormat::Bgra8UnormSrgb;
|
||||||
let mut config = SurfaceConfiguration {
|
let mut config = SurfaceConfiguration {
|
||||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
@ -58,6 +61,7 @@ async fn run() {
|
||||||
present_mode: PresentMode::Fifo,
|
present_mode: PresentMode::Fifo,
|
||||||
alpha_mode: CompositeAlphaMode::Opaque,
|
alpha_mode: CompositeAlphaMode::Opaque,
|
||||||
view_formats: vec![],
|
view_formats: vec![],
|
||||||
|
desired_maximum_frame_latency: 2,
|
||||||
};
|
};
|
||||||
surface.configure(&device, &config);
|
surface.configure(&device, &config);
|
||||||
|
|
||||||
|
@ -76,84 +80,80 @@ async fn run() {
|
||||||
buffer.set_text(&mut font_system, "Hello world! 👋\nThis is rendered with 🦅 glyphon 🦁\nThe text below should be partially clipped.\na b c d e f g h i j k l m n o p q r s t u v w x y z", Attrs::new().family(Family::SansSerif), Shaping::Advanced);
|
buffer.set_text(&mut font_system, "Hello world! 👋\nThis is rendered with 🦅 glyphon 🦁\nThe text below should be partially clipped.\na b c d e f g h i j k l m n o p q r s t u v w x y z", Attrs::new().family(Family::SansSerif), Shaping::Advanced);
|
||||||
buffer.shape_until_scroll(&mut font_system);
|
buffer.shape_until_scroll(&mut font_system);
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop
|
||||||
let _ = (&instance, &adapter);
|
.run(move |event, target| {
|
||||||
|
if let Event::WindowEvent {
|
||||||
|
window_id: _,
|
||||||
|
event,
|
||||||
|
} = event
|
||||||
|
{
|
||||||
|
match event {
|
||||||
|
WindowEvent::Resized(size) => {
|
||||||
|
config.width = size.width;
|
||||||
|
config.height = size.height;
|
||||||
|
surface.configure(&device, &config);
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
WindowEvent::RedrawRequested => {
|
||||||
|
text_renderer
|
||||||
|
.prepare(
|
||||||
|
&device,
|
||||||
|
&queue,
|
||||||
|
&mut font_system,
|
||||||
|
&mut atlas,
|
||||||
|
Resolution {
|
||||||
|
width: config.width,
|
||||||
|
height: config.height,
|
||||||
|
},
|
||||||
|
[TextArea {
|
||||||
|
buffer: &buffer,
|
||||||
|
left: 10.0,
|
||||||
|
top: 10.0,
|
||||||
|
scale: 1.0,
|
||||||
|
bounds: TextBounds {
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
right: 600,
|
||||||
|
bottom: 160,
|
||||||
|
},
|
||||||
|
default_color: Color::rgb(255, 255, 255),
|
||||||
|
}],
|
||||||
|
&mut cache,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
*control_flow = ControlFlow::Poll;
|
let frame = surface.get_current_texture().unwrap();
|
||||||
match event {
|
let view = frame.texture.create_view(&TextureViewDescriptor::default());
|
||||||
Event::WindowEvent {
|
let mut encoder = device
|
||||||
event: WindowEvent::Resized(size),
|
.create_command_encoder(&CommandEncoderDescriptor { label: None });
|
||||||
..
|
{
|
||||||
} => {
|
let mut pass = encoder.begin_render_pass(&RenderPassDescriptor {
|
||||||
config.width = size.width;
|
label: None,
|
||||||
config.height = size.height;
|
color_attachments: &[Some(RenderPassColorAttachment {
|
||||||
surface.configure(&device, &config);
|
view: &view,
|
||||||
window.request_redraw();
|
resolve_target: None,
|
||||||
}
|
ops: Operations {
|
||||||
Event::RedrawRequested(_) => {
|
load: LoadOp::Clear(wgpu::Color::BLACK),
|
||||||
text_renderer
|
store: wgpu::StoreOp::Store,
|
||||||
.prepare(
|
},
|
||||||
&device,
|
})],
|
||||||
&queue,
|
depth_stencil_attachment: None,
|
||||||
&mut font_system,
|
timestamp_writes: None,
|
||||||
&mut atlas,
|
occlusion_query_set: None,
|
||||||
Resolution {
|
});
|
||||||
width: config.width,
|
|
||||||
height: config.height,
|
|
||||||
},
|
|
||||||
[TextArea {
|
|
||||||
buffer: &buffer,
|
|
||||||
left: 10.0,
|
|
||||||
top: 10.0,
|
|
||||||
scale: 1.0,
|
|
||||||
bounds: TextBounds {
|
|
||||||
left: 0,
|
|
||||||
top: 0,
|
|
||||||
right: 600,
|
|
||||||
bottom: 160,
|
|
||||||
},
|
|
||||||
default_color: Color::rgb(255, 255, 255),
|
|
||||||
}],
|
|
||||||
&mut cache,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let frame = surface.get_current_texture().unwrap();
|
text_renderer.render(&atlas, &mut pass).unwrap();
|
||||||
let view = frame.texture.create_view(&TextureViewDescriptor::default());
|
}
|
||||||
let mut encoder =
|
|
||||||
device.create_command_encoder(&CommandEncoderDescriptor { label: None });
|
|
||||||
{
|
|
||||||
let mut pass = encoder.begin_render_pass(&RenderPassDescriptor {
|
|
||||||
label: None,
|
|
||||||
color_attachments: &[Some(RenderPassColorAttachment {
|
|
||||||
view: &view,
|
|
||||||
resolve_target: None,
|
|
||||||
ops: Operations {
|
|
||||||
load: LoadOp::Clear(wgpu::Color::BLACK),
|
|
||||||
store: wgpu::StoreOp::Store,
|
|
||||||
},
|
|
||||||
})],
|
|
||||||
depth_stencil_attachment: None,
|
|
||||||
timestamp_writes: None,
|
|
||||||
occlusion_query_set: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
text_renderer.render(&atlas, &mut pass).unwrap();
|
queue.submit(Some(encoder.finish()));
|
||||||
|
frame.present();
|
||||||
|
|
||||||
|
atlas.trim();
|
||||||
|
}
|
||||||
|
WindowEvent::CloseRequested => target.exit(),
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
queue.submit(Some(encoder.finish()));
|
|
||||||
frame.present();
|
|
||||||
|
|
||||||
atlas.trim();
|
|
||||||
}
|
}
|
||||||
Event::WindowEvent {
|
})
|
||||||
event: WindowEvent::CloseRequested,
|
.unwrap();
|
||||||
..
|
|
||||||
} => *control_flow = ControlFlow::Exit,
|
|
||||||
Event::MainEventsCleared => {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ pub(crate) struct GlyphToRender {
|
||||||
dim: [u16; 2],
|
dim: [u16; 2],
|
||||||
uv: [u16; 2],
|
uv: [u16; 2],
|
||||||
color: u32,
|
color: u32,
|
||||||
content_type: u32,
|
content_type_with_srgb: [u16; 2],
|
||||||
depth: f32,
|
depth: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,6 +111,8 @@ pub struct TextArea<'a> {
|
||||||
/// The visible bounds of the text area. This is used to clip the text and doesn't have to
|
/// The visible bounds of the text area. This is used to clip the text and doesn't have to
|
||||||
/// match the `left` and `top` values.
|
/// match the `left` and `top` values.
|
||||||
pub bounds: TextBounds,
|
pub bounds: TextBounds,
|
||||||
// The default color of the text area.
|
/// The default color of the text area.
|
||||||
pub default_color: Color,
|
pub default_color: Color,
|
||||||
|
/// The opacity to set the text area to (in gamma space)
|
||||||
|
pub opacity: f32,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ struct VertexInput {
|
||||||
@location(1) dim: u32,
|
@location(1) dim: u32,
|
||||||
@location(2) uv: u32,
|
@location(2) uv: u32,
|
||||||
@location(3) color: u32,
|
@location(3) color: u32,
|
||||||
@location(4) content_type: u32,
|
@location(4) content_type_with_srgb: u32,
|
||||||
@location(5) depth: f32,
|
@location(5) depth: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,14 @@ var mask_atlas_texture: texture_2d<f32>;
|
||||||
@group(0) @binding(3)
|
@group(0) @binding(3)
|
||||||
var atlas_sampler: sampler;
|
var atlas_sampler: sampler;
|
||||||
|
|
||||||
|
fn srgb_to_linear(c: f32) -> f32 {
|
||||||
|
if c <= 0.04045 {
|
||||||
|
return c / 12.92;
|
||||||
|
} else {
|
||||||
|
return pow((c + 0.055) / 1.055, 2.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vs_main(in_vert: VertexInput) -> VertexOutput {
|
fn vs_main(in_vert: VertexInput) -> VertexOutput {
|
||||||
var pos = in_vert.pos;
|
var pos = in_vert.pos;
|
||||||
|
@ -69,15 +77,31 @@ fn vs_main(in_vert: VertexInput) -> VertexOutput {
|
||||||
|
|
||||||
vert_output.position.y *= -1.0;
|
vert_output.position.y *= -1.0;
|
||||||
|
|
||||||
vert_output.color = vec4<f32>(
|
let content_type = in_vert.content_type_with_srgb & 0xffffu;
|
||||||
f32((color & 0x00ff0000u) >> 16u),
|
let srgb = (in_vert.content_type_with_srgb & 0xffff0000u) >> 16u;
|
||||||
f32((color & 0x0000ff00u) >> 8u),
|
|
||||||
f32(color & 0x000000ffu),
|
switch srgb {
|
||||||
f32((color & 0xff000000u) >> 24u),
|
case 0u: {
|
||||||
) / 255.0;
|
vert_output.color = vec4<f32>(
|
||||||
|
f32((color & 0x00ff0000u) >> 16u) / 255.0,
|
||||||
|
f32((color & 0x0000ff00u) >> 8u) / 255.0,
|
||||||
|
f32(color & 0x000000ffu) / 255.0,
|
||||||
|
f32((color & 0xff000000u) >> 24u) / 255.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case 1u: {
|
||||||
|
vert_output.color = vec4<f32>(
|
||||||
|
srgb_to_linear(f32((color & 0x00ff0000u) >> 16u) / 255.0),
|
||||||
|
srgb_to_linear(f32((color & 0x0000ff00u) >> 8u) / 255.0),
|
||||||
|
srgb_to_linear(f32(color & 0x000000ffu) / 255.0),
|
||||||
|
f32((color & 0xff000000u) >> 24u) / 255.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
default: {}
|
||||||
|
}
|
||||||
|
|
||||||
var dim: vec2<u32> = vec2(0u);
|
var dim: vec2<u32> = vec2(0u);
|
||||||
switch in_vert.content_type {
|
switch content_type {
|
||||||
case 0u: {
|
case 0u: {
|
||||||
dim = textureDimensions(color_atlas_texture);
|
dim = textureDimensions(color_atlas_texture);
|
||||||
break;
|
break;
|
||||||
|
@ -89,7 +113,7 @@ fn vs_main(in_vert: VertexInput) -> VertexOutput {
|
||||||
default: {}
|
default: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
vert_output.content_type = in_vert.content_type;
|
vert_output.content_type = content_type;
|
||||||
|
|
||||||
vert_output.uv = vec2<f32>(uv) / vec2<f32>(dim);
|
vert_output.uv = vec2<f32>(uv) / vec2<f32>(dim);
|
||||||
|
|
||||||
|
|
|
@ -131,8 +131,10 @@ impl InnerAtlas {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Better resizing logic (?)
|
// Grow each dimension by a factor of 2. The growth factor was chosen to match the growth
|
||||||
let new_size = (self.size + Self::INITIAL_SIZE).min(self.max_texture_dimension_2d);
|
// factor of `Vec`.`
|
||||||
|
const GROWTH_FACTOR: u32 = 2;
|
||||||
|
let new_size = (self.size * GROWTH_FACTOR).min(self.max_texture_dimension_2d);
|
||||||
|
|
||||||
self.packer.grow(size2(new_size as i32, new_size as i32));
|
self.packer.grow(size2(new_size as i32, new_size as i32));
|
||||||
|
|
||||||
|
@ -269,6 +271,7 @@ pub struct TextAtlas {
|
||||||
pub(crate) shader: ShaderModule,
|
pub(crate) shader: ShaderModule,
|
||||||
pub(crate) vertex_buffers: [wgpu::VertexBufferLayout<'static>; 1],
|
pub(crate) vertex_buffers: [wgpu::VertexBufferLayout<'static>; 1],
|
||||||
pub(crate) format: TextureFormat,
|
pub(crate) format: TextureFormat,
|
||||||
|
pub(crate) color_mode: ColorMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextAtlas {
|
impl TextAtlas {
|
||||||
|
@ -448,6 +451,7 @@ impl TextAtlas {
|
||||||
shader,
|
shader,
|
||||||
vertex_buffers,
|
vertex_buffers,
|
||||||
format,
|
format,
|
||||||
|
color_mode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
FontSystem, GlyphDetails, GlyphToRender, GpuCacheStatus, Params, PrepareError, RenderError,
|
ColorMode, FontSystem, GlyphDetails, GlyphToRender, GpuCacheStatus, Params, PrepareError,
|
||||||
Resolution, SwashCache, SwashContent, TextArea, TextAtlas,
|
RenderError, Resolution, SwashCache, SwashContent, TextArea, TextAtlas,
|
||||||
};
|
};
|
||||||
use std::{iter, mem::size_of, slice, sync::Arc};
|
use std::{iter, mem::size_of, slice, sync::Arc};
|
||||||
use wgpu::{
|
use wgpu::{
|
||||||
|
@ -268,6 +268,13 @@ impl TextRenderer {
|
||||||
None => text_area.default_color,
|
None => text_area.default_color,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let color = cosmic_text::Color::rgba(
|
||||||
|
(color.r() as f32 * text_area.opacity + 0.5) as u8,
|
||||||
|
(color.g() as f32 * text_area.opacity + 0.5) as u8,
|
||||||
|
(color.b() as f32 * text_area.opacity + 0.5) as u8,
|
||||||
|
(color.a() as f32 * text_area.opacity + 0.5) as u8,
|
||||||
|
);
|
||||||
|
|
||||||
let depth = metadata_to_depth(glyph.metadata);
|
let depth = metadata_to_depth(glyph.metadata);
|
||||||
|
|
||||||
glyph_vertices.extend(
|
glyph_vertices.extend(
|
||||||
|
@ -276,7 +283,13 @@ impl TextRenderer {
|
||||||
dim: [width as u16, height as u16],
|
dim: [width as u16, height as u16],
|
||||||
uv: [atlas_x, atlas_y],
|
uv: [atlas_x, atlas_y],
|
||||||
color: color.0,
|
color: color.0,
|
||||||
content_type: content_type as u32,
|
content_type_with_srgb: [
|
||||||
|
content_type as u16,
|
||||||
|
match atlas.color_mode {
|
||||||
|
ColorMode::Accurate => TextColorConversion::ConvertToLinear,
|
||||||
|
ColorMode::Web => TextColorConversion::None,
|
||||||
|
} as u16,
|
||||||
|
],
|
||||||
depth,
|
depth,
|
||||||
})
|
})
|
||||||
.take(4),
|
.take(4),
|
||||||
|
@ -405,13 +418,20 @@ impl TextRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u32)]
|
#[repr(u16)]
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
pub enum ContentType {
|
pub enum ContentType {
|
||||||
Color = 0,
|
Color = 0,
|
||||||
Mask = 1,
|
Mask = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(u16)]
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
|
enum TextColorConversion {
|
||||||
|
None = 0,
|
||||||
|
ConvertToLinear = 1,
|
||||||
|
}
|
||||||
|
|
||||||
fn next_copy_buffer_size(size: u64) -> u64 {
|
fn next_copy_buffer_size(size: u64) -> u64 {
|
||||||
let align_mask = COPY_BUFFER_ALIGNMENT - 1;
|
let align_mask = COPY_BUFFER_ALIGNMENT - 1;
|
||||||
((size.next_power_of_two() + align_mask) & !align_mask).max(COPY_BUFFER_ALIGNMENT)
|
((size.next_power_of_two() + align_mask) & !align_mask).max(COPY_BUFFER_ALIGNMENT)
|
||||||
|
|
Loading…
Reference in a new issue