493 lines
16 KiB
C++
493 lines
16 KiB
C++
#include "main.h"
|
|
|
|
#include <Core/Application/ApplicationManager.h>
|
|
#include <Core/Common/EnumUtils.h>
|
|
#include <Core/Common/String.h>
|
|
#include <Core/HAL/Display/Display.h>
|
|
#include <Core/HAL/Event/SystemEvent.h>
|
|
#include <Core/HAL/Filesystem/Filesystem.h>
|
|
#include <Core/JulietInit.h>
|
|
#include <Core/Logging/LogManager.h>
|
|
#include <Core/Logging/LogTypes.h>
|
|
#include <Core/Math/Matrix.h>
|
|
#include <Core/Memory/MemoryArena.h>
|
|
#include <Core/Memory/Utils.h>
|
|
#include <cstdlib>
|
|
#include <Engine/Debug/MemoryDebugger.h>
|
|
#include <Graphics/Camera.h>
|
|
#include <Graphics/DebugDisplay.h>
|
|
#include <Graphics/Graphics.h>
|
|
#include <Graphics/GraphicsConfig.h>
|
|
#include <Graphics/GraphicsPipeline.h>
|
|
#include <Graphics/RenderPass.h>
|
|
#include <Juliet.h>
|
|
|
|
#ifdef JULIET_ENABLE_IMGUI
|
|
#include <Graphics/ImGuiRenderer.h>
|
|
#include <imgui.h>
|
|
#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);
|
|
}
|
|
}
|
|
|
|
if (vertexShader)
|
|
{
|
|
DestroyShader(GraphicsDevice, vertexShader);
|
|
}
|
|
if (fragmentShader)
|
|
{
|
|
DestroyShader(GraphicsDevice, fragmentShader);
|
|
}
|
|
|
|
if (Running == false)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
GameCode.Functions = reinterpret_cast<void**>(&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;
|
|
}
|
|
}
|
|
|
|
// 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 }, { 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;
|
|
}
|
|
}
|
|
|
|
if (ShowMemoryDebugger)
|
|
{
|
|
Debug::DebugDrawMemoryArena();
|
|
}
|
|
}
|
|
|
|
void JulietApplication::OnPreRender(CommandList* cmd)
|
|
{
|
|
// Buffer uploads
|
|
if (ConstantBuffer && TransferBuffer)
|
|
{
|
|
void* ptr = MapGraphicsTransferBuffer(GraphicsDevice, TransferBuffer);
|
|
if (ptr)
|
|
{
|
|
auto vertices = static_cast<Vertex*>(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);
|
|
}
|
|
|
|
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;
|
|
}
|