834 lines
25 KiB
C++
834 lines
25 KiB
C++
#include "wiApplication.h"
|
|
#include "wiRenderPath.h"
|
|
#include "wiRenderer.h"
|
|
#include "wiHelper.h"
|
|
#include "wiTimer.h"
|
|
#include "wiInput.h"
|
|
#include "wiBacklog.h"
|
|
#include "wiApplication_BindLua.h"
|
|
#include "wiVersion.h"
|
|
#include "wiEnums.h"
|
|
#include "wiTextureHelper.h"
|
|
#include "wiProfiler.h"
|
|
#include "wiInitializer.h"
|
|
#include "wiArguments.h"
|
|
#include "wiFont.h"
|
|
#include "wiImage.h"
|
|
#include "wiEventHandler.h"
|
|
#include "wiPlatform.h"
|
|
|
|
#ifdef PLATFORM_PS5
|
|
#include "wiGraphicsDevice_PS5.h"
|
|
#else
|
|
#include "wiGraphicsDevice_DX12.h"
|
|
#include "wiGraphicsDevice_Vulkan.h"
|
|
#endif // PLATFORM_PS5
|
|
|
|
#include <string>
|
|
#include <algorithm>
|
|
#include <new>
|
|
#include <cstdlib>
|
|
#include <atomic>
|
|
|
|
//#define WICKED_ENGINE_HEAP_ALLOCATION_COUNTER
|
|
|
|
#ifdef WICKED_ENGINE_HEAP_ALLOCATION_COUNTER
|
|
static std::atomic<uint32_t> number_of_heap_allocations{ 0 };
|
|
static std::atomic<size_t> size_of_heap_allocations{ 0 };
|
|
#endif // WICKED_ENGINE_HEAP_ALLOCATION_COUNTER
|
|
|
|
using namespace wi::graphics;
|
|
|
|
namespace wi
|
|
{
|
|
|
|
void Application::Initialize()
|
|
{
|
|
if (initialized)
|
|
{
|
|
return;
|
|
}
|
|
initialized = true;
|
|
|
|
wi::initializer::InitializeComponentsAsync();
|
|
|
|
alwaysactive = wi::arguments::HasArgument("alwaysactive");
|
|
|
|
// Note: lua is always initialized immediately on main thread by wi::initializer, so this is safe to do:
|
|
assert(wi::initializer::IsInitializeFinished(wi::initializer::INITIALIZED_SYSTEM_LUA));
|
|
Luna<wi::lua::Application_BindLua>::push_global(wi::lua::GetLuaState(), "main", this);
|
|
Luna<wi::lua::Application_BindLua>::push_global(wi::lua::GetLuaState(), "application", this);
|
|
}
|
|
|
|
void Application::ActivatePath(RenderPath* component, float fadeSeconds, wi::Color fadeColor, FadeManager::FadeType fadetype)
|
|
{
|
|
if (component != nullptr)
|
|
{
|
|
component->init(canvas);
|
|
}
|
|
|
|
// Fade manager will activate on fadeout
|
|
fadeManager.Start(fadeSeconds, fadeColor, [this, component]() {
|
|
|
|
if (activePath != nullptr)
|
|
{
|
|
activePath->Stop();
|
|
}
|
|
|
|
if (component != nullptr)
|
|
{
|
|
component->Start();
|
|
}
|
|
activePath = component;
|
|
}, fadetype);
|
|
|
|
fadeManager.Update(0); // If user calls ActivatePath without fadeout, it will be instant
|
|
}
|
|
|
|
void Application::Run()
|
|
{
|
|
if (!initialized)
|
|
{
|
|
// Initialize in a lazy way, so the user application doesn't have to call this explicitly
|
|
Initialize();
|
|
initialized = true;
|
|
}
|
|
|
|
wi::font::UpdateAtlas(canvas.GetDPIScaling());
|
|
|
|
ColorSpace colorspace = graphicsDevice->GetSwapChainColorSpace(&swapChain);
|
|
if (colorspace == ColorSpace::HDR10_ST2084)
|
|
{
|
|
// In HDR10, we perform the compositing in a custom linear color space render target
|
|
// The reason is that blending doesn't look good in HDR10 color space
|
|
// In HDR10 the composition is done like:
|
|
// rendertargetPreHDR10:
|
|
// - RenderPath3D: linear space
|
|
// - RenderPath2D: SRGB space -> linear space with HDR scaling
|
|
// swapChain:
|
|
// - HDR10 composition: linear -> HDR10_ST2084
|
|
if (!rendertargetPreHDR10.IsValid())
|
|
{
|
|
TextureDesc desc;
|
|
desc.width = swapChain.desc.width;
|
|
desc.height = swapChain.desc.height;
|
|
desc.format = Format::R11G11B10_FLOAT;
|
|
desc.bind_flags = BindFlag::RENDER_TARGET | BindFlag::SHADER_RESOURCE;
|
|
bool success = graphicsDevice->CreateTexture(&desc, nullptr, &rendertargetPreHDR10);
|
|
assert(success);
|
|
graphicsDevice->SetName(&rendertargetPreHDR10, "Application::rendertargetPreHDR10");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If swapchain is SRGB or Linear HDR, it can be used for blending and rendertargetPreHDR10 is not needed
|
|
// - If it is SRGB, the render path will ensure tonemapping to SDR
|
|
// - If it is Linear HDR, we can blend trivially in linear space
|
|
rendertargetPreHDR10 = {};
|
|
}
|
|
|
|
if (!wi::initializer::IsInitializeFinished())
|
|
{
|
|
// Until engine is not loaded, present initialization screen...
|
|
CommandList cmd = graphicsDevice->BeginCommandList();
|
|
if (rendertargetPreHDR10.IsValid())
|
|
{
|
|
graphicsDevice->RenderPassBegin(&rendertargetPreHDR10, cmd, true);
|
|
}
|
|
else
|
|
{
|
|
graphicsDevice->RenderPassBegin(&swapChain, cmd);
|
|
}
|
|
Viewport viewport;
|
|
viewport.width = (float)swapChain.desc.width;
|
|
viewport.height = (float)swapChain.desc.height;
|
|
graphicsDevice->BindViewports(1, &viewport, cmd);
|
|
if (wi::initializer::IsInitializeFinished(wi::initializer::INITIALIZED_SYSTEM_FONT))
|
|
{
|
|
ColorSpace colorspace = graphicsDevice->GetSwapChainColorSpace(&swapChain);
|
|
wi::backlog::DrawOutputText(canvas, cmd, colorspace);
|
|
}
|
|
graphicsDevice->RenderPassEnd(cmd);
|
|
|
|
if (rendertargetPreHDR10.IsValid() && wi::initializer::IsInitializeFinished(wi::initializer::INITIALIZED_SYSTEM_IMAGE))
|
|
{
|
|
// In HDR10, we perform a final mapping from linear to HDR10, into the swapchain
|
|
graphicsDevice->RenderPassBegin(&swapChain, cmd);
|
|
wi::image::Params fx;
|
|
fx.enableFullScreen();
|
|
fx.enableHDR10OutputMapping(); // this is doing the linear -> HDR10_ST2084 conversion
|
|
wi::image::Draw(&rendertargetPreHDR10, fx, cmd);
|
|
graphicsDevice->RenderPassEnd(cmd);
|
|
}
|
|
|
|
graphicsDevice->SubmitCommandLists();
|
|
return;
|
|
}
|
|
|
|
static bool startup_script = false;
|
|
if (!startup_script)
|
|
{
|
|
startup_script = true;
|
|
std::string startup_lua_filename = wi::helper::GetCurrentPath() + "/startup.lua";
|
|
if (wi::helper::FileExists(startup_lua_filename))
|
|
{
|
|
if (wi::lua::RunFile(startup_lua_filename))
|
|
{
|
|
wi::backlog::post("Executed startup file: " + startup_lua_filename);
|
|
}
|
|
}
|
|
std::string startup_luab_filename = wi::helper::GetCurrentPath() + "/startup.luab";
|
|
if (wi::helper::FileExists(startup_luab_filename))
|
|
{
|
|
if (wi::lua::RunBinaryFile(startup_luab_filename))
|
|
{
|
|
wi::backlog::post("Executed startup file: " + startup_luab_filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!is_window_active && !alwaysactive)
|
|
{
|
|
// If the application is not active, disable Update loops:
|
|
deltaTimeAccumulator = 0;
|
|
wi::helper::Sleep(10);
|
|
wi::input::Update(window, canvas); // update input while inactive, this solves a problem with past inputs processed immediately after activation
|
|
timer.record_elapsed_seconds(); // after application becomes active, delta time shouldn't spike, could blow up gameplay or physics
|
|
return;
|
|
}
|
|
|
|
wi::profiler::BeginFrame();
|
|
|
|
deltaTime = float(timer.record_elapsed_seconds());
|
|
|
|
const float target_deltaTime = 1.0f / targetFrameRate;
|
|
if (framerate_lock && deltaTime < target_deltaTime)
|
|
{
|
|
wi::helper::QuickSleep((target_deltaTime - deltaTime) * 1000);
|
|
deltaTime += float(timer.record_elapsed_seconds());
|
|
}
|
|
|
|
// avoid instability caused by large delta time
|
|
deltaTime = clamp(deltaTime, 0.0f, 0.5f);
|
|
|
|
wi::input::Update(window, canvas);
|
|
|
|
// Wake up the events that need to be executed on the main thread, in thread safe manner:
|
|
wi::eventhandler::FireEvent(wi::eventhandler::EVENT_THREAD_SAFE_POINT, 0);
|
|
|
|
fadeManager.Update(deltaTime);
|
|
|
|
if (activePath != nullptr)
|
|
{
|
|
ColorSpace colorspace = graphicsDevice->GetSwapChainColorSpace(&swapChain);
|
|
activePath->colorspace = colorspace;
|
|
activePath->init(canvas);
|
|
activePath->PreUpdate();
|
|
}
|
|
|
|
// Fixed time update:
|
|
auto range = wi::profiler::BeginRangeCPU("Fixed Update");
|
|
{
|
|
if (frameskip)
|
|
{
|
|
deltaTimeAccumulator += deltaTime;
|
|
if (deltaTimeAccumulator > 10)
|
|
{
|
|
// application probably lost control, fixed update would take too long
|
|
deltaTimeAccumulator = 0;
|
|
}
|
|
|
|
const float targetFrameRateInv = 1.0f / targetFrameRate;
|
|
while (deltaTimeAccumulator >= targetFrameRateInv)
|
|
{
|
|
FixedUpdate();
|
|
deltaTimeAccumulator -= targetFrameRateInv;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FixedUpdate();
|
|
}
|
|
}
|
|
wi::profiler::EndRange(range); // Fixed Update
|
|
|
|
// Variable-timed update:
|
|
Update(deltaTime);
|
|
|
|
Render();
|
|
|
|
// Begin final compositing:
|
|
CommandList cmd = graphicsDevice->BeginCommandList();
|
|
|
|
// CrossFade texture save:
|
|
if (fadeManager.crossFadeTextureSaveRequired)
|
|
{
|
|
Texture backbuffer = rendertargetPreHDR10.IsValid() ? rendertargetPreHDR10 : graphicsDevice->GetBackBuffer(&swapChain);
|
|
if (
|
|
fadeManager.crossFadeTexture.desc.width != backbuffer.desc.width ||
|
|
fadeManager.crossFadeTexture.desc.height != backbuffer.desc.height ||
|
|
fadeManager.crossFadeTexture.desc.format != backbuffer.desc.format
|
|
)
|
|
{
|
|
TextureDesc desc = backbuffer.desc;
|
|
desc.bind_flags = BindFlag::SHADER_RESOURCE;
|
|
bool success = graphicsDevice->CreateTexture(&desc, nullptr, &fadeManager.crossFadeTexture);
|
|
assert(success);
|
|
graphicsDevice->SetName(&fadeManager.crossFadeTexture, "wiFadeManager::crossFadeTexture");
|
|
}
|
|
wi::renderer::PushBarrier(GPUBarrier::Image(&backbuffer, backbuffer.desc.layout, ResourceState::COPY_SRC));
|
|
wi::renderer::PushBarrier(GPUBarrier::Image(&fadeManager.crossFadeTexture, fadeManager.crossFadeTexture.desc.layout, ResourceState::COPY_DST));
|
|
wi::renderer::FlushBarriers(cmd);
|
|
graphicsDevice->CopyResource(&fadeManager.crossFadeTexture, &backbuffer, cmd);
|
|
wi::renderer::PushBarrier(GPUBarrier::Image(&fadeManager.crossFadeTexture, ResourceState::COPY_DST, fadeManager.crossFadeTexture.desc.layout));
|
|
wi::renderer::PushBarrier(GPUBarrier::Image(&backbuffer, ResourceState::COPY_SRC, backbuffer.desc.layout));
|
|
wi::renderer::FlushBarriers(cmd);
|
|
fadeManager.crossFadeTextureSaveRequired = false;
|
|
}
|
|
|
|
wi::image::SetCanvas(canvas);
|
|
wi::font::SetCanvas(canvas);
|
|
Viewport viewport;
|
|
viewport.width = (float)swapChain.desc.width;
|
|
viewport.height = (float)swapChain.desc.height;
|
|
graphicsDevice->BindViewports(1, &viewport, cmd);
|
|
|
|
if (rendertargetPreHDR10.IsValid())
|
|
{
|
|
graphicsDevice->RenderPassBegin(&rendertargetPreHDR10, cmd, true);
|
|
}
|
|
else
|
|
{
|
|
graphicsDevice->RenderPassBegin(&swapChain, cmd);
|
|
}
|
|
Compose(cmd);
|
|
graphicsDevice->RenderPassEnd(cmd);
|
|
|
|
if (rendertargetPreHDR10.IsValid())
|
|
{
|
|
// In HDR10, we perform a final mapping from linear to HDR10, into the swapchain
|
|
graphicsDevice->RenderPassBegin(&swapChain, cmd);
|
|
wi::image::Params fx;
|
|
fx.enableFullScreen();
|
|
fx.enableHDR10OutputMapping();
|
|
wi::image::Draw(&rendertargetPreHDR10, fx, cmd);
|
|
graphicsDevice->RenderPassEnd(cmd);
|
|
}
|
|
|
|
wi::input::ClearForNextFrame();
|
|
wi::profiler::EndFrame(cmd);
|
|
graphicsDevice->SubmitCommandLists();
|
|
}
|
|
|
|
void Application::Update(float dt)
|
|
{
|
|
auto range = wi::profiler::BeginRangeCPU("Update");
|
|
|
|
infoDisplay.rect = {};
|
|
|
|
wi::lua::SetDeltaTime(double(dt));
|
|
wi::lua::Update();
|
|
|
|
wi::backlog::Update(canvas, dt);
|
|
|
|
wi::resourcemanager::UpdateStreamingResources(dt);
|
|
|
|
if (activePath != nullptr)
|
|
{
|
|
activePath->Update(dt);
|
|
activePath->PostUpdate();
|
|
}
|
|
|
|
wi::profiler::EndRange(range); // Update
|
|
}
|
|
|
|
void Application::FixedUpdate()
|
|
{
|
|
wi::lua::FixedUpdate();
|
|
|
|
if (activePath != nullptr)
|
|
{
|
|
activePath->FixedUpdate();
|
|
}
|
|
}
|
|
|
|
void Application::Render()
|
|
{
|
|
auto range = wi::profiler::BeginRangeCPU("Render");
|
|
|
|
wi::lua::Render();
|
|
|
|
if (activePath != nullptr)
|
|
{
|
|
activePath->PreRender();
|
|
activePath->Render();
|
|
activePath->PostRender();
|
|
}
|
|
|
|
wi::profiler::EndRange(range); // Render
|
|
}
|
|
|
|
void Application::Compose(CommandList cmd)
|
|
{
|
|
auto range = wi::profiler::BeginRangeCPU("Compose");
|
|
ColorSpace colorspace = graphicsDevice->GetSwapChainColorSpace(&swapChain);
|
|
|
|
if (activePath != nullptr)
|
|
{
|
|
activePath->Compose(cmd);
|
|
}
|
|
|
|
if (fadeManager.IsActive())
|
|
{
|
|
// display fade rect
|
|
wi::image::Params fx;
|
|
fx.enableFullScreen();
|
|
fx.opacity = fadeManager.opacity;
|
|
if (fadeManager.type == FadeManager::FadeType::FadeToColor)
|
|
{
|
|
fx.color = fadeManager.color;
|
|
wi::image::Draw(nullptr, fx, cmd);
|
|
}
|
|
else if (fadeManager.type == FadeManager::FadeType::CrossFade)
|
|
{
|
|
wi::image::Draw(&fadeManager.crossFadeTexture, fx, cmd);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fadeManager.crossFadeTexture = {};
|
|
}
|
|
|
|
// Draw the information display
|
|
if (infoDisplay.active)
|
|
{
|
|
if (infoDisplay.rect.right > 0)
|
|
{
|
|
graphicsDevice->BindScissorRects(1, &infoDisplay.rect, cmd);
|
|
}
|
|
|
|
infodisplay_str.clear();
|
|
if (infoDisplay.watermark)
|
|
{
|
|
infodisplay_str += "Wicked Engine ";
|
|
infodisplay_str += wi::version::GetVersionString();
|
|
infodisplay_str += " ";
|
|
|
|
#if defined(PLATFORM_WINDOWS_DESKTOP)
|
|
infodisplay_str += "[Windows]";
|
|
#elif defined(PLATFORM_LINUX)
|
|
infodisplay_str += "[Linux]";
|
|
#elif defined(PLATFORM_PS5)
|
|
infodisplay_str += "[PS5]";
|
|
#elif defined(PLATFORM_XBOX)
|
|
infodisplay_str += "[Xbox]";
|
|
#endif // PLATFORM
|
|
|
|
#if defined(_ARM)
|
|
infodisplay_str += "[ARM]";
|
|
#elif defined(_WIN64)
|
|
infodisplay_str += "[64-bit]";
|
|
#elif defined(_WIN32)
|
|
infodisplay_str += "[32-bit]";
|
|
#endif // _ARM
|
|
|
|
#ifdef WICKEDENGINE_BUILD_DX12
|
|
if (dynamic_cast<GraphicsDevice_DX12*>(graphicsDevice.get()))
|
|
{
|
|
infodisplay_str += "[DX12]";
|
|
}
|
|
#endif // WICKEDENGINE_BUILD_DX12
|
|
#ifdef WICKEDENGINE_BUILD_VULKAN
|
|
if (dynamic_cast<GraphicsDevice_Vulkan*>(graphicsDevice.get()))
|
|
{
|
|
infodisplay_str += "[Vulkan]";
|
|
}
|
|
#endif // WICKEDENGINE_BUILD_VULKAN
|
|
|
|
#ifdef _DEBUG
|
|
infodisplay_str += "[DEBUG]";
|
|
#endif // _DEBUG
|
|
if (graphicsDevice->IsDebugDevice())
|
|
{
|
|
infodisplay_str += "[debugdevice]";
|
|
}
|
|
infodisplay_str += "\n";
|
|
}
|
|
if (infoDisplay.device_name)
|
|
{
|
|
infodisplay_str += "Device: ";
|
|
infodisplay_str += graphicsDevice->GetAdapterName();
|
|
infodisplay_str += "\n";
|
|
}
|
|
if (infoDisplay.resolution)
|
|
{
|
|
infodisplay_str += "Resolution: ";
|
|
infodisplay_str += std::to_string(canvas.GetPhysicalWidth());
|
|
infodisplay_str += " x ";
|
|
infodisplay_str += std::to_string(canvas.GetPhysicalHeight());
|
|
infodisplay_str += " (";
|
|
infodisplay_str += std::to_string(int(canvas.GetDPI()));
|
|
infodisplay_str += " dpi)\n";
|
|
}
|
|
if (infoDisplay.logical_size)
|
|
{
|
|
infodisplay_str += "Logical Size: ";
|
|
infodisplay_str += std::to_string(int(canvas.GetLogicalWidth()));
|
|
infodisplay_str += " x ";
|
|
infodisplay_str += std::to_string(int(canvas.GetLogicalHeight()));
|
|
infodisplay_str += "\n";
|
|
}
|
|
if (infoDisplay.colorspace)
|
|
{
|
|
infodisplay_str += "Color Space: ";
|
|
ColorSpace colorSpace = graphicsDevice->GetSwapChainColorSpace(&swapChain);
|
|
switch (colorSpace)
|
|
{
|
|
default:
|
|
case wi::graphics::ColorSpace::SRGB:
|
|
infodisplay_str += "sRGB";
|
|
break;
|
|
case wi::graphics::ColorSpace::HDR10_ST2084:
|
|
infodisplay_str += "ST.2084 (HDR10)";
|
|
break;
|
|
case wi::graphics::ColorSpace::HDR_LINEAR:
|
|
infodisplay_str += "Linear (HDR)";
|
|
break;
|
|
}
|
|
infodisplay_str += "\n";
|
|
}
|
|
if (infoDisplay.fpsinfo)
|
|
{
|
|
deltatimes[fps_avg_counter++ % arraysize(deltatimes)] = deltaTime;
|
|
float displaydeltatime = deltaTime;
|
|
if (fps_avg_counter > arraysize(deltatimes))
|
|
{
|
|
float avg_time = 0;
|
|
for (int i = 0; i < arraysize(deltatimes); ++i)
|
|
{
|
|
avg_time += deltatimes[i];
|
|
}
|
|
displaydeltatime = avg_time / arraysize(deltatimes);
|
|
}
|
|
|
|
infodisplay_str += std::to_string(int(std::round(1.0f / displaydeltatime))) + " FPS\n";
|
|
}
|
|
if (infoDisplay.heap_allocation_counter)
|
|
{
|
|
infodisplay_str += "Heap allocations per frame: ";
|
|
#ifdef WICKED_ENGINE_HEAP_ALLOCATION_COUNTER
|
|
infodisplay_str += std::to_string(number_of_heap_allocations.load());
|
|
infodisplay_str += " (";
|
|
infodisplay_str += std::to_string(size_of_heap_allocations.load());
|
|
infodisplay_str += " bytes)\n";
|
|
number_of_heap_allocations.store(0);
|
|
size_of_heap_allocations.store(0);
|
|
#else
|
|
infodisplay_str += "[disabled]\n";
|
|
#endif // WICKED_ENGINE_HEAP_ALLOCATION_COUNTER
|
|
}
|
|
if (infoDisplay.pipeline_count)
|
|
{
|
|
infodisplay_str += "Graphics pipelines active: ";
|
|
infodisplay_str += std::to_string(graphicsDevice->GetActivePipelineCount());
|
|
infodisplay_str += "\n";
|
|
}
|
|
if (infoDisplay.pipeline_creation && wi::renderer::IsPipelineCreationActive())
|
|
{
|
|
infodisplay_str += "Shader pipeline creation is running...\n";
|
|
}
|
|
|
|
wi::font::Params params = wi::font::Params(
|
|
4 + canvas.PhysicalToLogical((uint32_t)infoDisplay.rect.left),
|
|
4 + canvas.PhysicalToLogical((uint32_t)infoDisplay.rect.top),
|
|
infoDisplay.size,
|
|
wi::font::WIFALIGN_LEFT,
|
|
wi::font::WIFALIGN_TOP,
|
|
wi::Color::White(),
|
|
wi::Color::Shadow()
|
|
);
|
|
params.shadow_softness = 0.4f;
|
|
|
|
// Explanation: this compose pass is in LINEAR space if display output is linear or HDR10
|
|
// If HDR10, the HDR10 output mapping will be performed on whole image later when drawing to swapchain
|
|
if (colorspace != ColorSpace::SRGB)
|
|
{
|
|
params.enableLinearOutputMapping(9);
|
|
}
|
|
|
|
params.cursor = wi::font::Draw(infodisplay_str, params, cmd);
|
|
|
|
// VRAM:
|
|
{
|
|
GraphicsDevice::MemoryUsage vram = graphicsDevice->GetMemoryUsage();
|
|
bool warn = false;
|
|
if (vram.usage > vram.budget)
|
|
{
|
|
params.color = wi::Color::Error();
|
|
warn = true;
|
|
}
|
|
else if (float(vram.usage) / float(vram.budget) > 0.9f)
|
|
{
|
|
params.color = wi::Color::Warning();
|
|
warn = true;
|
|
}
|
|
if (infoDisplay.vram_usage || warn)
|
|
{
|
|
params.cursor = wi::font::Draw("VRAM usage: " + std::to_string(vram.usage / 1024 / 1024) + "MB / " + std::to_string(vram.budget / 1024 / 1024) + "MB\n", params, cmd);
|
|
params.color = wi::Color::White();
|
|
}
|
|
}
|
|
|
|
// Write warnings below:
|
|
params.color = wi::Color::Warning();
|
|
#ifdef _DEBUG
|
|
params.cursor = wi::font::Draw("Warning: This is a [DEBUG] build, performance will be slow!\n", params, cmd);
|
|
#endif
|
|
if (graphicsDevice->IsDebugDevice())
|
|
{
|
|
params.cursor = wi::font::Draw("Warning: Graphics is in [debugdevice] mode, performance will be slow!\n", params, cmd);
|
|
}
|
|
|
|
// Write errors below:
|
|
params.color = wi::Color::Error();
|
|
if (wi::renderer::GetShaderMissingCount() > 0)
|
|
{
|
|
params.cursor = wi::font::Draw(std::to_string(wi::renderer::GetShaderMissingCount()) + " shaders missing! Check the backlog for more information!\n", params, cmd);
|
|
}
|
|
if (wi::renderer::GetShaderErrorCount() > 0)
|
|
{
|
|
params.cursor = wi::font::Draw(std::to_string(wi::renderer::GetShaderErrorCount()) + " shader compilation errors! Check the backlog for more information!\n", params, cmd);
|
|
}
|
|
if (wi::backlog::GetUnseenLogLevelMax() >= wi::backlog::LogLevel::Error)
|
|
{
|
|
params.cursor = wi::font::Draw("Errors found, check the backlog for more information!", params, cmd);
|
|
}
|
|
|
|
if (infoDisplay.colorgrading_helper)
|
|
{
|
|
wi::image::Draw(
|
|
wi::texturehelper::getColorGradeDefault(),
|
|
wi::image::Params(
|
|
canvas.PhysicalToLogical((uint32_t)infoDisplay.rect.left),
|
|
canvas.PhysicalToLogical((uint32_t)infoDisplay.rect.top),
|
|
canvas.PhysicalToLogical(256),
|
|
canvas.PhysicalToLogical(16)
|
|
),
|
|
cmd
|
|
);
|
|
}
|
|
|
|
if (infoDisplay.rect.right > 0)
|
|
{
|
|
Rect rect;
|
|
rect.right = canvas.width;
|
|
rect.bottom = canvas.height;
|
|
graphicsDevice->BindScissorRects(1, &rect, cmd);
|
|
}
|
|
}
|
|
|
|
wi::profiler::DrawData(canvas, 4, 10, cmd, colorspace);
|
|
|
|
wi::backlog::Draw(canvas, cmd, colorspace);
|
|
|
|
wi::profiler::EndRange(range); // Compose
|
|
}
|
|
|
|
void Application::SetWindow(wi::platform::window_type window)
|
|
{
|
|
this->window = window;
|
|
|
|
// User can also create a graphics device if custom logic is desired, but they must do before this function!
|
|
if (graphicsDevice == nullptr)
|
|
{
|
|
ValidationMode validationMode = ValidationMode::Disabled;
|
|
if (wi::arguments::HasArgument("debugdevice"))
|
|
{
|
|
validationMode = ValidationMode::Enabled;
|
|
}
|
|
if (wi::arguments::HasArgument("gpuvalidation"))
|
|
{
|
|
validationMode = ValidationMode::GPU;
|
|
}
|
|
if (wi::arguments::HasArgument("gpu_verbose"))
|
|
{
|
|
validationMode = ValidationMode::Verbose;
|
|
}
|
|
|
|
GPUPreference preference = GPUPreference::Discrete;
|
|
if (wi::arguments::HasArgument("igpu"))
|
|
{
|
|
preference = GPUPreference::Integrated;
|
|
}
|
|
|
|
#ifdef PLATFORM_PS5
|
|
wi::renderer::SetShaderPath(wi::renderer::GetShaderPath() + "ps5/");
|
|
graphicsDevice = std::make_unique<GraphicsDevice_PS5>(validationMode);
|
|
|
|
#else
|
|
bool use_dx12 = wi::arguments::HasArgument("dx12");
|
|
bool use_vulkan = wi::arguments::HasArgument("vulkan");
|
|
|
|
#ifndef WICKEDENGINE_BUILD_DX12
|
|
if (use_dx12) {
|
|
wi::helper::messageBox("The engine was built without DX12 support!", "Error");
|
|
use_dx12 = false;
|
|
}
|
|
#endif // WICKEDENGINE_BUILD_DX12
|
|
#ifndef WICKEDENGINE_BUILD_VULKAN
|
|
if (use_vulkan) {
|
|
wi::helper::messageBox("The engine was built without Vulkan support!", "Error");
|
|
use_vulkan = false;
|
|
}
|
|
#endif // WICKEDENGINE_BUILD_VULKAN
|
|
|
|
if (!use_dx12 && !use_vulkan)
|
|
{
|
|
#if defined(WICKEDENGINE_BUILD_DX12)
|
|
use_dx12 = true;
|
|
#elif defined(WICKEDENGINE_BUILD_VULKAN)
|
|
use_vulkan = true;
|
|
#else
|
|
wi::backlog::post("No rendering backend is enabled! Please enable at least one so we can use it as default", wi::backlog::LogLevel::Error);
|
|
assert(false);
|
|
#endif
|
|
}
|
|
assert(use_dx12 || use_vulkan);
|
|
|
|
if (use_vulkan)
|
|
{
|
|
#ifdef WICKEDENGINE_BUILD_VULKAN
|
|
wi::renderer::SetShaderPath(wi::renderer::GetShaderPath() + "spirv/");
|
|
graphicsDevice = std::make_unique<GraphicsDevice_Vulkan>(window, validationMode, preference);
|
|
#endif
|
|
}
|
|
else if (use_dx12)
|
|
{
|
|
#ifdef WICKEDENGINE_BUILD_DX12
|
|
#ifdef PLATFORM_XBOX
|
|
wi::renderer::SetShaderPath(wi::renderer::GetShaderPath() + "hlsl6_xs/");
|
|
#else
|
|
wi::renderer::SetShaderPath(wi::renderer::GetShaderPath() + "hlsl6/");
|
|
#endif // PLATFORM_XBOX
|
|
graphicsDevice = std::make_unique<GraphicsDevice_DX12>(validationMode, preference);
|
|
#endif
|
|
}
|
|
#endif // PLATFORM_PS5
|
|
}
|
|
wi::graphics::GetDevice() = graphicsDevice.get();
|
|
|
|
canvas.init(window);
|
|
|
|
SwapChainDesc desc;
|
|
if (swapChain.IsValid())
|
|
{
|
|
// it will only resize, but keep format and other settings
|
|
desc = swapChain.desc;
|
|
}
|
|
else
|
|
{
|
|
// initialize for the first time
|
|
desc.buffer_count = 3;
|
|
if (graphicsDevice->CheckCapability(GraphicsDeviceCapability::R9G9B9E5_SHAREDEXP_RENDERABLE))
|
|
{
|
|
desc.format = Format::R9G9B9E5_SHAREDEXP;
|
|
}
|
|
else
|
|
{
|
|
desc.format = Format::R10G10B10A2_UNORM;
|
|
}
|
|
}
|
|
desc.width = canvas.GetPhysicalWidth();
|
|
desc.height = canvas.GetPhysicalHeight();
|
|
desc.allow_hdr = allow_hdr;
|
|
bool success = graphicsDevice->CreateSwapChain(&desc, window, &swapChain);
|
|
assert(success);
|
|
|
|
#ifdef PLATFORM_PS5
|
|
// PS5 swapchain resolution was decided in CreateSwapchain(), so reinit canvas:
|
|
canvas.init(swapChain.desc.width, swapChain.desc.height);
|
|
#endif // PLATFORM_PS5
|
|
|
|
swapChainVsyncChangeEvent = wi::eventhandler::Subscribe(wi::eventhandler::EVENT_SET_VSYNC, [this](uint64_t userdata) {
|
|
SwapChainDesc desc = swapChain.desc;
|
|
desc.vsync = userdata != 0;
|
|
bool success = graphicsDevice->CreateSwapChain(&desc, nullptr, &swapChain);
|
|
assert(success);
|
|
});
|
|
|
|
}
|
|
|
|
void Application::SetFullScreen(bool fullscreen)
|
|
{
|
|
#if defined(PLATFORM_WINDOWS_DESKTOP)
|
|
|
|
// Based on: https://devblogs.microsoft.com/oldnewthing/20100412-00/?p=14353
|
|
static WINDOWPLACEMENT wp = {};
|
|
DWORD dwStyle = GetWindowLong(window, GWL_STYLE);
|
|
bool currently_windowed = dwStyle & WS_OVERLAPPEDWINDOW;
|
|
if (currently_windowed && fullscreen) {
|
|
MONITORINFO mi = { sizeof(mi) };
|
|
if (GetWindowPlacement(window, &wp) &&
|
|
GetMonitorInfo(MonitorFromWindow(window,
|
|
MONITOR_DEFAULTTOPRIMARY), &mi)) {
|
|
SetWindowLong(window, GWL_STYLE,
|
|
dwStyle & ~WS_OVERLAPPEDWINDOW);
|
|
SetWindowPos(window, HWND_TOP,
|
|
mi.rcMonitor.left, mi.rcMonitor.top,
|
|
mi.rcMonitor.right - mi.rcMonitor.left,
|
|
mi.rcMonitor.bottom - mi.rcMonitor.top,
|
|
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
|
|
}
|
|
}
|
|
else if (!currently_windowed && !fullscreen) {
|
|
SetWindowLong(window, GWL_STYLE,
|
|
dwStyle | WS_OVERLAPPEDWINDOW);
|
|
SetWindowPlacement(window, &wp);
|
|
SetWindowPos(window, NULL, 0, 0, 0, 0,
|
|
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
|
|
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
|
|
}
|
|
|
|
#elif defined(PLATFORM_LINUX)
|
|
SDL_SetWindowFullscreen(window, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
|
|
#endif // PLATFORM_WINDOWS_DESKTOP
|
|
}
|
|
|
|
}
|
|
|
|
|
|
#ifdef WICKED_ENGINE_HEAP_ALLOCATION_COUNTER
|
|
// Heap alloc replacements are used to count heap allocations:
|
|
// It is good practice to reduce the amount of heap allocations that happen during the frame,
|
|
// so keep an eye on the info display of the engine while Application::InfoDisplayer::heap_allocation_counter is enabled
|
|
|
|
void* operator new(std::size_t size) {
|
|
number_of_heap_allocations.fetch_add(1);
|
|
size_of_heap_allocations.fetch_add(size);
|
|
void* p = malloc(size);
|
|
if (!p) throw std::bad_alloc();
|
|
return p;
|
|
}
|
|
void* operator new[](std::size_t size) {
|
|
number_of_heap_allocations.fetch_add(1);
|
|
size_of_heap_allocations.fetch_add(size);
|
|
void* p = malloc(size);
|
|
if (!p) throw std::bad_alloc();
|
|
return p;
|
|
}
|
|
void* operator new[](std::size_t size, const std::nothrow_t&) throw() {
|
|
number_of_heap_allocations.fetch_add(1);
|
|
size_of_heap_allocations.fetch_add(size);
|
|
return malloc(size);
|
|
}
|
|
void* operator new(std::size_t size, const std::nothrow_t&) throw() {
|
|
number_of_heap_allocations.fetch_add(1);
|
|
size_of_heap_allocations.fetch_add(size);
|
|
return malloc(size);
|
|
}
|
|
void operator delete(void* ptr) throw() { free(ptr); }
|
|
void operator delete (void* ptr, const std::nothrow_t&) throw() { free(ptr); }
|
|
void operator delete[](void* ptr) throw() { free(ptr); }
|
|
void operator delete[](void* ptr, const std::nothrow_t&) throw() { free(ptr); }
|
|
#endif // WICKED_ENGINE_HEAP_ALLOCATION_COUNTER
|