Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ Editor/Resources/Cache
Lux.slnx
Editor/SandboxProject/Assets/Cache/Thumbnails
Editor/SandboxProject/Assets/AssetRegistry.lzr
*.lap
*.aps
35 changes: 35 additions & 0 deletions Core/Source/Lux/Project/Project.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,28 @@ namespace Lux
if (!s_ActiveProject)
return;
Comment on lines 69 to 70
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not skip audio shutdown when clearing the active runtime project.

Line 70 returns on nullptr and bypasses the cleanup block at Line 80, so Project::SetActiveRuntime(nullptr, nullptr) can leave the audio engine initialized.

🛠️ Suggested fix
 		s_ActiveProject = project;
-		if (!s_ActiveProject)
-			return;
-
-		if (s_ActiveProject->m_ProjectDirectory.empty())
-			s_ActiveProject->m_ProjectDirectory = s_ActiveProject->m_Config.ProjectDirectory;
-		if (s_ActiveProject->m_ProjectFilePath.empty() && !s_ActiveProject->m_ProjectDirectory.empty())
-			s_ActiveProject->m_ProjectFilePath = s_ActiveProject->m_ProjectDirectory / s_ActiveProject->m_Config.ProjectFileName;
-
-		s_ActiveProject->m_Config.ProjectDirectory = s_ActiveProject->m_ProjectDirectory;
-		s_ActiveProject->m_Config.ProjectFileName = s_ActiveProject->m_ProjectFilePath.filename().string();
-
 		if (AudioEngine::HasInitializedEngine())
 		{
 			AudioEngine::Shutdown();
 			AudioEngine::SetInitalizedEngine(false);
 		}
+
+		if (!s_ActiveProject)
+			return;
+
+		if (s_ActiveProject->m_ProjectDirectory.empty())
+			s_ActiveProject->m_ProjectDirectory = s_ActiveProject->m_Config.ProjectDirectory;
+		if (s_ActiveProject->m_ProjectFilePath.empty() && !s_ActiveProject->m_ProjectDirectory.empty())
+			s_ActiveProject->m_ProjectFilePath = s_ActiveProject->m_ProjectDirectory / s_ActiveProject->m_Config.ProjectFileName;
+
+		s_ActiveProject->m_Config.ProjectDirectory = s_ActiveProject->m_ProjectDirectory;
+		s_ActiveProject->m_Config.ProjectFileName = s_ActiveProject->m_ProjectFilePath.filename().string();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!s_ActiveProject)
return;
s_ActiveProject = project;
if (AudioEngine::HasInitializedEngine())
{
AudioEngine::Shutdown();
AudioEngine::SetInitalizedEngine(false);
}
if (!s_ActiveProject)
return;
if (s_ActiveProject->m_ProjectDirectory.empty())
s_ActiveProject->m_ProjectDirectory = s_ActiveProject->m_Config.ProjectDirectory;
if (s_ActiveProject->m_ProjectFilePath.empty() && !s_ActiveProject->m_ProjectDirectory.empty())
s_ActiveProject->m_ProjectFilePath = s_ActiveProject->m_ProjectDirectory / s_ActiveProject->m_Config.ProjectFileName;
s_ActiveProject->m_Config.ProjectDirectory = s_ActiveProject->m_ProjectDirectory;
s_ActiveProject->m_Config.ProjectFileName = s_ActiveProject->m_ProjectFilePath.filename().string();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Source/Lux/Project/Project.cpp` around lines 69 - 70, The early return
when s_ActiveProject is null in Project::SetActiveRuntime skips the cleanup that
should uninitialize the audio engine; remove the early return (or move the
cleanup block above it) so the audio shutdown logic always runs when
SetActiveRuntime(nullptr, nullptr) is called, and explicitly invoke the audio
cleanup/uninitialize routine used by the project (the audio shutdown function
your project normally calls during project teardown) as part of the same cleanup
sequence to ensure the audio engine is always shut down even if s_ActiveProject
is nullptr.


if (s_ActiveProject->m_ProjectDirectory.empty())
s_ActiveProject->m_ProjectDirectory = s_ActiveProject->m_Config.ProjectDirectory;
if (s_ActiveProject->m_ProjectFilePath.empty() && !s_ActiveProject->m_ProjectDirectory.empty())
s_ActiveProject->m_ProjectFilePath = s_ActiveProject->m_ProjectDirectory / s_ActiveProject->m_Config.ProjectFileName;

s_ActiveProject->m_Config.ProjectDirectory = s_ActiveProject->m_ProjectDirectory;
s_ActiveProject->m_Config.ProjectFileName = s_ActiveProject->m_ProjectFilePath.filename().string();

if (AudioEngine::HasInitializedEngine())
{
AudioEngine::Shutdown();
AudioEngine::SetInitalizedEngine(false);
}

s_AssetManager = Ref<RuntimeAssetManager>::Create();
GetRuntimeAssetManager()->SetAssetPack(assetPack);

if (!AudioEngine::HasInitializedEngine())
{
AudioEngine::Init();
AudioEngine::SetInitalizedEngine(true);
}
}

Ref<Project> Project::New()
Expand All @@ -83,6 +101,23 @@ namespace Lux
return s_ActiveProject;
}

Ref<Project> Project::LoadRuntime(const std::filesystem::path& path, Ref<AssetPack> assetPack)
{
Ref<Project> project = Ref<Project>::Create();

ProjectSerializer serializer(project);
if (!serializer.DeserializeRuntime(path))
return nullptr;

project->m_ProjectFilePath = path.lexically_normal();
project->m_ProjectDirectory = path.parent_path();
project->m_Config.ProjectDirectory = project->m_ProjectDirectory;
project->m_Config.ProjectFileName = project->m_ProjectFilePath.filename().string();
Comment on lines +112 to +115

SetActiveRuntime(project, assetPack);
return s_ActiveProject;
}

Ref<Project> Project::Load(const std::filesystem::path& path)
{
Ref<Project> project = Ref<Project>::Create();
Expand Down
1 change: 1 addition & 0 deletions Core/Source/Lux/Project/Project.h
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ namespace Lux

static Ref<Project> New();
static Ref<Project> Load(const std::filesystem::path& path);
static Ref<Project> LoadRuntime(const std::filesystem::path& path, Ref<AssetPack> assetPack);
static bool SaveActive(const std::filesystem::path& path);

private:
Expand Down
62 changes: 55 additions & 7 deletions Core/Source/Lux/Renderer/Framebuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,31 @@

#include "Lux/Core/Application.h"
#include "Lux/Renderer/Renderer.h"
#include "Lux/Platform/Vulkan/VulkanSwapChain.h"

namespace Lux {

namespace
{
static void PopulateClearValues(const FramebufferSpecification& specification, std::vector<ClearValue>& clearValues)
{
clearValues.resize(specification.Attachments.Attachments.size());

for (uint32_t attachmentIndex = 0; attachmentIndex < specification.Attachments.Attachments.size(); attachmentIndex++)
{
const auto& attachmentSpec = specification.Attachments.Attachments[attachmentIndex];
if (Utils::IsDepthFormat(attachmentSpec.Format))
{
clearValues[attachmentIndex].DepthStencil = { specification.DepthClearValue, 0 };
continue;
}

const auto& clearColor = specification.ClearColor;
clearValues[attachmentIndex].Color = { { clearColor.r, clearColor.g, clearColor.b, clearColor.a } };
}
}
}

#if WENEEDTODEALWITHTHIS
namespace Utils {

Expand Down Expand Up @@ -39,6 +61,12 @@ namespace Lux {
m_Height = (uint32_t)(specification.Height * m_Specification.Scale);
}

if (m_Specification.SwapChainTarget)
{
PopulateClearValues(m_Specification, m_ClearValues);
return;
}
Comment on lines +64 to +68
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a swapchain-target attachment guard before the early return.

Line 67 returns before the constructor reaches the general attachment validation, so a swapchain framebuffer with zero attachments can leave m_ClearValues empty and later break clear indexing during render-pass setup.

🛠️ Suggested fix
 		if (m_Specification.SwapChainTarget)
 		{
+			LUX_CORE_ASSERT(!m_Specification.Attachments.Attachments.empty(), "SwapChainTarget framebuffer requires at least one attachment");
 			PopulateClearValues(m_Specification, m_ClearValues);
 			return;
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Source/Lux/Renderer/Framebuffer.cpp` around lines 64 - 68, Add a guard
when handling m_Specification.SwapChainTarget in the Framebuffer constructor so
we don't early-return with zero attachments: before the "if
(m_Specification.SwapChainTarget) { PopulateClearValues(...); return; }" check,
validate that m_Specification.Attachments is not empty (or assert/log+return)
and fail-fast if it is, ensuring PopulateClearValues is only called when there
are attachments and preventing m_ClearValues from remaining empty; reference
m_Specification.SwapChainTarget, m_Specification.Attachments,
PopulateClearValues, and m_ClearValues.


// Create all image objects immediately so we can start referencing them
// elsewhere
uint32_t attachmentIndex = 0;
Expand Down Expand Up @@ -96,6 +124,30 @@ namespace Lux {
Release();
}

uint32_t Framebuffer::GetWidth() const
{
if (m_Specification.SwapChainTarget)
return Application::Get().GetWindow().GetSwapChain().GetWidth();

return m_Width;
}

uint32_t Framebuffer::GetHeight() const
{
if (m_Specification.SwapChainTarget)
return Application::Get().GetWindow().GetSwapChain().GetHeight();

return m_Height;
}

nvrhi::FramebufferHandle Framebuffer::GetHandle() const
{
if (m_Specification.SwapChainTarget)
return Application::Get().GetWindow().GetSwapChain().GetCurrentFramebuffer();

return m_Handle;
}

void Framebuffer::Release()
{
#if DEAL
Expand Down Expand Up @@ -166,14 +218,10 @@ namespace Lux {

m_Width = (uint32_t)(width * m_Specification.Scale);
m_Height = (uint32_t)(height * m_Specification.Scale);
if (!m_Specification.SwapChainTarget)
{
RT_Invalidate();
}
if (m_Specification.SwapChainTarget)
PopulateClearValues(m_Specification, m_ClearValues);
else
{
LUX_CORE_VERIFY(false);
}
RT_Invalidate();

for (auto& callback : m_ResizeCallbacks)
callback(this);
Expand Down
6 changes: 3 additions & 3 deletions Core/Source/Lux/Renderer/Framebuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,15 @@ namespace Lux {
void Resize(uint32_t width, uint32_t height, bool forceRecreate = false);
void AddResizeCallback(const std::function<void(Ref<Framebuffer>)>& func);

virtual uint32_t GetWidth() const { return m_Width; }
virtual uint32_t GetHeight() const { return m_Height; }
virtual uint32_t GetWidth() const;
virtual uint32_t GetHeight() const;

Ref<Image2D> GetImage(uint32_t attachmentIndex = 0) const { LUX_CORE_ASSERT(attachmentIndex < m_AttachmentImages.size()); return m_AttachmentImages[attachmentIndex]; }
Ref<Image2D> GetDepthImage() const { return m_DepthAttachmentImage; }
size_t GetColorAttachmentCount() const { return m_Specification.SwapChainTarget ? 1 : m_AttachmentImages.size(); }
bool HasDepthAttachment() const { return (bool)m_DepthAttachmentImage; }

virtual nvrhi::FramebufferHandle GetHandle() const { return m_Handle; }
virtual nvrhi::FramebufferHandle GetHandle() const;
const nvrhi::FramebufferDesc& GetFramebufferDesc() const { return m_FramebufferDesc; }

const std::vector<ClearValue>& GetClearValues() const { return m_ClearValues; }
Expand Down
14 changes: 12 additions & 2 deletions Core/Source/Lux/Scripting/ScriptEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,12 @@ namespace Lux {

void ScriptEngine::Shutdown()
{
if (!s_Data)
return;

ShutdownMono();
delete s_Data;
s_Data = nullptr;
}

void ScriptEngine::InitMono()
Expand Down Expand Up @@ -232,10 +236,16 @@ namespace Lux {

void ScriptEngine::ShutdownMono()
{
if (!s_Data || !s_Data->RootDomain)
return;

mono_domain_set(mono_get_root_domain(), false);

mono_domain_unload(s_Data->AppDomain);
s_Data->AppDomain = nullptr;
if (s_Data->AppDomain)
{
mono_domain_unload(s_Data->AppDomain);
s_Data->AppDomain = nullptr;
}
Comment on lines +239 to +248
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stop file-watch callbacks before domain teardown to avoid shutdown races.

AppAssemblyFileWatcher can still trigger OnAppAssemblyFileSystemEvent while shutdown is in progress; that path dereferences s_Data and can crash after Line 205 sets it to nullptr. Reset the watcher before unload and guard callback/lambda dereferences.

Proposed fix
diff --git a/Core/Source/Lux/Scripting/ScriptEngine.cpp b/Core/Source/Lux/Scripting/ScriptEngine.cpp
@@
 static void OnAppAssemblyFileSystemEvent(const std::string& path, const filewatch::Event change_type)
 {
+    if (!s_Data)
+        return;
+
     if (!s_Data->AssemblyReloadPending && change_type == filewatch::Event::modified)
     {
         s_Data->AssemblyReloadPending = true;
 
         Application::Get().QueueEvent([]()
             {
+                if (!s_Data)
+                    return;
                 s_Data->AppAssemblyFileWatcher.reset();
                 ScriptEngine::ReloadAssembly();
             });
     }
 }
@@
 void ScriptEngine::Shutdown()
 {
     if (!s_Data)
         return;
 
+    s_Data->AppAssemblyFileWatcher.reset();
     ShutdownMono();
     delete s_Data;
     s_Data = nullptr;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Source/Lux/Scripting/ScriptEngine.cpp` around lines 239 - 248, The
shutdown sequence must stop and reset the AppAssemblyFileWatcher before tearing
down s_Data/AppDomain to avoid races; update the code in ScriptEngine (around
mono_domain_unload and s_Data handling) to stop/unregister the
AppAssemblyFileWatcher and set it to nullptr before calling mono_domain_unload
and clearing s_Data, and also update the OnAppAssemblyFileSystemEvent
callback/lambdas to defensively check s_Data and s_Data->RootDomain (or capture
weak state) before dereferencing; ensure AppAssemblyFileWatcher::Stop/Unregister
is invoked prior to mono_domain_unload and that any remaining callbacks bail out
if s_Data is null.


mono_jit_cleanup(s_Data->RootDomain);
s_Data->RootDomain = nullptr;
Expand Down
2 changes: 2 additions & 0 deletions Editor/SandboxProject/Assets/Scenes/NewSceneSystem.luxscene
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Entities:
Falloff: 1
Intensity: 2.5999999
Range: 24.5
ShadowDistance: 0
ShadowResolutionTier: 1
- Entity: 1639952346674441217
TagComponent:
Tag: Camera
Expand Down
28 changes: 15 additions & 13 deletions Editor/SandboxProject/Sandbox.luxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,53 +8,55 @@ Project:
AnimationPath: Animation
ScriptModulePath: Scripts/Binaries/Sandbox.dll
DefaultNamespace: Sandbox
StartScene: ""
StartScene: Scenes/NewSceneSystem.luxscene
AutomaticallyReloadAssembly: true
AutoSave: true
AutoSaveInterval: 300
RenderingTechnique: Forward
SceneRenderer:
Rendering:
QualityPreset: Cinematic
FrustumCulling: true
OcclusionCulling: true
GPUDrivenRendering: true
GTAO: true
GTAOBentNormals: true
GTAODenoisePasses: 8
AOShadowTolerance: 4
AOShadowTolerance: 1
SSR: true
JumpFloodOutline: true
RenderScaleMode: Scale100
DynamicResolutionMinScale: 0.5
DynamicResolutionMaxScale: 1
DynamicResolutionTargetGPUTime: 16.6700001
TextureMipBias: 0
DistanceMipBias: false
DistanceMipBiasStart: 50
DistanceMipBiasEnd: 250
DistanceMipBiasMax: 2
TextureMipBias: -1.5
DistanceMipBias: true
DistanceMipBiasStart: 10
DistanceMipBiasEnd: 100
DistanceMipBiasMax: 0.5
OcclusionDepthBias: 0.00300000003
OcclusionBoundsScale: 1.14999998
GTAOResolutionScale: 1
GTAOTemporalAccumulation: true
GTAOTemporalBlend: 0.850000024
GTAOTemporalBlend: 0.949999988
SSRQuality: Full
SSRResolutionScale: 1
SSRTemporalAccumulation: true
SSRTemporalBlend: 0.899999976
SSRTemporalBlend: 0.949999988
Shadows:
SoftShadows: true
ShadowCulling: true
MaxDistance: 200
DistanceFade: 25
MaxDistance: 450
DistanceFade: 50
SplitLambda: 0.920000017
NearOffset: 0
FarOffset: 50
CascadeFade: 1
ResolutionLimit: 8K
PostFX:
Bloom:
Enabled: true
ResolutionScale: 2
ResolutionScale: 1
Threshold: 1
Knee: 0.100000001
UpsampleScale: 1
Expand All @@ -67,7 +69,7 @@ Project:
BlurSize: 0.75
SSR:
HalfRes: false
MaxSteps: 70
MaxSteps: 128
Brightness: 0.699999988
DepthTolerance: 0.800000012
Audio:
Expand Down
Loading
Loading