WE BE RENDERING BABY

This commit is contained in:
2026-01-07 03:04:20 +01:00
parent c83c423b42
commit e306c5e23f
13 changed files with 377 additions and 37 deletions

View File

@@ -20,6 +20,8 @@ set(SRC_FILES
"src/Graphics/Pipelines/MeshPipeline.cpp"
"src/Input/InputManager.cpp"
"src/FS/AssetFS.cpp"
)

View File

@@ -9,6 +9,7 @@
#include <destrum/Graphics/GfxDevice.h>
#include <destrum/Graphics/Renderer.h>
#include <destrum/Input/InputManager.h>
class App {
@@ -41,6 +42,8 @@ protected:
MeshCache meshCache;
InputManager inputManager;
bool isRunning{false};
bool gamePaused{false};

View File

@@ -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

View File

@@ -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);

View File

@@ -0,0 +1,92 @@
#ifndef INPUTMANAGER_H
#define INPUTMANAGER_H
#include <SDL.h>
#include <unordered_map>
#include <unordered_set>
#include <string>
#include <vector>
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<SDL_Scancode> m_keysDown;
std::unordered_set<SDL_Scancode> m_keysPressed;
std::unordered_set<SDL_Scancode> m_keysReleased;
// Mouse button states
std::unordered_set<Uint8> m_mouseDown;
std::unordered_set<Uint8> m_mousePressed;
std::unordered_set<Uint8> 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<std::string, std::vector<Binding>> m_bindings;
private:
bool QueryBinding(const Binding& b, ButtonState state) const;
};
#endif //INPUTMANAGER_H

View File

@@ -4,6 +4,7 @@
#include <destrum/FS/AssetFS.h>
#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<float>(params.renderSize.x) / static_cast<float>(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

View File

@@ -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<float>() + 0.001f,
glm::half_pi<float>() - 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);
}

View File

@@ -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);

View File

@@ -2,6 +2,8 @@
#include <destrum/Graphics/Util.h>
#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);
}
}

View File

@@ -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() {

View File

@@ -0,0 +1,148 @@
#include <destrum/Input/InputManager.h>
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<SDL_Scancode>(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<Uint8>(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;
}

View File

@@ -15,7 +15,7 @@ add_subdirectory(sdl)
add_subdirectory(vk-bootstrap)
if (MSVC)
target_compile_definitions(vk-bootstrap PRIVATE $<$<CONFIG:Debug>:_ITERATOR_DEBUG_LEVEL=1>)
# target_compile_definitions(vk-bootstrap PRIVATE $<$<CONFIG:Debug>:_ITERATOR_DEBUG_LEVEL=1>)
endif()
if (BUILD_SHARED_LIBS)
set_target_properties(vk-bootstrap PROPERTIES