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);
+ }
+ }
+}