| @@ -1,5 +1,15 @@ | |||||
| # This file is automatically @generated by Cargo. | # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | # 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]] | [[package]] | ||||
| name = "ab_glyph_rasterizer" | name = "ab_glyph_rasterizer" | ||||
| version = "0.1.4" | version = "0.1.4" | ||||
| @@ -46,6 +56,15 @@ version = "0.2.3" | |||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" | 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]] | [[package]] | ||||
| name = "arrayvec" | name = "arrayvec" | ||||
| version = "0.5.2" | version = "0.5.2" | ||||
| @@ -709,6 +728,45 @@ dependencies = [ | |||||
| "gl_generator", | "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]] | [[package]] | ||||
| name = "hashbrown" | name = "hashbrown" | ||||
| version = "0.7.2" | version = "0.7.2" | ||||
| @@ -827,6 +885,12 @@ dependencies = [ | |||||
| "winapi 0.3.9", | "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]] | [[package]] | ||||
| name = "lock_api" | name = "lock_api" | ||||
| version = "0.4.2" | version = "0.4.2" | ||||
| @@ -1041,6 +1105,15 @@ dependencies = [ | |||||
| "version_check", | "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]] | [[package]] | ||||
| name = "num_cpus" | name = "num_cpus" | ||||
| version = "1.13.0" | version = "1.13.0" | ||||
| @@ -1088,6 +1161,24 @@ version = "1.5.2" | |||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" | 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]] | [[package]] | ||||
| name = "osmesa-sys" | name = "osmesa-sys" | ||||
| version = "0.1.2" | version = "0.1.2" | ||||
| @@ -1103,7 +1194,16 @@ version = "0.6.0" | |||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3" | checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3" | ||||
| dependencies = [ | 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]] | [[package]] | ||||
| @@ -1320,6 +1420,12 @@ version = "0.6.21" | |||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" | checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" | ||||
| [[package]] | |||||
| name = "rustc-hash" | |||||
| version = "1.1.0" | |||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
| checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" | |||||
| [[package]] | [[package]] | ||||
| name = "rusttype" | name = "rusttype" | ||||
| version = "0.9.2" | version = "0.9.2" | ||||
| @@ -1327,7 +1433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
| checksum = "dc7c727aded0be18c5b80c1640eae0ac8e396abf6fa8477d96cb37d18ee5ec59" | checksum = "dc7c727aded0be18c5b80c1640eae0ac8e396abf6fa8477d96cb37d18ee5ec59" | ||||
| dependencies = [ | dependencies = [ | ||||
| "ab_glyph_rasterizer", | "ab_glyph_rasterizer", | ||||
| "owned_ttf_parser", | |||||
| "owned_ttf_parser 0.6.0", | |||||
| ] | ] | ||||
| [[package]] | [[package]] | ||||
| @@ -1428,8 +1534,10 @@ dependencies = [ | |||||
| "gl", | "gl", | ||||
| "glc", | "glc", | ||||
| "glutin", | "glutin", | ||||
| "glyph_brush", | |||||
| "log", | "log", | ||||
| "num_cpus", | "num_cpus", | ||||
| "ordered-float 2.0.0", | |||||
| "rand", | "rand", | ||||
| "shrev", | "shrev", | ||||
| "specs", | "specs", | ||||
| @@ -1455,6 +1563,12 @@ dependencies = [ | |||||
| "tuple_utils", | "tuple_utils", | ||||
| ] | ] | ||||
| [[package]] | |||||
| name = "static_assertions" | |||||
| version = "1.1.0" | |||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
| checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" | |||||
| [[package]] | [[package]] | ||||
| name = "strsim" | name = "strsim" | ||||
| version = "0.9.3" | version = "0.9.3" | ||||
| @@ -1535,12 +1649,29 @@ version = "0.6.2" | |||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc" | checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc" | ||||
| [[package]] | |||||
| name = "ttf-parser" | |||||
| version = "0.8.3" | |||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
| checksum = "7622061403fd00f0820df288e5a580e87d3ce15a1c4313c59fd1ffb77129903f" | |||||
| [[package]] | [[package]] | ||||
| name = "tuple_utils" | name = "tuple_utils" | ||||
| version = "0.3.0" | version = "0.3.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "44834418e2c5b16f47bedf35c28e148db099187dd5feee6367fb2525863af4f1" | 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]] | [[package]] | ||||
| name = "tynm" | name = "tynm" | ||||
| version = "0.1.6" | version = "0.1.6" | ||||
| @@ -1793,6 +1924,12 @@ version = "2.2.0" | |||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" | checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" | ||||
| [[package]] | |||||
| name = "xi-unicode" | |||||
| version = "0.3.0" | |||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
| checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" | |||||
| [[package]] | [[package]] | ||||
| name = "xml-rs" | name = "xml-rs" | ||||
| version = "0.8.3" | version = "0.8.3" | ||||
| @@ -24,9 +24,12 @@ pub enum Error { | |||||
| #[error("Error while linking shader program: {0}")] | #[error("Error while linking shader program: {0}")] | ||||
| ShaderLink(String), | ShaderLink(String), | ||||
| #[error("Vertex Array Index is already in use: {0}!")] | |||||
| #[error("Vertex Array: Index is already in use: {0}!")] | |||||
| VertexArrayIndexAlreadyInUse(gl::GLuint), | VertexArrayIndexAlreadyInUse(gl::GLuint), | ||||
| #[error("Vertex Array: Expected pointer!")] | |||||
| VertexArrayExpectedPointer, | |||||
| #[error("Invalid Parameter!")] | #[error("Invalid Parameter!")] | ||||
| InvalidParameter, | InvalidParameter, | ||||
| @@ -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> | impl<T> Mul<Matrix4<T>> for Matrix4<T> | ||||
| where | where | ||||
| T: Element, | T: Element, | ||||
| @@ -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> { | pub fn upload(&mut self, data: &Data, generate_mipmap: bool) -> Result<(), Error> { | ||||
| let info = data.format.info(); | let info = data.format.info(); | ||||
| @@ -494,6 +494,7 @@ pub trait Element: | |||||
| fn sin(self) -> Self; | fn sin(self) -> Self; | ||||
| fn cos(self) -> Self; | fn cos(self) -> Self; | ||||
| fn sqrt(self) -> Self; | fn sqrt(self) -> Self; | ||||
| fn tan(self) -> Self::AsFloat; | |||||
| fn acos(self) -> Self::AsFloat; | fn acos(self) -> Self::AsFloat; | ||||
| fn atan(self) -> Self::AsFloat; | fn atan(self) -> Self::AsFloat; | ||||
| fn atan2(a: Self, b: Self) -> Self::AsFloat; | fn atan2(a: Self, b: Self) -> Self::AsFloat; | ||||
| @@ -530,6 +531,11 @@ impl Element for gl::GLfloat { | |||||
| f32::sqrt(self) | f32::sqrt(self) | ||||
| } | } | ||||
| #[inline] | |||||
| fn tan(self) -> Self::AsFloat { | |||||
| f32::tan(self) | |||||
| } | |||||
| #[inline] | #[inline] | ||||
| fn acos(self) -> Self::AsFloat { | fn acos(self) -> Self::AsFloat { | ||||
| f32::acos(self) | f32::acos(self) | ||||
| @@ -589,6 +595,11 @@ impl Element for gl::GLdouble { | |||||
| f64::sqrt(self) | f64::sqrt(self) | ||||
| } | } | ||||
| #[inline] | |||||
| fn tan(self) -> Self::AsFloat { | |||||
| f64::tan(self) | |||||
| } | |||||
| #[inline] | #[inline] | ||||
| fn acos(self) -> Self::AsFloat { | fn acos(self) -> Self::AsFloat { | ||||
| f64::acos(self) | f64::acos(self) | ||||
| @@ -648,6 +659,11 @@ impl Element for gl::GLint { | |||||
| f64::sqrt(self as f64) as i32 | f64::sqrt(self as f64) as i32 | ||||
| } | } | ||||
| #[inline] | |||||
| fn tan(self) -> Self::AsFloat { | |||||
| f32::tan(self as f32) | |||||
| } | |||||
| #[inline] | #[inline] | ||||
| fn acos(self) -> Self::AsFloat { | fn acos(self) -> Self::AsFloat { | ||||
| f32::acos(self as f32) | f32::acos(self as f32) | ||||
| @@ -77,8 +77,9 @@ impl Builder { | |||||
| let guard = BindGuard::new(&binding.buffer); | let guard = BindGuard::new(&binding.buffer); | ||||
| for pointer in binding.pointers { | for pointer in binding.pointers { | ||||
| Error::checked(|| gl::enable_vertex_attrib_array(pointer.index))?; | |||||
| Error::checked(|| { | Error::checked(|| { | ||||
| gl::enable_vertex_attrib_array(pointer.index); | |||||
| gl::vertex_attrib_pointer( | gl::vertex_attrib_pointer( | ||||
| pointer.index, | pointer.index, | ||||
| pointer.size, | pointer.size, | ||||
| @@ -92,6 +93,10 @@ impl Builder { | |||||
| pointer.offset as *const _, | pointer.offset as *const _, | ||||
| ) | ) | ||||
| })?; | })?; | ||||
| if let Some(divisor) = pointer.divisor { | |||||
| Error::checked(|| gl::vertex_attrib_divisor(pointer.index, divisor))?; | |||||
| } | |||||
| } | } | ||||
| drop(guard); | drop(guard); | ||||
| @@ -142,6 +147,7 @@ impl BindingBuilder { | |||||
| normalize, | normalize, | ||||
| stride, | stride, | ||||
| offset, | offset, | ||||
| divisor: None, | |||||
| }; | }; | ||||
| self.builder | self.builder | ||||
| @@ -154,6 +160,18 @@ impl BindingBuilder { | |||||
| Ok(self) | 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> { | pub fn build(self) -> Result<VertexArray, Error> { | ||||
| self.builder.build() | self.builder.build() | ||||
| } | } | ||||
| @@ -214,4 +232,5 @@ struct Pointer { | |||||
| normalize: bool, | normalize: bool, | ||||
| stride: gl::GLsizei, | stride: gl::GLsizei, | ||||
| offset: gl::GLsizei, | offset: gl::GLsizei, | ||||
| divisor: Option<gl::GLuint>, | |||||
| } | } | ||||
| @@ -10,8 +10,10 @@ futures = "0.3" | |||||
| gl = { version = "0.1", features = [ "use_log_crate" ] } | gl = { version = "0.1", features = [ "use_log_crate" ] } | ||||
| glc = "0.1" | glc = "0.1" | ||||
| glutin = "0.25" | glutin = "0.25" | ||||
| glyph_brush = "0.7" | |||||
| log = { version = "0.4", features = [ "max_level_trace", "release_max_level_warn" ] } | log = { version = "0.4", features = [ "max_level_trace", "release_max_level_warn" ] } | ||||
| num_cpus = "1.13" | num_cpus = "1.13" | ||||
| ordered-float = "2.0" | |||||
| rand = "0.7" | rand = "0.7" | ||||
| shrev = "1.1" | shrev = "1.1" | ||||
| specs = "0.16" | specs = "0.16" | ||||
| @@ -3,7 +3,8 @@ | |||||
| in vec4 gl_FragCoord; | in vec4 gl_FragCoord; | ||||
| layout (std140, binding = 0) uniform Camera { | layout (std140, binding = 0) uniform Camera { | ||||
| mat4 projection; | |||||
| mat4 proj_world; | |||||
| mat4 proj_ui; | |||||
| mat4 view; | mat4 view; | ||||
| } camera; | } camera; | ||||
| @@ -3,7 +3,8 @@ | |||||
| layout (location = 0) in vec3 position; | layout (location = 0) in vec3 position; | ||||
| layout (std140, binding = 0) uniform Camera { | layout (std140, binding = 0) uniform Camera { | ||||
| mat4 projection; | |||||
| mat4 proj_world; | |||||
| mat4 proj_ui; | |||||
| mat4 view; | mat4 view; | ||||
| } camera; | } camera; | ||||
| @@ -15,5 +16,5 @@ out FragmentData { | |||||
| void main() { | void main() { | ||||
| data.tex_coords = position.xy + vec2(0.5); | 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); | |||||
| } | } | ||||
| @@ -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); | |||||
| } | |||||
| @@ -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; | |||||
| } | |||||
| @@ -3,8 +3,7 @@ | |||||
| use glc::{ | use glc::{ | ||||
| array_buffer::{ArrayBuffer, Target, Usage}, | array_buffer::{ArrayBuffer, Target, Usage}, | ||||
| error::Error, | error::Error, | ||||
| matrix::{Angle, Matrix4f}, | |||||
| vector::Vector4f, | |||||
| matrix::Matrix4f, | |||||
| }; | }; | ||||
| pub struct Camera { | pub struct Camera { | ||||
| @@ -17,7 +16,8 @@ impl Camera { | |||||
| buffer.buffer_data( | buffer.buffer_data( | ||||
| Usage::StaticDraw, | Usage::StaticDraw, | ||||
| &[Data { | &[Data { | ||||
| projection: Matrix4f::identity(), | |||||
| proj_world: Matrix4f::identity(), | |||||
| proj_ui: Matrix4f::identity(), | |||||
| view: Matrix4f::identity(), | view: Matrix4f::identity(), | ||||
| }], | }], | ||||
| )?; | )?; | ||||
| @@ -25,94 +25,15 @@ impl Camera { | |||||
| Ok(Self { buffer }) | 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)?; | 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(()) | 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> { | pub fn bind(&self, index: gl::GLuint) -> Result<(), Error> { | ||||
| Error::checked(|| self.buffer.bind_buffer_base(index)) | Error::checked(|| self.buffer.bind_buffer_base(index)) | ||||
| } | } | ||||
| @@ -120,6 +41,7 @@ impl Camera { | |||||
| #[repr(C, packed)] | #[repr(C, packed)] | ||||
| struct Data { | struct Data { | ||||
| projection: Matrix4f, | |||||
| proj_world: Matrix4f, | |||||
| proj_ui: Matrix4f, | |||||
| view: Matrix4f, | view: Matrix4f, | ||||
| } | } | ||||
| @@ -9,9 +9,14 @@ pub struct FrameCounter { | |||||
| count: usize, | count: usize, | ||||
| fps: usize, | fps: usize, | ||||
| delta: f32, | delta: f32, | ||||
| updated: bool, | |||||
| } | } | ||||
| impl FrameCounter { | impl FrameCounter { | ||||
| pub fn updated(&self) -> bool { | |||||
| self.updated | |||||
| } | |||||
| #[inline] | #[inline] | ||||
| pub fn fps(&self) -> usize { | pub fn fps(&self) -> usize { | ||||
| self.fps | self.fps | ||||
| @@ -31,7 +36,7 @@ impl FrameCounter { | |||||
| self.time += self.delta; | self.time += self.delta; | ||||
| self.count += 1; | self.count += 1; | ||||
| if self.time >= 2.0 { | |||||
| self.updated = if self.time >= 2.0 { | |||||
| self.time = 0.0; | self.time = 0.0; | ||||
| self.fps = 0; | self.fps = 0; | ||||
| self.count = 0; | self.count = 0; | ||||
| @@ -44,7 +49,9 @@ impl FrameCounter { | |||||
| true | true | ||||
| } else { | } else { | ||||
| false | false | ||||
| } | |||||
| }; | |||||
| self.updated | |||||
| } | } | ||||
| } | } | ||||
| @@ -56,6 +63,7 @@ impl Default for FrameCounter { | |||||
| count: 0, | count: 0, | ||||
| fps: 0, | fps: 0, | ||||
| delta: 0.0, | delta: 0.0, | ||||
| updated: false, | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -2,6 +2,7 @@ pub mod camera; | |||||
| pub mod events; | pub mod events; | ||||
| pub mod frame_counter; | pub mod frame_counter; | ||||
| pub mod geometry; | pub mod geometry; | ||||
| pub mod text; | |||||
| pub mod vfs; | pub mod vfs; | ||||
| pub mod window; | pub mod window; | ||||
| @@ -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) | |||||
| } | |||||
| } | |||||
| @@ -13,6 +13,7 @@ use vfs_zip::ZipReadOnly as ZipFS; | |||||
| use crate::Error; | use crate::Error; | ||||
| #[derive(Clone)] | |||||
| pub struct Vfs(pub VfsPath); | pub struct Vfs(pub VfsPath); | ||||
| impl Vfs { | impl Vfs { | ||||
| @@ -54,6 +54,10 @@ impl Window { | |||||
| let context = unsafe { context.make_current().unwrap() }; | let context = unsafe { context.make_current().unwrap() }; | ||||
| gl::load_with(|s| context.get_proc_address(s)); | 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 }) | Ok(Self { context }) | ||||
| } | } | ||||
| @@ -6,8 +6,8 @@ use specs::{Dispatcher, DispatcherBuilder, World}; | |||||
| use crate::Error; | 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}; | use systems::{State, StateUpdate}; | ||||
| pub struct App<'a, 'b> { | pub struct App<'a, 'b> { | ||||
| @@ -19,16 +19,19 @@ pub struct App<'a, 'b> { | |||||
| impl<'a, 'b> App<'a, 'b> { | impl<'a, 'b> App<'a, 'b> { | ||||
| pub fn new(world: &mut World) -> Result<Self, Error> { | pub fn new(world: &mut World) -> Result<Self, Error> { | ||||
| let vfs = Vfs::new()?; | |||||
| let events = Events::new(world); | let events = Events::new(world); | ||||
| let window = Window::new(events.handle())?; | let window = Window::new(events.handle())?; | ||||
| let text_manager = TextManager::new(&vfs)?; | |||||
| world.insert(Vfs::new()?); | |||||
| world.insert(vfs); | |||||
| world.insert(Geometry::new()?); | world.insert(Geometry::new()?); | ||||
| let mut dispatcher = DispatcherBuilder::new() | let mut dispatcher = DispatcherBuilder::new() | ||||
| .with(StateUpdate::new(world), "state_update", &[]) | .with(StateUpdate::new(world), "state_update", &[]) | ||||
| .with_thread_local(Init::new(world)?) | .with_thread_local(Init::new(world)?) | ||||
| .with_thread_local(Test::new(world)?) | .with_thread_local(Test::new(world)?) | ||||
| .with_thread_local(Debug::new(text_manager)?) | |||||
| .build(); | .build(); | ||||
| dispatcher.setup(world); | dispatcher.setup(world); | ||||
| @@ -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); | |||||
| } | |||||
| } | |||||
| @@ -2,7 +2,7 @@ use glc::{ | |||||
| misc::Bindable, | misc::Bindable, | ||||
| shader::{Program, Type}, | shader::{Program, Type}, | ||||
| }; | }; | ||||
| use log::{error, info}; | |||||
| use log::error; | |||||
| use shrev::{EventChannel, ReaderId}; | use shrev::{EventChannel, ReaderId}; | ||||
| use specs::{ReadExpect, System, World, WriteExpect}; | use specs::{ReadExpect, System, World, WriteExpect}; | ||||
| @@ -75,20 +75,14 @@ impl<'a> System<'a> for Init { | |||||
| let w = *w as f32; | let w = *w as f32; | ||||
| let h = *h 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); | error!("Error while updating camera: {}", err); | ||||
| panic!("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(); | self.program.bind(); | ||||
| geometry.render_quad(); | geometry.render_quad(); | ||||
| @@ -1,5 +1,7 @@ | |||||
| mod debug; | |||||
| mod init; | mod init; | ||||
| mod test; | mod test; | ||||
| pub use debug::Debug; | |||||
| pub use init::{Global, Init}; | pub use init::{Global, Init}; | ||||
| pub use test::Test; | pub use test::Test; | ||||
| @@ -2,6 +2,7 @@ use std::io::Error as IoError; | |||||
| use glc::error::Error as GlcError; | use glc::error::Error as GlcError; | ||||
| use glutin::{ContextError as GlutinContextError, CreationError as GlutinCreationError}; | use glutin::{ContextError as GlutinContextError, CreationError as GlutinCreationError}; | ||||
| use glyph_brush::ab_glyph::InvalidFont; | |||||
| use thiserror::Error; | use thiserror::Error; | ||||
| use vfs::VfsError; | use vfs::VfsError; | ||||
| use vfs_zip::Error as VfsZipError; | use vfs_zip::Error as VfsZipError; | ||||
| @@ -26,11 +27,17 @@ pub enum Error { | |||||
| #[error("glutin Creation Error: {0}")] | #[error("glutin Creation Error: {0}")] | ||||
| GlutinCreationError(GlutinCreationError), | GlutinCreationError(GlutinCreationError), | ||||
| #[error("Unable to create OpenGL context")] | |||||
| #[error("Invalid Font: {0}")] | |||||
| InvalidFont(InvalidFont), | |||||
| #[error("Unable to create OpenGL context!")] | |||||
| CreateContext, | CreateContext, | ||||
| #[error("Unable to initialize VFS")] | |||||
| #[error("Unable to initialize VFS!")] | |||||
| InitVFS, | InitVFS, | ||||
| #[error("Font is not set!")] | |||||
| FontNotSet, | |||||
| } | } | ||||
| impl From<IoError> for Error { | impl From<IoError> for Error { | ||||
| @@ -68,3 +75,9 @@ impl From<GlutinCreationError> for Error { | |||||
| Self::GlutinCreationError(err) | Self::GlutinCreationError(err) | ||||
| } | } | ||||
| } | } | ||||
| impl From<InvalidFont> for Error { | |||||
| fn from(err: InvalidFont) -> Self { | |||||
| Self::InvalidFont(err) | |||||
| } | |||||
| } | |||||