Files
PuzGame/src/game/rendering/Rendering.cpp
2025-04-29 09:52:19 +02:00

627 lines
22 KiB
C++

#include "../../engine/Shared.h"
#include "../Global.h"
#include "../Instance.h"
#include "../Log.h"
#include "../Mesh.h"
#include "../Tools.h"
#include "Rendering.h"
#include "Dither.h"
#include "SDL3/SDL_events.h" // IWYU pragma: keep
#include "backends/imgui_impl_sdl3.h"
#include "bgfx/defines.h"
#include "bx/bx.h"
#include "bx/debug.h"
#include "bx/filepath.h"
#include "bx/string.h"
#include "bx/timer.h"
#include <bgfx/bgfx.h>
#include <bimg/bimg.h>
#include <bx/file.h>
#include <cstdint>
#include <cstdio>
#include <thread>
#include <tracy/Tracy.hpp>
#include "imgui-helper.h"
#include "imgui-style.h"
#include "imgui.h"
using namespace std::chrono_literals;
namespace Game
{
namespace
{
constexpr size_t ChunkSize = 1024;
constexpr size_t MaxFileSize = ChunkSize * 1024 * 1024;
constexpr size_t MaxChunkCount = MaxFileSize / ChunkSize;
bool BufferedFileRead(FILE* const file, uint8_t* const writePtrIn, size_t& outTotalReadCount)
{
uint8_t* writePtr = writePtrIn;
for (int32_t i = 0; i < MaxChunkCount; ++i)
{
size_t readCount = std::fread(writePtr, 1, ChunkSize, file);
writePtr += readCount;
outTotalReadCount += readCount;
if (readCount != ChunkSize)
{
if (std::feof(file))
{
return true;
}
int err = std::ferror(file);
if (err != 0)
{
LOG_ERROR("Error reading file: %i", err);
return false;
}
LOG_ERROR("This should never happen!");
return false;
}
}
if (!std::feof(file))
{
LOG_ERROR("File too big to be read!");
return false;
}
return true;
}
const bgfx::Memory* LoadBinaryFile(const char* path, int32_t retryCount = 1)
{
FILE* file = nullptr;
for (int32_t i = 0; i < retryCount; ++i)
{
file = std::fopen(path, "rb");
if (file == nullptr)
{
if (i < retryCount - 1)
{
std::this_thread::sleep_for(100ms);
LOG_WARN("Failed to open file, retrying...");
break;
}
else
{
LOG_ERROR("Failed to open file!");
return nullptr;
}
}
}
uint8_t* dataPtr = AllocateScratch(MaxFileSize);
if (dataPtr == nullptr)
{
LOG_ERROR("Failed to load file, exceeded scratch memory! %s", path);
return nullptr;
}
uint64_t totalReadCount = 0;
bool success = BufferedFileRead(file, dataPtr, totalReadCount);
std::fclose(file);
if (!success)
{
LOG_ERROR("Failed to read file %s", path);
ResizeLastScratchAlloc(0);
return nullptr;
}
if (!ResizeLastScratchAlloc(totalReadCount))
{
LOG_ERROR("This should never happen!");
return nullptr;
}
return bgfx::makeRef(dataPtr, totalReadCount);
}
const bgfx::Memory* loadFile(const char* path, bool appendZero = false, int32_t retryCount = 1)
{
FILE* file;
for (int32_t i = 0; i < retryCount; ++i)
{
file = std::fopen(path, "rb");
if (file == nullptr && i < retryCount - 1)
{
std::this_thread::sleep_for(100ms);
LOG_WARN("Failed to open file, retrying...");
break;
}
fseek(file, 0, SEEK_END);
long fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
long fileSizeX = appendZero ? (fileSize + 1) : fileSize;
void* rawMem = AllocateScratch(fileSizeX);
if (rawMem == nullptr)
{
LOG_ERROR("Failed to load file, exceeded scratch memory! %s", path);
return nullptr;
}
const bgfx::Memory* mem = bgfx::makeRef(rawMem, fileSizeX);
fread(mem->data, 1, fileSize, file);
if (appendZero)
{
mem->data[mem->size - 1] = '\0';
}
fclose(file);
return mem;
}
LOG_WARN("File inaccessible! %s", path);
return nullptr;
}
bgfx::ShaderHandle loadShader(const char* FILENAME)
{
const char* shaderPath = "???";
switch (bgfx::getRendererType())
{
case bgfx::RendererType::Agc:
case bgfx::RendererType::Nvn:
case bgfx::RendererType::Count:
case bgfx::RendererType::Noop:
break;
case bgfx::RendererType::Direct3D11:
case bgfx::RendererType::Direct3D12:
shaderPath = "game/compiled-shaders/dx11/";
break;
case bgfx::RendererType::Gnm:
shaderPath = "game/compiled-shaders/pssl/";
break;
case bgfx::RendererType::Metal:
shaderPath = "game/compiled-shaders/metal/";
break;
case bgfx::RendererType::OpenGL:
shaderPath = "game/compiled-shaders/glsl/";
break;
case bgfx::RendererType::OpenGLES:
shaderPath = "game/compiled-shaders/essl/";
break;
case bgfx::RendererType::Vulkan:
shaderPath = "game/compiled-shaders/spirv/";
break;
}
char buffer[512]{0};
bx::strCopy(buffer, sizeof(buffer), shaderPath);
bx::strCat(buffer, sizeof(buffer), FILENAME);
bx::strCat(buffer, sizeof(buffer), ".bin");
LOG("Loading shader at %s", buffer);
const bgfx::Memory* mem = loadFile(buffer, true, 3);
if (mem == nullptr)
{
LOG_WARN("Failed to load shader %s", FILENAME);
return {};
}
return bgfx::createShader(mem);
}
bgfx::TextureHandle loadTexture(const bx::FilePath& _filePath,
uint64_t _flags,
uint8_t _skip,
bgfx::TextureInfo* _info,
bimg::Orientation::Enum* _orientation)
{
BX_UNUSED(_skip);
bgfx::TextureHandle handle = BGFX_INVALID_HANDLE;
bx::Error err;
const bgfx::Memory* data = LoadBinaryFile(_filePath.getCPtr());
if (data == nullptr || data->data == nullptr || data->size == 0)
{
LOG_WARN("Failed to find image %s", _filePath.getCPtr());
return handle;
}
bimg::ImageContainer imageContainer;
if (!bimg::imageParse(imageContainer, data->data, data->size, &err))
{
LOG_WARN("Failed to load image %s", _filePath.getCPtr());
return handle;
}
if (NULL != _orientation)
{
*_orientation = imageContainer.m_orientation;
}
// We're working with ktx textures, so the size should be in front of the actual data
const uint32_t* texSizePtr = reinterpret_cast<uint32_t*>(data->data + imageContainer.m_offset);
uint8_t* dataPtr = data->data + imageContainer.m_offset + sizeof(uint32_t);
uint32_t dataSize = data->size - imageContainer.m_offset - sizeof(uint32_t);
// Extra sanity check
if (*texSizePtr != dataSize)
{
LOG_WARN("Texture size sanity check failed! %u != %u", texSizePtr, dataSize);
return handle;
}
const bgfx::Memory* mem = bgfx::makeRef(dataPtr, dataSize);
if (NULL != _info)
{
bgfx::calcTextureSize(*_info,
uint16_t(imageContainer.m_width),
uint16_t(imageContainer.m_height),
uint16_t(imageContainer.m_depth),
imageContainer.m_cubeMap,
1 < imageContainer.m_numMips,
imageContainer.m_numLayers,
bgfx::TextureFormat::Enum(imageContainer.m_format));
}
if (imageContainer.m_cubeMap)
{
handle = bgfx::createTextureCube(uint16_t(imageContainer.m_width),
1 < imageContainer.m_numMips,
imageContainer.m_numLayers,
bgfx::TextureFormat::Enum(imageContainer.m_format),
_flags,
mem);
}
else if (1 < imageContainer.m_depth)
{
handle = bgfx::createTexture3D(uint16_t(imageContainer.m_width),
uint16_t(imageContainer.m_height),
uint16_t(imageContainer.m_depth),
1 < imageContainer.m_numMips,
bgfx::TextureFormat::Enum(imageContainer.m_format),
_flags,
mem);
}
else if (bgfx::isTextureValid(0,
false,
imageContainer.m_numLayers,
bgfx::TextureFormat::Enum(imageContainer.m_format),
_flags))
{
handle = bgfx::createTexture2D(uint16_t(imageContainer.m_width),
uint16_t(imageContainer.m_height),
1 < imageContainer.m_numMips,
imageContainer.m_numLayers,
bgfx::TextureFormat::Enum(imageContainer.m_format),
_flags,
mem);
}
if (bgfx::isValid(handle))
{
const bx::StringView name(_filePath);
bgfx::setName(handle, name.getPtr(), name.getLength());
}
return handle;
}
GameRendering* Instance = nullptr;
} // namespace
GameRendering& GameRendering::Get()
{
assert(Instance != nullptr);
return *Instance;
}
void GameRendering::Setup(const RenderingSetup& setup)
{
LOG("--- RENDERING STARTUP ---");
ZoneScopedN("Setup");
SetupData = setup;
if (Instance != nullptr) LOG_WARN("old rendering wasn't destroyed!");
Instance = this;
SharedData& shared = GetShared();
bgfx::Init init;
init.type = bgfx::RendererType::Direct3D12;
#ifdef _DEBUG
init.debug = true;
// init.debug = false;
#else
init.debug = false;
#endif
init.platformData.nwh = shared.Window.Handle;
init.platformData.ndt = nullptr;
init.platformData.type = bgfx::NativeWindowHandleType::Default;
init.resolution.width = shared.Window.WindowWidth;
init.resolution.height = shared.Window.WindowHeight;
init.resolution.reset = ResetFlags;
LastWidth = shared.Window.WindowWidth;
LastHeight = shared.Window.WindowHeight;
LOG("%i by %i", init.resolution.width, init.resolution.height);
if (!bgfx::init(init))
{
LOG_ERROR("BGFX setup failed!");
}
else
{
LOG("BGFX setup succeded!");
}
bgfx::setViewClear(MainViewID, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x3399FFff, 1.0f, 0);
bgfx::setViewRect(MainViewID, 0, 0, shared.Window.WindowWidth, shared.Window.WindowHeight);
DefaultSampler = bgfx::createUniform("s_texColor", bgfx::UniformType::Sampler);
LoadTextures();
LoadModels(Models, ModelCount);
ReloadShaders();
if (SetupData.UseImgui)
{
imguiCreate();
SetImguiStyle();
if (!ImGui_ImplSDL3_InitForOther(shared.Window.SDLWindow))
{
LOG_ERROR("Failed to set up imgui implementation!");
return;
}
// ImGui::GetIO().BackendFlags |= ImGuiBackendFlags_RendererHasViewports;
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable;
// ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
// auto& platIO = ImGui::GetPlatformIO();
// platIO.Renderer_CreateWindow = TODO;
// platIO.Platform_DestroyWindow = TODO;
// platIO.Platform_SetWindowSize = TODO;
// platIO.Platform_RenderWindow = TODO;
}
GameInstance& inst = GetInstance();
if (!inst.IsInitialized)
{
inst.Time.StartTime = bx::getHPCounter();
}
if (SetupData.UseImgui)
{
if (inst.DebugData.ImguiIniSize > 0)
{
ImGui::LoadIniSettingsFromMemory(inst.DebugData.ImguiIni, inst.DebugData.ImguiIniSize);
}
else
{
ImGui::LoadIniSettingsFromDisk("imgui.ini");
}
}
DitherGen(DitherTextures, DitherRecursion);
}
void GameRendering::HandleEvents()
{
ZoneScopedN("Handle Events");
SharedData& shared = GetShared();
for (uint16_t i = 0; i < shared.Window.SDLEventCount; ++i)
{
if (SetupData.UseImgui)
{
ImGui_ImplSDL3_ProcessEvent(&shared.Window.SDLEvents[i]);
}
}
shared.Window.SDLEventCount = 0;
// Resize if necessary
if (shared.Window.WindowWidth != LastWidth || shared.Window.WindowHeight != LastHeight)
{
bgfx::reset(shared.Window.WindowWidth, shared.Window.WindowHeight, ResetFlags);
bgfx::setViewRect(MainViewID, 0, 0, shared.Window.WindowWidth, shared.Window.WindowHeight);
LastWidth = shared.Window.WindowWidth;
LastHeight = shared.Window.WindowHeight;
}
// Reload shaders if necessary
FileChangeNotification* shaderChange = nullptr;
if (shared.Dev.ChangedShaderCount > 0)
{
char buf[1024]{0};
for (int32_t i = 0; i < shared.Dev.ChangedShaderCount; ++i)
{
wcstombs(buf, shared.Dev.ChangedShaders[i].FileName, sizeof(buf));
LOG("Changed: %s", buf);
}
shared.Dev.ChangedShaderCount = 0;
// TODO: when to destroy shader?
// TODO: only reload changed shaders
ReloadShaders();
}
}
void GameRendering::LoadTextures()
{
for (int32_t i = 0; i < MaxTextures; ++i)
{
Textures[i] = {};
}
bx::Error err;
bx::DirectoryReader reader{};
if (!reader.open("textures", &err) || !err.isOk())
{
LOG_ERROR("Failed to read textures dir: %s", err.getMessage());
}
bx::FileInfo info;
int32_t textureFilePathCount = 0;
bx::FilePath textureFilePaths[GameRendering::MaxTextures];
while (err.isOk())
{
int32_t res = reader.read(&info, sizeof(info), &err);
if (res == 0) break; // EOF
if (res != sizeof(info))
{
LOG_ERROR("Dir iter error: %s", err.getMessage());
break;
}
const bx::StringView ext = info.filePath.getExt();
bool isDDS = bx::strCmp(ext, ".dds") == 0;
bool isKTX = bx::strCmp(ext, ".ktx") == 0;
if ((isDDS || isKTX) && !ext.isEmpty() && info.type == bx::FileType::File)
{
if (textureFilePathCount >= GameRendering::MaxTextures)
{
LOG_ERROR("Texture limit reached!");
break;
}
textureFilePaths[textureFilePathCount] = info.filePath;
textureFilePathCount++;
}
}
LOG("Found %u textures!", textureFilePathCount);
for (int32_t i = 0; i < textureFilePathCount; ++i)
{
bx::FilePath fullPath{"textures"};
fullPath.join(textureFilePaths[i]);
Textures[i].RenderHandle =
loadTexture(fullPath, BGFX_TEXTURE_NONE | BGFX_SAMPLER_NONE, 0, &Textures[i].Info, nullptr);
Textures[i].SamplerHandle = DefaultSampler;
Textures[i].TexHandle.TextureIdx = i;
Textures[i].TexHandle.Asset.Idx = CrcPath(fullPath.getCPtr());
auto& debug = GetInstance().DebugData;
if (debug.AssetCount < debug.MaxAssets)
{
debug.AssetHandles[debug.AssetCount] = Textures[i].TexHandle.Asset;
bx::strCopy(debug.AssetHandlePaths[debug.AssetCount],
sizeof(debug.AssetHandlePaths[debug.AssetCount]),
fullPath.getCPtr());
++debug.AssetCount;
}
}
}
void GameRendering::ReloadShaders()
{
Materials[Gen::EMaterial::Default] = Material::LoadFromShader("dither/vert", "dither/frag", MainViewID);
Materials[Gen::EMaterial::UI] = Material::LoadFromShader("normal/vert", "normal/frag", MainViewID);
}
void GameRendering::Update()
{
ZoneScopedN("Rendering");
SharedData& shared = GetShared();
auto& time = GetInstance().Time;
HandleEvents();
// Start Rendering
if (SetupData.UseImgui)
{
ZoneScopedN("Imgui Start Frame");
imguiBeginFrame(20);
ImGui_ImplSDL3_NewFrame();
ImGui::DockSpaceOverViewport(0, 0, ImGuiDockNodeFlags_PassthruCentralNode);
}
Tools::RenderDebugUI(*this);
GetInstance().GameLevel.Update();
GetInstance().GameLevel.Render(MainViewID, Models, Materials, Textures);
// Finish Frame
if (SetupData.UseImgui)
{
ZoneScopedN("Imgui End Frame");
imguiEndFrame();
}
if (SetupData.UseImgui && ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
ZoneScopedN("Imgui Platform Update");
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
}
{
ZoneScopedN("BGFX Frame");
START_PERF();
bgfx::frame();
END_PERF(shared.Window.PerfCounters, PerfCounterType::Submit, shared.Window.FrameCounter);
}
}
void GameRendering::Shutdown()
{
ZoneScopedN("Shutdown");
LOG("--- RENDERING_SHUTDOWN ---");
for (int32_t i = 0; i < BX_COUNTOF(Textures); ++i)
{
if (isValid(Textures[i].RenderHandle))
{
bgfx::destroy(Textures[i].RenderHandle);
Textures[i].RenderHandle = {bgfx::kInvalidHandle};
}
}
for (int32_t i = 0; i < ModelCount; ++i)
{
bgfx::destroy(Models[i].VertexBuffer);
bgfx::destroy(Models[i].IndexBuffer);
}
ModelCount = 0;
CleanupDitherData(DitherTextures);
if (SetupData.UseImgui)
{
ImGui::SaveIniSettingsToDisk("imgui.ini");
auto& debug = GetInstance().DebugData;
const char* iniData = ImGui::SaveIniSettingsToMemory(reinterpret_cast<uint64_t*>(&debug.ImguiIniSize));
assert(debug.ImguiIniSize <= BX_COUNTOF(InstanceDebugData::ImguiIni));
bx::memCopy(debug.ImguiIni, iniData, bx::min(debug.ImguiIniSize, BX_COUNTOF(InstanceDebugData::ImguiIni)));
ImGui_ImplSDL3_Shutdown();
imguiDestroy();
}
bgfx::shutdown();
Instance = nullptr;
}
Material Material::LoadFromShader(const char* vertPath, const char* fragPath, uint16_t view)
{
BX_ASSERT(vertPath != nullptr && fragPath != nullptr, "Invalid shader path!");
bgfx::ShaderHandle vertexShader = loadShader(vertPath);
bgfx::ShaderHandle fragmentShader = loadShader(fragPath);
Material mat;
mat.Shader = bgfx::createProgram(vertexShader, fragmentShader, true);
mat.State = 0 | BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_WRITE_Z | BGFX_STATE_DEPTH_TEST_LESS |
BGFX_STATE_CULL_CCW | BGFX_STATE_MSAA;
mat.Uniforms[Material::UTime] = bgfx::createUniform("u_time", bgfx::UniformType::Vec4);
mat.Uniforms[Material::UDotColor] = bgfx::createUniform("u_dotColor", bgfx::UniformType::Vec4);
mat.Uniforms[Material::UTexInfo] = bgfx::createUniform("u_texInfo", bgfx::UniformType::Vec4);
mat.Uniforms[Material::UBaseColor] = bgfx::createUniform("u_baseColor", bgfx::UniformType::Vec4);
mat.ViewID = view;
return mat;
}
Gen::ModelHandle GameRendering::GetModelHandleFromPath(const char* path)
{
uint32_t AssetHandle = CrcPath(path);
for (int32_t i = 0; i < ModelCount; ++i)
{
if (Models[i].Handle.Asset.Idx == AssetHandle)
{
return Models[i].Handle;
}
}
LOG_WARN("Failed to find model for path %s", path);
return {};
}
} // namespace Game