From e306c5e23f053a18726b2d3f58af944bffcb26f6 Mon Sep 17 00:00:00 2001 From: Bram Date: Wed, 7 Jan 2026 03:04:20 +0100 Subject: [PATCH] WE BE RENDERING BABY --- destrum/CMakeLists.txt | 2 + destrum/include/destrum/App.h | 3 + destrum/include/destrum/Graphics/Camera.h | 9 ++ destrum/include/destrum/Graphics/Renderer.h | 4 + destrum/include/destrum/Input/InputManager.h | 92 ++++++++++++ destrum/src/App.cpp | 49 +++++- destrum/src/Graphics/Camera.cpp | 25 ++++ destrum/src/Graphics/GfxDevice.cpp | 66 ++++----- destrum/src/Graphics/Renderer.cpp | 9 +- destrum/src/Graphics/Swapchain.cpp | 3 + destrum/src/Input/InputManager.cpp | 148 +++++++++++++++++++ destrum/third_party/CMakeLists.txt | 2 +- lightkeeper/src/Lightkeeper.cpp | 2 +- 13 files changed, 377 insertions(+), 37 deletions(-) create mode 100644 destrum/include/destrum/Input/InputManager.h create mode 100644 destrum/src/Input/InputManager.cpp diff --git a/destrum/CMakeLists.txt b/destrum/CMakeLists.txt index 8495c8c..735a860 100644 --- a/destrum/CMakeLists.txt +++ b/destrum/CMakeLists.txt @@ -20,6 +20,8 @@ set(SRC_FILES "src/Graphics/Pipelines/MeshPipeline.cpp" + "src/Input/InputManager.cpp" + "src/FS/AssetFS.cpp" ) diff --git a/destrum/include/destrum/App.h b/destrum/include/destrum/App.h index 6a3d707..2053407 100644 --- a/destrum/include/destrum/App.h +++ b/destrum/include/destrum/App.h @@ -9,6 +9,7 @@ #include #include +#include class App { @@ -41,6 +42,8 @@ protected: MeshCache meshCache; + InputManager inputManager; + bool isRunning{false}; bool gamePaused{false}; diff --git a/destrum/include/destrum/Graphics/Camera.h b/destrum/include/destrum/Graphics/Camera.h index 41295c7..94165f5 100644 --- a/destrum/include/destrum/Graphics/Camera.h +++ b/destrum/include/destrum/Graphics/Camera.h @@ -118,6 +118,12 @@ public: void SetMovementSpeed(float speed) { m_movementSpeed = speed; } [[nodiscard]] float GetMovementSpeed() const { return m_movementSpeed; } + void SetRotation(float yawRadians, float pitchRadians); + void SetRotation(const glm::vec2& yawPitchRadians); + + float GetYaw() const { return m_yaw; } + float GetPitch() const { return m_pitch; } + private: float fovAngle{90.f}; float fov{tanf(glm::radians(fovAngle) / 2.f)}; @@ -153,6 +159,9 @@ private: Frustum m_frustum{}; float m_movementSpeed{2.0f}; + + float m_yaw = 0.0f; // radians + float m_pitch = 0.0f; // radians }; #endif //VCAMERA_H diff --git a/destrum/include/destrum/Graphics/Renderer.h b/destrum/include/destrum/Graphics/Renderer.h index 9956ce7..06682b2 100644 --- a/destrum/include/destrum/Graphics/Renderer.h +++ b/destrum/include/destrum/Graphics/Renderer.h @@ -33,7 +33,11 @@ public: void cleanup(); void drawMesh(MeshID id, const glm::mat4& transform, MaterialId materialId); + const GPUImage& getDrawImage(const GfxDevice& gfx_device) const; + void resize(GfxDevice& gfxDevice, const glm::ivec2& newSize) { + createDrawImage(gfxDevice, newSize, false); + } private: void createDrawImage(GfxDevice& gfxDevice, const glm::ivec2& drawImageSize, bool firstCreate); diff --git a/destrum/include/destrum/Input/InputManager.h b/destrum/include/destrum/Input/InputManager.h new file mode 100644 index 0000000..016d6b5 --- /dev/null +++ b/destrum/include/destrum/Input/InputManager.h @@ -0,0 +1,92 @@ +#ifndef INPUTMANAGER_H +#define INPUTMANAGER_H + +#include +#include +#include +#include +#include + +class InputManager { +public: + // Call once at startup (optional, but convenient) + void Init(); + + // Call at the start of every frame + void BeginFrame(); + + // Feed SDL events into this (call for each SDL_PollEvent) + void ProcessEvent(const SDL_Event& e); + + // Call at end of frame if you want (not required) + void EndFrame() {} + + // ---- Queries ---- + bool IsKeyDown(SDL_Scancode sc) const; // held + bool WasKeyPressed(SDL_Scancode sc) const; // pressed this frame + bool WasKeyReleased(SDL_Scancode sc) const; // released this frame + + bool IsMouseDown(Uint8 button) const; // held (SDL_BUTTON_LEFT etc.) + bool WasMousePressed(Uint8 button) const; // pressed this frame + bool WasMouseReleased(Uint8 button) const; // released this frame + + // Mouse position (window space) + int MouseX() const { return m_mouseX; } + int MouseY() const { return m_mouseY; } + int MouseDeltaX() const { return m_mouseDX; } + int MouseDeltaY() const { return m_mouseDY; } + + // Mouse wheel (accumulated per frame) + int WheelX() const { return m_wheelX; } + int WheelY() const { return m_wheelY; } + + // ---- Text input ---- + void StartTextInput(); + void StopTextInput(); + bool IsTextInputActive() const { return m_textInputActive; } + const std::string& GetTextInput() const { return m_textInput; } // captured this frame + + // ---- Action mapping ---- + enum class Device { Keyboard, MouseButton, MouseWheel }; + enum class ButtonState { Down, Pressed, Released }; + + struct Binding { + Device device; + int code; // SDL_Scancode for keyboard, Uint8 for mouse button, wheel axis sign encoding + // For MouseWheel: code = +1/-1 for Y, +2/-2 for X (simple encoding) + }; + + void BindAction(const std::string& action, const Binding& binding); + bool GetAction(const std::string& action, ButtonState state) const; + +private: + // Key states + std::unordered_set m_keysDown; + std::unordered_set m_keysPressed; + std::unordered_set m_keysReleased; + + // Mouse button states + std::unordered_set m_mouseDown; + std::unordered_set m_mousePressed; + std::unordered_set m_mouseReleased; + + // Mouse position + delta + int m_mouseX = 0, m_mouseY = 0; + int m_prevMouseX = 0, m_prevMouseY = 0; + int m_mouseDX = 0, m_mouseDY = 0; + + // Wheel (per-frame) + int m_wheelX = 0, m_wheelY = 0; + + // Text input (per-frame) + bool m_textInputActive = false; + std::string m_textInput; + + // Action bindings + std::unordered_map> m_bindings; + +private: + bool QueryBinding(const Binding& b, ButtonState state) const; +}; + +#endif //INPUTMANAGER_H diff --git a/destrum/src/App.cpp b/destrum/src/App.cpp index 96b6cf5..c5956c7 100644 --- a/destrum/src/App.cpp +++ b/destrum/src/App.cpp @@ -4,6 +4,7 @@ #include +#include "glm/gtx/transform.hpp" #include "spdlog/spdlog.h" App::App(): renderer{meshCache} { @@ -49,6 +50,11 @@ void App::init(const AppParams& params) { float aspectRatio = static_cast(params.renderSize.x) / static_cast(params.renderSize.y); camera.setAspectRatio(aspectRatio); + //Look 90 deg to the right + camera.SetRotation(glm::radians(glm::vec2(90.f, 0.f))); + + inputManager.Init(); + } void App::run() { @@ -84,6 +90,7 @@ void App::run() { } while (accumulator >= dt) { + inputManager.BeginFrame(); SDL_Event event; while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { @@ -99,28 +106,68 @@ void App::run() { break; } } + inputManager.ProcessEvent(event); + + if (inputManager.IsKeyDown(SDL_SCANCODE_W)) { + camera.m_position += camera.GetForward() * dt * 5.f; + } + if (inputManager.IsKeyDown(SDL_SCANCODE_S)) { + camera.m_position -= camera.GetForward() * dt * 5.f; + } + if (inputManager.IsKeyDown(SDL_SCANCODE_A)) { + camera.m_position -= camera.GetRight() * dt * 5.f; + } + if (inputManager.IsKeyDown(SDL_SCANCODE_D)) { + camera.m_position += camera.GetRight() * dt * 5.f; + } + + // rotation + if (inputManager.IsKeyDown(SDL_SCANCODE_LEFT)) { + camera.SetRotation(camera.GetYaw() - glm::radians(90.f) * dt, camera.GetPitch()); + } + if (inputManager.IsKeyDown(SDL_SCANCODE_RIGHT)) { + camera.SetRotation(camera.GetYaw() + glm::radians(90.f) * dt, camera.GetPitch()); + } + if (inputManager.IsKeyDown(SDL_SCANCODE_UP)) { + camera.SetRotation(camera.GetYaw(), camera.GetPitch() + glm::radians(90.f) * dt); + } + if (inputManager.IsKeyDown(SDL_SCANCODE_DOWN)) { + camera.SetRotation(camera.GetYaw(), camera.GetPitch() - glm::radians(90.f) * dt); + } + + camera.Update(dt); } if (gfxDevice.needsSwapchainRecreate()) { spdlog::info("Recreating swapchain to size: {}x{}", m_params.windowSize.x, m_params.windowSize.y); gfxDevice.recreateSwapchain(m_params.windowSize.x, m_params.windowSize.y); + renderer.resize(gfxDevice, { m_params.windowSize.x, m_params.windowSize.y }); + float aspectRatio = float(m_params.windowSize.x) / float(m_params.windowSize.y); + camera.setAspectRatio(aspectRatio); } accumulator -= dt; } if (!gfxDevice.needsSwapchainRecreate()) { + glm::mat4 objMatrix = glm::mat4(1.f); + objMatrix = glm::translate(objMatrix, glm::vec3(0.f, -3.0f, 0.f)); renderer.beginDrawing(gfxDevice); renderer.drawMesh(testMeshID, glm::mat4(1.f), 0); + renderer.drawMesh(testMeshID, objMatrix, 0); renderer.endDrawing(); auto cmd = gfxDevice.beginFrame(); + const auto& drawImage = renderer.getDrawImage(gfxDevice); renderer.draw(cmd, gfxDevice, camera, GameRenderer::SceneData{ camera, glm::vec3(0.1f), 0.5f, glm::vec3(0.5f), 0.01f }); - gfxDevice.endFrame(cmd, GPUImage{}, {}); + gfxDevice.endFrame(cmd, drawImage, { + .clearColor = {{0.f, 0.f, 0.5f, 1.f}}, + .drawImageBlitRect = glm::ivec4{}} + ); } if (frameLimit) { // Delay to not overload the CPU diff --git a/destrum/src/Graphics/Camera.cpp b/destrum/src/Graphics/Camera.cpp index c3c0d44..0774947 100644 --- a/destrum/src/Graphics/Camera.cpp +++ b/destrum/src/Graphics/Camera.cpp @@ -193,3 +193,28 @@ void Camera::Target(const glm::vec3& target) { m_viewMatrix = glm::lookAt(m_position, m_position + m_forward, m_up); m_invMatrix = glm::inverse(m_viewMatrix); } + +void Camera::SetRotation(float yawRadians, float pitchRadians) { + m_yaw = yawRadians; + m_pitch = glm::clamp( + pitchRadians, + -glm::half_pi() + 0.001f, + glm::half_pi() - 0.001f + ); + + // Yaw around world Y, pitch around local Z (same convention you used) + const glm::mat4 yawMatrix = glm::rotate(glm::mat4(1.0f), -m_yaw, glm::vec3(0, 1, 0)); + const glm::mat4 pitchMatrix = glm::rotate(glm::mat4(1.0f), m_pitch, glm::vec3(0, 0, 1)); + const glm::mat4 rotation = yawMatrix * pitchMatrix; + + // Forward is +X in your camera space + m_forward = glm::normalize(glm::vec3(rotation * glm::vec4(1, 0, 0, 0))); + m_right = glm::normalize(glm::cross(m_forward, glm::vec3(0, 1, 0))); + m_up = glm::normalize(glm::cross(m_right, m_forward)); + + m_useTarget = false; // rotation overrides target mode +} + +void Camera::SetRotation(const glm::vec2& yawPitchRadians) { + SetRotation(yawPitchRadians.x, yawPitchRadians.y); +} diff --git a/destrum/src/Graphics/GfxDevice.cpp b/destrum/src/Graphics/GfxDevice.cpp index c981c7f..8872d31 100644 --- a/destrum/src/Graphics/GfxDevice.cpp +++ b/destrum/src/Graphics/GfxDevice.cpp @@ -160,49 +160,49 @@ void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const E auto swapchainLayout = VK_IMAGE_LAYOUT_UNDEFINED; { - // clear swapchain image - VkImageSubresourceRange clearRange = - vkinit::imageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT); + VkImageSubresourceRange clearRange =vkinit::imageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT); vkutil::transitionImage(cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_GENERAL); swapchainLayout = VK_IMAGE_LAYOUT_GENERAL; - + const auto clearValue = props.clearColor; + vkCmdClearColorImage(cmd, swapchainImage, VK_IMAGE_LAYOUT_GENERAL, &clearValue, 1, &clearRange); } - // if (props.copyImageIntoSwapchain) { + if (true) { // copy from draw image into swapchain - // vkutil::transitionImage( - // cmd, - // drawImage.getImage(), - // VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL, - // VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + vkutil::transitionImage( + cmd, + drawImage.image, + VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); vkutil::transitionImage( cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); swapchainLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - // const auto filter = props.drawImageLinearBlit ? VK_FILTER_LINEAR : VK_FILTER_NEAREST; - const auto filter = VK_FILTER_LINEAR; - // // if (props.drawImageBlitRect != glm::ivec4{}) { - // vkutil::copyImageToImage( - // cmd, - // drawImage.getImage(), - // swapchainImage, - // drawImage.getExtent2D(), - // props.drawImageBlitRect.x, - // props.drawImageBlitRect.y, - // props.drawImageBlitRect.z, - // props.drawImageBlitRect.w, - // filter); - // } else { - // // will stretch image to swapchain - // vkutil::copyImageToImage( - // cmd, - // drawImage.getImage(), - // swapchainImage, - // drawImage.getExtent2D(), - // getSwapchainExtent(), - // filter); - // } + auto filter = false ? VK_FILTER_LINEAR : VK_FILTER_NEAREST; + filter = VK_FILTER_NEAREST; + if (false) { + vkutil::copyImageToImage( + cmd, + drawImage.image, + swapchainImage, + drawImage.getExtent2D(), + props.drawImageBlitRect.x, + props.drawImageBlitRect.y, + props.drawImageBlitRect.z, + props.drawImageBlitRect.w, + filter); + } else { + // will stretch image to swapchain + vkutil::copyImageToImage( + cmd, + drawImage.image, + swapchainImage, + drawImage.getExtent2D(), + getSwapchainExtent(), + filter); + } + } // prepare for present vkutil::transitionImage(cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); diff --git a/destrum/src/Graphics/Renderer.cpp b/destrum/src/Graphics/Renderer.cpp index 88b2e58..45808b9 100644 --- a/destrum/src/Graphics/Renderer.cpp +++ b/destrum/src/Graphics/Renderer.cpp @@ -2,6 +2,8 @@ #include +#include "spdlog/spdlog.h" + GameRenderer::GameRenderer(MeshCache& meshCache): meshCache{meshCache} { } @@ -63,7 +65,7 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera& .colorImageView = drawImage.imageView, .colorImageClearValue = glm::vec4{0.f, 0.f, 0.f, 1.f}, .depthImageView = depthImage.imageView, - .depthImageClearValue = 0.f, + .depthImageClearValue = 1.f, }); vkCmdBeginRendering(cmd, &renderInfo.renderingInfo); @@ -99,6 +101,10 @@ void GameRenderer::drawMesh(MeshID id, const glm::mat4& transform, MaterialId ma }); } +const GPUImage& GameRenderer::getDrawImage(const GfxDevice& gfx_device) const { + return gfx_device.getImage(drawImageId); +} + void GameRenderer::createDrawImage(GfxDevice& gfxDevice, const glm::ivec2& drawImageSize, bool firstCreate) @@ -144,6 +150,7 @@ void GameRenderer::createDrawImage(GfxDevice& gfxDevice, }; depthImageId = gfxDevice.createImage(createInfo, "depth image", nullptr, depthImageId); + spdlog::info("Created depth image with id {}", depthImageId); } } diff --git a/destrum/src/Graphics/Swapchain.cpp b/destrum/src/Graphics/Swapchain.cpp index 84e130b..4dc43f6 100644 --- a/destrum/src/Graphics/Swapchain.cpp +++ b/destrum/src/Graphics/Swapchain.cpp @@ -63,6 +63,8 @@ void Swapchain::createSwapchain(GfxDevice* gfxDevice, VkFormat format, std::uint // TODO: if re-creation of swapchain is supported, don't forget to call // vkutil::initSwapchainViews here. + + extent = m_swapchain.extent; } void Swapchain::recreateSwapchain(const GfxDevice& gfxDevice, VkFormat format, std::uint32_t width, std::uint32_t height, bool vSync) { @@ -98,6 +100,7 @@ void Swapchain::recreateSwapchain(const GfxDevice& gfxDevice, VkFormat format, s imageViews = m_swapchain.get_image_views().value(); dirty = false; + extent = m_swapchain.extent; } void Swapchain::cleanup() { diff --git a/destrum/src/Input/InputManager.cpp b/destrum/src/Input/InputManager.cpp new file mode 100644 index 0000000..b071f9b --- /dev/null +++ b/destrum/src/Input/InputManager.cpp @@ -0,0 +1,148 @@ +#include + +void InputManager::Init() { + // Nothing mandatory here. You can also enable relative mouse mode, etc. + // SDL_SetRelativeMouseMode(SDL_TRUE); +} + +void InputManager::BeginFrame() { + m_keysPressed.clear(); + m_keysReleased.clear(); + m_mousePressed.clear(); + m_mouseReleased.clear(); + + m_wheelX = 0; + m_wheelY = 0; + + m_textInput.clear(); + + // Update mouse delta based on last known position + m_mouseDX = m_mouseX - m_prevMouseX; + m_mouseDY = m_mouseY - m_prevMouseY; + m_prevMouseX = m_mouseX; + m_prevMouseY = m_mouseY; +} + +void InputManager::ProcessEvent(const SDL_Event& e) { + switch (e.type) { + case SDL_KEYDOWN: { + if (e.key.repeat) break; // avoid repeat spam for "Pressed" + SDL_Scancode sc = e.key.keysym.scancode; + m_keysDown.insert(sc); + m_keysPressed.insert(sc); + } break; + + case SDL_KEYUP: { + SDL_Scancode sc = e.key.keysym.scancode; + m_keysDown.erase(sc); + m_keysReleased.insert(sc); + } break; + + case SDL_MOUSEBUTTONDOWN: { + Uint8 b = e.button.button; + m_mouseDown.insert(b); + m_mousePressed.insert(b); + } break; + + case SDL_MOUSEBUTTONUP: { + Uint8 b = e.button.button; + m_mouseDown.erase(b); + m_mouseReleased.insert(b); + } break; + + case SDL_MOUSEMOTION: { + m_mouseX = e.motion.x; + m_mouseY = e.motion.y; + } break; + + case SDL_MOUSEWHEEL: { + // Accumulate wheel per frame + m_wheelX += e.wheel.x; + m_wheelY += e.wheel.y; + } break; + + case SDL_TEXTINPUT: { + // text typed this frame + m_textInput += e.text.text; + } break; + + default: break; + } +} + +bool InputManager::IsKeyDown(SDL_Scancode sc) const { + return m_keysDown.find(sc) != m_keysDown.end(); +} +bool InputManager::WasKeyPressed(SDL_Scancode sc) const { + return m_keysPressed.find(sc) != m_keysPressed.end(); +} +bool InputManager::WasKeyReleased(SDL_Scancode sc) const { + return m_keysReleased.find(sc) != m_keysReleased.end(); +} + +bool InputManager::IsMouseDown(Uint8 button) const { + return m_mouseDown.find(button) != m_mouseDown.end(); +} +bool InputManager::WasMousePressed(Uint8 button) const { + return m_mousePressed.find(button) != m_mousePressed.end(); +} +bool InputManager::WasMouseReleased(Uint8 button) const { + return m_mouseReleased.find(button) != m_mouseReleased.end(); +} + +void InputManager::StartTextInput() { + if (!m_textInputActive) { + SDL_StartTextInput(); + m_textInputActive = true; + } +} +void InputManager::StopTextInput() { + if (m_textInputActive) { + SDL_StopTextInput(); + m_textInputActive = false; + } +} + +void InputManager::BindAction(const std::string& action, const Binding& binding) { + m_bindings[action].push_back(binding); +} + +bool InputManager::QueryBinding(const Binding& b, ButtonState state) const { + switch (b.device) { + case Device::Keyboard: { + auto sc = static_cast(b.code); + if (state == ButtonState::Down) return IsKeyDown(sc); + if (state == ButtonState::Pressed) return WasKeyPressed(sc); + if (state == ButtonState::Released) return WasKeyReleased(sc); + } break; + + case Device::MouseButton: { + auto mb = static_cast(b.code); + if (state == ButtonState::Down) return IsMouseDown(mb); + if (state == ButtonState::Pressed) return WasMousePressed(mb); + if (state == ButtonState::Released) return WasMouseReleased(mb); + } break; + + case Device::MouseWheel: { + // Encoding: + // +1/-1 => wheel Y (up/down), +2/-2 => wheel X (right/left) + const int c = b.code; + if (state != ButtonState::Pressed) return false; // wheel is "impulse" + if (c == 1) return m_wheelY > 0; + if (c == -1) return m_wheelY < 0; + if (c == 2) return m_wheelX > 0; + if (c == -2) return m_wheelX < 0; + } break; + } + return false; +} + +bool InputManager::GetAction(const std::string& action, ButtonState state) const { + auto it = m_bindings.find(action); + if (it == m_bindings.end()) return false; + + for (const auto& b : it->second) { + if (QueryBinding(b, state)) return true; + } + return false; +} diff --git a/destrum/third_party/CMakeLists.txt b/destrum/third_party/CMakeLists.txt index 2e9a0a0..5d76599 100644 --- a/destrum/third_party/CMakeLists.txt +++ b/destrum/third_party/CMakeLists.txt @@ -15,7 +15,7 @@ add_subdirectory(sdl) add_subdirectory(vk-bootstrap) if (MSVC) - target_compile_definitions(vk-bootstrap PRIVATE $<$:_ITERATOR_DEBUG_LEVEL=1>) +# target_compile_definitions(vk-bootstrap PRIVATE $<$:_ITERATOR_DEBUG_LEVEL=1>) endif() if (BUILD_SHARED_LIBS) set_target_properties(vk-bootstrap PROPERTIES diff --git a/lightkeeper/src/Lightkeeper.cpp b/lightkeeper/src/Lightkeeper.cpp index 42cecb5..406aacd 100644 --- a/lightkeeper/src/Lightkeeper.cpp +++ b/lightkeeper/src/Lightkeeper.cpp @@ -1,4 +1,4 @@ #include "Lightkeeper.h" -LightKeeper::LightKeeper(): App() { +LightKeeper::LightKeeper() { }