Merge remote-tracking branch 'origin/master' into 1.13

# Conflicts:
#	Content/Shaders/Editor/Grid.flax
#	Content/Shaders/GBuffer.flax
#	Content/Shaders/GlobalSignDistanceField.flax
#	Content/Shaders/ProbesFilter.flax
#	Content/Shaders/SSR.flax
#	Content/Shaders/VolumetricFog.flax
#	Development/Documentation/mono.md
#	Source/Editor/Modules/ContentDatabaseModule.cs
This commit is contained in:
2026-08-30 22:23:31 +02:00
34 changed files with 604 additions and 269 deletions
@@ -122,11 +122,8 @@ FlaxStorageReference ContentStorageManager::EnsureAccess(const StringView& path)
// Note: because we want to create new storage package it may exists.
// So let's check if any storage container is referencing that location and try to close it.
auto storage = TryGetStorage(path);
if (storage && storage->IsLoaded())
{
LOG(Info, "File \'{0}\' is in use. Trying to release handle to it.", path);
storage->CloseFileHandles();
}
if (storage && storage->IsLoaded() && storage->CloseFileHandles())
LOG(Warning, "Cannot release content storage handle for '{0}'.", path);
return storage;
}
@@ -1438,10 +1438,6 @@ FileReadStream* FlaxStorage::OpenFile()
bool FlaxStorage::CloseFileHandles()
{
// Guard the whole process so if new thread wants to lock the chunks will need to wait for this to end
Platform::InterlockedIncrement(&_isUnloadingData);
SCOPE_EXIT{ Platform::InterlockedDecrement(&_isUnloadingData); };
if (Platform::AtomicRead(&_chunksLock) == 0 && Platform::AtomicRead(&_files) == 0)
return false; // Early out when no files are opened
PROFILE_CPU();
@@ -1469,7 +1465,13 @@ bool FlaxStorage::CloseFileHandles()
}
}
}
waitTime = 100;
// Guard the whole process so if new thread wants to lock the chunks will need to wait for this to end
Platform::InterlockedIncrement(&_isUnloadingData);
SCOPE_EXIT{ Platform::InterlockedDecrement(&_isUnloadingData); };
// Wait for chunks lock again (with larger timeout for longer tasks)
waitTime = 1000;
while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
Platform::Sleep(1);
if (Platform::AtomicRead(&_chunksLock) != 0)
@@ -901,20 +901,21 @@ namespace FlaxEngine.Interop
Assembly assembly;
#if FLAX_EDITOR
// Load assembly from loaded bytes to prevent file locking in Editor
var assemblyBytes = File.ReadAllBytes(assemblyPath);
using MemoryStream stream = new MemoryStream(assemblyBytes);
// Load assembly with stream to prevent runtime from locking the assembly file
using FileStream stream = new FileStream(assemblyPath, FileMode.Open, FileAccess.Read);
var pdbPath = Path.ChangeExtension(assemblyPath, "pdb");
if (File.Exists(pdbPath))
{
// Load including debug symbols
using FileStream pdbStream = new FileStream(Path.ChangeExtension(assemblyPath, "pdb"), FileMode.Open);
using FileStream pdbStream = new FileStream(Path.ChangeExtension(assemblyPath, "pdb"), FileMode.Open, FileAccess.Read);
assembly = scriptingAssemblyLoadContext.LoadFromStream(stream, pdbStream);
}
else
{
assembly = scriptingAssemblyLoadContext.LoadFromStream(stream);
}
// TODO: Use new .NET 11 AssemblyLoadContext.SetAssemblyLocationOverride to specify correct Assembly.Location
#else
// Load assembly from file
assembly = scriptingAssemblyLoadContext.LoadFromAssemblyPath(assemblyPath);
+1 -1
View File
@@ -197,7 +197,7 @@ bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* ve
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
{
return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R16_UInt, vertices, triangles, normals, tangents, uvs, colors);
return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R32_UInt, vertices, triangles, normals, tangents, uvs, colors);
}
bool Mesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer)
@@ -305,7 +305,7 @@ bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Flo
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Int4* blendIndices, const Float4* blendWeights, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
{
return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R16_UInt, vertices, triangles, blendIndices, blendWeights, normals, tangents, uvs, colors);
return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R32_UInt, vertices, triangles, blendIndices, blendWeights, normals, tangents, uvs, colors);
}
void SkinnedMesh::Draw(const RenderContext& renderContext, const SkinnedMeshBones& pose, MaterialBase* material, const Matrix& world, StaticFlags flags, bool receiveDecals, DrawPass drawModes, float perInstanceRandom, int8 sortOrder, uint8 stencilValue) const
+5
View File
@@ -28,6 +28,11 @@ API_ENUM() enum class ClosingReason
/// The close event.
/// </summary>
CloseEvent,
/// <summary>
/// The scripts reload event.
/// </summary>
ScriptsReload,
};
/// <summary>
@@ -26,6 +26,8 @@
#if USE_EDITOR
#define COMPILE_WITH_ASSETS_IMPORTER 1 // Hack to use shaders importing in this module
#include "Engine/ContentImporters/AssetsImportingManager.h"
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/Utilities/Encryption.h"
#include "Engine/Platform/FileSystemWatcher.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/File.h"
@@ -499,6 +501,37 @@ String ShadersCompilation::CompactShaderPath(StringView path)
#if USE_EDITOR
bool ShadersCompilation::IsShaderSourceAssetUpToDate(const StringView& sourcePath, const StringView& assetPath)
{
PROFILE_CPU();
StringAnsi source;
if (File::ReadAllText(sourcePath, source))
return false;
if (!source.HasChars() || source[source.Length() - 1] != '\n')
source.Append('\n');
const auto storage = ContentStorageManager::GetStorage(assetPath);
AssetInitData data;
if (!storage
|| storage->GetEntriesCount() != 1
|| storage->GetEntry(0).TypeName != Shader::TypeName
|| storage->LoadAssetHeader(0, data)
|| data.SerializedVersion != Shader::SerializedVersion)
return false;
FlaxChunk* sourceChunk = data.Header.Chunks[SHADER_FILE_CHUNK_SOURCE];
if (!sourceChunk || storage->LoadAssetChunk(sourceChunk) || !sourceChunk->Data.IsValid())
return false;
BytesContainer embeddedSource;
embeddedSource.Copy(sourceChunk->Data);
if (embeddedSource.Length() != source.Length() + 1)
return false;
Encryption::DecryptBytes(embeddedSource.Get(), embeddedSource.Length());
embeddedSource.Get()[embeddedSource.Length() - 1] = 0;
return Platform::MemoryCompare(embeddedSource.Get(), source.Get(), source.Length()) == 0;
}
namespace
{
Array<FileSystemWatcher*> ShadersSourcesWatchers;
@@ -515,6 +548,13 @@ namespace
return result;
}
bool ImportShaderIfChanged(const StringView& sourcePath, const StringView& assetPath, Guid& assetId)
{
if (ShadersCompilation::IsShaderSourceAssetUpToDate(sourcePath, assetPath))
return false;
return AssetsImportingManager::Import(sourcePath, assetPath, assetId);
}
void OnWatcherShadersEvent(const String& path, FileSystemAction action)
{
if (action == FileSystemAction::Delete || !path.EndsWith(TEXT(".shader")))
@@ -538,7 +578,7 @@ namespace
const String name = StringUtils::GetPathWithoutExtension(localPath);
const String outputPath = shadersAssetsPath / name + ASSET_FILES_EXTENSION_WITH_DOT;
Guid id = GetShaderAssetId(name);
AssetsImportingManager::ImportIfEdited(path, outputPath, id);
ImportShaderIfChanged(path, outputPath, id);
}
void RegisterShaderWatchers(const ProjectInfo* project, HashSet<const ProjectInfo*>& projects)
@@ -567,7 +607,7 @@ namespace
const String name = StringUtils::GetPathWithoutExtension(localPath);
const String outputPath = shadersAssetsPath / name + ASSET_FILES_EXTENSION_WITH_DOT;
Guid id = GetShaderAssetId(name);
AssetsImportingManager::ImportIfEdited(path, outputPath, id);
ImportShaderIfChanged(path, outputPath, id);
}
}
@@ -47,6 +47,16 @@ public:
// Compacts the full shader file path into portable format with project name prefix such as './<ProjectName>/ShaderFile.hlsl'.
static String CompactShaderPath(StringView path);
#if USE_EDITOR
/// <summary>
/// Checks whether a shader asset embeds the current source file contents.
/// </summary>
/// <param name="sourcePath">The shader source file path.</param>
/// <param name="assetPath">The shader asset file path.</param>
/// <returns>True when the embedded source matches, otherwise false.</returns>
static bool IsShaderSourceAssetUpToDate(const StringView& sourcePath, const StringView& assetPath);
#endif
private:
static ShaderCompiler* RequestCompiler(ShaderProfile profile, PlatformType platform);
static void FreeCompiler(ShaderCompiler* compiler);
@@ -26,6 +26,29 @@ namespace FlaxEngine.Tests
}
}
private sealed class RemovingControl : MyControl
{
private readonly Control[] _controlsToRemove;
public RemovingControl(float x, float y, float width, float height, params Control[] controlsToRemove)
: base(x, y, width, height)
{
_controlsToRemove = controlsToRemove;
}
public override void OnMouseEnter(Float2 location)
{
for (int i = 0; i < _controlsToRemove.Length; i++)
{
var control = _controlsToRemove[i];
if (control.Parent == Parent)
control.Parent = null;
}
base.OnMouseEnter(location);
}
}
[Test]
public void TestChildren()
{
@@ -53,6 +76,25 @@ namespace FlaxEngine.Tests
Assert.AreEqual(cc1.GetChildAt(new Vector2(15, 5)), cc2);
Assert.AreEqual(cc1.GetChildAtRecursive(new Vector2(35, 25)), c3);
}
[Test]
public void TestMouseMoveAllowsChildrenRemoval()
{
var container = new MyContainerControl(0, 0, 100, 100);
var first = new MyControl(0, 0, 100, 100);
var second = new MyControl(0, 0, 100, 100);
var removing = new RemovingControl(0, 0, 100, 100, first, second);
container.AddChild(first);
container.AddChild(second);
container.AddChild(removing);
// The top-most child removes multiple siblings during input dispatch.
// Traversal must not use the now-stale next index.
container.OnMouseMove(new Float2(50, 50));
Assert.AreEqual(1, container.ChildrenCount);
Assert.AreEqual(removing, container.GetChild(0));
}
}
}
#endif
@@ -0,0 +1,42 @@
// Copyright (c) Wojciech Figat. All rights reserved.
#include "Engine/Core/ScopeExit.h"
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/ShadersCompilation/ShadersCompilation.h"
#include <ThirdParty/catch2/catch.hpp>
#if COMPILE_WITH_SHADER_COMPILER && USE_EDITOR
TEST_CASE("Shader source asset synchronization ignores timestamps")
{
const String sourcePath = Globals::StartupFolder / TEXT("Source/Shaders/VolumetricFog.shader");
const String assetPath = Globals::EngineContentFolder / TEXT("Shaders/VolumetricFog.flax");
REQUIRE(FileSystem::FileExists(sourcePath));
REQUIRE(FileSystem::FileExists(assetPath));
CHECK(ShadersCompilation::IsShaderSourceAssetUpToDate(sourcePath, assetPath));
const String tempRoot = Globals::TemporaryFolder / (TEXT("ShaderSourceSync-") + Guid::New().ToString(Guid::FormatType::N));
REQUIRE(!FileSystem::CreateDirectory(tempRoot));
SCOPE_EXIT
{
FileSystem::DeleteDirectory(tempRoot, true);
};
DataContainer<byte> assetData;
StringAnsi modifiedSource;
REQUIRE(!File::ReadAllBytes(assetPath, assetData));
REQUIRE(!File::ReadAllText(sourcePath, modifiedSource));
modifiedSource.Append("// Deliberately different source\n");
const String tempSourcePath = tempRoot / TEXT("VolumetricFog.shader");
const String tempAssetPath = tempRoot / TEXT("VolumetricFog.flax");
REQUIRE(!File::WriteAllBytes(tempSourcePath, modifiedSource.Get(), modifiedSource.Length()));
REQUIRE(!File::WriteAllBytes(tempAssetPath, assetData.Get(), assetData.Length()));
CHECK_FALSE(FileSystem::GetFileLastEditTime(tempSourcePath) > FileSystem::GetFileLastEditTime(tempAssetPath));
CHECK_FALSE(ShadersCompilation::IsShaderSourceAssetUpToDate(tempSourcePath, tempAssetPath));
}
#endif
+1
View File
@@ -21,6 +21,7 @@ public class Tests : EngineModule
base.Setup(options);
options.PrivateDependencies.Add("ModelTool");
options.PrivateDependencies.Add("ShadersCompilation");
}
/// <inheritdoc />
@@ -20,26 +20,29 @@
#include <ThirdParty/assimp/scene.h>
#include <ThirdParty/assimp/version.h>
#include <ThirdParty/assimp/postprocess.h>
#include <ThirdParty/assimp/LogStream.hpp>
#include <ThirdParty/assimp/DefaultLogger.hpp>
#include <ThirdParty/assimp/Logger.hpp>
class AssimpLogStream : public Assimp::LogStream
class AssimpLogger final : public Assimp::Logger
{
public:
AssimpLogStream()
AssimpLogger()
: Logger(NORMAL)
{
Assimp::DefaultLogger::create("");
Assimp::DefaultLogger::get()->attachStream(this);
}
~AssimpLogStream()
bool attachStream(Assimp::LogStream*, unsigned int) override
{
Assimp::DefaultLogger::get()->detachStream(this);
Assimp::DefaultLogger::kill();
return false;
}
void write(const char* message) override
bool detachStream(Assimp::LogStream*, unsigned int) override
{
return false;
}
private:
static void Write(const Char* type, const char* message)
{
String s(message);
if (s.Length() <= 0)
@@ -52,10 +55,49 @@ public:
else if (c >= 255)
c = '?';
}
LOG(Info, "[Assimp]: {0}", s);
LOG(Info, "[Assimp]: {0}: {1}", type, s);
}
void OnDebug(const char* message) override
{
if (m_Severity >= DEBUGGING)
Write(TEXT("Debug"), message);
}
void OnVerboseDebug(const char* message) override
{
if (m_Severity >= VERBOSE)
Write(TEXT("Debug"), message);
}
void OnInfo(const char* message) override
{
Write(TEXT("Info"), message);
}
void OnWarn(const char* message) override
{
Write(TEXT("Warn"), message);
}
void OnError(const char* message) override
{
Write(TEXT("Error"), message);
}
};
Assimp::Logger* GetAssimpLogger()
{
static Assimp::Logger* logger = []()
{
auto result = new AssimpLogger();
Assimp::DefaultLogger::set(result);
LOG(Info, "Assimp {0}.{1}.{2}", aiGetVersionMajor(), aiGetVersionMinor(), aiGetVersionRevision());
return result;
}();
return logger;
}
Float2 ToFloat2(const aiVector2D& v)
{
return Float2(v.x, v.y);
@@ -148,7 +190,6 @@ struct AssimpBone
struct AssimpImporterData
{
Assimp::Importer AssimpImporter;
AssimpLogStream AssimpLogStream;
const String Path;
const aiScene* Scene = nullptr;
const ModelTool::Options& Options;
@@ -705,12 +746,7 @@ void ImportAnimation(int32 index, ModelData& data, AssimpImporterData& importerD
bool ModelTool::ImportDataAssimp(const String& path, ModelData& data, Options& options, String& errorMsg)
{
static bool AssimpInited = false;
if (!AssimpInited)
{
AssimpInited = true;
LOG(Info, "Assimp {0}.{1}.{2}", aiGetVersionMajor(), aiGetVersionMinor(), aiGetVersionRevision());
}
GetAssimpLogger();
bool importMeshes = EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Geometry);
bool importAnimations = EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Animations);
AssimpImporterData context(path, options);
+14 -14
View File
@@ -910,7 +910,7 @@ namespace FlaxEngine.GUI
return false;
}
}
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible)
@@ -928,7 +928,7 @@ namespace FlaxEngine.GUI
public override void OnMouseEnter(Float2 location)
{
// Check all children collisions with mouse and fire events for them
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -948,7 +948,7 @@ namespace FlaxEngine.GUI
public override void OnMouseMove(Float2 location)
{
// Check all children collisions with mouse and fire events for them
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -998,7 +998,7 @@ namespace FlaxEngine.GUI
public override bool OnMouseWheel(Float2 location, float delta)
{
// Check all children collisions with mouse and fire events for them
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -1019,7 +1019,7 @@ namespace FlaxEngine.GUI
public override bool OnMouseDown(Float2 location, MouseButton button)
{
// Check all children collisions with mouse and fire events for them
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -1040,7 +1040,7 @@ namespace FlaxEngine.GUI
public override bool OnMouseUp(Float2 location, MouseButton button)
{
// Check all children collisions with mouse and fire events for them
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -1061,7 +1061,7 @@ namespace FlaxEngine.GUI
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
// Check all children collisions with mouse and fire events for them
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -1096,7 +1096,7 @@ namespace FlaxEngine.GUI
/// <inheritdoc />
public override void OnTouchEnter(Float2 location, int pointerId)
{
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled && !child.IsTouchPointerOver(pointerId))
@@ -1114,7 +1114,7 @@ namespace FlaxEngine.GUI
/// <inheritdoc />
public override bool OnTouchDown(Float2 location, int pointerId)
{
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -1139,7 +1139,7 @@ namespace FlaxEngine.GUI
/// <inheritdoc />
public override void OnTouchMove(Float2 location, int pointerId)
{
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -1168,7 +1168,7 @@ namespace FlaxEngine.GUI
/// <inheritdoc />
public override bool OnTouchUp(Float2 location, int pointerId)
{
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled && child.IsTouchPointerOver(pointerId))
@@ -1250,7 +1250,7 @@ namespace FlaxEngine.GUI
var result = base.OnDragEnter(ref location, data);
// Check all children collisions with mouse and fire events for them
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -1275,7 +1275,7 @@ namespace FlaxEngine.GUI
var result = base.OnDragMove(ref location, data);
// Check all children collisions with mouse and fire events for them
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -1333,7 +1333,7 @@ namespace FlaxEngine.GUI
var result = base.OnDragDrop(ref location, data);
// Check all children collisions with mouse and fire events for them
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)