Kaynağa Gözat

Implemented Text Manager

raster
Bergmann89 4 yıl önce
ebeveyn
işleme
46a8bc4b13
23 değiştirilmiş dosya ile 1055 ekleme ve 109 silme
  1. +139
    -2
      Cargo.lock
  2. +4
    -1
      glc/src/error.rs
  3. +48
    -0
      glc/src/matrix.rs
  4. +4
    -0
      glc/src/texture.rs
  5. +16
    -0
      glc/src/vector.rs
  6. +20
    -1
      glc/src/vertex_array.rs
  7. +2
    -0
      space-crush/Cargo.toml
  8. BIN
     
  9. +2
    -1
      space-crush/resources/shader/noise.frag
  10. +3
    -2
      space-crush/resources/shader/quad.vert
  11. +20
    -0
      space-crush/resources/shader/text.frag
  12. +52
    -0
      space-crush/resources/shader/text.vert
  13. +8
    -86
      space-crush/src/app/misc/camera.rs
  14. +10
    -2
      space-crush/src/app/misc/frame_counter.rs
  15. +1
    -0
      space-crush/src/app/misc/mod.rs
  16. +643
    -0
      space-crush/src/app/misc/text.rs
  17. +1
    -0
      space-crush/src/app/misc/vfs.rs
  18. +4
    -0
      space-crush/src/app/misc/window.rs
  19. +6
    -3
      space-crush/src/app/mod.rs
  20. +52
    -0
      space-crush/src/app/render/debug.rs
  21. +3
    -9
      space-crush/src/app/render/init.rs
  22. +2
    -0
      space-crush/src/app/render/mod.rs
  23. +15
    -2
      space-crush/src/error.rs

+ 139
- 2
Cargo.lock Dosyayı Görüntüle

@@ -1,5 +1,15 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "ab_glyph"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26a685fe66654266f321a8b572660953f4df36a2135706503a4c89981d76e1a2"
dependencies = [
"ab_glyph_rasterizer",
"owned_ttf_parser 0.8.0",
]

[[package]]
name = "ab_glyph_rasterizer"
version = "0.1.4"
@@ -46,6 +56,15 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407"

[[package]]
name = "approx"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278"
dependencies = [
"num-traits",
]

[[package]]
name = "arrayvec"
version = "0.5.2"
@@ -709,6 +728,45 @@ dependencies = [
"gl_generator",
]

[[package]]
name = "glyph_brush"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afd3e2cfd503a5218dd56172a8bf7c8655a4a7cf745737c606a6edfeea1b343f"
dependencies = [
"glyph_brush_draw_cache",
"glyph_brush_layout",
"log",
"ordered-float 1.1.0",
"rustc-hash",
"twox-hash",
]

[[package]]
name = "glyph_brush_draw_cache"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cef969a091be5565c2c10b31fd2f115cbeed9f783a27c96ae240ff8ceee067c"
dependencies = [
"ab_glyph",
"crossbeam-channel",
"crossbeam-deque",
"linked-hash-map",
"rayon",
"rustc-hash",
]

[[package]]
name = "glyph_brush_layout"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10bc06d530bf20c1902f1b02799ab7372ff43f6119770c49b0bc3f21bd148820"
dependencies = [
"ab_glyph",
"approx",
"xi-unicode",
]

[[package]]
name = "hashbrown"
version = "0.7.2"
@@ -827,6 +885,12 @@ dependencies = [
"winapi 0.3.9",
]

[[package]]
name = "linked-hash-map"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"

[[package]]
name = "lock_api"
version = "0.4.2"
@@ -1041,6 +1105,15 @@ dependencies = [
"version_check",
]

[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]

[[package]]
name = "num_cpus"
version = "1.13.0"
@@ -1088,6 +1161,24 @@ version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"

[[package]]
name = "ordered-float"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3741934be594d77de1c8461ebcbbe866f585ea616a9753aa78f2bdc69f0e4579"
dependencies = [
"num-traits",
]

[[package]]
name = "ordered-float"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fe9037165d7023b1228bc4ae9a2fa1a2b0095eca6c2998c624723dfd01314a5"
dependencies = [
"num-traits",
]

[[package]]
name = "osmesa-sys"
version = "0.1.2"
@@ -1103,7 +1194,16 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3"
dependencies = [
"ttf-parser",
"ttf-parser 0.6.2",
]

[[package]]
name = "owned_ttf_parser"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb477c7fd2a3a6e04e1dc6ca2e4e9b04f2df702021dc5a5d1cf078c587dc59f7"
dependencies = [
"ttf-parser 0.8.3",
]

[[package]]
@@ -1320,6 +1420,12 @@ version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"

[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"

[[package]]
name = "rusttype"
version = "0.9.2"
@@ -1327,7 +1433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc7c727aded0be18c5b80c1640eae0ac8e396abf6fa8477d96cb37d18ee5ec59"
dependencies = [
"ab_glyph_rasterizer",
"owned_ttf_parser",
"owned_ttf_parser 0.6.0",
]

[[package]]
@@ -1428,8 +1534,10 @@ dependencies = [
"gl",
"glc",
"glutin",
"glyph_brush",
"log",
"num_cpus",
"ordered-float 2.0.0",
"rand",
"shrev",
"specs",
@@ -1455,6 +1563,12 @@ dependencies = [
"tuple_utils",
]

[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"

[[package]]
name = "strsim"
version = "0.9.3"
@@ -1535,12 +1649,29 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc"

[[package]]
name = "ttf-parser"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7622061403fd00f0820df288e5a580e87d3ce15a1c4313c59fd1ffb77129903f"

[[package]]
name = "tuple_utils"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44834418e2c5b16f47bedf35c28e148db099187dd5feee6367fb2525863af4f1"

[[package]]
name = "twox-hash"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59"
dependencies = [
"cfg-if 0.1.10",
"rand",
"static_assertions",
]

[[package]]
name = "tynm"
version = "0.1.6"
@@ -1793,6 +1924,12 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57"

[[package]]
name = "xi-unicode"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a"

[[package]]
name = "xml-rs"
version = "0.8.3"


+ 4
- 1
glc/src/error.rs Dosyayı Görüntüle

@@ -24,9 +24,12 @@ pub enum Error {
#[error("Error while linking shader program: {0}")]
ShaderLink(String),

#[error("Vertex Array Index is already in use: {0}!")]
#[error("Vertex Array: Index is already in use: {0}!")]
VertexArrayIndexAlreadyInUse(gl::GLuint),

#[error("Vertex Array: Expected pointer!")]
VertexArrayExpectedPointer,

#[error("Invalid Parameter!")]
InvalidParameter,



+ 48
- 0
glc/src/matrix.rs Dosyayı Görüntüle

@@ -419,6 +419,54 @@ where
}
}

impl<T> Matrix4<T>
where
T: Element<AsFloat = T>,
{
#[inline]
pub fn ortho(left: T, right: T, bottom: T, top: T, near: T, far: T) -> Self {
let one = T::one();
let two = one + one;
let zero = T::zero();

Matrix4::new(
Vector4::new(two / (right - left), zero, zero, zero),
Vector4::new(zero, two / (top - bottom), zero, zero),
Vector4::new(zero, zero, -zero / (far - near), zero),
Vector4::new(
-(right + left) / (right - left),
-(top + bottom) / (top - bottom),
-(far + near) / (far - near),
one,
),
)
}

#[inline]
pub fn perspective(fov: Angle<T>, ratio: T, near: T, far: T) -> Self {
let one = T::one();
let two = one + one;
let zero = T::zero();

let top = near * fov.into_rad().into_inner().tan();
let bottom = -top;
let right = ratio * top;
let left = -right;

Matrix4::new(
Vector4::new(two * near / (right - left), zero, zero, zero),
Vector4::new(zero, two * near / (top - bottom), zero, zero),
Vector4::new(
(right + left) / (right - left),
(top + bottom) / (top - bottom),
-(far + near) / (far - near),
-one,
),
Vector4::new(zero, zero, -two * far * near / (far - near), zero),
)
}
}

impl<T> Mul<Matrix4<T>> for Matrix4<T>
where
T: Element,


+ 4
- 0
glc/src/texture.rs Dosyayı Görüntüle

@@ -38,6 +38,10 @@ impl Texture {
})
}

pub fn id(&self) -> gl::GLuint {
self.id
}

pub fn upload(&mut self, data: &Data, generate_mipmap: bool) -> Result<(), Error> {
let info = data.format.info();



+ 16
- 0
glc/src/vector.rs Dosyayı Görüntüle

@@ -494,6 +494,7 @@ pub trait Element:
fn sin(self) -> Self;
fn cos(self) -> Self;
fn sqrt(self) -> Self;
fn tan(self) -> Self::AsFloat;
fn acos(self) -> Self::AsFloat;
fn atan(self) -> Self::AsFloat;
fn atan2(a: Self, b: Self) -> Self::AsFloat;
@@ -530,6 +531,11 @@ impl Element for gl::GLfloat {
f32::sqrt(self)
}

#[inline]
fn tan(self) -> Self::AsFloat {
f32::tan(self)
}

#[inline]
fn acos(self) -> Self::AsFloat {
f32::acos(self)
@@ -589,6 +595,11 @@ impl Element for gl::GLdouble {
f64::sqrt(self)
}

#[inline]
fn tan(self) -> Self::AsFloat {
f64::tan(self)
}

#[inline]
fn acos(self) -> Self::AsFloat {
f64::acos(self)
@@ -648,6 +659,11 @@ impl Element for gl::GLint {
f64::sqrt(self as f64) as i32
}

#[inline]
fn tan(self) -> Self::AsFloat {
f32::tan(self as f32)
}

#[inline]
fn acos(self) -> Self::AsFloat {
f32::acos(self as f32)


+ 20
- 1
glc/src/vertex_array.rs Dosyayı Görüntüle

@@ -77,8 +77,9 @@ impl Builder {
let guard = BindGuard::new(&binding.buffer);

for pointer in binding.pointers {
Error::checked(|| gl::enable_vertex_attrib_array(pointer.index))?;

Error::checked(|| {
gl::enable_vertex_attrib_array(pointer.index);
gl::vertex_attrib_pointer(
pointer.index,
pointer.size,
@@ -92,6 +93,10 @@ impl Builder {
pointer.offset as *const _,
)
})?;

if let Some(divisor) = pointer.divisor {
Error::checked(|| gl::vertex_attrib_divisor(pointer.index, divisor))?;
}
}

drop(guard);
@@ -142,6 +147,7 @@ impl BindingBuilder {
normalize,
stride,
offset,
divisor: None,
};

self.builder
@@ -154,6 +160,18 @@ impl BindingBuilder {
Ok(self)
}

pub fn vertex_attrib_divisor(mut self, divisor: gl::GLuint) -> Result<Self, Error> {
let binding = self.builder.bindings.last_mut().unwrap();
let pointer = binding
.pointers
.last_mut()
.ok_or(Error::VertexArrayExpectedPointer)?;

pointer.divisor = Some(divisor);

Ok(self)
}

pub fn build(self) -> Result<VertexArray, Error> {
self.builder.build()
}
@@ -214,4 +232,5 @@ struct Pointer {
normalize: bool,
stride: gl::GLsizei,
offset: gl::GLsizei,
divisor: Option<gl::GLuint>,
}

+ 2
- 0
space-crush/Cargo.toml Dosyayı Görüntüle

@@ -10,8 +10,10 @@ futures = "0.3"
gl = { version = "0.1", features = [ "use_log_crate" ] }
glc = "0.1"
glutin = "0.25"
glyph_brush = "0.7"
log = { version = "0.4", features = [ "max_level_trace", "release_max_level_warn" ] }
num_cpus = "1.13"
ordered-float = "2.0"
rand = "0.7"
shrev = "1.1"
specs = "0.16"



+ 2
- 1
space-crush/resources/shader/noise.frag Dosyayı Görüntüle

@@ -3,7 +3,8 @@
in vec4 gl_FragCoord;

layout (std140, binding = 0) uniform Camera {
mat4 projection;
mat4 proj_world;
mat4 proj_ui;
mat4 view;
} camera;



+ 3
- 2
space-crush/resources/shader/quad.vert Dosyayı Görüntüle

@@ -3,7 +3,8 @@
layout (location = 0) in vec3 position;

layout (std140, binding = 0) uniform Camera {
mat4 projection;
mat4 proj_world;
mat4 proj_ui;
mat4 view;
} camera;

@@ -15,5 +16,5 @@ out FragmentData {

void main() {
data.tex_coords = position.xy + vec2(0.5);
gl_Position = camera.projection * camera.view * model * vec4(position, 1.0);
gl_Position = camera.proj_world * camera.view * model * vec4(position, 1.0);
}

+ 20
- 0
space-crush/resources/shader/text.frag Dosyayı Görüntüle

@@ -0,0 +1,20 @@
#version 450 core

in FragmentData {
vec2 tex_coord;
vec4 color;
} data;

uniform sampler2D tex;

out vec4 color;

void main() {
float alpha = texture(tex, data.tex_coord).r;

if (alpha <= 0.0) {
discard;
}

color = data.color * vec4(1.0, 1.0, 1.0, alpha);
}

+ 52
- 0
space-crush/resources/shader/text.vert Dosyayı Görüntüle

@@ -0,0 +1,52 @@
#version 450 core

layout (location = 0) in vec2 pos_min;
layout (location = 1) in vec2 pos_max;
layout (location = 2) in vec2 tex_min;
layout (location = 3) in vec2 tex_max;
layout (location = 4) in vec4 color;

layout (std140, binding = 0) uniform Camera {
mat4 proj_world;
mat4 proj_ui;
mat4 view;
} camera;

layout (location = 1) uniform mat4 model;

out FragmentData {
vec2 tex_coord;
vec4 color;
} data;

void main() {
vec2 position = vec2(0.0);
vec2 tex_coord = vec2(0.0);

switch (gl_VertexID) {
case 0:
position = vec2(pos_min.x, pos_max.y);
tex_coord = vec2(tex_min.x, tex_max.y);
break;

case 1:
position = vec2(pos_min.x, pos_min.y);
tex_coord = vec2(tex_min.x, tex_min.y);
break;

case 2:
position = vec2(pos_max.x, pos_max.y);
tex_coord = vec2(tex_max.x, tex_max.y);
break;

case 3:
position = vec2(pos_max.x, pos_min.y);
tex_coord = vec2(tex_max.x, tex_min.y);
break;
}

gl_Position = camera.proj_ui * vec4(position, 0.0, 1.0);

data.tex_coord = tex_coord;
data.color = color;
}

+ 8
- 86
space-crush/src/app/misc/camera.rs Dosyayı Görüntüle

@@ -3,8 +3,7 @@
use glc::{
array_buffer::{ArrayBuffer, Target, Usage},
error::Error,
matrix::{Angle, Matrix4f},
vector::Vector4f,
matrix::Matrix4f,
};

pub struct Camera {
@@ -17,7 +16,8 @@ impl Camera {
buffer.buffer_data(
Usage::StaticDraw,
&[Data {
projection: Matrix4f::identity(),
proj_world: Matrix4f::identity(),
proj_ui: Matrix4f::identity(),
view: Matrix4f::identity(),
}],
)?;
@@ -25,94 +25,15 @@ impl Camera {
Ok(Self { buffer })
}

pub fn ortho(
&mut self,
left: f32,
right: f32,
bottom: f32,
top: f32,
near: f32,
far: f32,
) -> Result<(), Error> {
pub fn resize(&mut self, w: f32, h: f32) -> Result<(), Error> {
let mut data = self.buffer.map_mut::<Data>(true)?;
data[0].projection = Matrix4f::new(
Vector4f::new(2.0 / (right - left), 0.0, 0.0, 0.0),
Vector4f::new(0.0, 2.0 / (top - bottom), 0.0, 0.0),
Vector4f::new(0.0, 0.0, -2.0 / (far - near), 0.0),
Vector4f::new(
-(right + left) / (right - left),
-(top + bottom) / (top - bottom),
-(far + near) / (far - near),
1.0,
),
);

Ok(())
}

pub fn perspective(
&mut self,
fov: Angle<f32>,
ratio: f32,
near: f32,
far: f32,
) -> Result<(), Error> {
let top = near * fov.into_rad().into_inner().tan();
let bottom = -top;
let right = ratio * top;
let left = -right;

let mut data = self.buffer.map_mut::<Data>(true)?;
data[0].projection = Matrix4f::new(
Vector4f::new(2.0 * near / (right - left), 0.0, 0.0, 0.0),
Vector4f::new(0.0, 2.0 * near / (top - bottom), 0.0, 0.0),
Vector4f::new(
(right + left) / (right - left),
(top + bottom) / (top - bottom),
-(far + near) / (far - near),
-1.0,
),
Vector4f::new(0.0, 0.0, -2.0 * far * near / (far - near), 0.0),
);

Ok(())
}

pub fn set_projection(&mut self, m: Matrix4f) -> Result<(), Error> {
let mut data = self.buffer.map_mut::<Data>(false)?;
data[0].projection = m;
data[0].proj_world = Matrix4f::ortho(-w / 2.0, w / 2.0, -h / 2.0, h / 2.0, -100.0, 100.0);
data[0].proj_ui = Matrix4f::ortho(0.0, w, h, 0.0, -100.0, 100.0);

Ok(())
}

pub fn set_view(&mut self, m: Matrix4f) -> Result<(), Error> {
let mut data = self.buffer.map_mut::<Data>(false)?;
data[0].view = m;

Ok(())
}

pub fn update_view(&mut self, m: Matrix4f) -> Result<(), Error> {
let mut data = self.buffer.map_mut::<Data>(false)?;
data[0].view = data[0].view * m;

Ok(())
}

pub fn projection(&self) -> Result<Matrix4f, Error> {
let data = self.buffer.map::<Data>()?;
let ret = data[0].view;

Ok(ret)
}

pub fn view(&self) -> Result<Matrix4f, Error> {
let data = self.buffer.map::<Data>()?;
let ret = data[0].view;

Ok(ret)
}

pub fn bind(&self, index: gl::GLuint) -> Result<(), Error> {
Error::checked(|| self.buffer.bind_buffer_base(index))
}
@@ -120,6 +41,7 @@ impl Camera {

#[repr(C, packed)]
struct Data {
projection: Matrix4f,
proj_world: Matrix4f,
proj_ui: Matrix4f,
view: Matrix4f,
}

+ 10
- 2
space-crush/src/app/misc/frame_counter.rs Dosyayı Görüntüle

@@ -9,9 +9,14 @@ pub struct FrameCounter {
count: usize,
fps: usize,
delta: f32,
updated: bool,
}

impl FrameCounter {
pub fn updated(&self) -> bool {
self.updated
}

#[inline]
pub fn fps(&self) -> usize {
self.fps
@@ -31,7 +36,7 @@ impl FrameCounter {
self.time += self.delta;
self.count += 1;

if self.time >= 2.0 {
self.updated = if self.time >= 2.0 {
self.time = 0.0;
self.fps = 0;
self.count = 0;
@@ -44,7 +49,9 @@ impl FrameCounter {
true
} else {
false
}
};

self.updated
}
}

@@ -56,6 +63,7 @@ impl Default for FrameCounter {
count: 0,
fps: 0,
delta: 0.0,
updated: false,
}
}
}

+ 1
- 0
space-crush/src/app/misc/mod.rs Dosyayı Görüntüle

@@ -2,6 +2,7 @@ pub mod camera;
pub mod events;
pub mod frame_counter;
pub mod geometry;
pub mod text;
pub mod vfs;
pub mod window;



+ 643
- 0
space-crush/src/app/misc/text.rs Dosyayı Görüntüle

@@ -0,0 +1,643 @@
#![allow(dead_code)]

use std::cell::RefCell;
use std::cmp::PartialEq;
use std::collections::HashMap;
use std::fmt::Display;
use std::hash::{Hash, Hasher};
use std::mem::size_of;
use std::ptr::null;
use std::rc::{Rc, Weak};

use glc::{
array_buffer::{ArrayBuffer, Target, Usage},
error::Error as GlcError,
misc::BindGuard,
shader::{Program, Type},
texture::{FilterMag, FilterMin, Target as TextureTarget, Texture, Wrap},
vector::{Vector2f, Vector4f},
vertex_array::{DataType, VertexArray},
};
use glyph_brush::{
ab_glyph::{FontArc, FontVec},
BrushAction, BrushError, FontId, GlyphBrush, GlyphBrushBuilder, GlyphVertex, OwnedSection,
OwnedText, Rectangle,
};
use log::warn;
use ordered_float::OrderedFloat;

use crate::Error;

use super::{load_program, vfs::Vfs};

/* TextManager */

#[derive(Clone)]
pub struct TextManager(Rc<RefCell<TextManagerInner>>);

struct TextManagerInner {
vfs: Vfs,
program: Rc<Program>,
fonts: HashMap<String, FontArc>,
}

impl TextManager {
pub fn new(vfs: &Vfs) -> Result<Self, Error> {
let vfs = vfs.clone();
let program = load_program(
&vfs,
vec![
(Type::Vertex, "resources/shader/text.vert"),
(Type::Fragment, "resources/shader/text.frag"),
],
)?;
let program = Rc::new(program);

Ok(Self(Rc::new(RefCell::new(TextManagerInner {
vfs,
program,
fonts: HashMap::new(),
}))))
}

pub fn create_cache(&self) -> Result<TextCache, Error> {
TextCache::new(self)
}

pub fn font_by_name(&self, name: &str) -> Result<FontArc, Error> {
if let Some(font) = self.0.borrow().fonts.get(name) {
return Ok(font.clone());
}

let path = format!("resources/fonts/{}", name);
self.font_by_path(&path)
}

pub fn font_by_path(&self, path: &str) -> Result<FontArc, Error> {
if let Some(font) = self.0.borrow().fonts.get(path) {
return Ok(font.clone());
}

let path = self.0.borrow().vfs.join(path)?;

let mut data = Vec::new();
path.open_file()?.read_to_end(&mut data)?;

let font = FontVec::try_from_vec(data)?;
let font = FontArc::new(font);

{
let mut this = self.0.borrow_mut();
this.fonts.insert(path.filename(), font.clone());
this.fonts.insert(path.as_str().to_owned(), font.clone());
}

Ok(font)
}
}

/* TextCache */

#[derive(Clone)]
pub struct TextCache(Rc<RefCell<TextCacheInner>>);

pub struct TextCacheInner {
manager: TextManager,
fonts: HashMap<String, FontId>,
glyphs: Option<GlyphBrush<Vertex, Extra>>,
texture: Rc<RefCell<Texture>>,
texts: HashMap<usize, Weak<RefCell<TextInner>>>,
text_ids: Vec<usize>,
next_text_id: usize,
}

impl TextCache {
fn new(manager: &TextManager) -> Result<Self, Error> {
let mut texture = Texture::new(TextureTarget::Texture2D)?;
texture.set_filter(FilterMin::Linear, FilterMag::Linear)?;
texture.set_wrap(Wrap::ClampToEdge, Wrap::ClampToEdge, Wrap::ClampToEdge)?;

Ok(Self(Rc::new(RefCell::new(TextCacheInner {
manager: manager.clone(),
fonts: HashMap::new(),
glyphs: None,
texture: Rc::new(RefCell::new(texture)),
texts: HashMap::new(),
text_ids: Vec::new(),
next_text_id: 0,
}))))
}

pub fn new_text(&self) -> TextBuilder {
TextBuilder::new(self)
}

fn font_by_name(&self, name: &str) -> Result<FontId, Error> {
let mut inner = self.0.borrow_mut();

if let Some(id) = inner.fonts.get(name) {
return Ok(*id);
}

let font = inner.manager.font_by_name(name)?;

inner.add_font(font)
}

fn font_by_path(&self, path: &str) -> Result<FontId, Error> {
let mut inner = self.0.borrow_mut();

if let Some(id) = inner.fonts.get(path) {
return Ok(*id);
}

let font = inner.manager.font_by_path(path)?;

inner.add_font(font)
}

fn next_text_id(&self) -> usize {
let mut inner = self.0.borrow_mut();

if let Some(id) = inner.text_ids.pop() {
return id;
}

let ret = inner.next_text_id;
inner.next_text_id += 1;

ret
}

fn add_text(&self, text: Weak<RefCell<TextInner>>) -> Result<(), Error> {
let text_id = self.next_text_id();
for section in &mut text.upgrade().unwrap().borrow_mut().sections {
for t in &mut section.text {
t.extra.text_id = text_id;
}
}

self.0.borrow_mut().texts.insert(text_id, text);

self.update()
}

fn update(&self) -> Result<(), Error> {
let mut inner = self.0.borrow_mut();
let inner = &mut *inner;

/* remove droped texts */
let texts = &mut inner.texts;
let text_ids = &mut inner.text_ids;
texts.retain(|id, t| {
if t.strong_count() > 0 {
true
} else {
text_ids.push(*id);

false
}
});

/* add sections to glyph queue */
let glyphs = inner.glyphs.as_mut().unwrap();
for text in texts.values_mut() {
for section in text.upgrade().unwrap().borrow_mut().sections.iter_mut() {
glyphs.queue(section.to_borrowed());
}
}

/* process glyph queue */
let action = loop {
let glyphs = inner.glyphs.as_mut().unwrap();
let texture = inner.texture.borrow();
let ret = glyphs.process_queued(
move |rect, data| update_texture(&*texture, rect, data),
create_vertex,
);

match ret {
Ok(action) => break action,
Err(BrushError::TextureTooSmall { suggested, .. }) => {
let new_size = glyphs.texture_dimensions();
let new_size = resize_texture(suggested, Some(new_size))?;
glyphs.resize_texture(new_size.0, new_size.1);
}
}
};

// upate vertex data
if let BrushAction::Draw(vertices) = action {
let mut map = Vec::<Vec<VertexData>>::new();
map.resize_with(inner.next_text_id, Default::default);

for vertex in vertices {
map[vertex.text_id].push(vertex.data);
}

for (text_id, data) in map.into_iter().enumerate() {
if data.is_empty() {
continue;
}

inner
.texts
.get(&text_id)
.unwrap()
.upgrade()
.unwrap()
.borrow_mut()
.update(data)?;
}
}

Ok(())
}
}

impl TextCacheInner {
fn add_font(&mut self, font: FontArc) -> Result<FontId, Error> {
let (id, size) = match self.glyphs.as_mut() {
Some(glyphs) => (glyphs.add_font(font), None),
None => {
let glyphs = GlyphBrushBuilder::using_font(font)
.draw_cache_position_tolerance(2.0)
.build();
let size = glyphs.texture_dimensions();

self.glyphs = Some(glyphs);

(FontId(0), Some(size))
}
};

if let Some(size) = size {
let texture = self.texture.borrow();
let _guard = BindGuard::new(&*texture);

let new_size = resize_texture(size, None)?;

self.glyphs
.as_mut()
.unwrap()
.resize_texture(new_size.0, new_size.1);
}

Ok(id)
}
}

fn resize_texture(new_size: (u32, u32), cur_size: Option<(u32, u32)>) -> Result<(u32, u32), Error> {
let mut max_size = 0;
gl::get_integer_v(gl::MAX_TEXTURE_SIZE, &mut max_size);

let max_size = max_size as u32;
let (w, h) = if let Some(cur_size) = cur_size {
if (new_size.0 > max_size || new_size.1 > max_size)
&& (cur_size.0 < max_size || cur_size.1 < max_size)
{
(max_size, max_size)
} else {
new_size
}
} else {
new_size
};

GlcError::checked(|| {
gl::tex_image_2d(
gl::TEXTURE_2D,
0,
gl::RED as _,
w as _,
h as _,
0,
gl::RED,
gl::UNSIGNED_BYTE,
null(),
)
})?;

Ok(new_size)
}

fn update_texture(texture: &Texture, rect: Rectangle<u32>, data: &[u8]) {
let ret = GlcError::checked(|| {
gl::texture_sub_image_2d(
texture.id(),
0,
rect.min[0] as _,
rect.min[1] as _,
rect.width() as _,
rect.height() as _,
gl::RED,
gl::UNSIGNED_BYTE,
data.as_ptr() as _,
)
});

if let Err(err) = ret {
warn!("Unable to update text texture: {}", err);
}
}

fn create_vertex(data: GlyphVertex<Extra>) -> Vertex {
let pos_min = Vector2f::new(data.pixel_coords.min.x, data.pixel_coords.min.y);
let pos_max = Vector2f::new(data.pixel_coords.max.x, data.pixel_coords.max.y);
let tex_min = Vector2f::new(data.tex_coords.min.x, data.tex_coords.min.y);
let tex_max = Vector2f::new(data.tex_coords.max.x, data.tex_coords.max.y);

Vertex {
text_id: data.extra.text_id,
data: VertexData {
pos_min,
pos_max,
tex_min,
tex_max,
color: data.extra.color,
},
}
}

/* TextBuilder */

pub struct TextBuilder {
cache: TextCache,
items: Vec<BuilderItem>,
}

enum BuilderItem {
Position(f32, f32),
FontName(String),
FontPath(String),
Color(Vector4f),
Text(String),
Scale(f32),
}

impl TextBuilder {
fn new(cache: &TextCache) -> Self {
Self {
cache: cache.clone(),
items: Vec::new(),
}
}

pub fn build(self) -> Result<Text, Error> {
let mut sections = Vec::<OwnedSection<Extra>>::new();

let mut scale = 20.0;
let mut font_id = None;
let mut color = Vector4f::new(0.0, 0.0, 0.0, 1.0);
let mut position = None;

for item in self.items {
match item {
BuilderItem::Text(text) => {
let font_id = font_id.ok_or(Error::FontNotSet)?;
let text = OwnedText::new(text)
.with_extra(Extra { text_id: 0, color })
.with_scale(scale)
.with_font_id(font_id);

match (sections.pop(), position.take()) {
(Some(section), Some(pos)) => {
sections.push(section);
sections.push(
OwnedSection::default()
.with_screen_position(pos)
.add_text(text),
);
}
(Some(section), None) => {
sections.push(section.add_text(text));
}
(None, Some(pos)) => sections.push(
OwnedSection::<Extra>::default()
.with_screen_position(pos)
.add_text(text),
),
(None, None) => {
sections.push(OwnedSection::<Extra>::default().add_text(text))
}
};
}
BuilderItem::Position(x, y) => position = Some((x, y)),
BuilderItem::Color(c) => color = c,
BuilderItem::Scale(s) => scale = s,
BuilderItem::FontName(name) => font_id = Some(self.cache.font_by_name(&name)?),
BuilderItem::FontPath(path) => font_id = Some(self.cache.font_by_path(&path)?),
}
}

let text = Text::new(&self.cache, sections)?;

let weak = Rc::downgrade(&text.inner);
self.cache.add_text(weak)?;

Ok(text)
}

pub fn position(mut self, x: f32, y: f32) -> Self {
self.items.push(BuilderItem::Position(x, y));

self
}

pub fn font_name<S>(mut self, name: S) -> Self
where
S: Display,
{
self.items.push(BuilderItem::FontName(name.to_string()));

self
}

pub fn font_path<S>(mut self, path: S) -> Self
where
S: Display,
{
self.items.push(BuilderItem::FontPath(path.to_string()));

self
}

pub fn color(self, r: f32, g: f32, b: f32, a: f32) -> Self {
self.color_vec(Vector4f::new(r, g, b, a))
}

pub fn color_vec(mut self, color: Vector4f) -> Self {
self.items.push(BuilderItem::Color(color));

self
}

pub fn text<S>(mut self, text: S) -> Self
where
S: Display,
{
self.items.push(BuilderItem::Text(text.to_string()));

self
}

pub fn scale(mut self, scale: f32) -> Self {
self.items.push(BuilderItem::Scale(scale));

self
}
}

/* Text */

pub struct Text {
cache: TextCache,
inner: Rc<RefCell<TextInner>>,
}

struct TextInner {
array: VertexArray,
program: Rc<Program>,
texture: Rc<RefCell<Texture>>,
sections: Vec<OwnedSection<Extra>>,
vertex_count: usize,
}

#[derive(Default, Debug, Clone)]
struct Extra {
text_id: usize,
color: Vector4f,
}

#[derive(Default, Clone)]
struct Vertex {
text_id: usize,
data: VertexData,
}

#[repr(C, packed)]
#[derive(Default, Debug, Clone)]
struct VertexData {
pos_min: Vector2f,
pos_max: Vector2f,
tex_min: Vector2f,
tex_max: Vector2f,
color: Vector4f,
}

impl Text {
fn new(cache: &TextCache, sections: Vec<OwnedSection<Extra>>) -> Result<Self, Error> {
const STRIDE: gl::GLsizei = size_of::<VertexData>() as _;

const SIZE_VEC2: gl::GLsizei = size_of::<Vector2f>() as _;

const OFFSET_POS_MIN: gl::GLsizei = 0;
const OFFSET_POS_MAX: gl::GLsizei = OFFSET_POS_MIN + SIZE_VEC2;
const OFFSET_TEX_MIN: gl::GLsizei = OFFSET_POS_MAX + SIZE_VEC2;
const OFFSET_TEX_MAX: gl::GLsizei = OFFSET_TEX_MIN + SIZE_VEC2;
const OFFSET_COLOR: gl::GLsizei = OFFSET_TEX_MAX + SIZE_VEC2;

let buffer = ArrayBuffer::new(Target::ArrayBuffer)?;
let array = VertexArray::builder()
.bind_buffer(buffer)
.vertex_attrib_pointer(0, 2, DataType::Float, false, STRIDE, OFFSET_POS_MIN)?
.vertex_attrib_divisor(1)?
.vertex_attrib_pointer(1, 2, DataType::Float, false, STRIDE, OFFSET_POS_MAX)?
.vertex_attrib_divisor(1)?
.vertex_attrib_pointer(2, 2, DataType::Float, false, STRIDE, OFFSET_TEX_MIN)?
.vertex_attrib_divisor(1)?
.vertex_attrib_pointer(3, 2, DataType::Float, false, STRIDE, OFFSET_TEX_MAX)?
.vertex_attrib_divisor(1)?
.vertex_attrib_pointer(4, 4, DataType::Float, false, STRIDE, OFFSET_COLOR)?
.vertex_attrib_divisor(1)?
.build()?;

let cache = cache.clone();
let program = cache.0.borrow().manager.0.borrow().program.clone();
let texture = cache.0.borrow().texture.clone();

let inner = TextInner {
array,
program,
texture,
sections,
vertex_count: 0,
};
let text = Text {
cache,
inner: Rc::new(RefCell::new(inner)),
};

Ok(text)
}

pub fn render(&self, enable_blending: bool) {
let inner = self.inner.borrow();
let texture = inner.texture.borrow();

if enable_blending {
gl::enable(gl::BLEND);
gl::blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
}

let _guard = BindGuard::new(&*texture);
let _guard = BindGuard::new(&inner.array);
let _guard = BindGuard::new(&*inner.program);
gl::draw_arrays_instanced(gl::TRIANGLE_STRIP, 0, 4, inner.vertex_count as _);

if enable_blending {
gl::disable(gl::BLEND);
}
}

pub fn update<S>(&mut self, mut index: usize, text: S) -> Result<(), Error>
where
S: Display,
{
let mut inner = self.inner.borrow_mut();

for section in &mut inner.sections {
if index < section.text.len() {
section.text[index].text = text.to_string();

break;
} else {
index -= section.text.len();
}
}

drop(inner);

self.cache.update()
}
}

impl TextInner {
fn update(&mut self, data: Vec<VertexData>) -> Result<(), Error> {
let buffers = self.array.buffers_mut();
buffers[0].buffer_data(Usage::StaticDraw, &data)?;

self.vertex_count = data.len();

Ok(())
}
}

impl Hash for Extra {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.text_id.hash(state);
OrderedFloat::from(self.color[0]).hash(state);
OrderedFloat::from(self.color[1]).hash(state);
OrderedFloat::from(self.color[2]).hash(state);
OrderedFloat::from(self.color[3]).hash(state);
}
}

impl PartialEq for Extra {
fn eq(&self, other: &Self) -> bool {
self.text_id.eq(&other.text_id) && self.color.eq(&other.color)
}
}

+ 1
- 0
space-crush/src/app/misc/vfs.rs Dosyayı Görüntüle

@@ -13,6 +13,7 @@ use vfs_zip::ZipReadOnly as ZipFS;

use crate::Error;

#[derive(Clone)]
pub struct Vfs(pub VfsPath);

impl Vfs {


+ 4
- 0
space-crush/src/app/misc/window.rs Dosyayı Görüntüle

@@ -54,6 +54,10 @@ impl Window {
let context = unsafe { context.make_current().unwrap() };
gl::load_with(|s| context.get_proc_address(s));

gl::disable(gl::CULL_FACE);
gl::disable(gl::DEPTH_TEST);
gl::pixel_store_i(gl::UNPACK_ALIGNMENT, 1);

Ok(Self { context })
}



+ 6
- 3
space-crush/src/app/mod.rs Dosyayı Görüntüle

@@ -6,8 +6,8 @@ use specs::{Dispatcher, DispatcherBuilder, World};

use crate::Error;

use misc::{events::Events, geometry::Geometry, vfs::Vfs, window::Window};
use render::{Init, Test};
use misc::{events::Events, geometry::Geometry, text::TextManager, vfs::Vfs, window::Window};
use render::{Debug, Init, Test};
use systems::{State, StateUpdate};

pub struct App<'a, 'b> {
@@ -19,16 +19,19 @@ pub struct App<'a, 'b> {

impl<'a, 'b> App<'a, 'b> {
pub fn new(world: &mut World) -> Result<Self, Error> {
let vfs = Vfs::new()?;
let events = Events::new(world);
let window = Window::new(events.handle())?;
let text_manager = TextManager::new(&vfs)?;

world.insert(Vfs::new()?);
world.insert(vfs);
world.insert(Geometry::new()?);

let mut dispatcher = DispatcherBuilder::new()
.with(StateUpdate::new(world), "state_update", &[])
.with_thread_local(Init::new(world)?)
.with_thread_local(Test::new(world)?)
.with_thread_local(Debug::new(text_manager)?)
.build();
dispatcher.setup(world);



+ 52
- 0
space-crush/src/app/render/debug.rs Dosyayı Görüntüle

@@ -0,0 +1,52 @@
use log::warn;
use specs::{ReadExpect, System};

use crate::Error;

use super::{
super::misc::text::{Text, TextManager},
Global,
};

/* Debug */

pub struct Debug {
text: Text,
}

impl Debug {
pub fn new(manager: TextManager) -> Result<Self, Error> {
let text = manager
.create_cache()?
.new_text()
.scale(12.0)
.font_name("DroidSansMono.ttf")
.color(0.7, 0.7, 0.7, 1.0)
.position(5.0, 5.0)
.text(format!("Space Crush v{}\n", env!("CARGO_PKG_VERSION")))
.text("\nFPS: ")
.text("-")
.text("\nResolution: ")
.text("1280 x 720")
.build()?;

Ok(Self { text })
}
}

impl<'a> System<'a> for Debug {
type SystemData = ReadExpect<'a, Global>;

fn run(&mut self, global: Self::SystemData) {
if global.frame_counter.updated() {
let ret = self
.text
.update(2, format!("{}", global.frame_counter.fps()));
if let Err(err) = ret {
warn!("Unable to update debug text: {}", err);
}
}

self.text.render(true);
}
}

+ 3
- 9
space-crush/src/app/render/init.rs Dosyayı Görüntüle

@@ -2,7 +2,7 @@ use glc::{
misc::Bindable,
shader::{Program, Type},
};
use log::{error, info};
use log::error;
use shrev::{EventChannel, ReaderId};
use specs::{ReadExpect, System, World, WriteExpect};

@@ -75,20 +75,14 @@ impl<'a> System<'a> for Init {
let w = *w as f32;
let h = *h as f32;

if let Err(err) =
global
.camera
.ortho(-w / 2.0, w / 2.0, -h / 2.0, h / 2.0, -100.0, 100.0)
{
if let Err(err) = global.camera.resize(w, h) {
error!("Error while updating camera: {}", err);
panic!("Error while updating camera: {}", err);
}
}
}

if global.frame_counter.next() {
info!("FPS: {}", global.frame_counter.fps());
}
global.frame_counter.next();

self.program.bind();
geometry.render_quad();


+ 2
- 0
space-crush/src/app/render/mod.rs Dosyayı Görüntüle

@@ -1,5 +1,7 @@
mod debug;
mod init;
mod test;

pub use debug::Debug;
pub use init::{Global, Init};
pub use test::Test;

+ 15
- 2
space-crush/src/error.rs Dosyayı Görüntüle

@@ -2,6 +2,7 @@ use std::io::Error as IoError;

use glc::error::Error as GlcError;
use glutin::{ContextError as GlutinContextError, CreationError as GlutinCreationError};
use glyph_brush::ab_glyph::InvalidFont;
use thiserror::Error;
use vfs::VfsError;
use vfs_zip::Error as VfsZipError;
@@ -26,11 +27,17 @@ pub enum Error {
#[error("glutin Creation Error: {0}")]
GlutinCreationError(GlutinCreationError),

#[error("Unable to create OpenGL context")]
#[error("Invalid Font: {0}")]
InvalidFont(InvalidFont),

#[error("Unable to create OpenGL context!")]
CreateContext,

#[error("Unable to initialize VFS")]
#[error("Unable to initialize VFS!")]
InitVFS,

#[error("Font is not set!")]
FontNotSet,
}

impl From<IoError> for Error {
@@ -68,3 +75,9 @@ impl From<GlutinCreationError> for Error {
Self::GlutinCreationError(err)
}
}

impl From<InvalidFont> for Error {
fn from(err: InvalidFont) -> Self {
Self::InvalidFont(err)
}
}

Yükleniyor…
İptal
Kaydet