#include "Application.h" #include "Layers/EditorLayer.h" #include "Core/EditorContext.h" #include "Core/EditorConsoleSink.h" #include "Core/EditorEvents.h" #include "Core/EventBus.h" #include #include #include #include #include #include #include #include namespace { std::string WideToUtf8(const std::wstring& value) { if (value.empty()) { return {}; } int len = WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, nullptr, 0, nullptr, nullptr); if (len <= 0) { return {}; } std::string result(len - 1, '\0'); WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, &result[0], len, nullptr, nullptr); return result; } std::wstring Utf8ToWide(const std::string& value) { if (value.empty()) { return {}; } int len = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, nullptr, 0); if (len <= 0) { return {}; } std::wstring result(len - 1, L'\0'); MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, &result[0], len); return result; } std::string GetExecutableDirectoryUtf8() { wchar_t exePath[MAX_PATH]; GetModuleFileNameW(nullptr, exePath, MAX_PATH); std::wstring exeDirW(exePath); const size_t pos = exeDirW.find_last_of(L"\\/"); if (pos != std::wstring::npos) { exeDirW = exeDirW.substr(0, pos); } return WideToUtf8(exeDirW); } std::string GetExecutableLogPath(const char* fileName) { return GetExecutableDirectoryUtf8() + "\\" + fileName; } } // namespace static LONG WINAPI GlobalExceptionFilter(EXCEPTION_POINTERS* exceptionPointers) { const std::string logPath = GetExecutableLogPath("crash.log"); FILE* f = nullptr; fopen_s(&f, logPath.c_str(), "a"); if (f) { fprintf(f, "[CRASH] ExceptionCode=0x%08X, Address=0x%p\n", exceptionPointers->ExceptionRecord->ExceptionCode, exceptionPointers->ExceptionRecord->ExceptionAddress); fclose(f); } fprintf(stderr, "[CRASH] ExceptionCode=0x%08X, Address=0x%p\n", exceptionPointers->ExceptionRecord->ExceptionCode, exceptionPointers->ExceptionRecord->ExceptionAddress); return EXCEPTION_EXECUTE_HANDLER; } namespace XCEngine { namespace Editor { Application& Application::Get() { static Application instance; return instance; } bool Application::Initialize(HWND hwnd) { // Set global exception filter first to catch any crashes SetUnhandledExceptionFilter(GlobalExceptionFilter); // Redirect stderr to log file to capture ImGui errors { const std::string stderrPath = GetExecutableLogPath("stderr.log"); freopen(stderrPath.c_str(), "w", stderr); fprintf(stderr, "[TEST] stderr redirection test - this should appear in stderr.log\n"); fflush(stderr); } // Initialize logging first Debug::Logger::Get().AddSink(std::make_unique()); Debug::Logger::Get().AddSink(std::make_unique()); // Get exe directory for log file path const std::string exeDir = GetExecutableDirectoryUtf8(); std::string logPath = exeDir + "\\editor.log"; Debug::Logger::Get().AddSink(std::make_unique(logPath.c_str())); Debug::Logger::Get().Info(Debug::LogCategory::General, "Editor Application starting..."); Debug::Logger::Get().Info(Debug::LogCategory::General, ("Log file: " + logPath).c_str()); m_hwnd = hwnd; if (!CreateDevice()) { MessageBoxW(hwnd, L"Failed to create D3D12 device", L"Error", MB_OK | MB_ICONERROR); return false; } if (!CreateRenderTarget()) { MessageBoxW(hwnd, L"Failed to create render target", L"Error", MB_OK | MB_ICONERROR); return false; } m_editorContext = std::make_shared(); m_editorContext->SetProjectPath(exeDir); m_exitRequestedHandlerId = m_editorContext->GetEventBus().Subscribe( [this](const EditorExitRequestedEvent&) { if (m_hwnd) { PostMessageW(m_hwnd, WM_CLOSE, 0, 0); } }); m_imguiSession.Initialize(m_editorContext->GetProjectPath()); m_imguiBackend.Initialize(hwnd, m_device, m_srvHeap); m_editorLayer = new EditorLayer(); m_editorLayer->SetContext(m_editorContext); m_layerStack.pushLayer(std::unique_ptr(m_editorLayer)); m_layerStack.onAttach(); return true; } void Application::Shutdown() { m_layerStack.onDetach(); if (m_editorContext && m_exitRequestedHandlerId) { m_editorContext->GetEventBus().Unsubscribe(m_exitRequestedHandlerId); m_exitRequestedHandlerId = 0; } m_imguiBackend.Shutdown(); m_imguiSession.Shutdown(); CleanupRenderTarget(); if (m_fence) m_fence->Release(); if (m_commandList) m_commandList->Release(); if (m_commandAllocator) m_commandAllocator->Release(); if (m_commandQueue) m_commandQueue->Release(); if (m_rtvHeap) m_rtvHeap->Release(); if (m_srvHeap) m_srvHeap->Release(); if (m_swapChain) m_swapChain->Release(); if (m_device) m_device->Release(); } void Application::Render() { m_imguiBackend.BeginFrame(); m_layerStack.onImGuiRender(); UpdateWindowTitle(); ImGui::Render(); m_frameIndex = m_swapChain->GetCurrentBackBufferIndex(); m_commandAllocator->Reset(); m_commandList->Reset(m_commandAllocator, nullptr); D3D12_RESOURCE_BARRIER barrier = {}; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; barrier.Transition.pResource = m_renderTargets[m_frameIndex]; barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; m_commandList->ResourceBarrier(1, &barrier); float clearColor[4] = { 0.22f, 0.22f, 0.22f, 1.0f }; D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_rtvHeap->GetCPUDescriptorHandleForHeapStart(); rtvHandle.ptr += m_frameIndex * m_rtvDescriptorSize; m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr); m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr); ID3D12DescriptorHeap* heaps[] = { m_srvHeap }; m_commandList->SetDescriptorHeaps(1, heaps); m_imguiBackend.RenderDrawData(m_commandList); barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; m_commandList->ResourceBarrier(1, &barrier); m_commandList->Close(); ID3D12CommandList* cmdLists[] = { m_commandList }; m_commandQueue->ExecuteCommandLists(1, cmdLists); m_swapChain->Present(1, 0); m_fenceValue++; m_commandQueue->Signal(m_fence, m_fenceValue); if (m_fence->GetCompletedValue() < m_fenceValue) { m_fence->SetEventOnCompletion(m_fenceValue, nullptr); } } void Application::UpdateWindowTitle() { if (!m_hwnd || !m_editorContext) { return; } auto& sceneManager = m_editorContext->GetSceneManager(); std::string sceneName = sceneManager.HasActiveScene() ? sceneManager.GetCurrentSceneName() : "No Scene"; if (sceneName.empty()) { sceneName = "Untitled Scene"; } if (sceneManager.IsSceneDirty()) { sceneName += " *"; } if (sceneManager.GetCurrentScenePath().empty()) { sceneName += " (Unsaved)"; } else { sceneName += " - "; sceneName += std::filesystem::path(sceneManager.GetCurrentScenePath()).filename().string(); } const std::wstring title = Utf8ToWide(sceneName + " - XCEngine Editor"); if (title != m_lastWindowTitle) { SetWindowTextW(m_hwnd, title.c_str()); m_lastWindowTitle = title; } } void Application::OnResize(int width, int height) { if (width <= 0 || height <= 0) return; m_width = width; m_height = height; CleanupRenderTarget(); if (m_swapChain) { DXGI_SWAP_CHAIN_DESC desc; m_swapChain->GetDesc(&desc); m_swapChain->ResizeBuffers(3, width, height, desc.BufferDesc.Format, desc.Flags); } CreateRenderTarget(); } bool Application::CreateDevice() { HRESULT hr = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device)); if (FAILED(hr)) { return false; } D3D12_COMMAND_QUEUE_DESC queueDesc = {}; queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; queueDesc.Priority = 0; queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; queueDesc.NodeMask = 0; hr = m_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_commandQueue)); if (FAILED(hr)) return false; hr = m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocator)); if (FAILED(hr)) return false; hr = m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocator, nullptr, IID_PPV_ARGS(&m_commandList)); if (FAILED(hr)) return false; m_commandList->Close(); hr = m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence)); if (FAILED(hr)) return false; IDXGIFactory4* factory = nullptr; hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory)); if (FAILED(hr)) return false; DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; swapChainDesc.BufferCount = 3; swapChainDesc.Width = m_width; swapChainDesc.Height = m_height; swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; swapChainDesc.SampleDesc.Count = 1; IDXGISwapChain1* swapChain1 = nullptr; hr = factory->CreateSwapChainForHwnd(m_commandQueue, m_hwnd, &swapChainDesc, nullptr, nullptr, &swapChain1); factory->Release(); if (FAILED(hr)) return false; hr = swapChain1->QueryInterface(IID_PPV_ARGS(&m_swapChain)); swapChain1->Release(); if (FAILED(hr)) return false; D3D12_DESCRIPTOR_HEAP_DESC rtvDesc = {}; rtvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; rtvDesc.NumDescriptors = 3; rtvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; hr = m_device->CreateDescriptorHeap(&rtvDesc, IID_PPV_ARGS(&m_rtvHeap)); if (FAILED(hr)) return false; m_rtvDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); D3D12_DESCRIPTOR_HEAP_DESC srvDesc = {}; srvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; srvDesc.NumDescriptors = 1; srvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; hr = m_device->CreateDescriptorHeap(&srvDesc, IID_PPV_ARGS(&m_srvHeap)); if (FAILED(hr)) return false; return true; } bool Application::CreateRenderTarget() { if (!m_swapChain || !m_device || !m_rtvHeap) return false; for (UINT i = 0; i < 3; i++) { HRESULT hr = m_swapChain->GetBuffer(i, IID_PPV_ARGS(&m_renderTargets[i])); if (FAILED(hr)) return false; D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_rtvHeap->GetCPUDescriptorHandleForHeapStart(); rtvHandle.ptr += i * m_rtvDescriptorSize; m_device->CreateRenderTargetView(m_renderTargets[i], nullptr, rtvHandle); } return true; } void Application::CleanupRenderTarget() { for (UINT i = 0; i < 3; i++) { if (m_renderTargets[i]) { m_renderTargets[i]->Release(); m_renderTargets[i] = nullptr; } } } } }