Initial plumbing
This commit is contained in:
commit
55275d7b05
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "glow_glyph"
|
||||
version = "0.1.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
glow = "0.4"
|
||||
glyph_brush = "0.6"
|
||||
log = "0.4"
|
||||
bytemuck = "1.2"
|
||||
|
||||
[dev-dependencies]
|
||||
glutin = "0.24"
|
||||
env_logger = "0.7"
|
Binary file not shown.
|
@ -0,0 +1,89 @@
|
|||
use glow::HasContext;
|
||||
use glow_glyph::{GlyphBrushBuilder, Scale, Section};
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
env_logger::init();
|
||||
|
||||
// Open window and create a surface
|
||||
let event_loop = glutin::event_loop::EventLoop::new();
|
||||
|
||||
let window_builder =
|
||||
glutin::window::WindowBuilder::new().with_resizable(false);
|
||||
|
||||
let context = glutin::ContextBuilder::new()
|
||||
.with_srgb(true)
|
||||
.build_windowed(window_builder, &event_loop)
|
||||
.expect("Open window");
|
||||
|
||||
let context =
|
||||
unsafe { context.make_current().expect("Make OpenGL context current") };
|
||||
|
||||
let mut size = context.window().inner_size();
|
||||
|
||||
// Initialize OpenGL
|
||||
let gl = glow::Context::from_loader_function(|s| {
|
||||
context.get_proc_address(s) as *const _
|
||||
});
|
||||
|
||||
// Prepare glyph_brush
|
||||
let inconsolata: &[u8] = include_bytes!("Inconsolata-Regular.ttf");
|
||||
let mut glyph_brush = GlyphBrushBuilder::using_font_bytes(inconsolata)
|
||||
.expect("Load fonts")
|
||||
.build(&gl);
|
||||
|
||||
// Render loop
|
||||
context.window().request_redraw();
|
||||
|
||||
unsafe {
|
||||
gl.enable(glow::FRAMEBUFFER_SRGB);
|
||||
gl.clear_color(0.4, 0.4, 0.4, 1.0);
|
||||
}
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
match event {
|
||||
glutin::event::Event::WindowEvent {
|
||||
event: glutin::event::WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = glutin::event_loop::ControlFlow::Exit,
|
||||
glutin::event::Event::WindowEvent {
|
||||
event: glutin::event::WindowEvent::Resized(new_size),
|
||||
..
|
||||
} => {
|
||||
context.resize(new_size);
|
||||
|
||||
size = new_size;
|
||||
}
|
||||
glutin::event::Event::RedrawRequested { .. } => {
|
||||
unsafe { gl.clear(glow::COLOR_BUFFER_BIT) }
|
||||
|
||||
glyph_brush.queue(Section {
|
||||
text: "Hello wgpu_glyph!",
|
||||
screen_position: (30.0, 30.0),
|
||||
color: [0.0, 0.0, 0.0, 1.0],
|
||||
scale: Scale { x: 40.0, y: 40.0 },
|
||||
bounds: (size.width as f32, size.height as f32),
|
||||
..Section::default()
|
||||
});
|
||||
|
||||
glyph_brush.queue(Section {
|
||||
text: "Hello wgpu_glyph!",
|
||||
screen_position: (30.0, 90.0),
|
||||
color: [1.0, 1.0, 1.0, 1.0],
|
||||
scale: Scale { x: 40.0, y: 40.0 },
|
||||
bounds: (size.width as f32, size.height as f32),
|
||||
..Section::default()
|
||||
});
|
||||
|
||||
// Draw the text!
|
||||
glyph_brush
|
||||
.draw_queued(&gl, size.width, size.height)
|
||||
.expect("Draw queued");
|
||||
|
||||
context.swap_buffers().expect("Swap buffers");
|
||||
}
|
||||
_ => {
|
||||
*control_flow = glutin::event_loop::ControlFlow::Wait;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
max_width=80
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2017 Alex Butler
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,79 @@
|
|||
use core::hash::BuildHasher;
|
||||
|
||||
use glyph_brush::delegate_glyph_brush_builder_fns;
|
||||
use glyph_brush::{rusttype, DefaultSectionHasher};
|
||||
use rusttype::{Error, Font, SharedBytes};
|
||||
|
||||
use super::GlyphBrush;
|
||||
|
||||
/// Builder for a [`GlyphBrush`](struct.GlyphBrush.html).
|
||||
pub struct GlyphBrushBuilder<'a, H = DefaultSectionHasher> {
|
||||
inner: glyph_brush::GlyphBrushBuilder<'a, H>,
|
||||
}
|
||||
|
||||
impl<'a, H> From<glyph_brush::GlyphBrushBuilder<'a, H>> for GlyphBrushBuilder<'a, H> {
|
||||
fn from(inner: glyph_brush::GlyphBrushBuilder<'a, H>) -> Self {
|
||||
GlyphBrushBuilder { inner }
|
||||
}
|
||||
}
|
||||
impl<'a> GlyphBrushBuilder<'a> {
|
||||
/// Specifies the default font data used to render glyphs.
|
||||
/// Referenced with `FontId(0)`, which is default.
|
||||
#[inline]
|
||||
pub fn using_font_bytes<B: Into<SharedBytes<'a>>>(font_0_data: B) -> Result<Self, Error> {
|
||||
let font = Font::from_bytes(font_0_data)?;
|
||||
|
||||
Ok(Self::using_font(font))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn using_fonts_bytes<B, V>(font_data: V) -> Result<Self, Error>
|
||||
where
|
||||
B: Into<SharedBytes<'a>>,
|
||||
V: Into<Vec<B>>,
|
||||
{
|
||||
let fonts = font_data
|
||||
.into()
|
||||
.into_iter()
|
||||
.map(Font::from_bytes)
|
||||
.collect::<Result<Vec<Font>, Error>>()?;
|
||||
|
||||
Ok(Self::using_fonts(fonts))
|
||||
}
|
||||
|
||||
/// Specifies the default font used to render glyphs.
|
||||
/// Referenced with `FontId(0)`, which is default.
|
||||
#[inline]
|
||||
pub fn using_font(font_0: Font<'a>) -> Self {
|
||||
Self::using_fonts(vec![font_0])
|
||||
}
|
||||
|
||||
pub fn using_fonts<V: Into<Vec<Font<'a>>>>(fonts: V) -> Self {
|
||||
GlyphBrushBuilder {
|
||||
inner: glyph_brush::GlyphBrushBuilder::using_fonts(fonts),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, H: BuildHasher> GlyphBrushBuilder<'a, H> {
|
||||
delegate_glyph_brush_builder_fns!(inner);
|
||||
|
||||
/// Sets the section hasher. `GlyphBrush` cannot handle absolute section
|
||||
/// hash collisions so use a good hash algorithm.
|
||||
///
|
||||
/// This hasher is used to distinguish sections, rather than for hashmap
|
||||
/// internal use.
|
||||
///
|
||||
/// Defaults to [seahash](https://docs.rs/seahash).
|
||||
pub fn section_hasher<T: BuildHasher>(self, section_hasher: T) -> GlyphBrushBuilder<'a, T> {
|
||||
GlyphBrushBuilder {
|
||||
inner: self.inner.section_hasher(section_hasher),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a `GlyphBrush` using the given `wgpu::Device` that can render
|
||||
/// text for texture views with the given `render_format`.
|
||||
pub fn build(self, gl: &glow::Context) -> GlyphBrush<'a, H> {
|
||||
GlyphBrush::<H>::new(gl, self.inner)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,322 @@
|
|||
//! A fast text renderer for [`wgpu`]. Powered by [`glyph_brush`].
|
||||
//!
|
||||
//! [`wgpu`]: https://github.com/gfx-rs/wgpu
|
||||
//! [`glyph_brush`]: https://github.com/alexheretic/glyph-brush/tree/master/glyph-brush
|
||||
#![deny(unused_results)]
|
||||
mod builder;
|
||||
mod pipeline;
|
||||
mod region;
|
||||
|
||||
pub use region::Region;
|
||||
|
||||
use pipeline::{Instance, Pipeline};
|
||||
|
||||
pub use builder::GlyphBrushBuilder;
|
||||
pub use glyph_brush::{
|
||||
rusttype::{self, Font, Point, PositionedGlyph, Rect, Scale, SharedBytes},
|
||||
BuiltInLineBreaker, FontId, FontMap, GlyphCruncher, GlyphPositioner, HorizontalAlign, Layout,
|
||||
LineBreak, LineBreaker, OwnedSectionText, OwnedVariedSection, PositionedGlyphIter, Section,
|
||||
SectionGeometry, SectionText, VariedSection, VerticalAlign,
|
||||
};
|
||||
|
||||
use core::hash::BuildHasher;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use glyph_brush::{BrushAction, BrushError, Color, DefaultSectionHasher};
|
||||
use log::{log_enabled, warn};
|
||||
|
||||
/// Object allowing glyph drawing, containing cache state. Manages glyph positioning cacheing,
|
||||
/// glyph draw caching & efficient GPU texture cache updating and re-sizing on demand.
|
||||
///
|
||||
/// Build using a [`GlyphBrushBuilder`](struct.GlyphBrushBuilder.html).
|
||||
pub struct GlyphBrush<'font, H = DefaultSectionHasher> {
|
||||
pipeline: Pipeline,
|
||||
glyph_brush: glyph_brush::GlyphBrush<'font, Instance, H>,
|
||||
}
|
||||
|
||||
impl<'font, H: BuildHasher> GlyphBrush<'font, H> {
|
||||
/// Queues a section/layout to be drawn by the next call of
|
||||
/// [`draw_queued`](struct.GlyphBrush.html#method.draw_queued). Can be
|
||||
/// called multiple times to queue multiple sections for drawing.
|
||||
///
|
||||
/// Benefits from caching, see [caching behaviour](#caching-behaviour).
|
||||
#[inline]
|
||||
pub fn queue<'a, S>(&mut self, section: S)
|
||||
where
|
||||
S: Into<Cow<'a, VariedSection<'a>>>,
|
||||
{
|
||||
self.glyph_brush.queue(section)
|
||||
}
|
||||
|
||||
/// Queues a section/layout to be drawn by the next call of
|
||||
/// [`draw_queued`](struct.GlyphBrush.html#method.draw_queued). Can be
|
||||
/// called multiple times to queue multiple sections for drawing.
|
||||
///
|
||||
/// Used to provide custom `GlyphPositioner` logic, if using built-in
|
||||
/// [`Layout`](enum.Layout.html) simply use
|
||||
/// [`queue`](struct.GlyphBrush.html#method.queue)
|
||||
///
|
||||
/// Benefits from caching, see [caching behaviour](#caching-behaviour).
|
||||
#[inline]
|
||||
pub fn queue_custom_layout<'a, S, G>(&mut self, section: S, custom_layout: &G)
|
||||
where
|
||||
G: GlyphPositioner,
|
||||
S: Into<Cow<'a, VariedSection<'a>>>,
|
||||
{
|
||||
self.glyph_brush.queue_custom_layout(section, custom_layout)
|
||||
}
|
||||
|
||||
/// Queues pre-positioned glyphs to be processed by the next call of
|
||||
/// [`draw_queued`](struct.GlyphBrush.html#method.draw_queued). Can be
|
||||
/// called multiple times.
|
||||
#[inline]
|
||||
pub fn queue_pre_positioned(
|
||||
&mut self,
|
||||
glyphs: Vec<(PositionedGlyph<'font>, Color, FontId)>,
|
||||
bounds: Rect<f32>,
|
||||
z: f32,
|
||||
) {
|
||||
self.glyph_brush.queue_pre_positioned(glyphs, bounds, z)
|
||||
}
|
||||
|
||||
/// Retains the section in the cache as if it had been used in the last
|
||||
/// draw-frame.
|
||||
///
|
||||
/// Should not be necessary unless using multiple draws per frame with
|
||||
/// distinct transforms, see [caching behaviour](#caching-behaviour).
|
||||
#[inline]
|
||||
pub fn keep_cached_custom_layout<'a, S, G>(&mut self, section: S, custom_layout: &G)
|
||||
where
|
||||
S: Into<Cow<'a, VariedSection<'a>>>,
|
||||
G: GlyphPositioner,
|
||||
{
|
||||
self.glyph_brush
|
||||
.keep_cached_custom_layout(section, custom_layout)
|
||||
}
|
||||
|
||||
/// Retains the section in the cache as if it had been used in the last
|
||||
/// draw-frame.
|
||||
///
|
||||
/// Should not be necessary unless using multiple draws per frame with
|
||||
/// distinct transforms, see [caching behaviour](#caching-behaviour).
|
||||
#[inline]
|
||||
pub fn keep_cached<'a, S>(&mut self, section: S)
|
||||
where
|
||||
S: Into<Cow<'a, VariedSection<'a>>>,
|
||||
{
|
||||
self.glyph_brush.keep_cached(section)
|
||||
}
|
||||
|
||||
fn process_queued(&mut self, context: &glow::Context) {
|
||||
let pipeline = &mut self.pipeline;
|
||||
|
||||
let mut brush_action;
|
||||
|
||||
loop {
|
||||
brush_action = self.glyph_brush.process_queued(
|
||||
|rect, tex_data| {
|
||||
let offset = [rect.min.x as u16, rect.min.y as u16];
|
||||
let size = [rect.width() as u16, rect.height() as u16];
|
||||
|
||||
pipeline.update_cache(context, offset, size, tex_data);
|
||||
},
|
||||
Instance::from,
|
||||
);
|
||||
|
||||
match brush_action {
|
||||
Ok(_) => break,
|
||||
Err(BrushError::TextureTooSmall { suggested }) => {
|
||||
// TODO: Obtain max texture dimensions using `wgpu`
|
||||
// This is currently not possible I think. Ask!
|
||||
let max_image_dimension = 2048;
|
||||
|
||||
let (new_width, new_height) = if (suggested.0 > max_image_dimension
|
||||
|| suggested.1 > max_image_dimension)
|
||||
&& (self.glyph_brush.texture_dimensions().0 < max_image_dimension
|
||||
|| self.glyph_brush.texture_dimensions().1 < max_image_dimension)
|
||||
{
|
||||
(max_image_dimension, max_image_dimension)
|
||||
} else {
|
||||
suggested
|
||||
};
|
||||
|
||||
if log_enabled!(log::Level::Warn) {
|
||||
warn!(
|
||||
"Increasing glyph texture size {old:?} -> {new:?}. \
|
||||
Consider building with `.initial_cache_size({new:?})` to avoid \
|
||||
resizing",
|
||||
old = self.glyph_brush.texture_dimensions(),
|
||||
new = (new_width, new_height),
|
||||
);
|
||||
}
|
||||
|
||||
pipeline.increase_cache_size(context, new_width, new_height);
|
||||
self.glyph_brush.resize_texture(new_width, new_height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match brush_action.unwrap() {
|
||||
BrushAction::Draw(verts) => {
|
||||
self.pipeline.upload(context, &verts);
|
||||
}
|
||||
BrushAction::ReDraw => {}
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the available fonts.
|
||||
///
|
||||
/// The `FontId` corresponds to the index of the font data.
|
||||
#[inline]
|
||||
pub fn fonts(&self) -> &[Font<'_>] {
|
||||
self.glyph_brush.fonts()
|
||||
}
|
||||
|
||||
/// Adds an additional font to the one(s) initially added on build.
|
||||
///
|
||||
/// Returns a new [`FontId`](struct.FontId.html) to reference this font.
|
||||
pub fn add_font_bytes<'a: 'font, B: Into<SharedBytes<'a>>>(&mut self, font_data: B) -> FontId {
|
||||
self.glyph_brush.add_font_bytes(font_data)
|
||||
}
|
||||
|
||||
/// Adds an additional font to the one(s) initially added on build.
|
||||
///
|
||||
/// Returns a new [`FontId`](struct.FontId.html) to reference this font.
|
||||
pub fn add_font<'a: 'font>(&mut self, font_data: Font<'a>) -> FontId {
|
||||
self.glyph_brush.add_font(font_data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'font, H: BuildHasher> GlyphBrush<'font, H> {
|
||||
fn new(gl: &glow::Context, raw_builder: glyph_brush::GlyphBrushBuilder<'font, H>) -> Self {
|
||||
let glyph_brush = raw_builder.build();
|
||||
let (cache_width, cache_height) = glyph_brush.texture_dimensions();
|
||||
GlyphBrush {
|
||||
pipeline: Pipeline::new(gl, cache_width, cache_height),
|
||||
glyph_brush,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws all queued sections onto a render target.
|
||||
/// See [`queue`](struct.GlyphBrush.html#method.queue).
|
||||
///
|
||||
/// It __does not__ submit the encoder command buffer to the device queue.
|
||||
///
|
||||
/// Trims the cache, see [caching behaviour](#caching-behaviour).
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the provided `target` has a texture format that does not match
|
||||
/// the `render_format` provided on creation of the `GlyphBrush`.
|
||||
#[inline]
|
||||
pub fn draw_queued(
|
||||
&mut self,
|
||||
context: &glow::Context,
|
||||
target_width: u32,
|
||||
target_height: u32,
|
||||
) -> Result<(), String> {
|
||||
self.draw_queued_with_transform(
|
||||
context,
|
||||
orthographic_projection(target_width, target_height),
|
||||
)
|
||||
}
|
||||
|
||||
/// Draws all queued sections onto a render target, applying a position
|
||||
/// transform (e.g. a projection).
|
||||
/// See [`queue`](struct.GlyphBrush.html#method.queue).
|
||||
///
|
||||
/// It __does not__ submit the encoder command buffer to the device queue.
|
||||
///
|
||||
/// Trims the cache, see [caching behaviour](#caching-behaviour).
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the provided `target` has a texture format that does not match
|
||||
/// the `render_format` provided on creation of the `GlyphBrush`.
|
||||
#[inline]
|
||||
pub fn draw_queued_with_transform(
|
||||
&mut self,
|
||||
context: &glow::Context,
|
||||
transform: [f32; 16],
|
||||
) -> Result<(), String> {
|
||||
self.process_queued(context);
|
||||
self.pipeline.draw(context, transform, None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Draws all queued sections onto a render target, applying a position
|
||||
/// transform (e.g. a projection) and a scissoring region.
|
||||
/// See [`queue`](struct.GlyphBrush.html#method.queue).
|
||||
///
|
||||
/// It __does not__ submit the encoder command buffer to the device queue.
|
||||
///
|
||||
/// Trims the cache, see [caching behaviour](#caching-behaviour).
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the provided `target` has a texture format that does not match
|
||||
/// the `render_format` provided on creation of the `GlyphBrush`.
|
||||
#[inline]
|
||||
pub fn draw_queued_with_transform_and_scissoring(
|
||||
&mut self,
|
||||
context: &glow::Context,
|
||||
transform: [f32; 16],
|
||||
region: Region,
|
||||
) -> Result<(), String> {
|
||||
self.process_queued(context);
|
||||
self.pipeline.draw(context, transform, Some(region));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to generate a generate a transform matrix.
|
||||
pub fn orthographic_projection(width: u32, height: u32) -> [f32; 16] {
|
||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
||||
[
|
||||
2.0 / width as f32, 0.0, 0.0, 0.0,
|
||||
0.0, -2.0 / height as f32, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
-1.0, 1.0, 0.0, 1.0,
|
||||
]
|
||||
}
|
||||
|
||||
impl<'font, H: BuildHasher> GlyphCruncher<'font> for GlyphBrush<'font, H> {
|
||||
#[inline]
|
||||
fn pixel_bounds_custom_layout<'a, S, L>(
|
||||
&mut self,
|
||||
section: S,
|
||||
custom_layout: &L,
|
||||
) -> Option<Rect<i32>>
|
||||
where
|
||||
L: GlyphPositioner + std::hash::Hash,
|
||||
S: Into<Cow<'a, VariedSection<'a>>>,
|
||||
{
|
||||
self.glyph_brush
|
||||
.pixel_bounds_custom_layout(section, custom_layout)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn glyphs_custom_layout<'a, 'b, S, L>(
|
||||
&'b mut self,
|
||||
section: S,
|
||||
custom_layout: &L,
|
||||
) -> PositionedGlyphIter<'b, 'font>
|
||||
where
|
||||
L: GlyphPositioner + std::hash::Hash,
|
||||
S: Into<Cow<'a, VariedSection<'a>>>,
|
||||
{
|
||||
self.glyph_brush
|
||||
.glyphs_custom_layout(section, custom_layout)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn fonts(&self) -> &[Font<'font>] {
|
||||
self.glyph_brush.fonts()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> std::fmt::Debug for GlyphBrush<'_, H> {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "GlyphBrush")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
mod cache;
|
||||
|
||||
use crate::Region;
|
||||
use cache::Cache;
|
||||
|
||||
use glow::HasContext;
|
||||
use glyph_brush::rusttype::{point, Rect};
|
||||
|
||||
pub struct Pipeline {
|
||||
sampler: <glow::Context as HasContext>::Sampler,
|
||||
program: <glow::Context as HasContext>::Program,
|
||||
instances: <glow::Context as HasContext>::Buffer,
|
||||
cache: Cache,
|
||||
current_instances: usize,
|
||||
supported_instances: usize,
|
||||
current_transform: [f32; 16],
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
gl: &glow::Context,
|
||||
cache_width: u32,
|
||||
cache_height: u32,
|
||||
) -> Pipeline {
|
||||
let sampler =
|
||||
unsafe { gl.create_sampler().expect("Create glyph sampler") };
|
||||
|
||||
let cache = Cache::new(gl, cache_width, cache_height);
|
||||
|
||||
let program = unsafe {
|
||||
create_program(
|
||||
gl,
|
||||
&[
|
||||
(glow::VERTEX_SHADER, include_str!("./shader/vertex.vert")),
|
||||
(
|
||||
glow::FRAGMENT_SHADER,
|
||||
include_str!("./shader/fragment.frag"),
|
||||
),
|
||||
],
|
||||
)
|
||||
};
|
||||
|
||||
let instances =
|
||||
unsafe { gl.create_buffer().expect("Create instance buffer") };
|
||||
|
||||
Pipeline {
|
||||
sampler,
|
||||
program,
|
||||
cache,
|
||||
instances,
|
||||
current_instances: 0,
|
||||
supported_instances: Instance::INITIAL_AMOUNT,
|
||||
current_transform: [0.0; 16],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
gl: &glow::Context,
|
||||
transform: [f32; 16],
|
||||
region: Option<Region>,
|
||||
) {
|
||||
}
|
||||
|
||||
pub fn update_cache(
|
||||
&mut self,
|
||||
gl: &glow::Context,
|
||||
offset: [u16; 2],
|
||||
size: [u16; 2],
|
||||
data: &[u8],
|
||||
) {
|
||||
self.cache.update(gl, offset, size, data);
|
||||
}
|
||||
|
||||
pub fn increase_cache_size(
|
||||
&mut self,
|
||||
gl: &glow::Context,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) {
|
||||
self.cache = Cache::new(gl, width, height);
|
||||
}
|
||||
|
||||
pub fn upload(&mut self, gl: &glow::Context, instances: &[Instance]) {
|
||||
if instances.is_empty() {
|
||||
self.current_instances = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if instances.len() > self.supported_instances {
|
||||
// TODO
|
||||
|
||||
self.supported_instances = instances.len();
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
||||
self.current_instances = instances.len();
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers
|
||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
||||
const IDENTITY_MATRIX: [f32; 16] = [
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Instance {
|
||||
left_top: [f32; 3],
|
||||
right_bottom: [f32; 2],
|
||||
tex_left_top: [f32; 2],
|
||||
tex_right_bottom: [f32; 2],
|
||||
color: [f32; 4],
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Zeroable for Instance {}
|
||||
unsafe impl bytemuck::Pod for Instance {}
|
||||
|
||||
impl Instance {
|
||||
const INITIAL_AMOUNT: usize = 50_000;
|
||||
}
|
||||
|
||||
impl From<glyph_brush::GlyphVertex> for Instance {
|
||||
#[inline]
|
||||
fn from(vertex: glyph_brush::GlyphVertex) -> Instance {
|
||||
let glyph_brush::GlyphVertex {
|
||||
mut tex_coords,
|
||||
pixel_coords,
|
||||
bounds,
|
||||
color,
|
||||
z,
|
||||
} = vertex;
|
||||
|
||||
let gl_bounds = bounds;
|
||||
|
||||
let mut gl_rect = Rect {
|
||||
min: point(pixel_coords.min.x as f32, pixel_coords.min.y as f32),
|
||||
max: point(pixel_coords.max.x as f32, pixel_coords.max.y as f32),
|
||||
};
|
||||
|
||||
// handle overlapping bounds, modify uv_rect to preserve texture aspect
|
||||
if gl_rect.max.x > gl_bounds.max.x {
|
||||
let old_width = gl_rect.width();
|
||||
gl_rect.max.x = gl_bounds.max.x;
|
||||
tex_coords.max.x = tex_coords.min.x
|
||||
+ tex_coords.width() * gl_rect.width() / old_width;
|
||||
}
|
||||
|
||||
if gl_rect.min.x < gl_bounds.min.x {
|
||||
let old_width = gl_rect.width();
|
||||
gl_rect.min.x = gl_bounds.min.x;
|
||||
tex_coords.min.x = tex_coords.max.x
|
||||
- tex_coords.width() * gl_rect.width() / old_width;
|
||||
}
|
||||
|
||||
if gl_rect.max.y > gl_bounds.max.y {
|
||||
let old_height = gl_rect.height();
|
||||
gl_rect.max.y = gl_bounds.max.y;
|
||||
tex_coords.max.y = tex_coords.min.y
|
||||
+ tex_coords.height() * gl_rect.height() / old_height;
|
||||
}
|
||||
|
||||
if gl_rect.min.y < gl_bounds.min.y {
|
||||
let old_height = gl_rect.height();
|
||||
gl_rect.min.y = gl_bounds.min.y;
|
||||
tex_coords.min.y = tex_coords.max.y
|
||||
- tex_coords.height() * gl_rect.height() / old_height;
|
||||
}
|
||||
|
||||
Instance {
|
||||
left_top: [gl_rect.min.x, gl_rect.max.y, z],
|
||||
right_bottom: [gl_rect.max.x, gl_rect.min.y],
|
||||
tex_left_top: [tex_coords.min.x, tex_coords.max.y],
|
||||
tex_right_bottom: [tex_coords.max.x, tex_coords.min.y],
|
||||
color,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn create_program(
|
||||
gl: &glow::Context,
|
||||
shader_sources: &[(u32, &str)],
|
||||
) -> <glow::Context as HasContext>::Program {
|
||||
let program = gl.create_program().expect("Cannot create program");
|
||||
|
||||
let mut shaders = Vec::with_capacity(shader_sources.len());
|
||||
|
||||
for (shader_type, shader_source) in shader_sources.iter() {
|
||||
let shader = gl
|
||||
.create_shader(*shader_type)
|
||||
.expect("Cannot create shader");
|
||||
|
||||
gl.shader_source(shader, shader_source);
|
||||
gl.compile_shader(shader);
|
||||
|
||||
if !gl.get_shader_compile_status(shader) {
|
||||
panic!(gl.get_shader_info_log(shader));
|
||||
}
|
||||
|
||||
gl.attach_shader(program, shader);
|
||||
|
||||
shaders.push(shader);
|
||||
}
|
||||
|
||||
gl.link_program(program);
|
||||
if !gl.get_program_link_status(program) {
|
||||
panic!(gl.get_program_info_log(program));
|
||||
}
|
||||
|
||||
for shader in shaders {
|
||||
gl.detach_shader(program, shader);
|
||||
gl.delete_shader(shader);
|
||||
}
|
||||
|
||||
program
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
use glow::HasContext;
|
||||
|
||||
pub struct Cache {
|
||||
texture: <glow::Context as HasContext>::Texture,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn new(gl: &glow::Context, width: u32, height: u32) -> Cache {
|
||||
let texture = unsafe {
|
||||
let handle = gl.create_texture().expect("Create glyph cache texture");
|
||||
|
||||
gl.tex_storage_2d(handle, 1, glow::R8, width as i32, height as i32);
|
||||
|
||||
handle
|
||||
};
|
||||
|
||||
Cache { texture }
|
||||
}
|
||||
|
||||
pub fn update(&self, gl: &glow::Context, offset: [u16; 2], size: [u16; 2], data: &[u8]) {
|
||||
// TODO
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
/// A region of the screen.
|
||||
pub struct Region {
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 1) uniform sampler2D font_sampler;
|
||||
|
||||
in vec2 f_tex_pos;
|
||||
in vec4 f_color;
|
||||
|
||||
out vec4 Target0;
|
||||
|
||||
void main() {
|
||||
float alpha = texture(font_sampler, f_tex_pos).r;
|
||||
|
||||
if (alpha <= 0.0) {
|
||||
discard;
|
||||
}
|
||||
|
||||
Target0 = f_color * vec4(1.0, 1.0, 1.0, alpha);
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) uniform mat4 transform;
|
||||
|
||||
in vec3 left_top;
|
||||
in vec2 right_bottom;
|
||||
in vec2 tex_left_top;
|
||||
in vec2 tex_right_bottom;
|
||||
in vec4 color;
|
||||
|
||||
out vec2 f_tex_pos;
|
||||
out vec4 f_color;
|
||||
|
||||
// generate positional data based on vertex ID
|
||||
void main() {
|
||||
vec2 pos = vec2(0.0);
|
||||
float left = left_top.x;
|
||||
float right = right_bottom.x;
|
||||
float top = left_top.y;
|
||||
float bottom = right_bottom.y;
|
||||
|
||||
switch (gl_VertexID) {
|
||||
case 0:
|
||||
pos = vec2(left, top);
|
||||
f_tex_pos = tex_left_top;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
pos = vec2(right, top);
|
||||
f_tex_pos = vec2(tex_right_bottom.x, tex_left_top.y);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
pos = vec2(left, bottom);
|
||||
f_tex_pos = vec2(tex_left_top.x, tex_right_bottom.y);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
pos = vec2(right, bottom);
|
||||
f_tex_pos = tex_right_bottom;
|
||||
break;
|
||||
}
|
||||
|
||||
f_color = color;
|
||||
gl_Position = transform * vec4(pos, left_top.z, 1.0);
|
||||
}
|
Loading…
Reference in New Issue