#include "Render.hpp" #include #include #include #include #include #include "AssetManager.hpp" #include "Event.hpp" #include "DebugInfo.hpp" #include "Game.hpp" #include "World.hpp" #include "GameState.hpp" #include "RendererWorld.hpp" #include "Settings.hpp" #include "Plugin.hpp" #include "Rml.hpp" #include "Gal.hpp" const std::map keyMapping = { {SDLK_BACKSPACE, Rml::Input::KI_BACK}, {SDLK_INSERT, Rml::Input::KI_INSERT}, {SDLK_DELETE, Rml::Input::KI_DELETE}, {SDLK_HOME, Rml::Input::KI_HOME}, {SDLK_END, Rml::Input::KI_END}, {SDLK_LEFT, Rml::Input::KI_LEFT}, {SDLK_RIGHT, Rml::Input::KI_RIGHT}, {SDLK_UP, Rml::Input::KI_UP}, {SDLK_DOWN, Rml::Input::KI_DOWN}, {SDLK_TAB, Rml::Input::KI_TAB}, {SDLK_RETURN, Rml::Input::KI_RETURN} }; inline int ConvertKeymodsSdlToRml(unsigned short keyMods) { int ret = 0; if (keyMods & KMOD_SHIFT) ret |= Rml::Input::KM_SHIFT; if (keyMods & KMOD_CTRL) ret |= Rml::Input::KM_CTRL; if (keyMods & KMOD_ALT) ret |= Rml::Input::KM_ALT; if (keyMods & KMOD_GUI) ret |= Rml::Input::KM_META; if (keyMods & KMOD_NUM) ret |= Rml::Input::KM_NUMLOCK; if (keyMods & KMOD_CAPS) ret |= Rml::Input::KM_CAPSLOCK; return ret; } Render::Render(unsigned int windowWidth, unsigned int windowHeight, std::string windowTitle) { InitEvents(); Settings::Load(); InitSdl(windowWidth, windowHeight, windowTitle); glCheckError(); InitGlew(); glCheckError(); AssetManager::InitAssetManager(); glCheckError(); PrepareToRendering(); glCheckError(); InitRml(); glCheckError(); AssetManager::InitPostRml(); glCheckError(); LOG(INFO) << "Supported threads: " << std::thread::hardware_concurrency(); } Render::~Render() { Rml::RemoveContext("default"); rmlRender.reset(); rmlSystem.reset(); PluginSystem::Init(); framebuffer.reset(); SDL_GL_DeleteContext(glContext); SDL_DestroyWindow(window); SDL_Quit(); } void Render::InitSdl(unsigned int WinWidth, unsigned int WinHeight, std::string WinTitle) { LOG(INFO) << "Creating window: " << WinWidth << "x" << WinHeight << " \"" << WinTitle << "\""; if (SDL_Init(SDL_INIT_VIDEO) < 0) throw std::runtime_error("SDL initalization failed: " + std::string(SDL_GetError())); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); window = SDL_CreateWindow( WinTitle.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WinWidth, WinHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); if (!window) throw std::runtime_error("Window creation failed: " + std::string(SDL_GetError())); glContext = SDL_GL_CreateContext(window); if (!glContext) throw std::runtime_error("OpenGl context creation failed: " + std::string(SDL_GetError())); SetMouseCapture(false); renderState.WindowWidth = WinWidth; renderState.WindowHeight = WinHeight; SDL_GL_SetSwapInterval(0); } void Render::InitGlew() { auto gal = Gal::GetImplementation(); gal->Init(); int width, height; SDL_GL_GetDrawableSize(window, &width, &height); gal->GetDefaultFramebuffer()->SetViewport(0, 0, width, height); gal->GetDefaultFramebuffer()->Clear(); } void Render::PrepareToRendering() { int width, height; SDL_GL_GetDrawableSize(window, &width, &height); float resolutionScale = Settings::ReadDouble("resolutionScale", 1.0f); size_t scaledW = width * resolutionScale, scaledH = height * resolutionScale; auto gal = Gal::GetImplementation(); gal->GetDefaultFramebuffer()->SetViewport(0, 0, width, height); auto dsTexConf = gal->CreateTexture2DConfig(scaledW, scaledH, Gal::Format::D24S8); dsTexConf->SetMinFilter(Gal::Filtering::Nearest); dsTexConf->SetMaxFilter(Gal::Filtering::Nearest); fbDepthStencil = gal->BuildTexture(dsTexConf); auto texConf = gal->CreateTexture2DConfig(scaledW, scaledH, Gal::Format::R8G8B8A8); texConf->SetMinFilter(Gal::Filtering::Nearest); texConf->SetMaxFilter(Gal::Filtering::Nearest); fbColor = gal->BuildTexture(texConf); auto fbConf = gal->CreateFramebufferConfig(); fbConf->SetTexture(0, fbColor); fbConf->SetDepthStencil(fbDepthStencil); framebuffer = gal->BuildFramebuffer(fbConf); framebuffer->SetViewport(0, 0, scaledW, scaledH); framebuffer->Clear(); std::string vertexSource, pixelSource; { auto vertAsset = AssetManager::GetAssetByAssetName("/altcraft/shaders/vert/fbo"); vertexSource = std::string((char*)vertAsset->data.data(), (char*)vertAsset->data.data() + vertAsset->data.size()); auto pixelAsset = AssetManager::GetAssetByAssetName("/altcraft/shaders/frag/fbo"); pixelSource = std::string((char*)pixelAsset->data.data(), (char*)pixelAsset->data.data() + pixelAsset->data.size()); } constexpr float quadVertices[] = { // positions // texCoords -1.0f, 1.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f }; fbBuffer = gal->CreateBuffer(); fbBuffer->SetData({ reinterpret_cast(quadVertices), reinterpret_cast(quadVertices) + sizeof(quadVertices) }); auto fbPPC = gal->CreatePipelineConfig(); fbPPC->SetTarget(gal->GetDefaultFramebuffer()); fbPPC->SetVertexShader(gal->LoadVertexShader(vertexSource)); fbPPC->SetPixelShader(gal->LoadPixelShader(pixelSource)); fbPPC->AddStaticTexture("inputTexture", fbColor); auto fbColorBB = fbPPC->BindVertexBuffer({ {"Pos", Gal::Type::Vec2}, {"TextureCoords", Gal::Type::Vec2} }); fbPipeline = gal->BuildPipeline(fbPPC); fbPipelineInstance = fbPipeline->CreateInstance({ {fbColorBB, fbBuffer} }); if (world) world->PrepareRender(framebuffer); } void Render::UpdateKeyboard() { SDL_Scancode toUpdate[] = { SDL_SCANCODE_A,SDL_SCANCODE_W,SDL_SCANCODE_S,SDL_SCANCODE_D,SDL_SCANCODE_SPACE }; const Uint8 *kbState = SDL_GetKeyboardState(0); for (auto key : toUpdate) { bool isPressed = kbState[key]; if (!isKeyPressed[key] && isPressed) { PUSH_EVENT("KeyPressed", key); } if (isKeyPressed[key] && isPressed) { //KeyHeld } if (isKeyPressed[key] && !isPressed) { PUSH_EVENT("KeyReleased", key); } isKeyPressed[key] = isPressed; } } void Render::RenderFrame() { OPTICK_EVENT(); Gal::GetImplementation()->GetDefaultFramebuffer()->Clear(); framebuffer->Clear(); //if (isWireframe) //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); if (renderWorld) world->Render(renderState); //if (isWireframe) //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); fbPipeline->Activate(); fbPipelineInstance->Activate(); fbPipelineInstance->Render(0, 6); RenderGui(); if (world) { world->Update(GetTime()->RemainTimeMs()); } OPTICK_EVENT("VSYNC"); SDL_GL_SwapWindow(window); } void Render::HandleEvents() { int rmlKeymods = ConvertKeymodsSdlToRml(sdlKeyMods); SDL_PumpEvents(); SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: { LOG(INFO) << "Received close event by window closing"; PUSH_EVENT("Exit",0); break; } case SDL_WINDOWEVENT: { switch (event.window.event) { case SDL_WINDOWEVENT_RESIZED: { int width, height; SDL_GL_GetDrawableSize(window, &width, &height); renderState.WindowWidth = width; renderState.WindowHeight = height; rmlRender->Update(width, height); rmlContext->SetDimensions(Rml::Vector2i(width, height)); PrepareToRendering(); break; } case SDL_WINDOWEVENT_FOCUS_GAINED: HasFocus = true; break; case SDL_WINDOWEVENT_FOCUS_LOST: { HasFocus = false; State state = GetState(); if (state == State::Inventory || state == State::Playing || state == State::Chat) { SetState(State::Paused); } break; } } break; } case SDL_KEYDOWN: { sdlKeyMods = event.key.keysym.mod; rmlKeymods = ConvertKeymodsSdlToRml(sdlKeyMods); auto it = keyMapping.find(event.key.keysym.sym); Rml::Input::KeyIdentifier ki = it != keyMapping.end() ? it->second : Rml::Input::KeyIdentifier::KI_UNKNOWN; rmlContext->ProcessKeyDown(ki, rmlKeymods); switch (event.key.keysym.scancode) { case SDL_SCANCODE_ESCAPE: { auto state = GetState(); if (state == State::Playing) { SetState(State::Paused); } else if (state == State::Paused || state == State::Inventory || state == State::Chat) { SetState(State::Playing); } else if (state == State::MainMenu) { LOG(INFO) << "Received close event by esc"; PUSH_EVENT("Exit", 0); } break; } case SDL_SCANCODE_E: { auto state = GetState(); if (state == State::Playing) { SetState(State::Inventory); } else if (state == State::Inventory) { SetState(State::Playing); } break; } case SDL_SCANCODE_SLASH: case SDL_SCANCODE_T: { auto state = GetState(); if (state == State::Playing) { SetState(State::Chat); } break; } case SDL_SCANCODE_F4: hideRml = !hideRml; break; case SDL_SCANCODE_F8: Rml::Debugger::SetVisible(!Rml::Debugger::IsVisible()); break; case SDL_SCANCODE_F7: { SetMouseCapture(!isMouseCaptured); break; } default: break; } break; } case SDL_KEYUP: { sdlKeyMods = event.key.keysym.mod; rmlKeymods = ConvertKeymodsSdlToRml(sdlKeyMods); auto it = keyMapping.find(event.key.keysym.sym); Rml::Input::KeyIdentifier ki = it != keyMapping.end() ? it->second : Rml::Input::KeyIdentifier::KI_UNKNOWN; rmlContext->ProcessKeyUp(ki, rmlKeymods); break; } case SDL_MOUSEMOTION: { if (isMouseCaptured) { double deltaX = event.motion.xrel; double deltaY = event.motion.yrel; deltaX *= sensetivity; deltaY *= sensetivity * -1; PUSH_EVENT("MouseMove", std::make_tuple(deltaX, deltaY)); } else { int mouseX, mouseY; SDL_GetMouseState(&mouseX, &mouseY); rmlContext->ProcessMouseMove(mouseX, mouseY, rmlKeymods); } break; } case SDL_MOUSEBUTTONDOWN: { if (isMouseCaptured) { if (event.button.button == SDL_BUTTON_LEFT) PUSH_EVENT("LmbPressed", 0); else if (event.button.button == SDL_BUTTON_RIGHT) PUSH_EVENT("RmbPressed", 0); } else { if (event.button.button == SDL_BUTTON_MIDDLE) event.button.button = SDL_BUTTON_RIGHT; else if (event.button.button == SDL_BUTTON_RIGHT) event.button.button = SDL_BUTTON_MIDDLE; rmlContext->ProcessMouseButtonDown(event.button.button - 1, rmlKeymods); } break; } case SDL_MOUSEBUTTONUP: { if (isMouseCaptured) { if (event.button.button == SDL_BUTTON_LEFT) PUSH_EVENT("LmbReleased", 0); else if (event.button.button == SDL_BUTTON_RIGHT) PUSH_EVENT("RmbReleased", 0); } else { if (event.button.button == SDL_BUTTON_MIDDLE) event.button.button = SDL_BUTTON_RIGHT; else if (event.button.button == SDL_BUTTON_RIGHT) event.button.button = SDL_BUTTON_MIDDLE; rmlContext->ProcessMouseButtonUp(event.button.button - 1, rmlKeymods); } break; } case SDL_MOUSEWHEEL: { rmlContext->ProcessMouseWheel(-event.wheel.y, rmlKeymods); break; } case SDL_TEXTINPUT: { rmlContext->ProcessTextInput(Rml::String(event.text.text)); break; } default: break; } } char* rawClipboard = SDL_GetClipboardText(); std::string clipboard = rawClipboard; SDL_free(rawClipboard); if (clipboard != rmlSystem->clipboard) { rmlSystem->clipboard = clipboard; } rmlContext->Update(); if (clipboard != rmlSystem->clipboard) { clipboard = rmlSystem->clipboard; SDL_SetClipboardText(clipboard.c_str()); } } void Render::HandleMouseCapture() { } void Render::SetMouseCapture(bool IsCaptured) { if (IsCaptured == isMouseCaptured) return; isMouseCaptured = IsCaptured; if (isMouseCaptured) { SDL_GetGlobalMouseState(&prevMouseX, &prevMouseY); } SDL_CaptureMouse(IsCaptured ? SDL_TRUE : SDL_FALSE); SDL_SetRelativeMouseMode(IsCaptured ? SDL_TRUE : SDL_FALSE); if (!isMouseCaptured) { SDL_WarpMouseGlobal(prevMouseX, prevMouseY); } } void Render::Update() { OPTICK_EVENT(); HandleEvents(); if (HasFocus && GetState() == State::Playing) UpdateKeyboard(); if (isMouseCaptured) HandleMouseCapture(); glCheckError(); RenderFrame(); listener.HandleAllEvents(); } void Render::RenderGui() { OPTICK_EVENT(); if (!hideRml) rmlContext->Render(); } void Render::InitEvents() { listener.RegisterHandler("ConnectionSuccessfull", [this](const Event&) { stateString = "Logging in..."; }); listener.RegisterHandler("PlayerConnected", [this](const Event&) { stateString = "Loading terrain..."; world = std::make_unique(framebuffer); world->MaxRenderingDistance = Settings::ReadDouble("renderDistance", 2.0f); PUSH_EVENT("UpdateSectionsRender", 0); }); listener.RegisterHandler("RemoveLoadingScreen", [this](const Event&) { stateString = "Playing"; renderWorld = true; SetState(State::Playing); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); GetGameState()->GetPlayer()->isFlying = Settings::ReadBool("flight", false); PUSH_EVENT("SetMinLightLevel", (float)Settings::ReadDouble("brightness", 0.2f)); }); listener.RegisterHandler("ConnectionFailed", [this](const Event& eventData) { stateString = "Connection failed: " + eventData.get (); renderWorld = false; world.reset(); SetState(State::MainMenu); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); PluginSystem::CallOnDisconnected("Connection failed: " + eventData.get ()); }); listener.RegisterHandler("Disconnected", [this](const Event& eventData) { stateString = "Disconnected: " + eventData.get(); renderWorld = false; world.reset(); SetState(State::MainMenu); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); PluginSystem::CallOnDisconnected("Disconnected: " + eventData.get()); }); listener.RegisterHandler("Connecting", [this](const Event&) { stateString = "Connecting to the server..."; SetState(State::Loading); }); listener.RegisterHandler("StateUpdated", [this](const Event& eventData) { switch (GetState()) { case State::Playing: SetMouseCapture(true); PluginSystem::CallOnChangeState("Playing"); break; case State::InitialLoading: PluginSystem::CallOnChangeState("InitialLoading"); SetMouseCapture(false); break; case State::MainMenu: PluginSystem::CallOnChangeState("MainMenu"); SetMouseCapture(false); break; case State::Loading: PluginSystem::CallOnChangeState("Loading"); SetMouseCapture(false); break; case State::Paused: PluginSystem::CallOnChangeState("Paused"); SetMouseCapture(false); break; case State::Inventory: PluginSystem::CallOnChangeState("Inventory"); SetMouseCapture(false); break; case State::Chat: PluginSystem::CallOnChangeState("Chat"); SetMouseCapture(false); break; case State::NeedRespawn: PluginSystem::CallOnChangeState("NeedRespawn"); SetMouseCapture(false); break; } }); listener.RegisterHandler("SettingsUpdate", [this](const Event& eventData) { if (world) { float renderDistance = Settings::ReadDouble("renderDistance", 2.0f); if (renderDistance != world->MaxRenderingDistance) { world->MaxRenderingDistance = renderDistance; PUSH_EVENT("UpdateSectionsRender", 0); } } float mouseSensetivity = Settings::ReadDouble("mouseSensetivity", 0.1f); if (mouseSensetivity != sensetivity) sensetivity = mouseSensetivity; if (GetGameState()) { bool flight = Settings::ReadBool("flight", false); GetGameState()->GetPlayer()->isFlying = flight; } bool wireframe = Settings::ReadBool("wireframe", false); isWireframe = wireframe; float targetFps = Settings::ReadDouble("targetFps", 60.0f); GetTime()->SetDelayLength(std::chrono::duration(1.0 / targetFps * 1000.0)); bool vsync = Settings::ReadBool("vsync", false); if (vsync) { GetTime()->SetDelayLength(std::chrono::milliseconds(0)); SDL_GL_SetSwapInterval(1); } else SDL_GL_SetSwapInterval(0); float brightness = Settings::ReadDouble("brightness", 0.2f); PUSH_EVENT("SetMinLightLevel", brightness); PrepareToRendering(); }); } void Render::InitRml() { LOG(INFO) << "Initializing Rml"; rmlSystem = std::make_unique(); Rml::SetSystemInterface(rmlSystem.get()); rmlRender = std::make_unique(renderState); Rml::SetRenderInterface(rmlRender.get()); rmlRender->Update(renderState.WindowWidth, renderState.WindowHeight); rmlFile = std::make_unique(); Rml::SetFileInterface(rmlFile.get()); if (!Rml::Initialise()) LOG(WARNING) << "Rml not initialized"; Rml::Lua::Initialise(PluginSystem::GetLuaState()); rmlContext = Rml::CreateContext("default", Rml::Vector2i(renderState.WindowWidth, renderState.WindowHeight)); if (!Rml::Debugger::Initialise(rmlContext)) LOG(WARNING) << "Rml debugger not initialized"; }