diff --git a/Source/Editor/CustomEditors/Editors/IBrushEditor.cs b/Source/Editor/CustomEditors/Editors/IBrushEditor.cs index b32f58923..ce5207414 100644 --- a/Source/Editor/CustomEditors/Editors/IBrushEditor.cs +++ b/Source/Editor/CustomEditors/Editors/IBrushEditor.cs @@ -26,6 +26,7 @@ namespace FlaxEditor.CustomEditors.Editors new OptionType("Texture 9-Slicing", typeof(Texture9SlicingBrush)), new OptionType("Sprite 9-Slicing", typeof(Sprite9SlicingBrush)), new OptionType("Video", typeof(VideoBrush)), + new OptionType("UI Brush", typeof(UIBrush)), }; } } diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index 68ac9f47a..322a08d64 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -287,10 +287,7 @@ namespace FlaxEditor.CustomEditors /// The created element. public ImageElement Image(SpriteHandle sprite) { - var element = new ImageElement(); - element.Image.Brush = new SpriteBrush(sprite); - OnAddElement(element); - return element; + return Image(new SpriteBrush(sprite)); } /// @@ -300,10 +297,7 @@ namespace FlaxEditor.CustomEditors /// The created element. public ImageElement Image(Texture texture) { - var element = new ImageElement(); - element.Image.Brush = new TextureBrush(texture); - OnAddElement(element); - return element; + return Image(new TextureBrush(texture)); } /// @@ -312,9 +306,19 @@ namespace FlaxEditor.CustomEditors /// The GPU texture. /// The created element. public ImageElement Image(GPUTexture texture) + { + return Image(new GPUTextureBrush(texture)); + } + + /// + /// Adds brush image to the layout. + /// + /// The brush. + /// The created element. + public ImageElement Image(IBrush brush) { var element = new ImageElement(); - element.Image.Brush = new GPUTextureBrush(texture); + element.Image.Brush = brush; OnAddElement(element); return element; } diff --git a/Source/Editor/Surface/Archetypes/Textures.cs b/Source/Editor/Surface/Archetypes/Textures.cs index 1e7dfb56f..56f8154d7 100644 --- a/Source/Editor/Surface/Archetypes/Textures.cs +++ b/Source/Editor/Surface/Archetypes/Textures.cs @@ -459,7 +459,7 @@ namespace FlaxEditor.Surface.Archetypes AlternativeTitles = new string[] { "Lightmap TexCoord" }, Description = "Lightmap UVs", Flags = NodeFlags.MaterialGraph, - Size = new Float2(110, 30), + Size = new Float2(110, 20), Elements = new [] { NodeElementArchetype.Factory.Output(0, "UVs", typeof(Float2), 0) @@ -493,6 +493,19 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Bool(190, Surface.Constants.LayoutOffsetY * 4, 4), } }, + new NodeArchetype + { + TypeID = 24, + Title = "Texture Size", + Description = "Gets the size of the texture (in pixels). If texture is during streaming, then returns size of the highest resident mip.", + Flags = NodeFlags.ParticleEmitterGraph, + Size = new Float2(160, 20), + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Texture", true, typeof(FlaxEngine.Object), 0), + NodeElementArchetype.Factory.Output(0, "Size", typeof(Float3), 1), + } + }, }; } } diff --git a/Source/Engine/Level/Actors/EnvironmentProbe.cpp b/Source/Engine/Level/Actors/EnvironmentProbe.cpp index 3c49611d4..71b403eb9 100644 --- a/Source/Engine/Level/Actors/EnvironmentProbe.cpp +++ b/Source/Engine/Level/Actors/EnvironmentProbe.cpp @@ -197,6 +197,7 @@ void EnvironmentProbe::Draw(RenderContext& renderContext) data.Position = position; data.Radius = radius; data.Brightness = Brightness; + data.SortOrder = SortOrder; data.HashID = GetHash(_id); renderContext.List->EnvironmentProbes.Add(data); } @@ -234,6 +235,7 @@ void EnvironmentProbe::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_MEMBER(Radius, _radius); SERIALIZE(CubemapResolution); SERIALIZE(Brightness); + SERIALIZE(SortOrder); SERIALIZE(UpdateMode); SERIALIZE(CaptureNearPlane); SERIALIZE_MEMBER(IsCustomProbe, _isUsingCustomProbe); @@ -248,6 +250,7 @@ void EnvironmentProbe::Deserialize(DeserializeStream& stream, ISerializeModifier DESERIALIZE_MEMBER(Radius, _radius); DESERIALIZE(CubemapResolution); DESERIALIZE(Brightness); + DESERIALIZE(SortOrder); DESERIALIZE(UpdateMode); DESERIALIZE(CaptureNearPlane); DESERIALIZE_MEMBER(IsCustomProbe, _isUsingCustomProbe); diff --git a/Source/Engine/Level/Actors/EnvironmentProbe.h b/Source/Engine/Level/Actors/EnvironmentProbe.h index 7131dc0ba..09f771437 100644 --- a/Source/Engine/Level/Actors/EnvironmentProbe.h +++ b/Source/Engine/Level/Actors/EnvironmentProbe.h @@ -51,6 +51,12 @@ public: API_FIELD(Attributes="EditorOrder(10), Limit(0, 1000, 0.01f), EditorDisplay(\"Probe\")") float Brightness = 1.0f; + /// + /// The probe rendering order. The higher values are render later (on top). + /// + API_FIELD(Attributes = "EditorOrder(25), EditorDisplay(\"Probe\")") + int32 SortOrder = 0; + /// /// The probe update mode. /// diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp index 58058afc1..a47e15275 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp @@ -162,6 +162,13 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTextures(Box* box, Node* node, value = Value::Zero; break; } + // Texture Size + case 24: + { + // TODO: support sampling textures in CPU particles + value = Value::Zero; + break; + } default: break; } diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp index 355548e37..820f8da3d 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp @@ -34,8 +34,8 @@ bool ParticleEmitterGPUGenerator::loadTexture(Node* caller, Box* box, const Seri Value location = tryGetValue(locationBox, Value::InitForZero(VariantType::Float2)); // Convert into a proper type - if (isCubemap || isVolume || isArray) - location = Value::Cast(location, VariantType::Float3); + if (isVolume || isArray) + location = Value::Cast(location, VariantType::Float4); else location = Value::Cast(location, VariantType::Float3); @@ -332,6 +332,21 @@ void ParticleEmitterGPUGenerator::ProcessGroupTextures(Box* box, Node* node, Val value = box == gradientBox ? gradient : distance; break; } + // Texture Size + case 24: + { + value = Value::Zero; + auto textureBox = node->GetBox(0); + if (!textureBox->HasConnection()) + break; + const auto texture = eatBox(textureBox->GetParent(), textureBox->FirstConnection()); + const auto textureParam = findParam(texture.Value); + if (!textureParam) + break; + value = writeLocal(VariantType::Float2, node); + _writer.Write(TEXT("\t{0}.GetDimensions({1}.x, {1}.y);\n"), textureParam->ShaderName, value.Value); + break; + } default: break; } diff --git a/Source/Engine/Particles/ParticleEmitter.cpp b/Source/Engine/Particles/ParticleEmitter.cpp index 0be85f136..3e1847b67 100644 --- a/Source/Engine/Particles/ParticleEmitter.cpp +++ b/Source/Engine/Particles/ParticleEmitter.cpp @@ -9,7 +9,9 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Types/DataContainer.h" #include "Engine/Graphics/Shaders/Cache/ShaderCacheManager.h" +#include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Level/Level.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Serialization/MemoryWriteStream.h" #include "Engine/Threading/Threading.h" @@ -60,6 +62,35 @@ ParticleEffect* ParticleEmitter::Spawn(Actor* parent, const Transform& transform return effect; } +void ParticleEmitter::WaitForAsset(Asset* asset) +{ + // TODO: make a tool for it? + if (auto* texture = Cast(asset)) + { + // Wait for asset to be loaded + if (!texture->WaitForLoaded() && !IsInMainThread() && texture->GetTexture()) + { + PROFILE_CPU_NAMED("WaitForTexture"); + + // Mark as used by rendering and wait for streaming to load it in + double waitTimeSeconds = 10000; + double now = Platform::GetTimeSeconds(); + double end = now + waitTimeSeconds; + const int32 mipLevels = texture->GetMipLevels(); + constexpr float requestedResidency = 0.7f; + const int32 minMipLevels = Math::Max(Math::Min(mipLevels, 7), (int32)(requestedResidency * (float)mipLevels)); + while (texture->GetResidentMipLevels() < minMipLevels && + now < end && + !texture->HasStreamingError()) + { + texture->GetTexture()->LastRenderTime = now; + Platform::Sleep(1); + now = Platform::GetTimeSeconds(); + } + } + } +} + #if COMPILE_WITH_PARTICLE_GPU_GRAPH && COMPILE_WITH_SHADER_COMPILER namespace @@ -297,6 +328,40 @@ Asset::LoadResult ParticleEmitter::load() SimulationMode = ParticlesSimulationMode::CPU; #endif + // Wait for resources used by the emitter to be loaded + // eg. texture used to place particles on spawn needs to be available + bool waitForAsset = false; + for (const auto& node : Graph.Nodes) + { + if ((node.Type == GRAPH_NODE_MAKE_TYPE(5, 1) || node.Type == GRAPH_NODE_MAKE_TYPE(5, 2)) && node.Assets.Count() > 0) + { + const auto texture = node.Assets[0].As(); + if (texture) + { + if (!waitForAsset) + { + waitForAsset = true; + Particles::SystemLocker.End(true); + } + WaitForAsset(texture); + } + } + } + for (const auto& parameter : Graph.Parameters) + { + if (parameter.Type.Type == VariantType::Asset) + { + if (!waitForAsset) + { + waitForAsset = true; + Particles::SystemLocker.End(true); + } + WaitForAsset((Asset*)parameter.Value); + } + } + if (waitForAsset) + Particles::SystemLocker.Begin(true); + return LoadResult::Ok; } diff --git a/Source/Engine/Particles/ParticleEmitter.h b/Source/Engine/Particles/ParticleEmitter.h index 772f5569e..1398de5db 100644 --- a/Source/Engine/Particles/ParticleEmitter.h +++ b/Source/Engine/Particles/ParticleEmitter.h @@ -168,6 +168,9 @@ public: /// The spawned effect. API_FUNCTION() ParticleEffect* Spawn(Actor* parent, const Transform& transform, float duration = MAX_float, bool autoDestroy = false); +private: + void WaitForAsset(Asset* asset); + public: // [BinaryAsset] #if USE_EDITOR diff --git a/Source/Engine/Renderer/ReflectionsPass.cpp b/Source/Engine/Renderer/ReflectionsPass.cpp index 3aa0ed6ee..631543010 100644 --- a/Source/Engine/Renderer/ReflectionsPass.cpp +++ b/Source/Engine/Renderer/ReflectionsPass.cpp @@ -335,12 +335,17 @@ void ReflectionsPass::Dispose() bool SortProbes(RenderEnvironmentProbeData const& p1, RenderEnvironmentProbeData const& p2) { - // Compare by radius - int32 res = static_cast(p2.Radius - p1.Radius); + // Compare by Sort Order + int32 res = p1.SortOrder - p2.SortOrder; if (res == 0) { - // Compare by ID to prevent flickering - res = p2.HashID - p1.HashID; + // Compare by radius + res = static_cast(p2.Radius - p1.Radius); + if (res == 0) + { + // Compare by ID to prevent flickering + res = p2.HashID - p1.HashID; + } } return res < 0; } diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index 469bc2cdd..d5288e6ee 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -155,6 +155,7 @@ struct RenderEnvironmentProbeData Float3 Position; float Radius; float Brightness; + int32 SortOrder; uint32 HashID; void SetShaderData(ShaderEnvProbeData& data) const; diff --git a/Source/Engine/UI/GUI/Brushes/UIBrush.cs b/Source/Engine/UI/GUI/Brushes/UIBrush.cs new file mode 100644 index 000000000..4441899c0 --- /dev/null +++ b/Source/Engine/UI/GUI/Brushes/UIBrush.cs @@ -0,0 +1,75 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +namespace FlaxEngine.GUI +{ + /// + /// Asset with that can be reused in different UI controls. + /// + /// + /// + public class UIBrushAsset + { + /// + /// Brush object. + /// + public IBrush Brush; + } + + /// + /// Implementation of for frame displaying. + /// + /// + /// + public sealed class UIBrush : IBrush + { + /// + /// The UI Brush asset to use. + /// + public JsonAssetReference Asset; + + /// + /// Initializes a new instance of the class. + /// + public UIBrush() + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The UI Brush asset to use. + public UIBrush(JsonAssetReference asset) + { + Asset = asset; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The UI Brush asset to use. + public UIBrush(JsonAsset asset) + { + Asset = asset; + } + + /// + public Float2 Size + { + get + { + var asset = (UIBrushAsset)Asset.Asset?.Instance; + if (asset != null && asset.Brush != null) + return asset.Brush.Size; + return Float2.Zero; + } + } + + /// + public void Draw(Rectangle rect, Color color) + { + var asset = (UIBrushAsset)Asset.Asset?.Instance; + if (asset != null && asset.Brush != null) + asset.Brush.Draw(rect, color); + } + } +}