#include "../../engine/Shared.h" #include "../Global.h" #include "../Instance.h" #include "../Log.h" #include "../Mesh.h" #include "Rendering.h" #include "SDL3/SDL_events.h" #include "backends/imgui_impl_sdl3.h" #include "bgfx/defines.h" #include "bx/bx.h" #include "bx/constants.h" #include "bx/filepath.h" #include "bx/math.h" #include "bx/timer.h" #include #include #include #include #include #include "imgui-helper.h" #include "imgui.h" using namespace std::chrono_literals; namespace Game { namespace { 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 = fopen(path, "rb"); if (file == nullptr && i < retryCount - 1) { std::this_thread::sleep_for(100ms); break; } fseek(file, 0, SEEK_END); long fileSize = ftell(file); fseek(file, 0, SEEK_SET); void* rawMem = AllocateScratch(fileSize + 1); const bgfx::Memory* mem = bgfx::makeRef(rawMem, fileSize + 1); fread(mem->data, 1, fileSize, file); if (appendZero) { mem->data[mem->size - 1] = '\0'; } fclose(file); return mem; } 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 = loadFile(_filePath.getCPtr()); if (data == nullptr) { 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; } const bgfx::Memory* mem = bgfx::makeRef(data->data + imageContainer.m_offset, data->size - imageContainer.m_offset); 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; } void DitherGen(DitherData& data, int32_t recursion) { data.Points[0] = {0.0f, 0.0f}; data.Points[1] = {0.5f, 0.5f}; data.Points[2] = {0.5f, 0.0f}; data.Points[3] = {0.0f, 0.5f}; data.PointCount = 4; for (int32_t i = 0; i < data.BrightnessBucketCount; ++i) data.BrightnessBuckets[i] = 0; for (int32_t recursionLevel = 0; recursionLevel < recursion; ++recursionLevel) { int32_t startCount = data.PointCount; float offset = bx::pow(0.5f, recursionLevel + 1); for (int32_t i = 0; i < 4; ++i) { for (int32_t j = 0; j < startCount; ++j) { data.Points[data.PointCount] = data.Points[j] + data.Points[i] * offset; data.PointCount++; } } } uint64_t dotsPerSide = bx::round(bx::pow(2, recursion)); data.DitherTexDepth = dotsPerSide * dotsPerSide; data.DitherTexWH = 16 * dotsPerSide; uint64_t texPixelCount = data.DitherTexWH * data.DitherTexWH * data.DitherTexDepth; if (BX_COUNTOF(DitherData::DitherTex) < texPixelCount) { LOG_ERROR("Too many pixels: %llu", texPixelCount); return; } float invRes = 1.0f / data.DitherTexWH; for (int32_t z = 0; z < data.DitherTexDepth; ++z) { int32_t dotCount = z + 1; float dotArea = 0.5f / dotCount; float dotRadius = bx::sqrt(dotArea / bx::kPi); int zOffset = z * data.DitherTexWH * data.DitherTexWH; for (int32_t y = 0; y < data.DitherTexWH; ++y) { int32_t yOffset = y * data.DitherTexWH; for (int32_t x = 0; x < data.DitherTexWH; ++x) { Vec2 point{(x + 0.5f) * invRes, (y + 0.5f) * invRes}; float dist = bx::kFloatInfinity; for (int32_t i = 0; i < dotCount; ++i) { Vec2 vec = point - data.Points[i]; Vec2 wrappedVec{bx::mod(vec.x + 0.5f, 1.0f) - 0.5f, bx::mod(vec.y + 0.5f, 1.0f) - 0.5f}; float curDist = wrappedVec.Magnitude(); dist = bx::min(dist, curDist); } dist = dist / (dotRadius * 2.4f); float val = bx::clamp(1.0f - dist, 0.0f, 1.0f); data.DitherTex[x + yOffset + zOffset] = Vec4{val, val, val, 1.0f}; // data.DitherTex[x + yOffset + zOffset] = Vec4{1.0, 0.0f, 0.0f, 1.0f}; int32_t bucket = bx::clamp(uint32_t(val * DitherData::BrightnessBucketCount), 0, DitherData::BrightnessBucketCount - 1); data.BrightnessBuckets[bucket] += 1; } } } // Brightness ramp int32_t sum = 0; for (int32_t i = 0; i < data.BrightnessBucketCount; ++i) { sum += data.BrightnessBuckets[data.BrightnessBucketCount - 1 - i]; data.BrightnessRamp[i + 1] = sum / (float)texPixelCount; } // Upload textures if (isValid(data.PreviewTex)) { bgfx::destroy(data.PreviewTex); data.PreviewTex = BGFX_INVALID_HANDLE; } if (isValid(data.FinalTex)) { bgfx::destroy(data.FinalTex); data.FinalTex = BGFX_INVALID_HANDLE; } if (isValid(data.RampTex)) { bgfx::destroy(data.RampTex); data.RampTex = BGFX_INVALID_HANDLE; } const bgfx::Memory* memPreview = bgfx::makeRef(data.DitherTex, texPixelCount * sizeof(Vec4)); const bgfx::Memory* memFinal = bgfx::makeRef(data.DitherTex, texPixelCount * sizeof(Vec4)); const bgfx::Memory* memRamp = bgfx::makeRef(data.BrightnessRamp, sizeof(data.BrightnessRamp)); data.PreviewTex = bgfx::createTexture2D(data.DitherTexWH, data.DitherTexWH * data.DitherTexDepth, false, false, bgfx::TextureFormat::RGBA32F, 0, memPreview); data.FinalTex = bgfx::createTexture3D(data.DitherTexWH, data.DitherTexWH, data.DitherTexDepth, false, bgfx::TextureFormat::RGBA32F, 0, memFinal); data.RampTex = bgfx::createTexture2D( BX_COUNTOF(data.BrightnessRamp), 1, false, 1, bgfx::TextureFormat::R32F, 0, memRamp); if (!isValid(data.DitherSampler)) { data.DitherSampler = bgfx::createUniform("s_ditherSampler", bgfx::UniformType::Sampler); } if (!isValid(data.RampSampler)) { data.RampSampler = bgfx::createUniform("s_rampSampler", bgfx::UniformType::Sampler); } } GameRendering* Instance = nullptr; } // namespace GameRendering& GameRendering::Get() { assert(Instance != nullptr); return *Instance; } void GameRendering::Setup() { LOG("--- RENDERING STARTUP ---"); if (Instance != nullptr) LOG_WARN("old rendering wasn't destroyed!"); Instance = this; SharedData& shared = GetShared(); bgfx::Init init; init.type = bgfx::RendererType::Direct3D12; init.debug = true; 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::setDebug(BGFX_DEBUG_TEXT); 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); Textures[0].Handle = loadTexture( bx::FilePath{"models/body.dds"}, BGFX_TEXTURE_NONE | BGFX_SAMPLER_NONE, 0, &Textures[0].Info, nullptr); Textures[0].SamplerHandle = DefaultSampler; LoadModels(Models, ModelCount); Materials[0] = Material::LoadFromShader("vert", "frag", MainViewID, Textures[0].Handle, Textures[0].SamplerHandle); imguiCreate(); 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 (inst.DebugData.ImguiIniSize > 0) { ImGui::LoadIniSettingsFromMemory(inst.DebugData.ImguiIni, inst.DebugData.ImguiIniSize); } else { ImGui::LoadIniSettingsFromDisk("imgui.ini"); } DitherGen(DitherTextures, DitherRecursion); } void GameRendering::Update() { SharedData& shared = GetShared(); // Handle events for (uint16_t i = 0; i < shared.Window.SDLEventCount; ++i) { 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) { shared.Dev.ChangedShaderCount = 0; // TODO: when to destroy shader? // TODO: only reload changed shaders bgfx::ShaderHandle vertexShader = loadShader("vert"); bgfx::ShaderHandle fragmentShader = loadShader("frag"); if (isValid(vertexShader) && isValid(fragmentShader)) { bgfx::ProgramHandle newProgram = bgfx::createProgram(vertexShader, fragmentShader, true); if (isValid(newProgram)) { Materials[0].Shader = newProgram; LastShaderLoadTime = GetInstance().Time.Now; } else { LOG_WARN("Failed to load shader!"); LastShaderLoadTime = -1.0f; } } } // Start Rendering imguiBeginFrame(20); ImGui_ImplSDL3_NewFrame(); ImGui::DockSpaceOverViewport(0, 0, ImGuiDockNodeFlags_PassthruCentralNode); auto& level = GetInstance().GameLevel; auto& debug = GetInstance().DebugData; if (UIVisible == UIVisibilityState::Debug) { if (ImGui::Begin("Rendering")) { if (LastShaderLoadTime >= 0.0f) { ImGui::TextColored({0.2f, 0.9f, 0.2f, 1.0f}, "Shader loaded %.0f seconds ago", GetInstance().Time.Now - LastShaderLoadTime); } else { ImGui::TextColored({0.9f, 0.2f, 0.2f, 1.0f}, "Shader load Failiure!"); } if (ImGui::Button("Reload Meshes")) { LoadModels(Models, ModelCount); } ImGui::SameLine(); if (ImGui::Button("Reload Level")) { auto& lvl = GetInstance().GameLevel; lvl = {}; lvl.Setup(shared.Game); } if (ImGui::Button("Dithergen")) { DitherGen(DitherTextures, DitherRecursion); } ImGui::SameLine(); ImGui::SliderInt("Recursion", &DitherRecursion, 1, 4); ImGui::Text( "%ux%ux%u", DitherTextures.DitherTexWH, DitherTextures.DitherTexWH, DitherTextures.DitherTexDepth); if (!isValid(DitherTextures.PreviewTex)) { ImGui::Text("Invalid Texture"); } else { ImGui::Image(DitherTextures.PreviewTex.idx, {(float)DitherTextures.DitherTexWH, (float)DitherTextures.DitherTexWH * DitherTextures.DitherTexDepth}); } if (isValid(DitherTextures.RampTex)) { ImGui::Image(DitherTextures.RampTex.idx, {BX_COUNTOF(DitherTextures.BrightnessRamp), 8}); } Vec3 quadPos = level.UIQuads.Get({0}).EData.Transform.GetPosition(); ImGui::Text("%f %f %f", quadPos.x, quadPos.y, quadPos.z); ImGui::Text("Shader log:"); ImGui::TextWrapped("%s", GetShared().Dev.ShaderLog); } ImGui::End(); if (ImGui::Begin("Puzzles")) { ImGui::Text("List"); for (int32_t i = 0; i < BX_COUNTOF(level.Puzzles); ++i) { auto& puzzleData = level.Puzzles[i].Data; bool isSelected = debug.SelectedDebugLevel == i; ImGui::PushID("selectable"); if (ImGui::Selectable(puzzleData.PuzzleName, isSelected)) { debug.SelectedDebugLevel = isSelected ? UINT16_MAX : i; } ImGui::PopID(); if (isSelected) { ImGui::PushID("edit field"); ImGui::InputText("", puzzleData.PuzzleName, sizeof(Puzzle::PuzzleData::PuzzleName)); ImGui::PopID(); if (!puzzleData.RenderDebugUI()) { debug.SelectedDebugLevel = UINT16_MAX; } } } } ImGui::End(); } GetInstance().GameLevel.Update(); GetInstance().GameLevel.Render(MainViewID, Models, Materials); // bgfx::dbgTextPrintf(1, 1, 0x0F, "Time: %.1fs", GetInstance().Time.Now); // for (int32_t i = 0; i < (int32_t)PerfCounterType::COUNT; ++i) // { // bgfx::dbgTextPrintf( // 1, 2 + i, 0x0F, "%s Max: %.3fs", PerfCounterNames[i], shared.Window.PerfCounters[i].GetMax()); // } // Finish Frame imguiEndFrame(); if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { ImGui::UpdatePlatformWindows(); ImGui::RenderPlatformWindowsDefault(); } START_PERF(); bgfx::frame(); END_PERF(shared.Window.PerfCounters, PerfCounterType::Submit, shared.Window.FrameCounter); } void GameRendering::Shutdown() { LOG("--- RENDERING_SHUTDOWN ---"); ImGui::SaveIniSettingsToDisk("imgui.ini"); auto& debug = GetInstance().DebugData; const char* iniData = ImGui::SaveIniSettingsToMemory(reinterpret_cast(&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, bgfx::TextureHandle tex, bgfx::UniformHandle sampler) { 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_testColor", bgfx::UniformType::Vec4); mat.Uniforms[Material::UTexInfo] = bgfx::createUniform("u_texInfo", bgfx::UniformType::Vec4); mat.Textures[0].Handle = tex; mat.Textures[0].SamplerHandle = sampler; mat.ViewID = view; return mat; } uint16_t GameRendering::GetModelHandleFromPath(const char* path) { uint32_t AssetHandle = CrcPath(path); for (int32_t i = 0; i < ModelCount; ++i) { if (Models[i].AssetHandle == AssetHandle) { return i; } } return 0; } } // namespace Game