#include "main.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef JULIET_ENABLE_IMGUI #include #include #endif static bool ShowMemoryDebugger = false; // TODO : Replace with message box from framework + call main and not winmain + subsystem // TODO : Think how to do the draw pipeline. // Ex: Expose a Draw method ? // Store a graphics context ? // For now. Put everything in Update down below. // Should split update from draw, update should have a != timestep than graphics (60fps or more) // TODO : Remove main.h. Useless // May be remove that Application class, useless too. using namespace Juliet; extern "C" { __declspec(dllexport) extern const unsigned int D3D12SDKVersion = 615; } extern "C" { __declspec(dllexport) extern const char* D3D12SDKPath = ".\\"; } namespace { using GameInit_t = void (*)(GameInitParams*); using GameShutdown_t = void (*)(void); using GameUpdate_t = void (*)(float deltaTime); struct GameFunctionTable { GameInit_t Init = nullptr; GameShutdown_t Shutdown = nullptr; GameUpdate_t Update = nullptr; } Game; const char* GameFunctionTable[] = { "GameInit", "GameShutdown", "GameUpdate" }; } // namespace struct Vertex { float Position[2]; float Color[4]; }; void JulietApplication::Init() { Log(LogLevel::Message, LogCategory::Tool, "Initializing Juliet Application..."); Log(LogLevel::Message, LogCategory::Tool, "%s", CStr(GetBasePath())); GraphicsConfig config; #if JULIET_DEBUG config.EnableDebug = true; #endif GraphicsDevice = CreateGraphicsDevice(config); MainWindow = CreatePlatformWindow("Juliet Editor", 1280, 720); Running = MainWindow != nullptr && GraphicsDevice != nullptr; if (Running) { AttachToWindow(GraphicsDevice, MainWindow); { // Create graphics pipeline String entryPoint = WrapString("main"); ShaderCreateInfo shaderCI = {}; shaderCI.EntryPoint = entryPoint; // TODO: Assets management that handles path to assets or something. String shaderPath = WrapString("../../Assets/compiled/Triangle.vert.dxil"); shaderCI.Stage = ShaderStage::Vertex; Shader* vertexShader = CreateShader(GraphicsDevice, shaderPath, shaderCI); shaderPath = WrapString("../../Assets/compiled/SolidColor.frag.dxil"); shaderCI.Stage = ShaderStage::Fragment; Shader* fragmentShader = CreateShader(GraphicsDevice, shaderPath, shaderCI); ColorTargetDescription colorTargetDescription = {}; colorTargetDescription.Format = GetSwapChainTextureFormat(GraphicsDevice, MainWindow); GraphicsPipelineCreateInfo pipelineCI = {}; pipelineCI.VertexShader = vertexShader; pipelineCI.FragmentShader = fragmentShader; pipelineCI.PrimitiveType = PrimitiveType::TriangleList; pipelineCI.TargetInfo = { .ColorTargetDescriptions = &colorTargetDescription, .NumColorTargets = 1, .DepthStencilFormat = TextureFormat::D32_FLOAT, .HasDepthStencilTarget = true }; pipelineCI.RasterizerState.FillMode = FillMode::Solid; pipelineCI.DepthStencilState.EnableDepthTest = true; pipelineCI.DepthStencilState.EnableDepthWrite = true; pipelineCI.DepthStencilState.CompareOperation = CompareOperation::Less; GraphicsPipeline = CreateGraphicsPipeline(GraphicsDevice, pipelineCI); if (GraphicsPipeline == nullptr) { LogError(LogCategory::Game, "Failed to create graphics pipeline!"); Running = false; } // Create Depth Buffer TextureCreateInfo depthCI = {}; depthCI.Type = TextureType::Texture_2D; depthCI.Width = 1280; depthCI.Height = 720; depthCI.Format = TextureFormat::D32_FLOAT; depthCI.Flags = TextureUsageFlag::DepthStencilTarget; depthCI.LayerCount = 1; depthCI.MipLevelCount = 1; depthCI.SampleCount = TextureSampleCount::One; DepthBuffer = CreateTexture(GraphicsDevice, depthCI); if (DepthBuffer == nullptr) { LogError(LogCategory::Game, "Failed to create depth buffer!"); Running = false; } // Create Buffers - Using StructuredBuffer for bindless SRV access in shader BufferCreateInfo bufferCI = {}; bufferCI.Size = 256; bufferCI.Usage = BufferUsage::StructuredBuffer; // SRV for ResourceDescriptorHeap access ConstantBuffer = CreateGraphicsBuffer(GraphicsDevice, bufferCI); TransferBufferCreateInfo transferCI = {}; transferCI.Size = 256; transferCI.Usage = TransferBufferUsage::Upload; TransferBuffer = CreateGraphicsTransferBuffer(GraphicsDevice, transferCI); // Upload Static Data for Test if (TransferBuffer && ConstantBuffer) { void* data = MapGraphicsTransferBuffer(GraphicsDevice, TransferBuffer); if (data) { Matrix projection = PerspectiveFov(60.0f * (3.14159f / 180.0f), 1200.0f / 800.0f, 0.1f, 1000.0f); Matrix view = LookAt({ 30.0f, 0.0f, 10.0f }, { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 1.0f }); Matrix model = Matrix::Identity(); Matrix mvp = projection * view * model; MemCopy(data, &mvp, sizeof(Matrix)); UnmapGraphicsTransferBuffer(GraphicsDevice, TransferBuffer); CommandList* initCmd = AcquireCommandList(GraphicsDevice); CopyBuffer(initCmd, ConstantBuffer, TransferBuffer, 256); TransitionBufferToReadable(initCmd, ConstantBuffer); SubmitCommandLists(initCmd); WaitUntilGPUIsIdle(GraphicsDevice); } } if (vertexShader) { DestroyShader(GraphicsDevice, vertexShader); } if (fragmentShader) { DestroyShader(GraphicsDevice, fragmentShader); } if (Running == false) { return; } } GameCode.Functions = reinterpret_cast(&Game); GameCode.FunctionCount = ArraySize(GameFunctionTable); GameCode.FunctionNames = GameFunctionTable; InitHotReloadCode(GameCode, ConstString("Game.dll"), ConstString("Game_Temp.dll"), ConstString("lock.tmp")); if ((Running = GameCode.IsValid)) { GameInitParams params; params.GameArena = GetGameArena(); params.ScratchArena = GetScratchArena(); Game.Init(¶ms); } } } void JulietApplication::Shutdown() { Log(LogLevel::Message, LogCategory::Tool, "Shutting down Juliet Application..."); if (GameCode.IsValid) { Game.Shutdown(); ShutdownHotReloadCode(GameCode); } if (GraphicsPipeline) { DestroyGraphicsPipeline(GraphicsDevice, GraphicsPipeline); } if (ConstantBuffer) { DestroyGraphicsBuffer(GraphicsDevice, ConstantBuffer); } if (TransferBuffer) { DestroyGraphicsTransferBuffer(GraphicsDevice, TransferBuffer); } if (DepthBuffer) { DestroyTexture(GraphicsDevice, DepthBuffer); } if (MainWindow && GraphicsDevice) { DetachFromWindow(GraphicsDevice, MainWindow); } if (MainWindow) { DestroyPlatformWindow(MainWindow); } if (GraphicsDevice) { DestroyGraphicsDevice(GraphicsDevice); } Log(LogLevel::Message, LogCategory::Tool, "Juliet App shutdown Completed"); } void JulietApplication::Update() { bool reloadShaders = false; static bool reloadShadersDebounce = false; SystemEvent evt; while (GetEvent(evt)) { if (evt.Type == EventType::Window_Close_Request) { if (evt.Data.Window.AssociatedWindowID == GetWindowID(MainWindow)) { Running = false; } } if (evt.Type == EventType::Key_Down) { } // Shader hot reload using keyboard. if (!reloadShadersDebounce && ((GetKeyModState() & KeyMod::Alt) != KeyMod::None) && IsKeyDown(ScanCode::R)) { reloadShaders = true; reloadShadersDebounce = true; } if (reloadShadersDebounce && !IsKeyDown(ScanCode::R)) { reloadShadersDebounce = false; } } #ifdef JULIET_ENABLE_IMGUI ImGui::ShowDemoWindow(); #endif // Debug display shapes - can be called from anywhere before engine flush DebugDisplay_DrawLine({ 0.0f, 0.0f, 0.0f }, { 10.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f }, false); DebugDisplay_DrawLine({ 0.0f, 0.0f, 0.0f }, { 0.0f, 10.0f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f }, true); DebugDisplay_DrawLine({ 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 10.0f }, { 0.0f, 0.0f, 1.0f, 1.0f }, true); DebugDisplay_DrawSphere({ 0.0f, 0.0f, 0.0f }, 5.0f, { 1.0f, 1.0f, 0.0f, 1.0f }, true); Game.Update(0.0f); if (ShouldReloadCode(GameCode)) { ReloadCode(GameCode); } if (reloadShaders) { WaitUntilGPUIsIdle(GraphicsDevice); #if ALLOW_SHADER_HOT_RELOAD String entryPoint = WrapString("main"); ShaderCreateInfo shaderCI = {}; shaderCI.EntryPoint = entryPoint; String shaderPath = WrapString("../../Assets/compiled/Triangle.vert.dxil"); shaderCI.Stage = ShaderStage::Vertex; Shader* vertexShader = CreateShader(GraphicsDevice, shaderPath, shaderCI); shaderPath = WrapString("../../Assets/compiled/SolidColor.frag.dxil"); shaderCI.Stage = ShaderStage::Fragment; Shader* fragmentShader = CreateShader(GraphicsDevice, shaderPath, shaderCI); UpdateGraphicsPipelineShaders(GraphicsDevice, GraphicsPipeline, vertexShader, fragmentShader); if (vertexShader) { DestroyShader(GraphicsDevice, vertexShader); } if (fragmentShader) { DestroyShader(GraphicsDevice, fragmentShader); } #endif } // Memory debugger toggle static bool toggleDebounce = false; if (IsKeyDown(Juliet::ScanCode::Home)) { if (!toggleDebounce) { ShowMemoryDebugger = !ShowMemoryDebugger; toggleDebounce = true; } } else { toggleDebounce = false; } // Auto-close logic if (AutoCloseFrameCount > 0) { AutoCloseFrameCount--; if (AutoCloseFrameCount == 0) { Log(LogLevel::Message, LogCategory::Tool, "Auto-closing application as requested."); Running = false; } } } void JulietApplication::OnPreRender(CommandList* cmd) { // Buffer uploads if (ConstantBuffer && TransferBuffer) { void* ptr = MapGraphicsTransferBuffer(GraphicsDevice, TransferBuffer); if (ptr) { auto vertices = static_cast(ptr); // Triangle 1 vertices[0] = { { -0.5f, -0.5f }, { 1.0f, 0.0f, 0.0f, 1.0f } }; // Red vertices[1] = { { 0.0f, 0.5f }, { 0.0f, 1.0f, 0.0f, 1.0f } }; // Green vertices[2] = { { 0.5f, -0.5f }, { 0.0f, 0.0f, 1.0f, 1.0f } }; // Blue // Triangle 2 vertices[3] = { { -0.5f, 0.5f }, { 1.0f, 1.0f, 0.0f, 1.0f } }; // Yellow vertices[4] = { { 0.0f, 0.8f }, { 0.0f, 1.0f, 1.0f, 1.0f } }; // Cyan vertices[5] = { { 0.5f, 0.5f }, { 1.0f, 0.0f, 1.0f, 1.0f } }; // Magenta UnmapGraphicsTransferBuffer(GraphicsDevice, TransferBuffer); } CopyBuffer(cmd, ConstantBuffer, TransferBuffer, 256); TransitionBufferToReadable(cmd, ConstantBuffer); } } void JulietApplication::OnRender(RenderPass* pass, CommandList* cmd) { BindGraphicsPipeline(pass, GraphicsPipeline); uint32 descriptorIndex = GetDescriptorIndex(GraphicsDevice, ConstantBuffer); struct PushData { float ViewProjection[16]; uint32 BufferIndex; } pushData = {}; pushData.BufferIndex = descriptorIndex; SetPushConstants(cmd, ShaderStage::Vertex, 0, sizeof(pushData) / 4, &pushData); DrawPrimitives(pass, 6, 1, 0, 0); if (ShowMemoryDebugger) { MemoryDebugger::DrawGlobalArenas(); } } ColorTargetInfo JulietApplication::GetColorTargetInfo(Texture* swapchainTexture) { ColorTargetInfo info = {}; info.TargetTexture = swapchainTexture; info.ClearColor = { .R = 0.0f, .G = 0.0f, .B = 0.0f, .A = 1.0f }; info.LoadOperation = LoadOperation::Clear; info.StoreOperation = StoreOperation::Store; return info; } DepthStencilTargetInfo* JulietApplication::GetDepthTargetInfo() { static DepthStencilTargetInfo info = {}; info.TargetTexture = DepthBuffer; info.ClearDepth = 1.0f; info.LoadOperation = LoadOperation::Clear; info.StoreOperation = StoreOperation::Store; return &info; } Camera JulietApplication::GetDebugCamera() { static float orbitTime = 0.0f; orbitTime += 0.016f; float radius = 30.0f; Camera cam = {}; cam.Position = { cosf(orbitTime) * radius, sinf(orbitTime) * radius, 10.0f }; cam.Target = { 0.0f, 0.0f, 0.0f }; cam.Up = { 0.0f, 0.0f, 1.0f }; cam.FOV = 1.047f; cam.AspectRatio = 1200.0f / 800.0f; cam.NearPlane = 0.1f; cam.FarPlane = 1000.0f; return cam; } bool JulietApplication::IsRunning() { return Running; } namespace { JulietApplication EditorApplication; } JulietApplication& GetEditorApplication() { return EditorApplication; } int main(int /*argc*/, char** /*argv*/) { // Allow only one instance to be launched. // Need to create Mutex code in the lib because i dont want to include windows.h in this file anymore // CreateMutex(0, false, L"Local\\Juliet.App"); // if (GetLastError() == ERROR_ALREADY_EXISTS) // { // MessageBox(nullptr, L"An instance of Juliet is already running.", L"Juliet", MB_OK | MB_ICONEXCLAMATION); // return EXIT_FAILURE; // } setvbuf(stdout, nullptr, _IONBF, 0); if (__argc > 1) { for (int i = 1; i < __argc; ++i) { if (strcmp(__argv[i], "-autoclose") == 0 && (i + 1 < __argc)) { int frames = atoi(__argv[i + 1]); EditorApplication.SetAutoCloseFrameCount(frames); } } } StartApplication(EditorApplication, JulietInit_Flags::Display); // Pause here to not close the console window immediatly on stop // Only pause if not in auto-close mode if (EditorApplication.GetAutoCloseFrameCount() == -1) { system("PAUSE"); } return EXIT_SUCCESS; }