diff --git a/private/sdl_gpu_test/5_input/app.cpp b/private/sdl_gpu_test/5_input/app.cpp new file mode 100644 index 0000000..eb6e4ca --- /dev/null +++ b/private/sdl_gpu_test/5_input/app.cpp @@ -0,0 +1,246 @@ + +#include "./app.hpp" + +#include "../util/mesh.hpp" + +#include +#include +#include +#include +#include + +#include "../sdlpp/keyboard.hpp" +#include "../util/mesh.hpp" + +namespace sdl_gpu_test +{ +namespace +{ +inline constexpr float Y_POS_MIN = -10.f; +inline constexpr float Y_POS_MAX = 10.f; +inline constexpr Uint16 AXIS_DEADZONE = 5000; + +struct VertexShaderParameters +{ + glm::mat4 worldToView; + glm::mat4 viewToClip; +}; +} + +void InputApp::init(const AppInitArgs& args) +{ + Application::init(args); + + // create shaders + const sdlpp::GPUShader vertexShader = loadShader("shaders/glsl/textured_3dtriangles_from_buffer.vert.spv", { + .format = sdlpp::GPUShaderFormat::SPIRV, + .stage = sdlpp::GPUShaderStage::VERTEX, + .numUniformBuffers = 1 + }); + const sdlpp::GPUShader fragmentShader = loadShader("shaders/glsl/color_from_texture.frag.spv", { + .format = sdlpp::GPUShaderFormat::SPIRV, + .stage = sdlpp::GPUShaderStage::FRAGMENT, + .numSamplers = 1, + .numUniformBuffers = 1 + }); + + // create depth buffer + mDepthBuffer.create(mDevice, { + .format = sdlpp::GPUTextureFormat::D16_UNORM, + .usage = sdlpp::GPUTextureUsageFlags{.depthStencilTarget = true}, + .width = 1280, + .height = 720 + }); + + // create graphics pipeline + std::array colorTargetsDescs = { + sdlpp::GPUColorTargetDescription{ + .format = mDevice.getSwapchainTextureFormat(mWindow), + .blendState = { + .enableBlend = true, + .srcColorBlendfactor = sdlpp::GPUBlendFactor::SRC_ALPHA, + .dstColorBlendfactor = sdlpp::GPUBlendFactor::ONE_MINUS_SRC_ALPHA, + .colorBlendOp = sdlpp::GPUBlendOp::ADD, + .srcAlphaBlendfactor = sdlpp::GPUBlendFactor::ONE, + .dstAlphaBlendfactor = sdlpp::GPUBlendFactor::ZERO, + .alphaBlendOp = sdlpp::GPUBlendOp::ADD + } + } + }; + std::array vertexBindings = { + sdlpp::GPUVertexBinding{ + .index = 0, + .pitch = sizeof(Vertex) + } + }; + std::array vertexAttributes = { + sdlpp::GPUVertexAttribute{ + .location = 0, + .bindingIndex = 0, + .format = sdlpp::GPUVertexElementFormat::FLOAT3, + .offset = offsetof(Vertex, pos) + }, + sdlpp::GPUVertexAttribute{ + .location = 1, + .bindingIndex = 0, + .format = sdlpp::GPUVertexElementFormat::FLOAT2, + .offset = offsetof(Vertex, texcoord) + } + }; + mPipeline.create(mDevice, { + .vertexShader = vertexShader, + .fragmentShader = fragmentShader, + .vertexInputState = { + .vertexBindings = vertexBindings, + .vertexAttributes = vertexAttributes + }, + .rasterizerState = { + .cullMode = sdlpp::GPUCullMode::BACK + }, + .targetInfo = { + .colorTargetDescriptions = colorTargetsDescs, + .depthStencilFormat = sdlpp::GPUTextureFormat::D16_UNORM + } + }); + + // load the mesh + const Mesh mesh = loadMesh(mFileSystem.getPath("meshes/cube.obj")); + mNumVertices = static_cast(mesh.vertices.size()); + + // create vertex buffer + mVertexBuffer.create(mDevice, { + .usage = {.vertex = true}, + .size = static_cast(mesh.vertices.size() * sizeof(Vertex)) + }); + uploadVertexData(mVertexBuffer, std::span(mesh.vertices.begin(), mesh.vertices.end())); + + // create texture and sampler + sdlpp::GPUTextureCreateArgs textureArgs = { + .format = sdlpp::GPUTextureFormat::R8G8B8A8_UNORM_SRGB, + .usage = {.sampler = true} + }; + mTexture = loadTexture("bitmaps/cube.png", textureArgs); + mSampler.create(mDevice, {}); + + // open gamepad + const std::vector gamepads = sdlpp::getGamepads(); + if (!gamepads.empty()) + { + mGamepad.open(gamepads[0]); + } +} + +void InputApp::update(const AppUpdateArgs& args) +{ + Application::update(args); + + sdlpp::GPUCommandBuffer cmdBuffer = mDevice.acquireCommandBuffer(); + Uint32 swapchainWidth = 0, swapchainHeight = 0; + const sdlpp::GPUTexture swapchainTexture = cmdBuffer.acquireSwapchainTexture(mWindow, swapchainWidth, swapchainHeight); + + if (swapchainWidth != mLastSwapchainWidth || swapchainHeight != mLastSwapchainHeight) + { + mDepthBuffer.destroy(); + mDepthBuffer.create(mDevice, { + .format = sdlpp::GPUTextureFormat::D16_UNORM, + .usage = sdlpp::GPUTextureUsageFlags{.depthStencilTarget = true}, + .width = swapchainWidth, + .height = swapchainHeight + }); + mLastSwapchainWidth = swapchainWidth; + mLastSwapchainHeight = swapchainHeight; + } + + const std::span keystates = sdlpp::getKeyboardState(); + if (keystates[SDL_SCANCODE_LEFT]) + { + mRotation -= 40.f * args.tickSeconds; + } + if (keystates[SDL_SCANCODE_RIGHT]) + { + mRotation += 40.f * args.tickSeconds; + } + if (keystates[SDL_SCANCODE_UP]) + { + mYPos -= 2.f * args.tickSeconds; + } + if (keystates[SDL_SCANCODE_DOWN]) + { + mYPos += 2.f * args.tickSeconds; + } + if (mGamepad) + { + const Sint16 xAxis = mGamepad.getAxis(sdlpp::GamepadAxis::LEFTX); + if (std::abs(xAxis) > AXIS_DEADZONE) + { + mRotation += 0.001f * args.tickSeconds * static_cast(xAxis); + } + const Sint16 yAxis = mGamepad.getAxis(sdlpp::GamepadAxis::LEFTY); + if (std::abs(yAxis) > AXIS_DEADZONE) + { + mYPos += 0.0002f * args.tickSeconds * yAxis; + } + } + + while (mRotation >= 360.f) { mRotation -= 360.f; } + while (mRotation < 0.f) { mRotation += 360.f; } + mYPos = std::clamp(mYPos, Y_POS_MIN, Y_POS_MAX); + + const VertexShaderParameters vertexShaderParameters = { + .worldToView = glm::lookAt(glm::vec3(2.f, mYPos, 2.f), glm::vec3(0.f, 0.f, 0.f), glm::vec3(0.f, 1.f, 0.f)) + * glm::rotate(glm::mat4(1.f), glm::radians(mRotation), glm::vec3(0.f, 1.f, 0.f)), + .viewToClip = glm::perspectiveFov( + /* fov = */ glm::radians(90.f), + /* width = */ static_cast(swapchainWidth), + /* height = */ static_cast(swapchainHeight), + /* zNear = */ 0.1f, + /* zFar = */ 100.f + ) + }; + + std::array colorTargets = {sdlpp::GPUColorTargetInfo{ + .texture = swapchainTexture, + .clearColor = {.r = 0.f, .g = 0.f, .b = 0.f, .a = 1.f}, + .loadOp = sdlpp::GPULoadOp::CLEAR, + }}; + sdlpp::GPURenderPass renderPass = cmdBuffer.beginRenderPass({ + .colorTargetInfos = colorTargets, + .depthStencilTargetInfo = sdlpp::GPUDepthStencilTargetInfo{ + .texture = mDepthBuffer, + .loadOp = sdlpp::GPULoadOp::CLEAR + } + }); + static const glm::vec4 WHITE(1.f, 1.f, 1.f, 1.f); + cmdBuffer.pushFragmentUniformData(0, std::span(&WHITE, 1)); + cmdBuffer.pushVertexUniformData(0, std::span(&vertexShaderParameters, 1)); + renderPass.bindFragmentSampler({.texture = mTexture, .sampler = mSampler}); + renderPass.bindGraphicsPipeline(mPipeline); + renderPass.bindVertexBuffer({.buffer = mVertexBuffer}); + renderPass.drawPrimitives({.numVertices = mNumVertices}); + renderPass.end(); + cmdBuffer.submit(); +} + +void InputApp::handleKeyboardEvent(const sdlpp::KeyboardEvent& event) +{ + switch (event.key) + { + case SDLK_Q: + if (!event.down) + { + mRunning = false; + } + break; + default: break; + } +} + +void InputApp::handleMouseMotionEvent(const sdlpp::MouseMotionEvent& event) +{ + if (event.state.left) + { + mRotation += 0.5f * event.xrel; + mYPos = std::clamp(mYPos + 0.02f * event.yrel, Y_POS_MIN, Y_POS_MAX); + } +} +} diff --git a/private/sdl_gpu_test/5_input/app.hpp b/private/sdl_gpu_test/5_input/app.hpp new file mode 100644 index 0000000..26b2bf4 --- /dev/null +++ b/private/sdl_gpu_test/5_input/app.hpp @@ -0,0 +1,34 @@ + +#pragma once + +#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_5_INPUT_APP_HPP_INCLUDED) +#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_5_INPUT_APP_HPP_INCLUDED 1 + +#include "../application.hpp" +#include "../sdlpp/gamepad.hpp" + +namespace sdl_gpu_test +{ +class InputApp : public Application +{ +private: + sdlpp::GPUBuffer mVertexBuffer; + sdlpp::GPUTexture mDepthBuffer; + sdlpp::GPUGraphicsPipeline mPipeline; + sdlpp::GPUTexture mTexture; + sdlpp::GPUSampler mSampler; + sdlpp::Gamepad mGamepad; + Uint32 mNumVertices = 0; + Uint32 mLastSwapchainWidth = 1280; + Uint32 mLastSwapchainHeight = 720; + float mRotation = 0.f; + float mYPos = 1.5f; +public: + void init(const AppInitArgs& args) override; + void update(const AppUpdateArgs& args) override; + void handleKeyboardEvent(const sdlpp::KeyboardEvent& event) override; + void handleMouseMotionEvent(const sdlpp::MouseMotionEvent& event) override; +}; +} // namespace sdl_gpu_test + +#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_5_INPUT_APP_HPP_INCLUDED) diff --git a/private/sdl_gpu_test/SModule b/private/sdl_gpu_test/SModule index bc851d5..efdbf4a 100644 --- a/private/sdl_gpu_test/SModule +++ b/private/sdl_gpu_test/SModule @@ -13,6 +13,7 @@ src_files = Split(""" 2_triangle_with_texcoords/app.cpp 3_textured_quad/app.cpp 4_textured_cube/app.cpp + 5_input/app.cpp """) shader_files = env.Glob("#assets/shaders/glsl/*.frag") \ diff --git a/private/sdl_gpu_test/application.cpp b/private/sdl_gpu_test/application.cpp index 0f6f0d8..785f15b 100644 --- a/private/sdl_gpu_test/application.cpp +++ b/private/sdl_gpu_test/application.cpp @@ -5,10 +5,9 @@ #include #include -#include -#include #include "./util/bitmap.hpp" +#include "./util/spdlog_wrapper.hpp" namespace sdl_gpu_test { @@ -56,21 +55,28 @@ void Application::run(std::span args) }); const clock_t::time_point startTime = clock_t::now(); + clock_t::time_point lastFrameTime = startTime; while (mRunning) { std::optional event; while ((event = sdlpp::pollEvent()).has_value()) { std::visit(mijin::Visitor{ - [&](const sdlpp::QuitEvent&) { mRunning = false; }, - [](const auto&) {} // default handler + [&](const sdlpp::QuitEvent&) { mRunning = false; }, + [&](const sdlpp::KeyboardEvent& event) { handleKeyboardEvent(event); }, + [&](const sdlpp::MouseButtonEvent& event) { handleMouseButtonEvent(event); }, + [&](const sdlpp::MouseMotionEvent& event) { handleMouseMotionEvent(event); }, + [&](const sdlpp::GamepadButtonEvent& event) { handleGamepadButtonEvent(event); }, + [](const auto&) {} // default handler }, *event); } const clock_t::time_point frameTime = clock_t::now(); update({ - .secondsSinceStart = std::chrono::duration_cast>(frameTime - startTime).count() + .secondsSinceStart = std::chrono::duration_cast>(frameTime - startTime).count(), + .tickSeconds = std::chrono::duration_cast>(frameTime - lastFrameTime).count() }); + lastFrameTime = frameTime; } cleanup({}); } diff --git a/private/sdl_gpu_test/application.hpp b/private/sdl_gpu_test/application.hpp index 51a30b9..771cb44 100644 --- a/private/sdl_gpu_test/application.hpp +++ b/private/sdl_gpu_test/application.hpp @@ -27,6 +27,7 @@ struct AppCleanupArgs struct AppUpdateArgs { float secondsSinceStart = 0.f; + float tickSeconds = 0.f; }; class Application @@ -42,6 +43,10 @@ public: virtual void init(const AppInitArgs& args); virtual void cleanup(const AppCleanupArgs& args); virtual void update(const AppUpdateArgs& args); + virtual void handleKeyboardEvent(const sdlpp::KeyboardEvent& /* event */) {} + virtual void handleMouseButtonEvent(const sdlpp::MouseButtonEvent& /* event */) {} + virtual void handleMouseMotionEvent(const sdlpp::MouseMotionEvent& /* event */) {} + virtual void handleGamepadButtonEvent(const sdlpp::GamepadButtonEvent& /* event */) {} void run(std::span args); diff --git a/private/sdl_gpu_test/main.cpp b/private/sdl_gpu_test/main.cpp index 9b441be..0a2d170 100644 --- a/private/sdl_gpu_test/main.cpp +++ b/private/sdl_gpu_test/main.cpp @@ -7,6 +7,7 @@ #include "./2_triangle_with_texcoords/app.hpp" #include "./3_textured_quad/app.hpp" #include "./4_textured_cube/app.hpp" +#include "./5_input/app.hpp" #include "./util/spdlog_wrapper.hpp" namespace @@ -34,7 +35,8 @@ const std::array APPS = { makeAppHelper("green_triangle"), makeAppHelper("triangle_with_texcoords"), makeAppHelper("textured_quad"), - makeAppHelper("textured_cube") + makeAppHelper("textured_cube"), + makeAppHelper("input") }; } @@ -45,7 +47,7 @@ int main(int argc, char* argv[]) return APP_ERROR; } - if (SDL_Init(0) != SDL_TRUE) + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD) != SDL_TRUE) { spdlog::error("Error initializing SDL."); return APP_ERROR; diff --git a/private/sdl_gpu_test/sdlpp/common.hpp b/private/sdl_gpu_test/sdlpp/common.hpp index 57d71c1..9060ed2 100644 --- a/private/sdl_gpu_test/sdlpp/common.hpp +++ b/private/sdl_gpu_test/sdlpp/common.hpp @@ -26,8 +26,10 @@ protected: protected: Base() noexcept = default; explicit Base(THandle* handle) noexcept : mHandle(handle) {} + Base(const Base&) noexcept = default; Base(Base&& other) noexcept : mHandle(std::exchange(other.mHandle, nullptr)) {} + Base& operator=(const Base&) noexcept = default; Base& operator=(Base&& other) noexcept { if (this != &other) @@ -38,15 +40,22 @@ protected: return *this; } public: - Base(const Base&) = delete; ~Base() noexcept { static_cast(*this).destroy(); } - Base& operator=(const Base&) = delete; auto operator<=>(const Base& other) const noexcept = default; operator THandle*() const noexcept { return mHandle; } + + explicit operator bool() const noexcept + { + return mHandle != nullptr; + } + bool operator!() const noexcept + { + return mHandle == nullptr; + } }; template diff --git a/private/sdl_gpu_test/sdlpp/event.hpp b/private/sdl_gpu_test/sdlpp/event.hpp index 04c5e46..73bf0b2 100644 --- a/private/sdl_gpu_test/sdlpp/event.hpp +++ b/private/sdl_gpu_test/sdlpp/event.hpp @@ -11,6 +11,26 @@ namespace sdlpp { +// +// flags +// +struct MouseButtonFlags : mijin::BitFlags +{ + bool left : 1 = false; + bool middle : 1 = false; + bool right : 1 = false; + bool x1 : 1 = false; + bool x2 : 1 = false; + + static MouseButtonFlags from(SDL_MouseButtonFlags base) noexcept + { + return std::bit_cast(static_cast(base)); + } +}; + +// +// structs +// struct Event { Uint64 timestamp; @@ -18,17 +38,77 @@ struct Event template explicit Event(const TBase& base) noexcept : timestamp(base.timestamp) {} }; + +struct KeyboardEvent : Event +{ + SDL_WindowID windowID; + SDL_KeyboardID which; + SDL_Scancode scancode; + SDL_Keycode key; + SDL_Keymod mod; + bool down; + bool repeat; + + explicit KeyboardEvent(const SDL_KeyboardEvent& base) noexcept : Event(base), windowID(base.windowID), + which(base.which), scancode(base.scancode), key(base.key), mod(base.mod), down(base.down), repeat(base.repeat) + { + } +}; + +struct MouseButtonEvent : Event +{ + SDL_WindowID windowID; + SDL_MouseID which; + Uint8 button; + bool down; + Uint8 clicks; + float x; + float y; + + MouseButtonEvent(const SDL_MouseButtonEvent& base) noexcept : Event(base), windowID(base.windowID), which(base.which), + button(base.button), down(base.down), clicks(base.clicks), x(base.x), y(base.y) + { + } +}; + +struct MouseMotionEvent : Event +{ + SDL_WindowID windowID; + SDL_MouseID which; + MouseButtonFlags state; + float x; + float y; + float xrel; + float yrel; + + MouseMotionEvent(const SDL_MouseMotionEvent& base) noexcept : Event(base), windowID(base.windowID), which(base.which), + state(MouseButtonFlags::from(base.state)), x(base.x), y(base.y), xrel(base.xrel), yrel(base.yrel) + { + } +}; + +struct GamepadButtonEvent : Event +{ + SDL_JoystickID which; + Uint8 button; + SDL_bool down; + + GamepadButtonEvent(const SDL_GamepadButtonEvent& base) noexcept : Event(base), which(base.which), button(base.button), + down(base.down) + { + } +}; + struct QuitEvent : Event { explicit QuitEvent(const SDL_QuitEvent& base) noexcept : Event(base) {} }; -struct WindowEvent : Event -{ - -}; using sdl_event_t = std::variant< - WindowEvent, + KeyboardEvent, + MouseButtonEvent, + MouseMotionEvent, + GamepadButtonEvent, QuitEvent >; @@ -40,6 +120,17 @@ inline std::optional pollEvent() noexcept { switch (event.type) { + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + return KeyboardEvent(event.key); + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: + return MouseButtonEvent(event.button); + case SDL_EVENT_MOUSE_MOTION: + return MouseMotionEvent(event.motion); + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + case SDL_EVENT_GAMEPAD_BUTTON_UP: + return GamepadButtonEvent(event.gbutton); case SDL_EVENT_QUIT: return QuitEvent(event.quit); default: diff --git a/private/sdl_gpu_test/sdlpp/gamepad.hpp b/private/sdl_gpu_test/sdlpp/gamepad.hpp new file mode 100644 index 0000000..3f4e755 --- /dev/null +++ b/private/sdl_gpu_test/sdlpp/gamepad.hpp @@ -0,0 +1,68 @@ + +#pragma once + +#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_GAMEPAD_HPP_INCLUDED) +#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_GAMEPAD_HPP_INCLUDED 1 + +#include "./common.hpp" + +namespace sdlpp +{ +enum class GamepadAxis +{ + INVALID = SDL_GAMEPAD_AXIS_INVALID, + LEFTX = SDL_GAMEPAD_AXIS_LEFTX, + LEFTY = SDL_GAMEPAD_AXIS_LEFTY, + RIGHTX = SDL_GAMEPAD_AXIS_RIGHTX, + RIGHTY = SDL_GAMEPAD_AXIS_RIGHTY, + LEFT_TRIGGER = SDL_GAMEPAD_AXIS_LEFT_TRIGGER, + RIGHT_TRIGGER = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, +}; + +class Gamepad : public Base +{ +public: + Gamepad() noexcept = default; + Gamepad(Gamepad&&) noexcept = default; + + Gamepad& operator=(Gamepad&&) = default; + + void open(SDL_JoystickID instanceID) + { + MIJIN_ASSERT(mHandle == nullptr, "Gamepad already opened."); + mHandle = SDL_OpenGamepad(instanceID); + if (mHandle == nullptr) + { + throw SDLError(); + } + } + + void destroy() noexcept + { + if (mHandle != nullptr) + { + SDL_CloseGamepad(mHandle); + mHandle = nullptr; + } + } + + [[nodiscard]] + Sint16 getAxis(GamepadAxis axis) const noexcept + { + return SDL_GetGamepadAxis(mHandle, static_cast(axis)); + } +}; + +[[nodiscard]] +inline std::vector getGamepads() noexcept +{ + int count = 0; + SDL_JoystickID* gamepads = SDL_GetGamepads(&count); + std::vector result; + result.resize(count); + std::copy_n(gamepads, count, result.begin()); + return result; +} +} // namespace sdlpp + +#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_GAMEPAD_HPP_INCLUDED) diff --git a/private/sdl_gpu_test/sdlpp/keyboard.hpp b/private/sdl_gpu_test/sdlpp/keyboard.hpp new file mode 100644 index 0000000..dcbd263 --- /dev/null +++ b/private/sdl_gpu_test/sdlpp/keyboard.hpp @@ -0,0 +1,20 @@ + +#pragma once + +#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_KEYBOARD_HPP_INCLUDED) +#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_KEYBOARD_HPP_INCLUDED 1 + +#include "./common.hpp" + +namespace sdlpp +{ +[[nodiscard]] +inline std::span getKeyboardState() noexcept +{ + int numkeys = 0; + const SDL_bool* states = SDL_GetKeyboardState(&numkeys); + return {states, static_cast(numkeys)}; +} +} // namespace sdlpp + +#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_KEYBOARD_HPP_INCLUDED)