diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 09e527776..ae599bd83 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -19,6 +19,7 @@ lazy_static = "1.4.0" ron = "0.8.0" serde = "^1.0.0" toml = { version = "0.8.10", default-features = false, features = ["parse"] } +toml_edit = "0.22.6" strum = "0.26.1" strum_macros = "0.26.1" open = "5" diff --git a/editor/src/lib.rs b/editor/src/lib.rs index f8223cab3..1ae71db5a 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -169,21 +169,39 @@ use plugins::inspector::InspectorPlugin; use std::{ cell::RefCell, collections::VecDeque, - io::{BufRead, BufReader}, + io::{BufRead, BufReader, Cursor, Read}, path::{Path, PathBuf}, process::Stdio, rc::Rc, sync::{ atomic::{AtomicBool, Ordering}, mpsc::{self, channel, Receiver}, - Arc, + Arc, LazyLock, }, time::{Duration, Instant}, }; +use toml_edit::DocumentMut; pub const FIXED_TIMESTEP: f32 = 1.0 / 60.0; pub const MSG_SYNC_FLAG: u64 = 1; +static EDITOR_VERSION: LazyLock = LazyLock::new(|| { + let manifest = include_bytes!("../Cargo.toml"); + let mut file = Cursor::new(&manifest); + let mut toml = String::new(); + if file.read_to_string(&mut toml).is_ok() { + if let Ok(document) = toml.parse::() { + if let Some(package) = document.get("package").and_then(|i| i.as_table()) { + if let Some(version) = package.get("version") { + return version.to_string().replace('\"', ""); + } + } + } + } + + "".to_string() +}); + pub fn send_sync_message(ui: &UserInterface, mut msg: UiMessage) { msg.flags = MSG_SYNC_FLAG; ui.send_message(msg); @@ -559,6 +577,10 @@ pub struct Editor { impl Editor { pub fn new(startup_data: Option) -> Self { + // Useful for debugging purposes when users don't bother to mention editor version + // they're using. + Log::info(format!("Editor version: {}", &*EDITOR_VERSION)); + let (log_message_sender, log_message_receiver) = channel(); Log::add_listener(log_message_sender); @@ -2065,9 +2087,11 @@ impl Editor { let graphics_context = engine.graphics_context.as_initialized_mut(); - graphics_context - .window - .set_title(&format!("Fyroxed: {}", working_directory.to_string_lossy())); + graphics_context.window.set_title(&format!( + "FyroxEd {}: {}", + *EDITOR_VERSION, + working_directory.to_string_lossy() + )); match FileSystemWatcher::new(&working_directory, Duration::from_secs(1)) { Ok(watcher) => { diff --git a/fyrox-build-tools/src/build.rs b/fyrox-build-tools/src/build.rs index f0b2dcbe7..54b51f813 100644 --- a/fyrox-build-tools/src/build.rs +++ b/fyrox-build-tools/src/build.rs @@ -86,6 +86,7 @@ impl BuildWindow { let window = WindowBuilder::new(WidgetBuilder::new().with_width(420.0).with_height(200.0)) .can_minimize(false) .can_close(false) + .can_maximize(false) .open(false) .with_content( GridBuilder::new( @@ -96,12 +97,17 @@ impl BuildWindow { .with_child( TextBuilder::new( WidgetBuilder::new() - .with_margin(Thickness::uniform(1.0)) + .with_margin(Thickness { + left: 5.0, + top: 1.0, + right: 1.0, + bottom: 1.0, + }) .on_column(0), ) .with_text(format!( - "Please wait while {project_name} is building... Build Log:" - )) + "Please wait while {project_name} is building..." + )) .build(ctx), ) .with_child({ diff --git a/fyrox-core/src/log.rs b/fyrox-core/src/log.rs index 9e8ae0d90..188fb7712 100644 --- a/fyrox-core/src/log.rs +++ b/fyrox-core/src/log.rs @@ -249,3 +249,24 @@ impl Log { } } } + +#[macro_export] +macro_rules! info { + ($($arg:tt)*) => { + $crate::log::Log::info(format!($($arg)*)) + }; +} + +#[macro_export] +macro_rules! warn { + ($($arg:tt)*) => { + $crate::log::Log::warn(format!($($arg)*)) + }; +} + +#[macro_export] +macro_rules! err { + ($($arg:tt)*) => { + $crate::log::Log::err(format!($($arg)*)) + }; +} diff --git a/fyrox-core/src/visitor.rs b/fyrox-core/src/visitor.rs index 7a5e10ab9..53bbedab9 100644 --- a/fyrox-core/src/visitor.rs +++ b/fyrox-core/src/visitor.rs @@ -40,6 +40,7 @@ use crate::{ Complex, Const, Matrix, Matrix2, Matrix3, Matrix4, Quaternion, RawStorage, RawStorageMut, SVector, Scalar, UnitComplex, UnitQuaternion, Vector2, Vector3, Vector4, U1, }, + array_as_u8_slice_mut, io::{self, FileLoadError}, pool::{Handle, Pool}, replace_slashes, @@ -540,24 +541,27 @@ impl_field_data!(Vector4, FieldKind::Vector4U64); impl Visit for BinaryBlob<'_, T> where - T: Copy, + T: Copy + bytemuck::Pod, { fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult { if visitor.reading { if let Some(field) = visitor.find_field(name) { match &field.kind { FieldKind::BinaryBlob(data) => { - let mut bytes = std::mem::ManuallyDrop::new(data.clone()); + let len = data.len() / size_of::(); + let mut vec = Vec::::with_capacity(len); - // SAFETY: This is kinda safe, but may cause portability issues because of various byte order. - // However it seems to be fine, since big-endian is pretty much dead and unused nowadays. - *self.vec = unsafe { - Vec::from_raw_parts( - bytes.as_mut_ptr() as *mut T, - bytes.len() / std::mem::size_of::(), - bytes.capacity() / std::mem::size_of::(), - ) - }; + unsafe { + std::ptr::copy_nonoverlapping( + data.as_ptr(), + array_as_u8_slice_mut(&mut vec).as_mut_ptr(), + data.len(), + ); + + vec.set_len(len); + } + + *self.vec = vec; Ok(()) } diff --git a/fyrox-graphics/src/buffer.rs b/fyrox-graphics/src/buffer.rs index 29222e5a0..454efc6f0 100644 --- a/fyrox-graphics/src/buffer.rs +++ b/fyrox-graphics/src/buffer.rs @@ -18,49 +18,108 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +//! Buffer is a type-agnostic data storage located directly in GPU memory. It could be considered +//! as a data block which content is a pile of bytes, whose meaning is defined externally. + use crate::error::FrameworkError; use bytemuck::Pod; use fyrox_core::{array_as_u8_slice, array_as_u8_slice_mut}; use std::any::Any; +/// GPU buffer kind. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum BufferKind { + /// Vertex buffer. It is used to supply vertex data (such as positions, normals, texture + /// coordinates, etc.) to GPU. Vertex, + /// Index buffer. It is used to describe how vertices forms specific types of primitives. + /// For example a quad could be described by a set of 4 vertices, but 2 triangles which in their + /// turn consists of 6 indices (0,1,2,0,2,3 vertex indices). Index, + /// Uniform buffer. It is used to supply context-specific data that is needed for rendering. + /// Usually it contains data that changes from frame-to-frame (world transform, color, etc.). Uniform, + /// Pixel read buffer. It is a special buffer that is used to asynchronously read-back data + /// from GPU. PixelRead, + /// Pixel write buffer. It is a special buffer that is used to write some data to GPU + /// asynchronously. PixelWrite, } +/// A hint for video driver that allows it to optimize buffer's content for more efficient use. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum BufferUsage { + /// The buffer contents will be modified once and used at most a few times. + /// The buffer contents are modified by the application, and used as the source for + /// drawing and image specification commands. StreamDraw, + /// The buffer contents will be modified once and used at most a few times. + /// The buffer contents are modified by reading data from the GPU, and used to return that + /// data when queried by the application. StreamRead, + /// The buffer contents will be modified once and used at most a few times. + /// The buffer contents are modified by the application, and used as the source for + /// drawing and image specification commands. StreamCopy, + /// The buffer contents will be modified once and used many times. + /// The buffer contents are modified by the application, and used as the source for + /// drawing and image specification commands. StaticDraw, + /// The buffer contents will be modified once and used many times. + /// The buffer contents are modified by reading data from the GPU, and used to return that + /// data when queried by the application. StaticRead, + /// The buffer contents will be modified once and used many times. + /// The buffer contents are modified by reading data from the GPU, and used as the source for + /// drawing and image specification commands. StaticCopy, + /// The buffer contents will be modified repeatedly and used many times. + /// The buffer contents are modified by the application, and used as the source for + /// drawing and image specification commands. DynamicDraw, + /// The buffer contents will be modified repeatedly and used many times. + /// The buffer contents are modified by reading data from the GPU, and used to return that + /// data when queried by the application. DynamicRead, + /// The buffer contents will be modified repeatedly and used many times. + /// The buffer contents are modified by reading data from the GPU, and used as the source for + /// drawing and image specification commands. DynamicCopy, } +/// Buffer is a type-agnostic data storage located directly in GPU memory. It could be considered +/// as a data block which content is a pile of bytes, whose meaning is defined externally. pub trait Buffer: Any { + /// Downcasts self reference as reference to [`Any`]. fn as_any(&self) -> &dyn Any; + /// Downcasts self reference as reference to [`Any`]. fn as_any_mut(&mut self) -> &mut dyn Any; + /// Returns usage kind of the buffer. fn usage(&self) -> BufferUsage; + /// Returns buffer kind. fn kind(&self) -> BufferKind; + /// Returns total size of the buffer in bytes. fn size(&self) -> usize; + /// Writes an arbitrary number of bytes from the given slice. fn write_data(&self, data: &[u8]) -> Result<(), FrameworkError>; + /// Read an arbitrary number of bytes from the buffer (GPU memory) to the given slice. The + /// amount of the data that will be attempted to read is defined by the length of the given + /// slice. fn read_data(&self, data: &mut [u8]) -> Result<(), FrameworkError>; } impl dyn Buffer { + /// Tries to write typed data to the buffer. The data type must implement [`Pod`] trait for + /// safe usage. pub fn write_data_of_type(&self, data: &[T]) -> Result<(), FrameworkError> { let data = array_as_u8_slice(data); Buffer::write_data(self, data) } + /// Tries to read data from the buffer and convert them to the given type. The data type must + /// implement [`Pod`] trait for safe usage. The amount of the data that will be attempted to + /// read is defined by the length of the given slice. pub fn read_data_of_type(&self, data: &mut [T]) -> Result<(), FrameworkError> { let data = array_as_u8_slice_mut(data); Buffer::read_data(self, data) diff --git a/fyrox-graphics/src/geometry_buffer.rs b/fyrox-graphics/src/geometry_buffer.rs index 8a03eb01b..7393f810e 100644 --- a/fyrox-graphics/src/geometry_buffer.rs +++ b/fyrox-graphics/src/geometry_buffer.rs @@ -83,6 +83,7 @@ pub struct VertexBufferDescriptor<'a> { pub struct GeometryBufferDescriptor<'a> { pub element_kind: ElementKind, pub buffers: &'a [VertexBufferDescriptor<'a>], + pub usage: BufferUsage, } pub trait GeometryBuffer { diff --git a/fyrox-graphics/src/gl/geometry_buffer.rs b/fyrox-graphics/src/gl/geometry_buffer.rs index b4411e3df..da7e8a27d 100644 --- a/fyrox-graphics/src/gl/geometry_buffer.rs +++ b/fyrox-graphics/src/gl/geometry_buffer.rs @@ -18,17 +18,16 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -use crate::geometry_buffer::{AttributeKind, GeometryBuffer, GeometryBufferDescriptor}; use crate::{ - buffer::{Buffer, BufferKind, BufferUsage}, + buffer::{Buffer, BufferKind}, core::{array_as_u8_slice, math::TriangleDefinition}, error::FrameworkError, + geometry_buffer::{AttributeKind, GeometryBuffer, GeometryBufferDescriptor}, gl::{buffer::GlBuffer, server::GlGraphicsServer, ToGlConstant}, ElementKind, }; use glow::HasContext; -use std::any::Any; -use std::{cell::Cell, marker::PhantomData, rc::Weak}; +use std::{any::Any, cell::Cell, marker::PhantomData, rc::Weak}; impl AttributeKind { fn gl_type(self) -> u32 { @@ -61,7 +60,7 @@ impl GlGeometryBuffer { server.set_vertex_array_object(Some(vao)); - let element_buffer = GlBuffer::new(server, 0, BufferKind::Index, BufferUsage::StaticDraw)?; + let element_buffer = GlBuffer::new(server, 0, BufferKind::Index, desc.usage)?; let mut buffers = Vec::new(); for buffer in desc.buffers { diff --git a/fyrox-graphics/src/lib.rs b/fyrox-graphics/src/lib.rs index 98fcfc14c..2665f0e1d 100644 --- a/fyrox-graphics/src/lib.rs +++ b/fyrox-graphics/src/lib.rs @@ -40,6 +40,7 @@ pub mod server; pub mod stats; pub mod uniform; +/// A set of possible polygon filling modes. #[derive( Copy, Clone, @@ -55,20 +56,21 @@ pub mod uniform; EnumString, VariantNames, TypeUuidProvider, + Default, )] #[type_uuid(id = "47aff01a-7daa-427c-874c-87464a7ffe28")] pub enum PolygonFillMode { + /// Only vertices of polygons are rendered. Their size is 1px by default. Point, + /// Only edges of polygons are rendered using 1px lines. This mode is useful for wireframe + /// drawing. Line, + /// The entire polygon surface is rendered. This is default rendering mode. + #[default] Fill, } -impl Default for PolygonFillMode { - fn default() -> Self { - Self::Fill - } -} - +/// A function used to compare two values. Usually it is used for depth and stencil testing. #[derive( Copy, Clone, @@ -85,6 +87,7 @@ impl Default for PolygonFillMode { AsRefStr, EnumString, VariantNames, + Default, )] pub enum CompareFunc { /// Never passes. @@ -97,6 +100,7 @@ pub enum CompareFunc { Equal, /// Passes if the incoming value is less than or equal to the stored value. + #[default] LessOrEqual, /// Passes if the incoming value is greater than the stored value. @@ -112,12 +116,7 @@ pub enum CompareFunc { Always, } -impl Default for CompareFunc { - fn default() -> Self { - Self::LessOrEqual - } -} - +/// Defines a set values (per color) for blending operation. #[derive( Copy, Clone, @@ -134,8 +133,10 @@ impl Default for CompareFunc { AsRefStr, EnumString, VariantNames, + Default, )] pub enum BlendFactor { + #[default] Zero, One, SrcColor, @@ -157,29 +158,38 @@ pub enum BlendFactor { OneMinusSrc1Alpha, } -impl Default for BlendFactor { - fn default() -> Self { - Self::Zero - } -} - +/// Defines an operation used in blending equation. #[derive( - Copy, Clone, Hash, PartialOrd, PartialEq, Eq, Ord, Serialize, Deserialize, Visit, Debug, Reflect, + Copy, + Clone, + Hash, + PartialOrd, + PartialEq, + Eq, + Ord, + Serialize, + Deserialize, + Visit, + Debug, + Reflect, + Default, )] pub enum BlendMode { + /// Addition of two operands (`Source + Dest`). This is default operation. + #[default] Add, + /// Subtraction of two operands (`Source - Dest`). Subtract, + /// Reverse subtraction of two operands (`Dest - Source`). ReverseSubtract, + /// Per-component min function of two operands (`min(Source, Dest)`). Min, + /// Per-component max function of two operands (`max(Source, Dest)`). Max, } -impl Default for BlendMode { - fn default() -> Self { - Self::Add - } -} - +/// An equation used for blending a source pixel color with the destination color (the one that is +/// already in a frame buffer). This equation has different modes for rgb/alpha parts. #[derive( Copy, Clone, @@ -196,21 +206,33 @@ impl Default for BlendMode { Reflect, )] pub struct BlendEquation { + /// An operation for RGB part. pub rgb: BlendMode, + /// An operation for alpha part. pub alpha: BlendMode, } +/// Blending function defines sources of data for both operands in blending equation (separately +/// for RGB and Alpha parts). Default blending function is replacing destination values with the +/// source ones. #[derive( Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash, Serialize, Deserialize, Visit, Debug, Reflect, )] pub struct BlendFunc { + /// Data for source (the value that is produced by a shader) in the blending equation (RGB part). pub sfactor: BlendFactor, + /// Data for destination (the value that is already in a frame buffer) in the blending equation + /// (RGB part). pub dfactor: BlendFactor, + /// Data for source (the value that is produced by a shader) in the blending equation (alpha part). pub alpha_sfactor: BlendFactor, + /// Data for destination (the value that is already in a frame buffer) in the blending equation + /// (alpha part). pub alpha_dfactor: BlendFactor, } impl BlendFunc { + /// Creates a new blending function where both RGB and Alpha parts have the same blending factor. pub fn new(sfactor: BlendFactor, dfactor: BlendFactor) -> Self { Self { sfactor, @@ -220,6 +242,7 @@ impl BlendFunc { } } + /// Creates a new blending function where RGB and Alpha parts have different blending factor. pub fn new_separate( sfactor: BlendFactor, dfactor: BlendFactor, @@ -246,13 +269,19 @@ impl Default for BlendFunc { } } +/// A mask that defines which colors will be stored in a frame buffer during rendering operation. +/// By default, all colors are stored (every field is set to `true`). #[derive( Copy, Clone, PartialOrd, PartialEq, Hash, Debug, Serialize, Deserialize, Visit, Eq, Reflect, )] pub struct ColorMask { + /// A flag, that defines whether the red channel is written or not in a frame buffer. pub red: bool, + /// A flag, that defines whether the green channel is written or not in a frame buffer. pub green: bool, + /// A flag, that defines whether the blue channel is written or not in a frame buffer. pub blue: bool, + /// A flag, that defines whether the alpha channel is written or not in a frame buffer. pub alpha: bool, } @@ -268,6 +297,7 @@ impl Default for ColorMask { } impl ColorMask { + /// Creates a new color mask where all the components will have the specified value. pub fn all(value: bool) -> Self { Self { red: value, @@ -278,6 +308,8 @@ impl ColorMask { } } +/// Defines a polygon face that will be rendered. This is usually used for back face culling. +/// Default value is [`PolygonFace::FrontAndBack`]. #[derive( Copy, Clone, @@ -292,25 +324,32 @@ impl ColorMask { AsRefStr, EnumString, VariantNames, + Default, )] pub enum PolygonFace { + /// Only front faces will be rendered. Front, + /// Only back faces will be rendered. Back, + /// Both, back and front faces will be rendered. + #[default] FrontAndBack, } -impl Default for PolygonFace { - fn default() -> Self { - Self::FrontAndBack - } -} - +/// Defines a function that used in a stencil test. #[derive( Copy, Clone, PartialOrd, PartialEq, Hash, Debug, Serialize, Deserialize, Visit, Eq, Reflect, )] pub struct StencilFunc { + /// A function that is used to compare two values. Default value is [`CompareFunc::Always`]. pub func: CompareFunc, + /// Reference value that is used to compare against the current value in the stencil buffer. + /// Default value is 0. pub ref_value: u32, + /// A mask value that is used to filter out some bits (using logical AND operation) of a value + /// in the stencil buffer and the ref value (for example if a [`CompareFunc::Less`] is used + /// then the entire equation will look like `(ref & mask) < (stencil & mask)`). Default value + /// is `0xFFFFFFFF`. pub mask: u32, } @@ -324,6 +363,7 @@ impl Default for StencilFunc { } } +/// An action with the stencil value in the stencil buffer is the stencil test passed. #[derive( Copy, Clone, @@ -339,9 +379,11 @@ impl Default for StencilFunc { AsRefStr, EnumString, VariantNames, + Default, )] pub enum StencilAction { - /// Keeps the current value. + /// Keeps the current value. This is the default variant. + #[default] Keep, /// Sets the stencil buffer value to 0. @@ -372,19 +414,19 @@ pub enum StencilAction { Invert, } -impl Default for StencilAction { - fn default() -> Self { - Self::Keep - } -} - +/// A set of actions that will be performed with the stencil buffer during various testing stages. #[derive( Copy, Clone, PartialOrd, PartialEq, Hash, Debug, Serialize, Deserialize, Visit, Eq, Reflect, )] pub struct StencilOp { + /// An action that happens when the stencil test has failed. pub fail: StencilAction, + /// An action that happens when the depth test has failed. pub zfail: StencilAction, + /// An action that happens when the depth test has passed. pub zpass: StencilAction, + /// A mask that is used to filter out some bits (using `AND` logical operation) from the source + /// value before writing it to the stencil buffer. pub write_mask: u32, } @@ -399,43 +441,70 @@ impl Default for StencilOp { } } +/// A face side to cull. #[derive( - Copy, Clone, PartialOrd, PartialEq, Hash, Debug, Serialize, Deserialize, Visit, Eq, Reflect, + Copy, + Clone, + PartialOrd, + PartialEq, + Hash, + Debug, + Serialize, + Deserialize, + Visit, + Eq, + Reflect, + Default, )] pub enum CullFace { + /// Cull only back faces. + #[default] Back, + /// Cull only front faces. Front, } -impl Default for CullFace { - fn default() -> Self { - Self::Back - } -} - +/// Blending parameters (such as blending function and its equation). #[derive(Serialize, Deserialize, Default, Visit, Debug, PartialEq, Clone, Eq, Reflect)] pub struct BlendParameters { + /// Blending function, see [`BlendFunc`] for more info. pub func: BlendFunc, + /// Blending equation, see [`BlendEquation`] for more info. pub equation: BlendEquation, } +/// A rectangular area that defines which pixels will be rendered in a frame buffer or not. #[derive(Serialize, Deserialize, Default, Visit, Debug, PartialEq, Clone, Copy, Eq, Reflect)] pub struct ScissorBox { + /// X coordinate of the box's origin. pub x: i32, + /// Y coordinate of the box's origin. Located at the bottom of the rectangle. pub y: i32, + /// Width of the box. pub width: i32, + /// Height of the box. pub height: i32, } +/// A set of drawing parameters, that are used during draw call. It defines pretty much all pipeline +/// settings all at once. #[derive(Serialize, Deserialize, Visit, Debug, PartialEq, Clone, Eq, Reflect)] pub struct DrawParameters { + /// An optional cull face. If [`None`], then the culling is disabled. pub cull_face: Option, + /// Color write mask. pub color_write: ColorMask, + /// A flag, that defines whether the depth values should be written to the depth buffer or not. pub depth_write: bool, + /// Stencil test options. If [`None`], then the stencil test is disabled. pub stencil_test: Option, + /// Depth test options. If [`None`], then the depth test is disabled. pub depth_test: Option, + /// Blending options. If [`None`], then the blending is disabled. pub blend: Option, + /// Stencil operation. pub stencil_op: StencilOp, + /// Optional scissor box. If [`None`], then the scissor test is disabled. pub scissor_box: Option, } @@ -454,22 +523,30 @@ impl Default for DrawParameters { } } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +/// A range of elements (usually it's triangles) to draw in a draw call. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)] pub enum ElementRange { + /// All available elements. This is the default option. + #[default] Full, - Specific { offset: usize, count: usize }, -} - -impl Default for ElementRange { - fn default() -> Self { - Self::Full - } + /// Specific range of elements. Useful if you have a large buffer that contains multiple smaller + /// elements at once. + Specific { + /// Offset (in elements) from the beginning of the buffer. + offset: usize, + /// Total count of elements to draw. + count: usize, + }, } +/// Element kind of geometry. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum ElementKind { + /// Triangles. Triangle, + /// Lines. Line, + /// Points. Point, } diff --git a/fyrox-graphics/src/server.rs b/fyrox-graphics/src/server.rs index 55e233972..dcd0c8b1a 100644 --- a/fyrox-graphics/src/server.rs +++ b/fyrox-graphics/src/server.rs @@ -18,6 +18,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +#![warn(missing_docs)] + +//! Graphics server is an abstraction layer over various graphics APIs used on different platforms +//! supported by the engine. + use crate::gpu_texture::{ GpuTextureKind, MagnificationFilter, MinificationFilter, PixelKind, WrapMode, }; @@ -39,6 +44,7 @@ use std::{ rc::{Rc, Weak}, }; +/// Graphics server capabilities. #[derive(Debug)] pub struct ServerCapabilities { /// The maximum size in basic machine units of a uniform block, which must be at least 16384. @@ -50,32 +56,65 @@ pub struct ServerCapabilities { pub max_lod_bias: f32, } +/// A shared reference to a graphics server. pub type SharedGraphicsServer = Rc; +/// Graphics server is an abstraction layer over various graphics APIs used on different platforms +/// supported by the engine. Such abstraction layer tries to provide more or less high-level and +/// unified interface, that can be used to build graphics pipelines quickly and more or less efficiently. +/// +/// Low-level GAPI-specific optimizations could be performed using direct access to the underlying API, +/// by downcasting to a specific type. pub trait GraphicsServer: Any { + /// Downcasts self reference as reference to [`Any`]. + fn as_any(&self) -> &dyn Any; + + /// Downcasts self reference as reference to [`Any`]. + fn as_any_mut(&mut self) -> &mut dyn Any; + + /// Creates a GPU buffer with the given size and kind. Usage is a hint to the video driver + /// that allows to perform some potential performance optimizations. fn create_buffer( &self, size: usize, buffer_kind: BufferKind, buffer_usage: BufferUsage, ) -> Result, FrameworkError>; + + /// Creates a new GPU texture using the given descriptor. fn create_texture( &self, desc: GpuTextureDescriptor, ) -> Result>, FrameworkError>; + + /// Creates a new frame buffer using the given depth and color attachments. Depth attachment + /// not exist, but there must be at least one color attachment of a format that supports rendering. fn create_frame_buffer( &self, depth_attachment: Option, color_attachments: Vec, ) -> Result, FrameworkError>; + + /// Creates a frame buffer that "connected" to the final image that will be displayed to the + /// screen. fn back_buffer(&self) -> Box; + + /// Creates a new GPU query, that can perform asynchronous data fetching from GPU. Usually it + /// is used to create occlusion queries. fn create_query(&self) -> Result, FrameworkError>; + + /// Creates a new named GPU program using a pair of vertex and fragment shaders. The name could + /// be used for debugging purposes. fn create_program( &self, name: &str, vertex_source: &str, fragment_source: &str, ) -> Result, FrameworkError>; + + /// Almost the same as [`Self::create_program`], but accepts additional array of resource + /// definitions. The implementation of graphics server will generate proper resource bindings + /// in the shader code for you. fn create_program_with_properties( &self, name: &str, @@ -83,26 +122,58 @@ pub trait GraphicsServer: Any { fragment_source: &str, properties: &[ShaderResourceDefinition], ) -> Result, FrameworkError>; + + /// Creates a new read-back buffer, that can be used to obtain texture data from GPU. It can be + /// used to read rendering result from GPU to CPU memory and save the result to disk. fn create_async_read_buffer( &self, pixel_size: usize, pixel_count: usize, ) -> Result, FrameworkError>; + + /// Creates a new geometry buffer, which consists of one or more vertex buffers and only one + /// element buffer. Geometry buffer could be considered as a complex mesh storage allocated on + /// GPU. fn create_geometry_buffer( &self, desc: GeometryBufferDescriptor, ) -> Result, FrameworkError>; - fn as_any(&self) -> &dyn Any; - fn as_any_mut(&mut self) -> &mut dyn Any; + + /// Creates a weak reference to the shared graphics server. fn weak(self: Rc) -> Weak; + + /// Sends all scheduled GPU command buffers for execution on GPU without waiting for a certain + /// threshold. fn flush(&self); + + /// Waits until all the scheduled GPU commands are fully executed. This is blocking operation, and + /// it blocks the current thread until all the commands are fully executed. fn finish(&self); + + /// Unbinds the all bound resources from the graphics pipeline. fn invalidate_resource_bindings_cache(&self); + + /// Returns GPU pipeline statistics. See [`PipelineStatistics`] for more info. fn pipeline_statistics(&self) -> PipelineStatistics; + + /// Swaps the front and back buffers and thus presenting the final image on screen. There could + /// be more than two buffers, and it is up to the graphics server implementation to choose the + /// right amount, but it can't be less than two. fn swap_buffers(&self) -> Result<(), FrameworkError>; + + /// Notifies the graphics server that the size of the back buffer has changed. It has very limited + /// use and there are very few platforms (Linux with Wayland mostly) that needs this function to + /// be called. fn set_frame_size(&self, new_size: (u32, u32)); + + /// Returns current capabilities of the graphics server. See [`ServerCapabilities`] for more info. fn capabilities(&self) -> ServerCapabilities; + + /// Sets current polygon fill mode. See [`PolygonFace`] and [`PolygonFillMode`] docs for more info. fn set_polygon_fill_mode(&self, polygon_face: PolygonFace, polygon_fill_mode: PolygonFillMode); + + /// A shortcut for [`Self::create_texture`], that creates a rectangular texture with the given + /// size and pixel kind. fn create_2d_render_target( &self, pixel_kind: PixelKind, diff --git a/fyrox-impl/src/engine/mod.rs b/fyrox-impl/src/engine/mod.rs index 771d09d0f..57c6c2080 100644 --- a/fyrox-impl/src/engine/mod.rs +++ b/fyrox-impl/src/engine/mod.rs @@ -1467,7 +1467,11 @@ impl Engine { params: params.clone(), }); - self.sound_engine.initialize_audio_output_device()?; + if let Err(err) = self.sound_engine.initialize_audio_output_device() { + Log::err(format!( + "Unable to initialize audio output device! Reason: {err:?}" + )); + } Ok(()) } else { diff --git a/fyrox-impl/src/renderer/debug_renderer.rs b/fyrox-impl/src/renderer/debug_renderer.rs index 0739c8af3..d2738b9d5 100644 --- a/fyrox-impl/src/renderer/debug_renderer.rs +++ b/fyrox-impl/src/renderer/debug_renderer.rs @@ -125,6 +125,7 @@ impl DebugRenderer { ], data: VertexBufferData::new::(None), }], + usage: BufferUsage::DynamicDraw, }; Ok(Self { diff --git a/fyrox-impl/src/renderer/framework/mod.rs b/fyrox-impl/src/renderer/framework/mod.rs index 42cdc39c9..99a67becb 100644 --- a/fyrox-impl/src/renderer/framework/mod.rs +++ b/fyrox-impl/src/renderer/framework/mod.rs @@ -78,6 +78,7 @@ impl GeometryBufferExt for dyn GeometryBuffer { bytes: Some(data.vertex_buffer.raw_data()), }, }], + usage, }; let geometry_buffer = server.create_geometry_buffer(geometry_buffer_desc)?; diff --git a/fyrox-impl/src/renderer/ui_renderer.rs b/fyrox-impl/src/renderer/ui_renderer.rs index a0684dd3b..480b1df87 100644 --- a/fyrox-impl/src/renderer/ui_renderer.rs +++ b/fyrox-impl/src/renderer/ui_renderer.rs @@ -137,6 +137,7 @@ impl UiRenderer { ], data: VertexBufferData::new::(None), }], + usage: BufferUsage::DynamicDraw, }; let clipping_geometry_buffer_desc = GeometryBufferDescriptor { @@ -155,6 +156,7 @@ impl UiRenderer { ], data: VertexBufferData::new::(None), }], + usage: BufferUsage::DynamicDraw, }; Ok(Self { diff --git a/fyrox-ui/src/image.rs b/fyrox-ui/src/image.rs index 654fd36c6..4cf6a7cf2 100644 --- a/fyrox-ui/src/image.rs +++ b/fyrox-ui/src/image.rs @@ -26,18 +26,18 @@ use crate::{ brush::Brush, color::draw_checker_board, - core::{algebra::Vector2, color::Color, math::Rect, pool::Handle}, - core::{reflect::prelude::*, type_traits::prelude::*, visitor::prelude::*}, + core::{ + algebra::Vector2, color::Color, math::Rect, pool::Handle, reflect::prelude::*, + type_traits::prelude::*, variable::InheritableVariable, visitor::prelude::*, + }, define_constructor, draw::{CommandTexture, Draw, DrawingContext}, message::{MessageDirection, UiMessage}, widget::{Widget, WidgetBuilder}, BuildContext, Control, UiNode, UserInterface, }; -use fyrox_core::uuid_provider; -use fyrox_core::variable::InheritableVariable; use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor}; -use fyrox_texture::TextureResource; +use fyrox_texture::{TextureKind, TextureResource}; use std::ops::{Deref, DerefMut}; /// A set of messages that could be used to alter [`Image`] widget state at runtime. @@ -91,19 +91,20 @@ impl ImageMessage { /// # }; /// /// fn create_image(ctx: &mut BuildContext, texture: TextureResource) -> Handle { -/// // You must explicitly set width and height of the image, otherwise it will collapse to a -/// // point and you won't see anything. -/// let width = 100.0; -/// let height = 100.0; -/// ImageBuilder::new(WidgetBuilder::new().with_width(width).with_height(height)) +/// ImageBuilder::new(WidgetBuilder::new()) /// .with_texture(texture) /// .build(ctx) /// } /// ``` /// -/// There are one common pitfall when using Image widget - you must explicitly set width and height of the image if it is -/// not placed to some panel, that will stretch it automatically. In other words if you created an image with undefined -/// width and height, then putting it to some container like Grid' cell will stretch the image to fit cell bounds. +/// By default, the Image widget will try to use the size of the texture as its desired size for layout +/// process. This means that the widget will be as large as the texture if the outer bounds allows +/// that. You can specify the desired width and height manually and the image will shrink/expand +/// automatically. +/// +/// Keep in mind, that texture is a resource, and it could be loaded asynchronously, and during that +/// process, the UI can't fetch texture's size, and it will be collapsed into a point. After it fully +/// loaded, the widget will take texture's size as normal. /// /// ## Vertical Flip /// @@ -177,7 +178,8 @@ impl ImageMessage { /// It is useful if you have many custom UI elements packed in a single texture atlas. Drawing using atlases is much more /// efficient and faster. This could also be used for animations, when you have multiple frames packed in a single atlas /// and changing texture coordinates over the time. -#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)] +#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider, TypeUuidProvider)] +#[type_uuid(id = "18e18d0f-cb84-4ac1-8050-3480a2ec3de5")] pub struct Image { /// Base widget of the image. pub widget: Widget, @@ -210,9 +212,29 @@ impl ConstructorProvider for Image { crate::define_widget_deref!(Image); -uuid_provider!(Image = "18e18d0f-cb84-4ac1-8050-3480a2ec3de5"); - impl Control for Image { + fn measure_override(&self, ui: &UserInterface, available_size: Vector2) -> Vector2 { + let mut size: Vector2 = self.widget.measure_override(ui, available_size); + + if let Some(texture) = self.texture.as_ref() { + let state = texture.state(); + if let Some(data) = state.data_ref() { + if let TextureKind::Rectangle { width, height } = data.kind() { + let width = width as f32; + let height = height as f32; + if size.x < width { + size.x = width; + } + if size.y < height { + size.y = height; + } + } + } + } + + size + } + fn draw(&self, drawing_context: &mut DrawingContext) { let bounds = self.widget.bounding_rect(); diff --git a/project-manager/resources/clean.png b/project-manager/resources/clean.png new file mode 100644 index 000000000..66b4b703d Binary files /dev/null and b/project-manager/resources/clean.png differ diff --git a/project-manager/src/manager.rs b/project-manager/src/manager.rs index 251138f2e..93f844cb2 100644 --- a/project-manager/src/manager.rs +++ b/project-manager/src/manager.rs @@ -55,8 +55,7 @@ use fyrox::{ VerticalAlignment, }, }; -use fyrox_build_tools::build::BuildWindow; -use fyrox_build_tools::{BuildProfile, CommandDescriptor}; +use fyrox_build_tools::{build::BuildWindow, BuildProfile, CommandDescriptor}; use std::{ collections::VecDeque, path::{Path, PathBuf}, @@ -65,7 +64,7 @@ use std::{ pub enum Mode { Normal, - Build { + CommandExecution { queue: VecDeque, process: Option, current_dir: PathBuf, @@ -74,7 +73,7 @@ pub enum Mode { impl Mode { pub fn is_build(&self) -> bool { - matches!(self, Mode::Build { .. }) + matches!(self, Mode::CommandExecution { .. }) } } @@ -144,6 +143,7 @@ pub struct ProjectManager { settings_window: Option, no_projects_warning: Handle, exclude_project: Handle, + clean_project: Handle, pub focused: bool, pub update_loop_state: UpdateLoopState, } @@ -454,6 +454,7 @@ impl ProjectManager { (can be changed in settings).\nHotkey: Ctrl+O"; let exclude_project_tooltip = "Removes the project from the project manager, \ but does NOT delete it.\nHotkey: Ctrl+E"; + let clean_project_tooltip = "Removes project's build artifacts.\nHotkey: Ctrl+N"; let edit = make_text_and_image_button_with_tooltip( ctx, @@ -546,6 +547,19 @@ impl ProjectManager { Color::ORANGE, font_size, ); + let clean_project = make_text_and_image_button_with_tooltip( + ctx, + "Clean", + 22.0, + 22.0, + load_image(include_bytes!("../resources/clean.png")), + clean_project_tooltip, + 0, + 0, + Some(11), + Color::LIGHT_STEEL_BLUE, + font_size, + ); let hot_reload = CheckBoxBuilder::new( WidgetBuilder::new() .with_tab_index(Some(4)) @@ -568,6 +582,7 @@ impl ProjectManager { .with_child(run) .with_child(open_ide) .with_child(upgrade) + .with_child(clean_project) .with_child(locate) .with_child(delete) .with_child(exclude_project), @@ -672,6 +687,7 @@ impl ProjectManager { settings_window: None, no_projects_warning, exclude_project, + clean_project, focused: true, update_loop_state: Default::default(), } @@ -698,7 +714,7 @@ impl ProjectManager { } fn handle_modes(&mut self, ui: &mut UserInterface) { - let Mode::Build { + let Mode::CommandExecution { ref mut process, ref mut queue, ref current_dir, @@ -916,6 +932,19 @@ impl ProjectManager { self.refresh(ui); } + fn on_clean_clicked(&mut self, ui: &mut UserInterface) { + self.run_selected_project_command( + "clean", + vec![CommandDescriptor { + command: "cargo".to_string(), + args: vec!["clean".to_string()], + environment_variables: vec![], + }] + .into(), + ui, + ) + } + fn on_button_click(&mut self, button: Handle, ui: &mut UserInterface) { if button == self.create { self.on_create_clicked(ui); @@ -936,11 +965,13 @@ impl ProjectManager { } else if button == self.locate { self.on_locate_click(); } else if button == self.open_ide { - self.on_open_ide_click(); + self.on_open_ide_click(ui); } else if button == self.open_settings { self.on_open_settings_click(ui); } else if button == self.exclude_project { self.on_exclude_project_clicked(ui); + } else if button == self.clean_project { + self.on_clean_clicked(ui); } } @@ -950,7 +981,7 @@ impl ProjectManager { Log::verify(open::that_detached(folder)); } - fn on_open_ide_click(&mut self) { + fn on_open_ide_click(&mut self, ui: &mut UserInterface) { let project = some_or_return!(self.selection.and_then(|i| self.settings.projects.get(i))); let mut open_ide_command = self.settings.open_ide_command.clone(); if let Some(manifest_path_arg) = open_ide_command @@ -968,6 +999,8 @@ impl ProjectManager { "Unable to open the IDE using {} command. Reason: {:?}", open_ide_command, err )); + + self.on_open_settings_click(ui); } } @@ -976,21 +1009,40 @@ impl ProjectManager { self.settings_window = Some(SettingsWindow::new(&self.settings, ctx)); } + fn run_selected_project_command( + &mut self, + action_name: &str, + queue: VecDeque, + ui: &mut UserInterface, + ) { + let index = some_or_return!(self.selection); + let project = some_or_return!(self.settings.projects.get(index)); + self.mode = Mode::CommandExecution { + queue, + process: None, + current_dir: project.manifest_path.parent().unwrap().into(), + }; + self.build_window = Some(BuildWindow::new(action_name, &mut ui.build_ctx())); + } + fn run_build_profile( &mut self, name: &str, build_profile: &BuildProfile, ui: &mut UserInterface, ) { - let index = some_or_return!(self.selection); - let project = some_or_return!(self.settings.projects.get(index)); - - self.mode = Mode::Build { - queue: build_profile.build_and_run_queue(), - process: None, - current_dir: project.manifest_path.parent().unwrap().into(), - }; - self.build_window = Some(BuildWindow::new(name, &mut ui.build_ctx())); + let mut build_profile = build_profile.clone(); + // Force run `cargo update` before running the project to prevent various issues with + // dependency versions incompatibility. + build_profile.build_commands.insert( + 0, + CommandDescriptor { + command: "cargo".to_string(), + args: vec!["update".to_string()], + environment_variables: vec![], + }, + ); + self.run_selected_project_command(name, build_profile.build_and_run_queue(), ui); } fn on_hot_reload_changed(&mut self, value: bool, ui: &mut UserInterface) { @@ -1019,9 +1071,10 @@ impl ProjectManager { KeyCode::KeyC if modifiers.control => self.on_create_clicked(ui), KeyCode::KeyH if modifiers.control => self.on_hot_reload_changed(true, ui), KeyCode::KeyL if modifiers.control => self.on_locate_click(), - KeyCode::KeyO if modifiers.control => self.on_open_ide_click(), + KeyCode::KeyO if modifiers.control => self.on_open_ide_click(ui), KeyCode::KeyS if modifiers.control => self.on_open_settings_click(ui), KeyCode::KeyE if modifiers.control => self.on_exclude_project_clicked(ui), + KeyCode::KeyN if modifiers.control => self.on_clean_clicked(ui), _ => (), } } diff --git a/template-core/src/lib.rs b/template-core/src/lib.rs index 88d66bb16..33979ce44 100644 --- a/template-core/src/lib.rs +++ b/template-core/src/lib.rs @@ -26,18 +26,32 @@ use std::{ collections::HashMap, fmt::Display, fs::{create_dir_all, read_dir, remove_dir_all, File}, - io::{Read, Write}, + io::{Cursor, Read, Write}, path::Path, process::Command, + sync::LazyLock, }; use toml_edit::{table, value, DocumentMut}; use uuid::Uuid; -// Ideally, this should be taken from respective Cargo.toml of the engine and the editor. -// However, it does not seem to work with builds published to crates.io, because when -// the template generator is published, it does not have these Cargo.toml's available -// and to solve this we just hard code these values and pray for the best. -pub const CURRENT_VERSION: &str = "0.36.0"; +// Template generator version must match the engine version. This is checked by the CI. +pub static CURRENT_VERSION: LazyLock = LazyLock::new(|| { + let manifest = include_bytes!("../Cargo.toml"); + + let mut file = Cursor::new(&manifest); + let mut toml = String::new(); + if file.read_to_string(&mut toml).is_ok() { + if let Ok(document) = toml.parse::() { + if let Some(package) = document.get("package").and_then(|i| i.as_table()) { + if let Some(version) = package.get("version") { + return version.to_string().replace('\"', ""); + } + } + } + } + + panic!("Project manager's Cargo.toml is malformed!"); +}); fn write_file, S: AsRef>(path: P, content: S) -> Result<(), String> { let mut file = File::create(path.as_ref()).map_err(|e| e.to_string())?; @@ -582,6 +596,7 @@ fn init_workspace(base_path: &Path, vcs: &str) -> Result<(), String> { } // Write Cargo.toml + let version = &*CURRENT_VERSION; write_file( base_path.join("Cargo.toml"), format!( @@ -591,10 +606,10 @@ members = ["editor", "executor", "executor-wasm", "executor-android", "game", "g resolver = "2" [workspace.dependencies.fyrox] -version = "{CURRENT_VERSION}" +version = "{version}" default-features = false [workspace.dependencies.fyroxed_base] -version = "{CURRENT_VERSION}" +version = "{version}" default-features = false # Separate build profiles for hot reloading. These profiles ensures that build artifacts for @@ -821,10 +836,10 @@ pub fn upgrade_project(root_path: &Path, version: &str, local: bool) -> Result<( dependencies["fyrox_scripts"] = scripts_table; } } else { - dependencies["fyrox"] = value(CURRENT_VERSION); - dependencies["fyroxed_base"] = value(CURRENT_VERSION); + dependencies["fyrox"] = value(&*CURRENT_VERSION); + dependencies["fyroxed_base"] = value(&*CURRENT_VERSION); if dependencies.contains_key("fyrox_scripts") { - dependencies["fyrox_scripts"] = value(CURRENT_VERSION); + dependencies["fyrox_scripts"] = value(&*CURRENT_VERSION); } } } else if version == "nightly" {