#include "main.h" #include #include #include #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 #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 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 = 1024; bufferCI.Usage = BufferUsage::StructuredBuffer; // SRV for ResourceDescriptorHeap access ConstantBuffer = CreateGraphicsBuffer(GraphicsDevice, bufferCI); TransferBufferCreateInfo transferCI = {}; transferCI.Size = 1024; 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); } } CubeArena = ArenaAllocate({ .ReserveSize = Kilobytes(1llu), .CommitSize = Kilobytes(1llu) } JULIET_DEBUG_PARAM("CubeArena")); CubeMesh = CreateCubeMesh(CubeArena); // CubeMesh = CreateQuadMesh(CubeArena); 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 = GameArena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Game Arena")); params.ScratchArena = GameScratchArena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Scratch Arena")); Game.Init(¶ms); } } } void JulietApplication::Shutdown() { Log(LogLevel::Message, LogCategory::Tool, "Shutting down Juliet Application..."); ArenaRelease(CubeArena); 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; } } // 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 DebugDisplay_DrawLine({ 0.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f }, false); DebugDisplay_DrawLine({ 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f }, true); DebugDisplay_DrawLine({ 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f, 1.0f, 1.0f }, true); DebugDisplay_DrawSphere({ 0.0f, 0.0f, 0.0f }, 0.5f, { 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; } } if (ShowMemoryDebugger) { #if JULIET_DEBUG Debug::DebugDrawMemoryArena(); #endif } ArenaClear(GameScratchArena); } void JulietApplication::OnPreRender(CommandList* cmd) { index_t index = 0; // Buffer uploads if (ConstantBuffer && TransferBuffer) { void* ptr = MapGraphicsTransferBuffer(GraphicsDevice, TransferBuffer); if (ptr) { Vertex* vertices = static_cast(ptr); // // Triangle 1 // vertices[index++] = { { -0.5f, -0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } }; // Red // vertices[index++] = { { 0.0f, 0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } }; // Green // vertices[index++] = { { 0.5f, -0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } }; // Blue // // // Triangle 2 // vertices[index++] = { { -0.5f, 0.5f, 0.0f }, { 1.0f, 1.0f, 0.0f, 1.0f } }; // Yellow // vertices[index++] = { { 0.0f, 0.8f, 0.0f }, { 0.0f, 1.0f, 1.0f, 1.0f } }; // Cyan // vertices[index++] = { { 0.5f, 0.5f, 0.0f }, { 1.0f, 0.0f, 1.0f, 1.0f } }; // Magenta if (CubeMesh) { CubeMesh->VertexOffset = index; size_t vertexSize = CubeMesh->VertexCount * sizeof(Vertex); MemCopy(vertices + index, CubeMesh->Vertices, vertexSize); CubeMesh->IndexByteOffset = (index + CubeMesh->VertexCount) * sizeof(Vertex); // Align CubeMesh->IndexByteOffset = (CubeMesh->IndexByteOffset + 255) & static_cast(~255); size_t indexSize = CubeMesh->IndexCount * sizeof(uint16); CubeMesh->IndexOffset = 0; uint8* ptrOneByte = static_cast(ptr); uint16* dst = reinterpret_cast(ptrOneByte + CubeMesh->IndexByteOffset); MemCopy(dst, CubeMesh->Indices, indexSize); } UnmapGraphicsTransferBuffer(GraphicsDevice, TransferBuffer); } CopyBuffer(cmd, ConstantBuffer, TransferBuffer, CubeMesh->IndexByteOffset + (CubeMesh->IndexCount * sizeof(uint16))); TransitionBufferToReadable(cmd, ConstantBuffer); } } void JulietApplication::OnRender(RenderPass* pass, CommandList* cmd) { BindGraphicsPipeline(pass, GraphicsPipeline); uint32 descriptorIndex = GetDescriptorIndex(GraphicsDevice, ConstantBuffer); struct PushData { Matrix ViewProjection; uint32 BufferIndex; } pushData = {}; pushData.BufferIndex = descriptorIndex; pushData.ViewProjection = Camera_GetViewProjectionMatrix(GetDebugCamera()); SetPushConstants(cmd, ShaderStage::Vertex, 0, sizeof(pushData) / 4, &pushData); SetIndexBuffer(cmd, ConstantBuffer, IndexFormat::UInt16, CubeMesh->IndexCount, CubeMesh->IndexByteOffset); // DrawIndexedPrimitives(pass, static_cast(CubeMesh->IndexCount), 1, 0, 0, 0); DrawIndexedPrimitives(pass, static_cast(CubeMesh->IndexCount), 1, static_cast(CubeMesh->IndexOffset), static_cast(CubeMesh->VertexOffset), 0); } 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 time = 0.0f; time += 0.016f; float orbitSpeed = 0.5f; float currentOrbitTime = time * orbitSpeed; // --- Adjusted for 1-Meter Scale --- float baseRadius = 2.5f; // Hover 2.5 meters away (down from 15.0f) float radius = baseRadius; /* Uncomment for active zoom float zoomAmplitude = 1.0f; // Oscillate between 1.5m and 3.5m away float zoomSpeed = 0.8f; radius = baseRadius + (sinf(time * zoomSpeed) * zoomAmplitude); */ float zHeight = radius * 0.5f; // Keep a nice downward viewing angle Camera cam = {}; cam.Position = { cosf(currentOrbitTime) * radius, sinf(currentOrbitTime) * radius, zHeight }; 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 JulietMain(int, wchar_t**) { 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; }