diff --git a/docs/design/XCEngine输入系统设计.md b/docs/design/XCEngine输入系统设计.md new file mode 100644 index 00000000..b9be6b48 --- /dev/null +++ b/docs/design/XCEngine输入系统设计.md @@ -0,0 +1,546 @@ +# XCEngine 输入系统设计与实现 + +> **文档信息** +> - **版本**: 1.0 +> - **日期**: 2026-03-22 +> - **状态**: 设计文档 +> - **目标**: 设计引擎级输入系统 + +--- + +## 1. 概述 + +### 1.1 设计目标 + +XCEngine 输入系统需要提供: +1. **统一的跨平台输入抽象** - 支持键盘、鼠标、手柄、触摸 +2. **与引擎架构无缝集成** - 使用现有的 `Core::Event` 系统 +3. **轮询 + 事件混合模式** - 既支持 `IsKeyDown()` 轮询,也支持事件回调 +4. **UI 系统支持** - 为 UI 组件 (Button, Slider 等) 提供指针事件 + +### 1.2 当前状态分析 + +| 模块 | 状态 | 说明 | +|-----|------|------| +| Core::Event | ✅ 完备 | 线程安全,Subscribe/Unsubscribe 模式 | +| RHI::RHISwapChain | ⚠️ PollEvents 空实现 | 需要填充 Windows 消息泵 | +| 现有 Input (mvs) | ❌ 耦合 Windows | 直接处理 HWND 消息,不适合引擎架构 | +| Platform/Window | ❌ 不存在 | 需要新建 | + +--- + +## 2. 架构设计 + +### 2.1 模块结构 + +``` +engine/include/XCEngine/ +├── Core/ +│ └── Event.h # 已有,复用 +├── Input/ +│ ├── InputTypes.h # 枚举和结构体定义 +│ ├── InputEvent.h # 输入事件结构体 +│ ├── InputManager.h # 输入管理器(单例) +│ └── InputModule.h # 平台相关输入处理模块接口 +└── Platform/ + ├── PlatformTypes.h # 平台类型抽象 + ├── Window.h # 窗口抽象接口 + └── Windows/ + ├── WindowsPlatform.h # Windows 平台实现 + └── WindowsInputModule.h # Windows 输入模块实现 +``` + +### 2.2 核心设计原则 + +1. **事件驱动 + 状态轮询双模式** + - 事件:用于 UI 交互、一次性按键响应 + - 轮询:用于连续输入检测(角色移动、视角控制) + +2. **平台抽象层** + - `InputModule` 接口:抽象平台特定的输入处理 + - `Window` 接口:抽象平台特定的窗口管理 + +3. **与现有引擎组件集成** + - 使用 `Core::Event` 作为事件系统 + - 使用 `Math::Vector2` 作为 2D 坐标类型 + +--- + +## 3. 详细设计 + +### 3.1 输入类型定义 (`InputTypes.h`) + +```cpp +#pragma once +#include "Core/Types.h" +#include "Math/Vector2.h" + +namespace XCEngine { +namespace Input { + +enum class KeyCode : uint8 { + None = 0, + A = 4, B = 5, C = 6, D = 7, E = 8, F = 9, G = 10, + H = 11, I = 12, J = 13, K = 14, L = 15, M = 16, N = 17, + O = 18, P = 19, Q = 20, R = 21, S = 22, T = 23, U = 24, + V = 25, W = 26, X = 27, Y = 28, Z = 29, + F1 = 58, F2 = 59, F3 = 60, F4 = 61, F5 = 62, F6 = 63, + F7 = 64, F8 = 65, F9 = 66, F10 = 67, F11 = 68, F12 = 69, + Space = 49, Tab = 48, Enter = 36, Escape = 53, + LeftShift = 56, RightShift = 60, LeftCtrl = 59, RightCtrl = 62, + LeftAlt = 58, RightAlt = 61, + Up = 126, Down = 125, Left = 123, Right = 124, + Home = 115, End = 119, PageUp = 116, PageDown = 121, + Delete = 51, Backspace = 51 +}; + +enum class MouseButton : uint8 { + Left = 0, + Right = 1, + Middle = 2, + Button4 = 3, + Button5 = 4 +}; + +enum class JoystickButton : uint8 { + Button0 = 0, + Button1 = 1, + Button2 = 2, + // ... Xbox/PlayStation 标准按钮 +}; + +enum class JoystickAxis : uint8 { + LeftX = 0, + LeftY = 1, + RightX = 2, + RightY = 3, + LeftTrigger = 4, + RightTrigger = 5 +}; + +} // namespace Input +} // namespace XCEngine +``` + +### 3.2 输入事件结构体 (`InputEvent.h`) + +```cpp +#pragma once +#include "InputTypes.h" +#include "Math/Vector2.h" +#include "Containers/String.h" + +namespace XCEngine { +namespace Input { + +struct KeyEvent { + KeyCode keyCode; + bool alt; + bool ctrl; + bool shift; + bool meta; + enum Type { Down, Up, Repeat } type; +}; + +struct MouseButtonEvent { + MouseButton button; + Math::Vector2 position; + enum Type { Pressed, Released } type; +}; + +struct MouseMoveEvent { + Math::Vector2 position; + Math::Vector2 delta; // 相对上一帧的移动量 +}; + +struct MouseWheelEvent { + Math::Vector2 position; + float delta; // 滚轮滚动量 +}; + +struct TextInputEvent { + char character; + Containers::String text; // 完整输入文本 +}; + +struct TouchState { + int touchId; + Math::Vector2 position; + Math::Vector2 deltaPosition; + float deltaTime; + int tapCount; + enum Phase { Began, Moved, Stationary, Ended, Canceled } phase; +}; + +} // namespace Input +} // namespace XCEngine +``` + +### 3.3 输入管理器 (`InputManager.h`) + +```cpp +#pragma once +#include "Core/Event.h" +#include "InputTypes.h" +#include "InputEvent.h" +#include "Math/Vector2.h" + +namespace XCEngine { +namespace Input { + +class InputManager { +public: + static InputManager& Get(); + + void Initialize(void* platformWindowHandle); + void Shutdown(); + void Update(); // 每帧调用,更新输入状态 + + // ============ 轮询接口 ============ + + // 键盘 + bool IsKeyDown(KeyCode key) const; + bool IsKeyUp(KeyCode key) const; + bool IsKeyPressed(KeyCode key) const; // 当前帧按下 + + // 鼠标 + Math::Vector2 GetMousePosition() const; + Math::Vector2 GetMouseDelta() const; + float GetMouseScrollDelta() const; + bool IsMouseButtonDown(MouseButton button) const; + bool IsMouseButtonUp(MouseButton button) const; + bool IsMouseButtonClicked(MouseButton button) const; // 当前帧点击 + + // 手柄 + int GetJoystickCount() const; + bool IsJoystickConnected(int joystick) const; + Math::Vector2 GetJoystickAxis(int joystick, JoystickAxis axis) const; + bool IsJoystickButtonDown(int joystick, JoystickButton button) const; + + // ============ 事件接口 ============ + + Core::Event& OnKeyEvent() { return m_onKeyEvent; } + Core::Event& OnMouseButton() { return m_onMouseButton; } + Core::Event& OnMouseMove() { return m_onMouseMove; } + Core::Event& OnMouseWheel() { return m_onMouseWheel; } + Core::Event& OnTextInput() { return m_onTextInput; } + + // 内部方法(供 PlatformInputModule 调用) + void ProcessKeyDown(KeyCode key, bool repeat); + void ProcessKeyUp(KeyCode key); + void ProcessMouseMove(int x, int y, int deltaX, int deltaY); + void ProcessMouseButton(MouseButton button, bool pressed, int x, int y); + void ProcessMouseWheel(float delta, int x, int y); + void ProcessTextInput(char c); + +private: + InputManager() = default; + ~InputManager() = default; + + void* m_platformWindowHandle = nullptr; + + // 键盘状态 + std::vector m_keyDownThisFrame; + std::vector m_keyDownLastFrame; + std::vector m_keyDown; + + // 鼠标状态 + Math::Vector2 m_mousePosition; + Math::Vector2 m_mouseDelta; + float m_mouseScrollDelta = 0.0f; + std::vector m_mouseButtonDownThisFrame; + std::vector m_mouseButtonDownLastFrame; + std::vector m_mouseButtonDown; + + // 事件 + Core::Event m_onKeyEvent; + Core::Event m_onMouseButton; + Core::Event m_onMouseMove; + Core::Event m_onMouseWheel; + Core::Event m_onTextInput; +}; + +} // namespace Input +} // namespace XCEngine +``` + +### 3.4 平台输入模块接口 (`InputModule.h`) + +```cpp +#pragma once + +namespace XCEngine { +namespace Input { + +class InputModule { +public: + virtual ~InputModule() = default; + + virtual void Initialize(void* windowHandle) = 0; + virtual void Shutdown() = 0; + virtual void PumpEvents() = 0; // 抽取并处理平台事件 + +protected: + InputModule() = default; +}; + +} // namespace Input +} // namespace XCEngine +``` + +### 3.5 Windows 输入模块实现 (`WindowsInputModule.h`) + +```cpp +#pragma once +#include "InputModule.h" +#include + +namespace XCEngine { +namespace Input { +namespace Platform { + +class WindowsInputModule : public InputModule { +public: + void Initialize(void* windowHandle) override; + void Shutdown() override; + void PumpEvents() override; + + // 供 Window 调用的消息处理 + void HandleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + +private: + void ProcessKeyDown(WPARAM wParam, LPARAM lParam); + void ProcessKeyUp(WPARAM wParam, LPARAM lParam); + void ProcessMouseMove(WPARAM wParam, LPARAM lParam); + void ProcessMouseButton(WPARAM wParam, LPARAM lParam, bool pressed); + void ProcessMouseWheel(WPARAM wParam, LPARAM lParam); + void ProcessCharInput(WPARAM wParam, LPARAM lParam); + + HWND m_hwnd = nullptr; + bool m_captureMouse = false; +}; + +} // namespace Platform +} // namespace Input +} // namespace XCEngine +``` + +--- + +## 4. 与引擎其他模块的集成 + +### 4.1 与 RHI/SwapChain 的集成 + +`RHISwapChain::PollEvents()` 需要调用 `InputManager::Update()` 和平台输入模块的 `PumpEvents()`: + +```cpp +// D3D12SwapChain.cpp +void D3D12SwapChain::PollEvents() { + // 抽取 Windows 消息 + if (m_inputModule) { + m_inputModule->PumpEvents(); + } + + // 更新输入管理器状态 + Input::InputManager::Get().Update(); + + // 处理关闭请求 + MSG msg; + if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) { + m_shouldClose = true; + } + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} +``` + +### 4.2 与 UI 系统的集成 + +UI 组件 (Button, Slider 等) 通过订阅 `InputManager` 的事件来响应用户输入: + +```cpp +// ButtonComponent.cpp +void ButtonComponent::Update(float deltaTime) { + if (!IsEnabled()) return; + + auto& input = Input::InputManager::Get(); + Vector2 mousePos = input.GetMousePosition(); + + // 射线检测是否悬停在按钮上 + if (IsPointInRect(mousePos, m_rect)) { + if (input.IsMouseButtonClicked(Input::MouseButton::Left)) { + OnClick.Invoke(); + } + } +} +``` + +### 4.3 与场景生命周期的集成 + +```cpp +// Scene.cpp +void Scene::Awake() { + // 初始化输入系统 + Input::InputManager::Get().Initialize(m_windowHandle); +} + +void Scene::OnDestroy() { + Input::InputManager::Get().Shutdown(); +} +``` + +--- + +## 5. Windows 消息到引擎事件的映射 + +| Windows Message | Engine Event | +|----------------|---------------| +| WM_KEYDOWN | `InputManager::ProcessKeyDown` | +| WM_KEYUP | `InputManager::ProcessKeyUp` | +| WM_CHAR | `InputManager::ProcessTextInput` | +| WM_MOUSEMOVE | `InputManager::ProcessMouseMove` | +| WM_LBUTTONDOWN/RBUTTONDOWN/MBUTTONDOWN | `InputManager::ProcessMouseButton` | +| WM_LBUTTONUP/RBUTTONUP/MBUTTONUP | `InputManager::ProcessMouseButton` | +| WM_MOUSEWHEEL | `InputManager::ProcessMouseWheel` | + +### 5.1 Windows VK 码到 KeyCode 的映射 + +```cpp +// WindowsVKToKeyCode 映射表 +static const KeyCode VK_TO_KEYCODE[] = { + // VK 0-29 对应 KeyCode A-Z + KeyCode::A, KeyCode::B, KeyCode::C, KeyCode::D, KeyCode::E, + KeyCode::F, KeyCode::G, KeyCode::H, KeyCode::I, KeyCode::J, + KeyCode::K, KeyCode::L, KeyCode::M, KeyCode::N, KeyCode::O, + KeyCode::P, KeyCode::Q, KeyCode::R, KeyCode::S, KeyCode::T, + KeyCode::U, KeyCode::V, KeyCode::W, KeyCode::X, KeyCode::Y, + KeyCode::Z, + // ... 其他映射 +}; +``` + +--- + +## 6. 实现计划 + +### Phase 1: 核心输入系统 + +| 任务 | 文件 | 说明 | +|-----|------|------| +| 创建 Input 模块目录 | `engine/include/XCEngine/Input/` | | +| 实现 InputTypes.h | KeyCode, MouseButton 等枚举 | | +| 实现 InputEvent.h | 事件结构体 | | +| 实现 InputManager.h/cpp | 单例管理器 | | +| 实现 WindowsInputModule.h/cpp | Windows 平台实现 | | + +### Phase 2: 与 RHI 集成 + +| 任务 | 文件 | 说明 | +|-----|------|------| +| 修改 RHISwapChain | 添加 InputModule 成员 | | +| 实现 D3D12SwapChain::PollEvents | 填充消息泵逻辑 | | +| 实现 OpenGLSwapChain::PollEvents | GL 消息处理 | | + +### Phase 3: UI 输入支持 + +| 任务 | 说明 | +|-----|------| +| 实现 Canvas 组件的射线检测 | 将屏幕坐标转换为 UI 元素 | +| Button/Slider 事件集成 | 使用 InputManager 事件 | + +--- + +## 7. 使用示例 + +### 7.1 轮询模式(角色移动) + +```cpp +void PlayerController::Update(float deltaTime) { + auto& input = Input::InputManager::Get(); + + if (input.IsKeyDown(Input::KeyCode::W)) { + m_velocity.z = 1.0f; + } else if (input.IsKeyDown(Input::KeyCode::S)) { + m_velocity.z = -1.0f; + } + + if (input.IsKeyDown(Input::KeyCode::A)) { + m_velocity.x = -1.0f; + } else if (input.IsKeyDown(Input::KeyCode::D)) { + m_velocity.x = 1.0f; + } +} +``` + +### 7.2 事件模式(UI 交互) + +```cpp +class MyUIButton : public Component { + uint64_t m_clickHandlerId = 0; + + void Awake() override { + m_clickHandlerId = Input::InputManager::Get().OnMouseButton().Subscribe( + [this](const Input::MouseButtonEvent& event) { + if (event.type == Input::MouseButtonEvent::Pressed && + event.button == Input::MouseButton::Left && + IsPointInButton(event.position)) { + OnClicked(); + } + } + ); + } + + void OnDestroy() override { + Input::InputManager::Get().OnMouseButton().Unsubscribe(m_clickHandlerId); + } +}; +``` + +### 7.3 文本输入 + +```cpp +void InputFieldComponent::Update(float deltaTime) { + auto& input = Input::InputManager::Get(); + + uint64_t textHandlerId = input.OnTextInput().Subscribe( + [this](const Input::TextInputEvent& event) { + if (m_isFocused) { + m_text += event.character; + OnValueChanged.Invoke(m_text); + } + } + ); + + // 清理 + input.OnTextInput().Unsubscribe(textHandlerId); +} +``` + +--- + +## 8. 待讨论问题 + +1. **多窗口支持**: 引擎是否需要支持多窗口?如果是,输入系统如何路由事件到正确的窗口? + +2. **手柄/游戏手柄支持**: 是否需要实现 XInput 支持?这会增加复杂性。 + +3. **原始输入 vs 输入模式**: Unity 有 "Input.GetAxis" vs "Input.GetButtonDown"。是否需要实现类似的轴概念? + +4. **IME/输入法支持**: 对于中文、日文输入,是否需要特殊处理? + +5. **平台模块位置**: `InputModule` 放在 `Input/` 还是 `Platform/` 命名空间下? + +--- + +## 9. 结论 + +XCEngine 输入系统设计遵循以下核心原则: + +1. **复用现有 Core::Event** - 不重复造轮子 +2. **平台抽象** - 通过 `InputModule` 接口隔离平台差异 +3. **双模式支持** - 轮询 + 事件,兼顾性能和响应性 +4. **与 UI 架构协同** - 为 Canvas/Button 提供必要的事件支持 + +该设计参考了 Unity 的 Input 系统架构,同时适配了 XCEngine 现有的组件模式和事件系统。 diff --git a/docs/plan/输入模块的设计与实现.md b/docs/plan/输入模块的设计与实现.md new file mode 100644 index 00000000..2688f68e --- /dev/null +++ b/docs/plan/输入模块的设计与实现.md @@ -0,0 +1,922 @@ +# XCEngine 输入系统设计与实现 + +> **文档信息** +> - **版本**: 1.1 +> - **日期**: 2026-03-22 +> - **状态**: 设计文档 +> - **目标**: 设计引擎级输入系统 + +--- + +## 1. 概述 + +### 1.1 设计目标 + +XCEngine 输入系统需要提供: +1. **统一的跨平台输入抽象** - 支持键盘、鼠标、触摸(手柄暂不考虑) +2. **与引擎架构无缝集成** - 使用现有的 `Core::Event` 系统 +3. **轮询 + 事件混合模式** - 既支持 `IsKeyDown()` 轮询,也支持事件回调 +4. **UI 系统支持** - 为 UI 组件 (Button, Slider 等) 提供指针事件 +5. **输入轴支持** - 参考 Unity 的 Input.GetAxis 设计,支持键盘/手柄统一映射 + +### 1.2 当前状态分析 + +| 模块 | 状态 | 说明 | +|-----|------|------| +| Core::Event | ✅ 完备 | 线程安全,Subscribe/Unsubscribe 模式 | +| RHI::RHISwapChain | ⚠️ PollEvents 空实现 | 需要填充 Windows 消息泵 | +| 现有 Input (mvs) | ❌ 耦合 Windows | 直接处理 HWND 消息,不适合引擎架构 | +| Platform/Window | ❌ 不存在 | 需要新建 | + +### 1.3 设计决策 + +| 问题 | 决策 | +|-----|------| +| 多窗口 | ❌ 单窗口,简化设计 | +| 手柄支持 | ❌ 暂不考虑,预留接口即可 | +| 输入轴 | ✅ 参考 Unity 实现 Input.GetAxis | +| IME/多语言 | ❌ 暂不考虑,只处理英文字符 | +| 模块位置 | ✅ InputModule 放在 Input/ 命名空间 | + +--- + +## 2. 架构设计 + +### 2.1 模块结构 + +``` +engine/include/XCEngine/ +├── Core/ +│ └── Event.h # 已有,复用 +├── Input/ +│ ├── InputTypes.h # 枚举和结构体定义 +│ ├── InputEvent.h # 输入事件结构体 +│ ├── InputAxis.h # 输入轴定义 +│ ├── InputManager.h # 输入管理器(单例) +│ └── InputModule.h # 平台相关输入处理模块接口 +└── Platform/ + ├── PlatformTypes.h # 平台类型抽象 + ├── Window.h # 窗口抽象接口 + └── Windows/ + ├── WindowsPlatform.h # Windows 平台实现 + └── WindowsInputModule.h # Windows 输入模块实现 +``` + +### 2.2 核心设计原则 + +1. **事件驱动 + 状态轮询双模式** + - 事件:用于 UI 交互、一次性按键响应 + - 轮询:用于连续输入检测(角色移动、视角控制) + +2. **平台抽象层** + - `InputModule` 接口:抽象平台特定的输入处理 + - `Window` 接口:抽象平台特定的窗口管理 + +3. **输入轴映射** + - 键盘和手柄可以映射到同一个轴,实现统一输入 + - 支持在配置文件中定义轴映射关系 + +4. **与现有引擎组件集成** + - 使用 `Core::Event` 作为事件系统 + - 使用 `Math::Vector2` 作为 2D 坐标类型 + +--- + +## 3. 详细设计 + +### 3.1 输入类型定义 (`InputTypes.h`) + +```cpp +#pragma once +#include "Core/Types.h" + +namespace XCEngine { +namespace Input { + +enum class KeyCode : uint8 { + None = 0, + + // 字母键 A-Z + A = 4, B = 5, C = 6, D = 7, E = 8, F = 9, G = 10, + H = 11, I = 12, J = 13, K = 14, L = 15, M = 16, N = 17, + O = 18, P = 19, Q = 20, R = 21, S = 22, T = 23, U = 24, + V = 25, W = 26, X = 27, Y = 28, Z = 29, + + // 功能键 F1-F12 + F1 = 58, F2 = 59, F3 = 60, F4 = 61, F5 = 62, F6 = 63, + F7 = 64, F8 = 65, F9 = 66, F10 = 67, F11 = 68, F12 = 69, + + // 控制键 + Space = 49, Tab = 48, Enter = 36, Escape = 53, + LeftShift = 56, RightShift = 60, LeftCtrl = 59, RightCtrl = 62, + LeftAlt = 58, RightAlt = 61, + + // 方向键 + Up = 126, Down = 125, Left = 123, Right = 124, + + // 编辑键 + Home = 115, End = 119, PageUp = 116, PageDown = 121, + Delete = 51, Backspace = 51, + + // 数字键 0-9 + Zero = 39, One = 30, Two = 31, Three = 32, + Four = 33, Five = 34, Six = 35, Seven = 37, + Eight = 38, Nine = 40, + + // 符号键 + Minus = 43, Equals = 46, BracketLeft = 47, BracketRight = 54, + Semicolon = 42, Quote = 40, Comma = 54, Period = 55, + Slash = 44, Backslash = 45, Backtick = 41 +}; + +enum class MouseButton : uint8 { + Left = 0, + Right = 1, + Middle = 2, + Button4 = 3, + Button5 = 4 +}; + +enum class JoystickAxis : uint8 { + LeftX = 0, + LeftY = 1, + RightX = 2, + RightY = 3, + LeftTrigger = 4, + RightTrigger = 5 +}; + +} // namespace Input +} // namespace XCEngine +``` + +### 3.2 输入事件结构体 (`InputEvent.h`) + +```cpp +#pragma once +#include "InputTypes.h" +#include "Math/Vector2.h" +#include "Containers/String.h" + +namespace XCEngine { +namespace Input { + +struct KeyEvent { + KeyCode keyCode; + bool alt; + bool ctrl; + bool shift; + bool meta; + enum Type { Down, Up, Repeat } type; +}; + +struct MouseButtonEvent { + MouseButton button; + Math::Vector2 position; + enum Type { Pressed, Released } type; +}; + +struct MouseMoveEvent { + Math::Vector2 position; + Math::Vector2 delta; +}; + +struct MouseWheelEvent { + Math::Vector2 position; + float delta; +}; + +struct TextInputEvent { + char character; + Containers::String text; +}; + +struct TouchState { + int touchId; + Math::Vector2 position; + Math::Vector2 deltaPosition; + float deltaTime; + int tapCount; + enum Phase { Began, Moved, Stationary, Ended, Canceled } phase; +}; + +} // namespace Input +} // namespace XCEngine +``` + +### 3.3 输入轴定义 (`InputAxis.h`) + +```cpp +#pragma once +#include "InputTypes.h" +#include "Math/Vector2.h" +#include "Containers/String.h" + +namespace XCEngine { +namespace Input { + +class InputAxis { +public: + InputAxis() = default; + InputAxis(const Containers::String& name, KeyCode positive, KeyCode negative = KeyCode::None) + : m_name(name), m_positiveKey(positive), m_negativeKey(negative) {} + + const Containers::String& GetName() const { return m_name; } + KeyCode GetPositiveKey() const { return m_positiveKey; } + KeyCode GetNegativeKey() const { return m_negativeKey; } + + void SetKeys(KeyCode positive, KeyCode negative) { + m_positiveKey = positive; + m_negativeKey = negative; + } + + float GetValue() const { return m_value; } + void SetValue(float value) { m_value = value; } + +private: + Containers::String m_name; + KeyCode m_positiveKey = KeyCode::None; + KeyCode m_negativeKey = KeyCode::None; + float m_value = 0.0f; +}; + +} // namespace Input +} // namespace XCEngine +``` + +### 3.4 输入管理器 (`InputManager.h`) + +```cpp +#pragma once +#include "Core/Event.h" +#include "InputTypes.h" +#include "InputEvent.h" +#include "InputAxis.h" +#include "Math/Vector2.h" + +namespace XCEngine { +namespace Input { + +class InputManager { +public: + static InputManager& Get(); + + void Initialize(void* platformWindowHandle); + void Shutdown(); + void Update(float deltaTime); + + // ============ 轮询接口 ============ + + // 键盘 + bool IsKeyDown(KeyCode key) const; + bool IsKeyUp(KeyCode key) const; + bool IsKeyPressed(KeyCode key) const; + + // 鼠标 + Math::Vector2 GetMousePosition() const; + Math::Vector2 GetMouseDelta() const; + float GetMouseScrollDelta() const; + bool IsMouseButtonDown(MouseButton button) const; + bool IsMouseButtonUp(MouseButton button) const; + bool IsMouseButtonClicked(MouseButton button) const; + + // 触摸 + int GetTouchCount() const; + TouchState GetTouch(int index) const; + + // ============ 轴接口 (参考 Unity) ============ + + float GetAxis(const Containers::String& axisName) const; + float GetAxisRaw(const Containers::String& axisName) const; + + bool GetButton(const Containers::String& buttonName) const; + bool GetButtonDown(const Containers::String& buttonName) const; + bool GetButtonUp(const Containers::String& buttonName) const; + + // 注册轴 + void RegisterAxis(const InputAxis& axis); + void RegisterButton(const Containers::String& name, KeyCode key); + void ClearAxes(); + + // ============ 事件接口 ============ + + Core::Event& OnKeyEvent() { return m_onKeyEvent; } + Core::Event& OnMouseButton() { return m_onMouseButton; } + Core::Event& OnMouseMove() { return m_onMouseMove; } + Core::Event& OnMouseWheel() { return m_onMouseWheel; } + Core::Event& OnTextInput() { return m_onTextInput; } + + // 内部方法(供 PlatformInputModule 调用) + void ProcessKeyDown(KeyCode key, bool repeat); + void ProcessKeyUp(KeyCode key); + void ProcessMouseMove(int x, int y, int deltaX, int deltaY); + void ProcessMouseButton(MouseButton button, bool pressed, int x, int y); + void ProcessMouseWheel(float delta, int x, int y); + void ProcessTextInput(char c); + +private: + InputManager() = default; + ~InputManager() = default; + + void* m_platformWindowHandle = nullptr; + + // 键盘状态 + std::vector m_keyDownThisFrame; + std::vector m_keyDownLastFrame; + std::vector m_keyDown; + + // 鼠标状态 + Math::Vector2 m_mousePosition; + Math::Vector2 m_mouseDelta; + float m_mouseScrollDelta = 0.0f; + std::vector m_mouseButtonDownThisFrame; + std::vector m_mouseButtonDownLastFrame; + std::vector m_mouseButtonDown; + + // 触摸状态 + std::vector m_touches; + + // 轴映射 + std::unordered_map m_axes; + std::unordered_map m_buttons; + std::vector m_buttonDownThisFrame; + std::vector m_buttonDownLastFrame; + + // 事件 + Core::Event m_onKeyEvent; + Core::Event m_onMouseButton; + Core::Event m_onMouseMove; + Core::Event m_onMouseWheel; + Core::Event m_onTextInput; +}; + +} // namespace Input +} // namespace XCEngine +``` + +### 3.5 平台输入模块接口 (`InputModule.h`) + +```cpp +#pragma once + +namespace XCEngine { +namespace Input { + +class InputModule { +public: + virtual ~InputModule() = default; + + virtual void Initialize(void* windowHandle) = 0; + virtual void Shutdown() = 0; + virtual void PumpEvents() = 0; + +protected: + InputModule() = default; +}; + +} // namespace Input +} // namespace XCEngine +``` + +### 3.6 Windows 输入模块实现 (`WindowsInputModule.h`) + +```cpp +#pragma once +#include "InputModule.h" +#include + +namespace XCEngine { +namespace Input { +namespace Platform { + +class WindowsInputModule : public InputModule { +public: + void Initialize(void* windowHandle) override; + void Shutdown() override; + void PumpEvents() override; + + void HandleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + +private: + void ProcessKeyDown(WPARAM wParam, LPARAM lParam); + void ProcessKeyUp(WPARAM wParam, LPARAM lParam); + void ProcessMouseMove(WPARAM wParam, LPARAM lParam); + void ProcessMouseButton(WPARAM wParam, LPARAM lParam, bool pressed); + void ProcessMouseWheel(WPARAM wParam, LPARAM lParam); + void ProcessCharInput(WPARAM wParam, LPARAM lParam); + + KeyCode VKCodeToKeyCode(int vkCode); + + HWND m_hwnd = nullptr; + Math::Vector2 m_lastMousePosition; + bool m_captureMouse = false; + bool m_isInitialized = false; +}; + +} // namespace Platform +} // namespace Input +} // namespace XCEngine +``` + +--- + +## 4. InputManager 实现细节 + +### 4.1 状态追踪机制 + +``` +每帧 Update() 调用时: +1. m_keyDownLastFrame = m_keyDownThisFrame +2. 处理 m_keyDownThisFrame = m_keyDown (当前帧按下状态) +3. 清零 m_mouseDelta, m_mouseScrollDelta + +IsKeyPressed(key) = m_keyDownThisFrame[key] && !m_keyDownLastFrame[key] // 当前帧按下 +IsKeyDown(key) = m_keyDown[key] // 当前正按下 +IsKeyUp(key) = !m_keyDown[key] // 当前已释放 +``` + +### 4.2 轴值计算 + +```cpp +float InputManager::GetAxis(const Containers::String& axisName) const { + auto it = m_axes.find(axisName); + if (it == m_axes.end()) return 0.0f; + + const auto& axis = it->second; + float value = 0.0f; + + if (axis.GetPositiveKey() != KeyCode::None && IsKeyDown(axis.GetPositiveKey())) { + value += 1.0f; + } + if (axis.GetNegativeKey() != KeyCode::None && IsKeyDown(axis.GetNegativeKey())) { + value -= 1.0f; + } + + return value; +} + +float InputManager::GetAxisRaw(const Containers::String& axisName) const { + auto it = m_axes.find(axisName); + if (it == m_axes.end()) return 0.0f; + + const auto& axis = it->second; + float value = 0.0f; + + if (axis.GetPositiveKey() != KeyCode::None && IsKeyPressed(axis.GetPositiveKey())) { + value += 1.0f; + } + if (axis.GetNegativeKey() != KeyCode::None && IsKeyPressed(axis.GetNegativeKey())) { + value -= 1.0f; + } + + return value; +} +``` + +### 4.3 默认轴配置 + +```cpp +void InputManager::Initialize(void* platformWindowHandle) { + // 注册默认轴 (类似 Unity) + RegisterAxis(InputAxis("Horizontal", KeyCode::D, KeyCode::A)); + RegisterAxis(InputAxis("Vertical", KeyCode::W, KeyCode::S)); + RegisterAxis(InputAxis("Mouse X", KeyCode::None, KeyCode::None)); // 鼠标驱动 + RegisterAxis(InputAxis("Mouse Y", KeyCode::None, KeyCode::None)); // 鼠标驱动 + + // 注册默认按钮 + RegisterButton("Jump", KeyCode::Space); + RegisterButton("Fire1", KeyCode::LeftCtrl); + RegisterButton("Fire2", KeyCode::LeftAlt); + RegisterButton("Fire3", KeyCode::LeftShift); +} +``` + +--- + +## 5. Windows 消息到引擎事件的映射 + +### 5.1 消息映射表 + +| Windows Message | Engine Method | 说明 | +|----------------|---------------|------| +| WM_KEYDOWN | `ProcessKeyDown` | 按键按下,wParam=VK码 | +| WM_KEYUP | `ProcessKeyUp` | 按键释放,wParam=VK码 | +| WM_CHAR | `ProcessTextInput` | 字符输入,wParam=字符 | +| WM_MOUSEMOVE | `ProcessMouseMove` | 鼠标移动 | +| WM_LBUTTONDOWN | `ProcessMouseButton(Left, Pressed)` | 左键按下 | +| WM_LBUTTONUP | `ProcessMouseButton(Left, Released)` | 左键释放 | +| WM_RBUTTONDOWN | `ProcessMouseButton(Right, Pressed)` | 右键按下 | +| WM_RBUTTONUP | `ProcessMouseButton(Right, Released)` | 右键释放 | +| WM_MBUTTONDOWN | `ProcessMouseButton(Middle, Pressed)` | 中键按下 | +| WM_MBUTTONUP | `ProcessMouseButton(Middle, Released)` | 中键释放 | +| WM_MOUSEWHEEL | `ProcessMouseWheel` | 滚轮滚动 | +| WM_XBUTTONDOWN | `ProcessMouseButton(Button4/5, Pressed)` | 侧键按下 | +| WM_XBUTTONUP | `ProcessMouseButton(Button4/5, Released)` | 侧键释放 | + +### 5.2 VK 码到 KeyCode 映射 + +```cpp +KeyCode WindowsInputModule::VKCodeToKeyCode(int vkCode) { + switch (vkCode) { + case 'A': return KeyCode::A; + case 'B': return KeyCode::B; + case 'C': return KeyCode::C; + // ... Z + case VK_SPACE: return KeyCode::Space; + case VK_TAB: return KeyCode::Tab; + case VK_RETURN: return KeyCode::Enter; + case VK_ESCAPE: return KeyCode::Escape; + case VK_SHIFT: return KeyCode::LeftShift; + case VK_CONTROL: return KeyCode::LeftCtrl; + case VK_MENU: return KeyCode::LeftAlt; + case VK_UP: return KeyCode::Up; + case VK_DOWN: return KeyCode::Down; + case VK_LEFT: return KeyCode::Left; + case VK_RIGHT: return KeyCode::Right; + case VK_HOME: return KeyCode::Home; + case VK_END: return KeyCode::End; + case VK_PRIOR: return KeyCode::PageUp; + case VK_NEXT: return KeyCode::PageDown; + case VK_DELETE: return KeyCode::Delete; + case VK_BACK: return KeyCode::Backspace; + case VK_F1: return KeyCode::F1; + case VK_F2: return KeyCode::F2; + // ... F12 + case '0': return KeyCode::Zero; + case '1': return KeyCode::One; + // ... 9 + case VK_OEM_MINUS: return KeyCode::Minus; + case VK_OEM_PLUS: return KeyCode::Equals; + // ... 其他 OEM 键 + default: return KeyCode::None; + } +} +``` + +--- + +## 6. 与引擎其他模块的集成 + +### 6.1 与 RHI/SwapChain 的集成 + +`RHISwapChain::PollEvents()` 需要调用 `InputModule::PumpEvents()` 和 `InputManager::Update()`: + +```cpp +// D3D12SwapChain.cpp +void D3D12SwapChain::PollEvents() { + // 抽取 Windows 消息 + if (m_inputModule) { + m_inputModule->PumpEvents(); + } + + // 处理其他 Windows 消息(关闭请求等) + MSG msg; + while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) { + m_shouldClose = true; + break; + } + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + +// 在 GameLoop 中每帧调用 +void GameLoop::Update(float deltaTime) { + // 更新输入状态(计算 IsKeyPressed 等) + Input::InputManager::Get().Update(deltaTime); + + // 更新场景和组件 + m_currentScene->Update(deltaTime); +} +``` + +### 6.2 与 UI 系统的集成 + +UI 组件通过订阅 `InputManager` 的事件来响应用户输入: + +```cpp +// ButtonComponent.cpp +void ButtonComponent::Update(float deltaTime) { + if (!IsEnabled()) return; + + auto& input = Input::InputManager::Get(); + Vector2 mousePos = input.GetMousePosition(); + + // 检测鼠标是否在按钮区域内 + if (IsPointInRect(mousePos, m_rect)) { + // 检测悬停状态变化 + if (!m_isHovered) { + m_isHovered = true; + OnPointerEnter.Invoke(); + } + + // 检测点击 + if (input.IsMouseButtonClicked(Input::MouseButton::Left)) { + OnClick.Invoke(); + } + + // 检测按下/释放 + if (input.IsMouseButtonDown(Input::MouseButton::Left)) { + OnPointerDown.Invoke(); + } else if (m_wasMouseDown && !input.IsMouseButtonDown(Input::MouseButton::Left)) { + OnPointerUp.Invoke(); + } + } else if (m_isHovered) { + m_isHovered = false; + OnPointerExit.Invoke(); + } + + m_wasMouseDown = input.IsMouseButtonDown(Input::MouseButton::Left); +} +``` + +### 6.3 与场景生命周期的集成 + +```cpp +// Scene.cpp +void Scene::Awake() { + // 获取窗口句柄并初始化输入系统 + void* windowHandle = GetEngine()->GetWindowHandle(); + Input::InputManager::Get().Initialize(windowHandle); +} + +void Scene::OnDestroy() { + Input::InputManager::Get().Shutdown(); +} +``` + +--- + +## 7. 使用示例 + +### 7.1 轮询模式(角色移动) + +```cpp +void PlayerController::Update(float deltaTime) { + auto& input = Input::InputManager::Get(); + + // 使用轴 (推荐方式,兼容手柄) + float horizontal = input.GetAxis("Horizontal"); + float vertical = input.GetAxis("Vertical"); + + m_velocity.x = horizontal * m_moveSpeed; + m_velocity.z = vertical * m_moveSpeed; + + // 或者使用原始轴(无平滑) + float rawH = input.GetAxisRaw("Horizontal"); + + // 直接轮询也可以 + if (input.IsKeyDown(Input::KeyCode::W)) { + m_velocity.z = 1.0f; + } +} +``` + +### 7.2 事件模式(UI 交互) + +```cpp +class MyUIButton : public Component { + uint64_t m_clickHandlerId = 0; + uint64_t m_hoverHandlerId = 0; + + void Awake() override { + // 订阅鼠标按钮事件 + m_clickHandlerId = Input::InputManager::Get().OnMouseButton().Subscribe( + [this](const Input::MouseButtonEvent& event) { + if (event.type == Input::MouseButtonEvent::Pressed && + event.button == Input::MouseButton::Left && + IsPointInButton(event.position)) { + OnClicked(); + } + } + ); + + // 订阅鼠标移动事件用于悬停检测 + m_hoverHandlerId = Input::InputManager::Get().OnMouseMove().Subscribe( + [this](const Input::MouseMoveEvent& event) { + bool isHovering = IsPointInButton(event.position); + if (isHovering && !m_isHovered) { + m_isHovered = true; + OnPointerEnter.Invoke(); + } else if (!isHovering && m_isHovered) { + m_isHovered = false; + OnPointerExit.Invoke(); + } + } + ); + } + + void OnDestroy() override { + Input::InputManager::Get().OnMouseButton().Unsubscribe(m_clickHandlerId); + Input::InputManager::Get().OnMouseMove().Unsubscribe(m_hoverHandlerId); + } +}; +``` + +### 7.3 文本输入 + +```cpp +void InputFieldComponent::Update(float deltaTime) { + static uint64_t textHandlerId = 0; + + if (m_isFocused) { + if (textHandlerId == 0) { + textHandlerId = Input::InputManager::Get().OnTextInput().Subscribe( + [this](const Input::TextInputEvent& event) { + if (event.character == '\b') { // Backspace + if (!m_text.empty()) { + m_text.pop_back(); + } + } else if (event.character == '\r') { // Enter + OnSubmit.Invoke(); + } else if (isprint(event.character)) { + if (m_characterLimit == 0 || m_text.length() < m_characterLimit) { + m_text += event.character; + OnValueChanged.Invoke(m_text); + } + } + } + ); + } + } else if (textHandlerId != 0) { + Input::InputManager::Get().OnTextInput().Unsubscribe(textHandlerId); + textHandlerId = 0; + } +} +``` + +### 7.4 射击游戏开火 + +```cpp +void WeaponComponent::Update(float deltaTime) { + auto& input = Input::InputManager::Get(); + + // 方式1: 按钮按下检测 + if (input.GetButtonDown("Fire1")) { + Fire(); + } + + // 方式2: 轮询检测(适合连发) + if (input.GetButton("Fire1") && m_fireRateTimer <= 0) { + Fire(); + m_fireRateTimer = m_fireRate; + } +} +``` + +--- + +## 8. 实现计划 + +### Phase 1: 核心输入系统 + +| 任务 | 文件 | 优先级 | +|-----|------|-------| +| 创建目录 | `engine/include/XCEngine/Input/` | P0 | +| 实现 InputTypes.h | KeyCode, MouseButton 枚举 | P0 | +| 实现 InputEvent.h | 事件结构体 | P0 | +| 实现 InputAxis.h | 输入轴定义 | P1 | +| 实现 InputManager.h/cpp | 单例管理器 | P0 | +| 实现 InputModule.h | 平台输入模块接口 | P0 | +| 实现 WindowsInputModule.h/cpp | Windows 平台实现 | P0 | + +### Phase 2: 与 RHI 集成 + +| 任务 | 文件 | 优先级 | +|-----|------|-------| +| 修改 RHISwapChain | 添加 InputModule 成员 | P0 | +| 实现 D3D12SwapChain::PollEvents | 填充消息泵逻辑 | P0 | +| 实现 OpenGLSwapChain::PollEvents | GL 消息处理 | P1 | + +### Phase 3: 轴系统完善 + +| 任务 | 说明 | 优先级 | +|-----|------|-------| +| 默认轴配置 | 注册 Horizontal, Vertical 等默认轴 | P1 | +| 轴平滑处理 | GetAxis 的平滑插值 | P2 | +| 配置文件支持 | 从 JSON/配置加载轴映射 | P2 | + +### Phase 4: UI 输入支持 + +| 任务 | 说明 | 优先级 | +|-----|------|-------| +| Canvas 射线检测 | 将屏幕坐标转换为 UI 元素 | P1 | +| Button 事件 | OnClick, OnPointerDown/Up/Enter/Exit | P1 | +| Slider 拖拽 | 使用鼠标事件实现滑块拖拽 | P1 | +| InputField 文本输入 | 接收 TextInputEvent | P1 | + +--- + +## 9. 测试策略 + +### 9.1 单元测试 + +```cpp +// test_input_manager.cpp +TEST(InputManager, KeyDownDetection) { + InputManager::Get().Initialize(nullptr); + + // 模拟按下 W 键 + InputManager::Get().ProcessKeyDown(KeyCode::W, false); + + EXPECT_TRUE(InputManager::Get().IsKeyDown(KeyCode::W)); + EXPECT_TRUE(InputManager::Get().IsKeyPressed(KeyCode::W)); // 第一帧按下 + EXPECT_FALSE(InputManager::Get().IsKeyUp(KeyCode::W)); + + // 模拟释放 + InputManager::Get().ProcessKeyUp(KeyCode::W); + + EXPECT_FALSE(InputManager::Get().IsKeyDown(KeyCode::W)); + EXPECT_TRUE(InputManager::Get().IsKeyUp(KeyCode::W)); + + InputManager::Get().Shutdown(); +} + +TEST(InputManager, AxisRegistration) { + InputManager::Get().Initialize(nullptr); + + InputManager::Get().RegisterAxis(InputAxis("TestAxis", KeyCode::W, KeyCode::S)); + + EXPECT_EQ(InputManager::Get().GetAxis("TestAxis"), 0.0f); + + InputManager::Get().ProcessKeyDown(KeyCode::W, false); + EXPECT_EQ(InputManager::Get().GetAxis("TestAxis"), 1.0f); + + InputManager::Get().ProcessKeyUp(KeyCode::W); + InputManager::Get().ProcessKeyDown(KeyCode::S, false); + EXPECT_EQ(InputManager::Get().GetAxis("TestAxis"), -1.0f); + + InputManager::Get().Shutdown(); +} + +TEST(InputManager, MousePositionTracking) { + InputManager::Get().Initialize(nullptr); + + InputManager::Get().ProcessMouseMove(100, 200, 0, 0); + EXPECT_EQ(InputManager::Get().GetMousePosition(), Math::Vector2(100, 200)); + + InputManager::Get().ProcessMouseMove(105, 205, 5, 5); + EXPECT_EQ(InputManager::Get().GetMouseDelta(), Math::Vector2(5, 5)); + + InputManager::Get().Shutdown(); +} +``` + +### 9.2 集成测试 + +- 测试输入系统与 SwapChain 的集成 +- 测试输入系统与 Scene 的集成 +- 测试 UI Button 点击响应 + +--- + +## 10. 文件清单 + +### 头文件 + +``` +engine/include/XCEngine/Input/ +├── InputTypes.h # 48 行 +├── InputEvent.h # 78 行 +├── InputAxis.h # 45 行 +├── InputManager.h # 130 行 +└── InputModule.h # 20 行 + +engine/include/XCEngine/Platform/ +├── PlatformTypes.h # (新建) +├── Window.h # (新建) +└── Windows/ + └── WindowsInputModule.h # 55 行 +``` + +### 源文件 + +``` +engine/src/Input/ +├── InputManager.cpp # 180 行 +└── Platform/ + └── Windows/ + └── WindowsInputModule.cpp # 200 行 +``` + +### 测试文件 + +``` +tests/Input/ +├── test_input_types.cpp # KeyCode/MouseButton 枚举测试 +├── test_input_event.cpp # 事件结构体测试 +├── test_input_axis.cpp # 轴注册和值计算测试 +└── test_input_manager.cpp # 核心功能测试 +``` + +--- + +## 11. 设计原则总结 + +1. **复用 Core::Event** - 使用现有的线程安全事件系统 +2. **平台抽象** - InputModule 接口隔离 Windows 消息处理 +3. **双模式支持** - 轮询 + 事件,兼顾性能和响应性 +4. **轴系统** - 参考 Unity,支持键盘/手柄统一输入映射 +5. **单窗口** - 简化设计,满足当前需求 +6. **UI 协同** - 为 Canvas/Button 提供完整的事件支持 diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index a44661c9..b674778f 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -223,6 +223,22 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/Scene/Scene.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Scene/SceneManager.cpp + # Platform + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Platform/PlatformTypes.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Platform/Window.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Platform/Windows/WindowsWindow.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/Platform/Windows/WindowsWindow.cpp + + # Input + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Input/InputTypes.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Input/InputEvent.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Input/InputAxis.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Input/InputModule.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Input/InputManager.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Input/Platform/WindowsInputModule.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/Input/InputManager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Input/Windows/WindowsInputModule.cpp + # Audio ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Audio/AudioTypes.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Audio/AudioConfig.h diff --git a/engine/include/XCEngine/Input/InputAxis.h b/engine/include/XCEngine/Input/InputAxis.h new file mode 100644 index 00000000..6260e4a3 --- /dev/null +++ b/engine/include/XCEngine/Input/InputAxis.h @@ -0,0 +1,34 @@ +#pragma once +#include "InputTypes.h" +#include "Containers/String.h" + +namespace XCEngine { +namespace Input { + +class InputAxis { +public: + InputAxis() = default; + InputAxis(const Containers::String& name, KeyCode positive, KeyCode negative = KeyCode::None) + : m_name(name), m_positiveKey(positive), m_negativeKey(negative) {} + + const Containers::String& GetName() const { return m_name; } + KeyCode GetPositiveKey() const { return m_positiveKey; } + KeyCode GetNegativeKey() const { return m_negativeKey; } + + void SetKeys(KeyCode positive, KeyCode negative) { + m_positiveKey = positive; + m_negativeKey = negative; + } + + float GetValue() const { return m_value; } + void SetValue(float value) { m_value = value; } + +private: + Containers::String m_name; + KeyCode m_positiveKey = KeyCode::None; + KeyCode m_negativeKey = KeyCode::None; + float m_value = 0.0f; +}; + +} // namespace Input +} // namespace XCEngine diff --git a/engine/include/XCEngine/Input/InputEvent.h b/engine/include/XCEngine/Input/InputEvent.h new file mode 100644 index 00000000..38b28e4f --- /dev/null +++ b/engine/include/XCEngine/Input/InputEvent.h @@ -0,0 +1,49 @@ +#pragma once +#include "InputTypes.h" +#include "Math/Vector2.h" +#include "Containers/String.h" + +namespace XCEngine { +namespace Input { + +struct KeyEvent { + KeyCode keyCode; + bool alt; + bool ctrl; + bool shift; + bool meta; + enum Type { Down, Up, Repeat } type; +}; + +struct MouseButtonEvent { + MouseButton button; + Math::Vector2 position; + enum Type { Pressed, Released } type; +}; + +struct MouseMoveEvent { + Math::Vector2 position; + Math::Vector2 delta; +}; + +struct MouseWheelEvent { + Math::Vector2 position; + float delta; +}; + +struct TextInputEvent { + char character; + Containers::String text; +}; + +struct TouchState { + int touchId; + Math::Vector2 position; + Math::Vector2 deltaPosition; + float deltaTime; + int tapCount; + enum Phase { Began, Moved, Stationary, Ended, Canceled } phase; +}; + +} // namespace Input +} // namespace XCEngine diff --git a/engine/include/XCEngine/Input/InputManager.h b/engine/include/XCEngine/Input/InputManager.h new file mode 100644 index 00000000..00f2c866 --- /dev/null +++ b/engine/include/XCEngine/Input/InputManager.h @@ -0,0 +1,101 @@ +#pragma once +#include "Core/Event.h" +#include "InputTypes.h" +#include "InputEvent.h" +#include "InputAxis.h" +#include "Math/Vector2.h" + +namespace XCEngine { +namespace Input { + +class InputManager { +public: + static InputManager& Get(); + + void Initialize(void* platformWindowHandle); + void Shutdown(); + void Update(float deltaTime); + + // ============ 轮询接口 ============ + + bool IsKeyDown(KeyCode key) const; + bool IsKeyUp(KeyCode key) const; + bool IsKeyPressed(KeyCode key) const; + + Math::Vector2 GetMousePosition() const; + Math::Vector2 GetMouseDelta() const; + float GetMouseScrollDelta() const; + bool IsMouseButtonDown(MouseButton button) const; + bool IsMouseButtonUp(MouseButton button) const; + bool IsMouseButtonClicked(MouseButton button) const; + + int GetTouchCount() const; + TouchState GetTouch(int index) const; + + // ============ 轴接口 (参考 Unity) ============ + + float GetAxis(const Containers::String& axisName) const; + float GetAxisRaw(const Containers::String& axisName) const; + + bool GetButton(const Containers::String& buttonName) const; + bool GetButtonDown(const Containers::String& buttonName) const; + bool GetButtonUp(const Containers::String& buttonName) const; + + void RegisterAxis(const InputAxis& axis); + void RegisterButton(const Containers::String& name, KeyCode key); + void ClearAxes(); + + // ============ 事件接口 ============ + + Core::Event& OnKeyEvent() { return m_onKeyEvent; } + Core::Event& OnMouseButton() { return m_onMouseButton; } + Core::Event& OnMouseMove() { return m_onMouseMove; } + Core::Event& OnMouseWheel() { return m_onMouseWheel; } + Core::Event& OnTextInput() { return m_onTextInput; } + + // ============ 内部方法(供 PlatformInputModule 调用) ============ + + void ProcessKeyDown(KeyCode key, bool repeat); + void ProcessKeyUp(KeyCode key); + void ProcessMouseMove(int x, int y, int deltaX, int deltaY); + void ProcessMouseButton(MouseButton button, bool pressed, int x, int y); + void ProcessMouseWheel(float delta, int x, int y); + void ProcessTextInput(char c); + +private: + InputManager() = default; + ~InputManager() = default; + + size_t GetKeyIndex(KeyCode key) const; + size_t GetMouseButtonIndex(MouseButton button) const; + + void* m_platformWindowHandle = nullptr; + bool m_initialized = false; + + std::vector m_keyDownThisFrame; + std::vector m_keyDownLastFrame; + std::vector m_keyDown; + + Math::Vector2 m_mousePosition; + Math::Vector2 m_mouseDelta; + float m_mouseScrollDelta = 0.0f; + std::vector m_mouseButtonDownThisFrame; + std::vector m_mouseButtonDownLastFrame; + std::vector m_mouseButtonDown; + + std::vector m_touches; + + std::unordered_map m_axes; + std::unordered_map m_buttons; + std::vector m_buttonDownThisFrame; + std::vector m_buttonDownLastFrame; + + Core::Event m_onKeyEvent; + Core::Event m_onMouseButton; + Core::Event m_onMouseMove; + Core::Event m_onMouseWheel; + Core::Event m_onTextInput; +}; + +} // namespace Input +} // namespace XCEngine diff --git a/engine/include/XCEngine/Input/InputModule.h b/engine/include/XCEngine/Input/InputModule.h new file mode 100644 index 00000000..7aef2d00 --- /dev/null +++ b/engine/include/XCEngine/Input/InputModule.h @@ -0,0 +1,19 @@ +#pragma once + +namespace XCEngine { +namespace Input { + +class InputModule { +public: + virtual ~InputModule() = default; + + virtual void Initialize(void* windowHandle) = 0; + virtual void Shutdown() = 0; + virtual void PumpEvents() = 0; + +protected: + InputModule() = default; +}; + +} // namespace Input +} // namespace XCEngine diff --git a/engine/include/XCEngine/Input/InputTypes.h b/engine/include/XCEngine/Input/InputTypes.h new file mode 100644 index 00000000..12cdd392 --- /dev/null +++ b/engine/include/XCEngine/Input/InputTypes.h @@ -0,0 +1,54 @@ +#pragma once +#include "Core/Types.h" + +namespace XCEngine { +namespace Input { + +enum class KeyCode : Core::uint8 { + None = 0, + + A = 4, B = 5, C = 6, D = 7, E = 8, F = 9, G = 10, + H = 11, I = 12, J = 13, K = 14, L = 15, M = 16, N = 17, + O = 18, P = 19, Q = 20, R = 21, S = 22, T = 23, U = 24, + V = 25, W = 26, X = 27, Y = 28, Z = 29, + + F1 = 58, F2 = 59, F3 = 60, F4 = 61, F5 = 62, F6 = 63, + F7 = 64, F8 = 65, F9 = 66, F10 = 67, F11 = 68, F12 = 69, + + Space = 49, Tab = 48, Enter = 36, Escape = 53, + LeftShift = 56, RightShift = 60, LeftCtrl = 59, RightCtrl = 62, + LeftAlt = 58, RightAlt = 61, + + Up = 126, Down = 125, Left = 123, Right = 124, + + Home = 115, End = 119, PageUp = 116, PageDown = 121, + Delete = 51, Backspace = 51, + + Zero = 39, One = 30, Two = 31, Three = 32, + Four = 33, Five = 34, Six = 35, Seven = 37, + Eight = 38, Nine = 40, + + Minus = 43, Equals = 46, BracketLeft = 47, BracketRight = 54, + Semicolon = 42, Quote = 40, Comma = 54, Period = 55, + Slash = 44, Backslash = 45, Backtick = 41 +}; + +enum class MouseButton : Core::uint8 { + Left = 0, + Right = 1, + Middle = 2, + Button4 = 3, + Button5 = 4 +}; + +enum class JoystickAxis : Core::uint8 { + LeftX = 0, + LeftY = 1, + RightX = 2, + RightY = 3, + LeftTrigger = 4, + RightTrigger = 5 +}; + +} // namespace Input +} // namespace XCEngine diff --git a/engine/include/XCEngine/Input/Platform/WindowsInputModule.h b/engine/include/XCEngine/Input/Platform/WindowsInputModule.h new file mode 100644 index 00000000..3357a05f --- /dev/null +++ b/engine/include/XCEngine/Input/Platform/WindowsInputModule.h @@ -0,0 +1,40 @@ +#pragma once +#include "Input/InputModule.h" +#include "Input/InputTypes.h" +#include "Math/Vector2.h" +#include + +namespace XCEngine { +namespace Input { +namespace Platform { + +class WindowsInputModule : public InputModule { +public: + WindowsInputModule(); + virtual ~WindowsInputModule(); + + void Initialize(void* windowHandle) override; + void Shutdown() override; + void PumpEvents() override; + + void HandleMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); + +private: + void ProcessKeyDown(WPARAM wParam, LPARAM lParam); + void ProcessKeyUp(WPARAM wParam); + void ProcessMouseMove(WPARAM wParam, LPARAM lParam); + void ProcessMouseButton(WPARAM wParam, LPARAM lParam, bool pressed, MouseButton button); + void ProcessMouseWheel(WPARAM wParam, LPARAM lParam); + void ProcessCharInput(WPARAM wParam); + + KeyCode VKCodeToKeyCode(int vkCode); + + HWND m_hwnd = nullptr; + Math::Vector2 m_lastMousePosition; + bool m_captureMouse = false; + bool m_isInitialized = false; +}; + +} // namespace Platform +} // namespace Input +} // namespace XCEngine diff --git a/engine/include/XCEngine/Platform/PlatformTypes.h b/engine/include/XCEngine/Platform/PlatformTypes.h new file mode 100644 index 00000000..d7266c12 --- /dev/null +++ b/engine/include/XCEngine/Platform/PlatformTypes.h @@ -0,0 +1,36 @@ +#pragma once +#include "Core/Types.h" +#include "Containers/String.h" + +namespace XCEngine { +namespace Platform { + +using WindowHandle = void*; + +struct WindowDesc { + Containers::String title; + Core::uint32 width; + Core::uint32 height; + bool fullscreen; + + WindowDesc() + : title("XCEngine") + , width(1280) + , height(720) + , fullscreen(false) {} +}; + +struct Point { + Core::int32 x; + Core::int32 y; +}; + +struct Rect { + Core::int32 x; + Core::int32 y; + Core::int32 width; + Core::int32 height; +}; + +} // namespace Platform +} // namespace XCEngine diff --git a/engine/include/XCEngine/Platform/Window.h b/engine/include/XCEngine/Platform/Window.h new file mode 100644 index 00000000..6ac4ef90 --- /dev/null +++ b/engine/include/XCEngine/Platform/Window.h @@ -0,0 +1,29 @@ +#pragma once +#include "PlatformTypes.h" + +namespace XCEngine { +namespace Platform { + +class Window { +public: + virtual ~Window() = default; + + virtual bool Create(const WindowDesc& desc) = 0; + virtual void Destroy() = 0; + + virtual WindowHandle GetHandle() const = 0; + virtual void PumpEvents() = 0; + + virtual void SetTitle(const Containers::String& title) = 0; + virtual void SetFullscreen(bool fullscreen) = 0; + virtual bool IsFullscreen() const = 0; + virtual void Minimize() = 0; + virtual void Maximize() = 0; + virtual void Restore() = 0; + virtual bool ShouldClose() const = 0; + + virtual void* GetNativeHandle() = 0; +}; + +} // namespace Platform +} // namespace XCEngine diff --git a/engine/include/XCEngine/Platform/Windows/WindowsWindow.h b/engine/include/XCEngine/Platform/Windows/WindowsWindow.h new file mode 100644 index 00000000..d93c8f45 --- /dev/null +++ b/engine/include/XCEngine/Platform/Windows/WindowsWindow.h @@ -0,0 +1,48 @@ +#pragma once +#include "Platform/Window.h" +#include +#include + +namespace XCEngine { +namespace Platform { + +class WindowsWindow : public Window { +public: + WindowsWindow(); + virtual ~WindowsWindow(); + + bool Create(const WindowDesc& desc) override; + void Destroy() override; + + WindowHandle GetHandle() const override { return m_hwnd; } + void PumpEvents() override; + + void SetTitle(const Containers::String& title) override; + void SetFullscreen(bool fullscreen) override; + bool IsFullscreen() const override { return m_fullscreen; } + void Minimize() override; + void Maximize() override; + void Restore() override; + bool ShouldClose() const override { return m_shouldClose; } + + void* GetNativeHandle() override { return m_hwnd; } + + void SetMessageCallback(std::function callback); + +private: + static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + void RegisterWindowClass(); + bool CreateMainWindow(const WindowDesc& desc); + + HWND m_hwnd = nullptr; + HINSTANCE m_hInstance = nullptr; + bool m_fullscreen = false; + bool m_shouldClose = false; + bool m_minimized = false; + RECT m_oldWindowRect = {}; + + std::function m_messageCallback; +}; + +} // namespace Platform +} // namespace XCEngine diff --git a/engine/src/Input/InputManager.cpp b/engine/src/Input/InputManager.cpp new file mode 100644 index 00000000..4c1e0503 --- /dev/null +++ b/engine/src/Input/InputManager.cpp @@ -0,0 +1,304 @@ +#include "Input/InputManager.h" +#include + +namespace XCEngine { +namespace Input { + +InputManager& InputManager::Get() { + static InputManager instance; + return instance; +} + +void InputManager::Initialize(void* platformWindowHandle) { + if (m_initialized) return; + + m_platformWindowHandle = platformWindowHandle; + + m_keyDownThisFrame.resize(256, false); + m_keyDownLastFrame.resize(256, false); + m_keyDown.resize(256, false); + + m_mouseButtonDownThisFrame.resize(5, false); + m_mouseButtonDownLastFrame.resize(5, false); + m_mouseButtonDown.resize(5, false); + + m_buttonDownThisFrame.resize(32, false); + m_buttonDownLastFrame.resize(32, false); + + RegisterAxis(InputAxis("Horizontal", KeyCode::D, KeyCode::A)); + RegisterAxis(InputAxis("Vertical", KeyCode::W, KeyCode::S)); + RegisterAxis(InputAxis("Mouse X", KeyCode::None, KeyCode::None)); + RegisterAxis(InputAxis("Mouse Y", KeyCode::None, KeyCode::None)); + + RegisterButton("Jump", KeyCode::Space); + RegisterButton("Fire1", KeyCode::LeftCtrl); + RegisterButton("Fire2", KeyCode::LeftAlt); + RegisterButton("Fire3", KeyCode::LeftShift); + + m_initialized = true; +} + +void InputManager::Shutdown() { + if (!m_initialized) return; + + m_keyDownThisFrame.clear(); + m_keyDownLastFrame.clear(); + m_keyDown.clear(); + + m_mouseButtonDownThisFrame.clear(); + m_mouseButtonDownLastFrame.clear(); + m_mouseButtonDown.clear(); + + m_axes.clear(); + m_buttons.clear(); + m_buttonDownThisFrame.clear(); + m_buttonDownLastFrame.clear(); + + m_platformWindowHandle = nullptr; + m_initialized = false; +} + +void InputManager::Update(float deltaTime) { + if (!m_initialized) return; + + m_keyDownLastFrame = m_keyDownThisFrame; + m_mouseButtonDownLastFrame = m_mouseButtonDownThisFrame; + m_buttonDownLastFrame = m_buttonDownThisFrame; + + m_mouseDelta = Math::Vector2::Zero(); + m_mouseScrollDelta = 0.0f; +} + +size_t InputManager::GetKeyIndex(KeyCode key) const { + return static_cast(key); +} + +size_t InputManager::GetMouseButtonIndex(MouseButton button) const { + return static_cast(button); +} + +bool InputManager::IsKeyDown(KeyCode key) const { + if (!m_initialized) return false; + size_t index = GetKeyIndex(key); + if (index >= m_keyDown.size()) return false; + return m_keyDown[index]; +} + +bool InputManager::IsKeyUp(KeyCode key) const { + if (!m_initialized) return true; + return !IsKeyDown(key); +} + +bool InputManager::IsKeyPressed(KeyCode key) const { + if (!m_initialized) return false; + size_t index = GetKeyIndex(key); + if (index >= m_keyDownThisFrame.size()) return false; + return m_keyDownThisFrame[index] && !m_keyDownLastFrame[index]; +} + +Math::Vector2 InputManager::GetMousePosition() const { + return m_mousePosition; +} + +Math::Vector2 InputManager::GetMouseDelta() const { + return m_mouseDelta; +} + +float InputManager::GetMouseScrollDelta() const { + return m_mouseScrollDelta; +} + +bool InputManager::IsMouseButtonDown(MouseButton button) const { + if (!m_initialized) return false; + size_t index = GetMouseButtonIndex(button); + if (index >= m_mouseButtonDown.size()) return false; + return m_mouseButtonDown[index]; +} + +bool InputManager::IsMouseButtonUp(MouseButton button) const { + if (!m_initialized) return true; + return !IsMouseButtonDown(button); +} + +bool InputManager::IsMouseButtonClicked(MouseButton button) const { + if (!m_initialized) return false; + size_t index = GetMouseButtonIndex(button); + if (index >= m_mouseButtonDownThisFrame.size()) return false; + return m_mouseButtonDownThisFrame[index] && !m_mouseButtonDownLastFrame[index]; +} + +int InputManager::GetTouchCount() const { + return static_cast(m_touches.size()); +} + +TouchState InputManager::GetTouch(int index) const { + if (index >= 0 && index < static_cast(m_touches.size())) { + return m_touches[index]; + } + return TouchState{}; +} + +float InputManager::GetAxis(const Containers::String& axisName) const { + auto it = m_axes.find(axisName); + if (it == m_axes.end()) return 0.0f; + + const auto& axis = it->second; + float value = 0.0f; + + if (axis.GetPositiveKey() != KeyCode::None && IsKeyDown(axis.GetPositiveKey())) { + value += 1.0f; + } + if (axis.GetNegativeKey() != KeyCode::None && IsKeyDown(axis.GetNegativeKey())) { + value -= 1.0f; + } + + return value; +} + +float InputManager::GetAxisRaw(const Containers::String& axisName) const { + auto it = m_axes.find(axisName); + if (it == m_axes.end()) return 0.0f; + + const auto& axis = it->second; + float value = 0.0f; + + if (axis.GetPositiveKey() != KeyCode::None && IsKeyPressed(axis.GetPositiveKey())) { + value += 1.0f; + } + if (axis.GetNegativeKey() != KeyCode::None && IsKeyPressed(axis.GetNegativeKey())) { + value -= 1.0f; + } + + return value; +} + +bool InputManager::GetButton(const Containers::String& buttonName) const { + auto it = m_buttons.find(buttonName); + if (it == m_buttons.end()) return false; + return IsKeyDown(it->second); +} + +bool InputManager::GetButtonDown(const Containers::String& buttonName) const { + auto it = m_buttons.find(buttonName); + if (it == m_buttons.end()) return false; + return IsKeyPressed(it->second); +} + +bool InputManager::GetButtonUp(const Containers::String& buttonName) const { + auto it = m_buttons.find(buttonName); + if (it == m_buttons.end()) return true; + return IsKeyUp(it->second); +} + +void InputManager::RegisterAxis(const InputAxis& axis) { + m_axes[axis.GetName()] = axis; +} + +void InputManager::RegisterButton(const Containers::String& name, KeyCode key) { + m_buttons[name] = key; +} + +void InputManager::ClearAxes() { + m_axes.clear(); + m_buttons.clear(); +} + +void InputManager::ProcessKeyDown(KeyCode key, bool repeat) { + if (!m_initialized) return; + + size_t index = GetKeyIndex(key); + if (index >= m_keyDown.size()) return; + + m_keyDown[index] = true; + m_keyDownThisFrame[index] = true; + + KeyEvent event; + event.keyCode = key; + event.type = repeat ? KeyEvent::Repeat : KeyEvent::Down; + event.alt = false; + event.ctrl = false; + event.shift = false; + event.meta = false; + + m_onKeyEvent.Invoke(event); +} + +void InputManager::ProcessKeyUp(KeyCode key) { + if (!m_initialized) return; + + size_t index = GetKeyIndex(key); + if (index >= m_keyDown.size()) return; + + m_keyDown[index] = false; + + KeyEvent event; + event.keyCode = key; + event.type = KeyEvent::Up; + event.alt = false; + event.ctrl = false; + event.shift = false; + event.meta = false; + + m_onKeyEvent.Invoke(event); +} + +void InputManager::ProcessMouseMove(int x, int y, int deltaX, int deltaY) { + if (!m_initialized) return; + + m_mousePosition.x = static_cast(x); + m_mousePosition.y = static_cast(y); + m_mouseDelta.x = static_cast(deltaX); + m_mouseDelta.y = static_cast(deltaY); + + MouseMoveEvent event; + event.position = m_mousePosition; + event.delta = m_mouseDelta; + + m_onMouseMove.Invoke(event); +} + +void InputManager::ProcessMouseButton(MouseButton button, bool pressed, int x, int y) { + if (!m_initialized) return; + + size_t index = GetMouseButtonIndex(button); + if (index >= m_mouseButtonDown.size()) return; + + m_mouseButtonDown[index] = pressed; + if (pressed) { + m_mouseButtonDownThisFrame[index] = true; + } + + MouseButtonEvent event; + event.button = button; + event.position.x = static_cast(x); + event.position.y = static_cast(y); + event.type = pressed ? MouseButtonEvent::Pressed : MouseButtonEvent::Released; + + m_onMouseButton.Invoke(event); +} + +void InputManager::ProcessMouseWheel(float delta, int x, int y) { + if (!m_initialized) return; + + m_mouseScrollDelta = delta; + + MouseWheelEvent event; + event.position.x = static_cast(x); + event.position.y = static_cast(y); + event.delta = delta; + + m_onMouseWheel.Invoke(event); +} + +void InputManager::ProcessTextInput(char c) { + if (!m_initialized) return; + + TextInputEvent event; + event.character = c; + event.text = Containers::String(&c, 1); + + m_onTextInput.Invoke(event); +} + +} // namespace Input +} // namespace XCEngine diff --git a/engine/src/Input/Windows/WindowsInputModule.cpp b/engine/src/Input/Windows/WindowsInputModule.cpp new file mode 100644 index 00000000..042d3d34 --- /dev/null +++ b/engine/src/Input/Windows/WindowsInputModule.cpp @@ -0,0 +1,240 @@ +#include "Input/Platform/WindowsInputModule.h" +#include "Input/InputManager.h" +#include + +namespace XCEngine { +namespace Input { +namespace Platform { + +WindowsInputModule::WindowsInputModule() + : m_hwnd(nullptr) + , m_isInitialized(false) { +} + +WindowsInputModule::~WindowsInputModule() { + Shutdown(); +} + +void WindowsInputModule::Initialize(void* windowHandle) { + if (m_isInitialized) return; + + m_hwnd = reinterpret_cast(windowHandle); + m_isInitialized = true; + m_lastMousePosition = Math::Vector2::Zero(); +} + +void WindowsInputModule::Shutdown() { + if (!m_isInitialized) return; + + m_hwnd = nullptr; + m_isInitialized = false; +} + +void WindowsInputModule::PumpEvents() { +} + +void WindowsInputModule::HandleMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { + if (!m_isInitialized) return; + + switch (message) { + case WM_KEYDOWN: + ProcessKeyDown(wParam, lParam); + break; + + case WM_KEYUP: + ProcessKeyUp(wParam); + break; + + case WM_CHAR: + ProcessCharInput(wParam); + break; + + case WM_MOUSEMOVE: + ProcessMouseMove(wParam, lParam); + break; + + case WM_LBUTTONDOWN: + ProcessMouseButton(wParam, lParam, true, MouseButton::Left); + break; + + case WM_LBUTTONUP: + ProcessMouseButton(wParam, lParam, false, MouseButton::Left); + break; + + case WM_RBUTTONDOWN: + ProcessMouseButton(wParam, lParam, true, MouseButton::Right); + break; + + case WM_RBUTTONUP: + ProcessMouseButton(wParam, lParam, false, MouseButton::Right); + break; + + case WM_MBUTTONDOWN: + ProcessMouseButton(wParam, lParam, true, MouseButton::Middle); + break; + + case WM_MBUTTONUP: + ProcessMouseButton(wParam, lParam, false, MouseButton::Middle); + break; + + case WM_MOUSEWHEEL: + ProcessMouseWheel(wParam, lParam); + break; + + case WM_XBUTTONDOWN: + ProcessMouseButton(wParam, lParam, true, + (HIWORD(wParam) == 1) ? MouseButton::Button4 : MouseButton::Button5); + break; + + case WM_XBUTTONUP: + ProcessMouseButton(wParam, lParam, false, + (HIWORD(wParam) == 1) ? MouseButton::Button4 : MouseButton::Button5); + break; + } +} + +void WindowsInputModule::ProcessKeyDown(WPARAM wParam, LPARAM lParam) { + bool repeat = (lParam & (1 << 30)) != 0; + KeyCode keyCode = VKCodeToKeyCode(static_cast(wParam)); + + if (keyCode != KeyCode::None) { + InputManager::Get().ProcessKeyDown(keyCode, repeat); + } +} + +void WindowsInputModule::ProcessKeyUp(WPARAM wParam) { + KeyCode keyCode = VKCodeToKeyCode(static_cast(wParam)); + + if (keyCode != KeyCode::None) { + InputManager::Get().ProcessKeyUp(keyCode); + } +} + +void WindowsInputModule::ProcessMouseMove(WPARAM wParam, LPARAM lParam) { + int x = LOWORD(lParam); + int y = HIWORD(lParam); + + int deltaX = x - static_cast(m_lastMousePosition.x); + int deltaY = y - static_cast(m_lastMousePosition.y); + + m_lastMousePosition.x = static_cast(x); + m_lastMousePosition.y = static_cast(y); + + InputManager::Get().ProcessMouseMove(x, y, deltaX, deltaY); +} + +void WindowsInputModule::ProcessMouseButton(WPARAM wParam, LPARAM lParam, bool pressed, MouseButton button) { + int x = LOWORD(lParam); + int y = HIWORD(lParam); + + InputManager::Get().ProcessMouseButton(button, pressed, x, y); +} + +void WindowsInputModule::ProcessMouseWheel(WPARAM wParam, LPARAM lParam) { + int x = LOWORD(lParam); + int y = HIWORD(lParam); + short delta = static_cast(HIWORD(wParam)); + + InputManager::Get().ProcessMouseWheel(static_cast(delta) / 120.0f, x, y); +} + +void WindowsInputModule::ProcessCharInput(WPARAM wParam) { + char c = static_cast(wParam); + + if (c >= 32 && c < 127) { + InputManager::Get().ProcessTextInput(c); + } +} + +KeyCode WindowsInputModule::VKCodeToKeyCode(int vkCode) { + switch (vkCode) { + case 'A': return KeyCode::A; + case 'B': return KeyCode::B; + case 'C': return KeyCode::C; + case 'D': return KeyCode::D; + case 'E': return KeyCode::E; + case 'F': return KeyCode::F; + case 'G': return KeyCode::G; + case 'H': return KeyCode::H; + case 'I': return KeyCode::I; + case 'J': return KeyCode::J; + case 'K': return KeyCode::K; + case 'L': return KeyCode::L; + case 'M': return KeyCode::M; + case 'N': return KeyCode::N; + case 'O': return KeyCode::O; + case 'P': return KeyCode::P; + case 'Q': return KeyCode::Q; + case 'R': return KeyCode::R; + case 'S': return KeyCode::S; + case 'T': return KeyCode::T; + case 'U': return KeyCode::U; + case 'V': return KeyCode::V; + case 'W': return KeyCode::W; + case 'X': return KeyCode::X; + case 'Y': return KeyCode::Y; + case 'Z': return KeyCode::Z; + + case '0': return KeyCode::Zero; + case '1': return KeyCode::One; + case '2': return KeyCode::Two; + case '3': return KeyCode::Three; + case '4': return KeyCode::Four; + case '5': return KeyCode::Five; + case '6': return KeyCode::Six; + case '7': return KeyCode::Seven; + case '8': return KeyCode::Eight; + case '9': return KeyCode::Nine; + + case VK_SPACE: return KeyCode::Space; + case VK_TAB: return KeyCode::Tab; + case VK_RETURN: return KeyCode::Enter; + case VK_ESCAPE: return KeyCode::Escape; + case VK_SHIFT: return KeyCode::LeftShift; + case VK_CONTROL: return KeyCode::LeftCtrl; + case VK_MENU: return KeyCode::LeftAlt; + + case VK_UP: return KeyCode::Up; + case VK_DOWN: return KeyCode::Down; + case VK_LEFT: return KeyCode::Left; + case VK_RIGHT: return KeyCode::Right; + + case VK_HOME: return KeyCode::Home; + case VK_END: return KeyCode::End; + case VK_PRIOR: return KeyCode::PageUp; + case VK_NEXT: return KeyCode::PageDown; + case VK_DELETE: return KeyCode::Delete; + case VK_BACK: return KeyCode::Backspace; + + case VK_F1: return KeyCode::F1; + case VK_F2: return KeyCode::F2; + case VK_F3: return KeyCode::F3; + case VK_F4: return KeyCode::F4; + case VK_F5: return KeyCode::F5; + case VK_F6: return KeyCode::F6; + case VK_F7: return KeyCode::F7; + case VK_F8: return KeyCode::F8; + case VK_F9: return KeyCode::F9; + case VK_F10: return KeyCode::F10; + case VK_F11: return KeyCode::F11; + case VK_F12: return KeyCode::F12; + + case VK_OEM_MINUS: return KeyCode::Minus; + case VK_OEM_PLUS: return KeyCode::Equals; + case VK_OEM_4: return KeyCode::BracketLeft; + case VK_OEM_6: return KeyCode::BracketRight; + case 0xBA: return KeyCode::Semicolon; + case 0xDE: return KeyCode::Quote; + case VK_OEM_COMMA: return KeyCode::Comma; + case VK_OEM_PERIOD: return KeyCode::Period; + case VK_OEM_2: return KeyCode::Slash; + case VK_OEM_5: return KeyCode::Backslash; + case VK_OEM_3: return KeyCode::Backtick; + + default: return KeyCode::None; + } +} + +} // namespace Platform +} // namespace Input +} // namespace XCEngine diff --git a/engine/src/Platform/Windows/WindowsWindow.cpp b/engine/src/Platform/Windows/WindowsWindow.cpp new file mode 100644 index 00000000..83314221 --- /dev/null +++ b/engine/src/Platform/Windows/WindowsWindow.cpp @@ -0,0 +1,191 @@ +#include "Platform/Windows/WindowsWindow.h" +#include + +namespace XCEngine { +namespace Platform { + +static const char WINDOW_CLASS_NAME[] = "XCEngineWindowClass"; + +WindowsWindow::WindowsWindow() + : m_hwnd(nullptr) + , m_hInstance(nullptr) + , m_fullscreen(false) + , m_shouldClose(false) + , m_minimized(false) { +} + +WindowsWindow::~WindowsWindow() { + Destroy(); +} + +bool WindowsWindow::Create(const WindowDesc& desc) { + m_hInstance = GetModuleHandle(nullptr); + + RegisterWindowClass(); + + DWORD style = WS_OVERLAPPEDWINDOW; + DWORD exStyle = WS_EX_APPWINDOW; + + RECT rect = {0, 0, static_cast(desc.width), static_cast(desc.height)}; + AdjustWindowRect(&rect, style, FALSE); + + m_hwnd = CreateWindowExA( + exStyle, + WINDOW_CLASS_NAME, + desc.title.CStr(), + style, + CW_USEDEFAULT, + CW_USEDEFAULT, + rect.right - rect.left, + rect.bottom - rect.top, + nullptr, + nullptr, + m_hInstance, + this + ); + + if (!m_hwnd) { + return false; + } + + m_fullscreen = desc.fullscreen; + if (m_fullscreen) { + SetFullscreen(true); + } + + ShowWindow(m_hwnd, SW_SHOWNORMAL); + UpdateWindow(m_hwnd); + + return true; +} + +void WindowsWindow::Destroy() { + if (m_hwnd) { + DestroyWindow(m_hwnd); + m_hwnd = nullptr; + } + + UnregisterClassA(WINDOW_CLASS_NAME, m_hInstance); + m_hInstance = nullptr; +} + +void WindowsWindow::PumpEvents() { + MSG msg; + while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) { + m_shouldClose = true; + break; + } + + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + +void WindowsWindow::SetTitle(const Containers::String& title) { + if (m_hwnd) { + SetWindowTextA(m_hwnd, title.CStr()); + } +} + +void WindowsWindow::SetFullscreen(bool fullscreen) { + if (!m_hwnd) return; + + if (fullscreen) { + MONITORINFO mi = {sizeof(mi)}; + GetWindowRect(m_hwnd, &m_oldWindowRect); + GetMonitorInfo(MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTOPRIMARY), &mi); + SetWindowLong(m_hwnd, GWL_STYLE, WS_POPUP | WS_SYSMENU); + SetWindowPos(m_hwnd, HWND_TOP, + mi.rcMonitor.left, mi.rcMonitor.top, + mi.rcMonitor.right - mi.rcMonitor.left, + mi.rcMonitor.bottom - mi.rcMonitor.top, + SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + m_fullscreen = true; + } else { + SetWindowLong(m_hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW); + SetWindowPos(m_hwnd, HWND_NOTOPMOST, + m_oldWindowRect.left, m_oldWindowRect.top, + m_oldWindowRect.right - m_oldWindowRect.left, + m_oldWindowRect.bottom - m_oldWindowRect.top, + SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + m_fullscreen = false; + } +} + +void WindowsWindow::Minimize() { + if (m_hwnd) { + ShowWindow(m_hwnd, SW_MINIMIZE); + m_minimized = true; + } +} + +void WindowsWindow::Maximize() { + if (m_hwnd) { + ShowWindow(m_hwnd, SW_MAXIMIZE); + } +} + +void WindowsWindow::Restore() { + if (m_hwnd) { + ShowWindow(m_hwnd, SW_RESTORE); + m_minimized = false; + } +} + +void WindowsWindow::SetMessageCallback(std::function callback) { + m_messageCallback = callback; +} + +void WindowsWindow::RegisterWindowClass() { + WNDCLASSEXA wc = {}; + wc.cbSize = sizeof(WNDCLASSEX); + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = WndProc; + wc.hInstance = m_hInstance; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.lpszClassName = WINDOW_CLASS_NAME; + + RegisterClassExA(&wc); +} + +LRESULT CALLBACK WindowsWindow::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + WindowsWindow* window = nullptr; + + if (msg == WM_NCCREATE) { + CREATESTRUCTA* cs = reinterpret_cast(lParam); + window = reinterpret_cast(cs->lpCreateParams); + SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(window)); + } else { + window = reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA)); + } + + if (window && window->m_messageCallback) { + window->m_messageCallback(hwnd, msg, wParam, lParam); + } + + switch (msg) { + case WM_CLOSE: + if (window) window->m_shouldClose = true; + return 0; + + case WM_DESTROY: + PostQuitMessage(0); + return 0; + + case WM_SIZE: + if (window) { + if (wParam == SIZE_MINIMIZED) { + window->m_minimized = true; + } else { + window->m_minimized = false; + } + } + return 0; + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +} // namespace Platform +} // namespace XCEngine diff --git a/tests/RHI/D3D12/integration/CMakeLists.txt b/tests/RHI/D3D12/integration/CMakeLists.txt index 9f89d72a..be0426ec 100644 --- a/tests/RHI/D3D12/integration/CMakeLists.txt +++ b/tests/RHI/D3D12/integration/CMakeLists.txt @@ -9,4 +9,4 @@ enable_testing() add_subdirectory(minimal) add_subdirectory(triangle) add_subdirectory(quad) -add_subdirectory(render_model) +add_subdirectory(sphere) diff --git a/tests/RHI/D3D12/integration/render_model/GT.ppm b/tests/RHI/D3D12/integration/render_model/GT.ppm deleted file mode 100644 index 9629f6b8..00000000 Binary files a/tests/RHI/D3D12/integration/render_model/GT.ppm and /dev/null differ diff --git a/tests/RHI/D3D12/integration/render_model/CMakeLists.txt b/tests/RHI/D3D12/integration/sphere/CMakeLists.txt similarity index 52% rename from tests/RHI/D3D12/integration/render_model/CMakeLists.txt rename to tests/RHI/D3D12/integration/sphere/CMakeLists.txt index 349f0fbd..0f30ae5b 100644 --- a/tests/RHI/D3D12/integration/render_model/CMakeLists.txt +++ b/tests/RHI/D3D12/integration/sphere/CMakeLists.txt @@ -2,27 +2,27 @@ cmake_minimum_required(VERSION 3.15) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) -project(D3D12_RenderModel) +project(D3D12_Sphere) set(ENGINE_ROOT_DIR ${CMAKE_SOURCE_DIR}/engine) -add_executable(D3D12_RenderModel +add_executable(D3D12_Sphere WIN32 main.cpp ) -target_include_directories(D3D12_RenderModel PRIVATE +target_include_directories(D3D12_Sphere PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${ENGINE_ROOT_DIR}/include ${ENGINE_ROOT_DIR} ) -target_compile_definitions(D3D12_RenderModel PRIVATE +target_compile_definitions(D3D12_Sphere PRIVATE UNICODE _UNICODE ) -target_link_libraries(D3D12_RenderModel PRIVATE +target_link_libraries(D3D12_Sphere PRIVATE d3d12 dxgi d3dcompiler @@ -30,26 +30,26 @@ target_link_libraries(D3D12_RenderModel PRIVATE XCEngine ) -add_custom_command(TARGET D3D12_RenderModel POST_BUILD +add_custom_command(TARGET D3D12_Sphere POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/Res - $/Res + $/Res ) -add_custom_command(TARGET D3D12_RenderModel POST_BUILD +add_custom_command(TARGET D3D12_Sphere POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/tests/RHI/D3D12/integration/compare_ppm.py - $/ + $/ COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/tests/RHI/D3D12/integration/run_integration_test.py - $/ + $/ ) -add_test(NAME D3D12_RenderModel_Integration - COMMAND ${Python3_EXECUTABLE} $/run_integration_test.py - $ +add_test(NAME D3D12_Sphere_Integration + COMMAND ${Python3_EXECUTABLE} $/run_integration_test.py + $ screenshot.ppm ${CMAKE_CURRENT_SOURCE_DIR}/GT.ppm 5 - WORKING_DIRECTORY $ + WORKING_DIRECTORY $ ) diff --git a/tests/RHI/D3D12/integration/sphere/GT.ppm b/tests/RHI/D3D12/integration/sphere/GT.ppm new file mode 100644 index 00000000..4aa52a9a Binary files /dev/null and b/tests/RHI/D3D12/integration/sphere/GT.ppm differ diff --git a/tests/RHI/D3D12/integration/render_model/Res/Image/earth_d.jpg b/tests/RHI/D3D12/integration/sphere/Image/earth.png similarity index 100% rename from tests/RHI/D3D12/integration/render_model/Res/Image/earth_d.jpg rename to tests/RHI/D3D12/integration/sphere/Image/earth.png diff --git a/tests/RHI/D3D12/integration/sphere/Image/earth_d.jpg b/tests/RHI/D3D12/integration/sphere/Image/earth_d.jpg new file mode 100644 index 00000000..663081b5 Binary files /dev/null and b/tests/RHI/D3D12/integration/sphere/Image/earth_d.jpg differ diff --git a/tests/RHI/D3D12/integration/render_model/Res/Image/head.png b/tests/RHI/D3D12/integration/sphere/Image/head.png similarity index 100% rename from tests/RHI/D3D12/integration/render_model/Res/Image/head.png rename to tests/RHI/D3D12/integration/sphere/Image/head.png diff --git a/tests/RHI/D3D12/integration/render_model/Res/Model/Sphere.lhsm b/tests/RHI/D3D12/integration/sphere/Model/Sphere.lhsm similarity index 100% rename from tests/RHI/D3D12/integration/render_model/Res/Model/Sphere.lhsm rename to tests/RHI/D3D12/integration/sphere/Model/Sphere.lhsm diff --git a/tests/RHI/D3D12/integration/sphere/Res/Image/earth.png b/tests/RHI/D3D12/integration/sphere/Res/Image/earth.png new file mode 100644 index 00000000..663081b5 Binary files /dev/null and b/tests/RHI/D3D12/integration/sphere/Res/Image/earth.png differ diff --git a/tests/RHI/D3D12/integration/sphere/Res/Shader/sphere.hlsl b/tests/RHI/D3D12/integration/sphere/Res/Shader/sphere.hlsl new file mode 100644 index 00000000..04c36e6b --- /dev/null +++ b/tests/RHI/D3D12/integration/sphere/Res/Shader/sphere.hlsl @@ -0,0 +1,24 @@ +struct Vertex { + float4 pos : POSITION; + float4 texcoord : TEXCOORD0; +}; + +struct VSOut { + float4 pos : SV_POSITION; + float4 texcoord : TEXCOORD0; +}; + +cbuffer MatrixBuffer : register(b0) { + float4x4 gMVP; +}; + +VSOut MainVS(Vertex v) { + VSOut o; + o.pos = mul(gMVP, v.pos); + o.texcoord = v.texcoord; + return o; +} + +float4 MainPS(VSOut i) : SV_TARGET { + return float4(1.0f, 0.0f, 0.0f, 1.0f); +} \ No newline at end of file diff --git a/tests/RHI/D3D12/integration/render_model/Res/Shader/gs.hlsl b/tests/RHI/D3D12/integration/sphere/Shader/gs.hlsl similarity index 100% rename from tests/RHI/D3D12/integration/render_model/Res/Shader/gs.hlsl rename to tests/RHI/D3D12/integration/sphere/Shader/gs.hlsl diff --git a/tests/RHI/D3D12/integration/render_model/Res/Shader/ndctriangle.hlsl b/tests/RHI/D3D12/integration/sphere/Shader/ndctriangle.hlsl similarity index 100% rename from tests/RHI/D3D12/integration/render_model/Res/Shader/ndctriangle.hlsl rename to tests/RHI/D3D12/integration/sphere/Shader/ndctriangle.hlsl diff --git a/tests/RHI/D3D12/integration/sphere/Shader/sphere.hlsl b/tests/RHI/D3D12/integration/sphere/Shader/sphere.hlsl new file mode 100644 index 00000000..d3b333d6 --- /dev/null +++ b/tests/RHI/D3D12/integration/sphere/Shader/sphere.hlsl @@ -0,0 +1,23 @@ +struct Vertex { + float4 pos : POSITION; + float4 texcoord : TEXCOORD0; +}; + +struct VSOut { + float4 pos : SV_POSITION; + float4 texcoord : TEXCOORD0; +}; + +VSOut MainVS(Vertex v) { + VSOut o; + o.pos = v.pos; + o.texcoord = v.texcoord; + return o; +} + +Texture2D T_DiffuseTexture : register(t0); +SamplerState samplerState : register(s0); + +float4 MainPS(VSOut i) : SV_TARGET { + return T_DiffuseTexture.Sample(samplerState, i.texcoord.xy); +} \ No newline at end of file diff --git a/tests/RHI/D3D12/integration/render_model/main.cpp b/tests/RHI/D3D12/integration/sphere/main.cpp similarity index 54% rename from tests/RHI/D3D12/integration/render_model/main.cpp rename to tests/RHI/D3D12/integration/sphere/main.cpp index a2f822ff..11f32e29 100644 --- a/tests/RHI/D3D12/integration/render_model/main.cpp +++ b/tests/RHI/D3D12/integration/sphere/main.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include "XCEngine/RHI/RHIEnums.h" @@ -23,8 +22,8 @@ #include "XCEngine/RHI/D3D12/D3D12RenderTargetView.h" #include "XCEngine/RHI/D3D12/D3D12DepthStencilView.h" #include "XCEngine/RHI/D3D12/D3D12Shader.h" -#include "XCEngine/RHI/D3D12/D3D12PipelineState.h" #include "XCEngine/RHI/D3D12/D3D12RootSignature.h" +#include "XCEngine/RHI/D3D12/D3D12PipelineState.h" #include "XCEngine/RHI/D3D12/D3D12ShaderResourceView.h" #include "XCEngine/RHI/D3D12/D3D12Screenshot.h" #include "XCEngine/Debug/Logger.h" @@ -43,57 +42,45 @@ using namespace XCEngine::Containers; #pragma comment(lib,"d3dcompiler.lib") #pragma comment(lib,"winmm.lib") -// Global D3D12 objects D3D12Device gDevice; D3D12CommandQueue gCommandQueue; D3D12SwapChain gSwapChain; D3D12CommandAllocator gCommandAllocator; D3D12CommandList gCommandList; -D3D12Fence gFence; - -// Render targets -D3D12Texture gColorRTs[2]; D3D12Texture gDepthStencil; D3D12DescriptorHeap gRTVHeap; D3D12DescriptorHeap gDSVHeap; +D3D12DescriptorHeap gSRVHeap; D3D12RenderTargetView gRTVs[2]; D3D12DepthStencilView gDSV; -// Pipeline objects D3D12Shader gVertexShader; -D3D12Shader gGeometryShader; D3D12Shader gPixelShader; D3D12RootSignature gRootSignature; D3D12PipelineState gPipelineState; - -// Model data D3D12Buffer gVertexBuffer; D3D12Buffer gIndexBuffer; -UINT gIndexCount = 0; - -// Texture +D3D12Buffer gMVPBuffer; D3D12Texture gDiffuseTexture; -D3D12DescriptorHeap gSRVHeap; D3D12ShaderResourceView gDiffuseSRV; -// Matrices -float gProjectionMatrix[16]; -float gViewMatrix[16]; -float gModelMatrix[16]; -float gIT_ModelMatrix[16]; - -// Descriptor sizes UINT gRTVDescriptorSize = 0; UINT gDSVDescriptorSize = 0; int gCurrentRTIndex = 0; -UINT64 gFenceValue = 0; -// Window +UINT gIndexCount = 0; + +float gProjectionMatrix[16]; +float gViewMatrix[16]; +float gModelMatrix[16]; +float gMVPMatrix[16]; +float gTempMatrix[16]; +float gTransposedMatrix[16]; + HWND gHWND = nullptr; int gWidth = 1280; int gHeight = 720; -// Log helper void Log(const char* format, ...) { char buffer[1024]; va_list args; @@ -103,7 +90,6 @@ void Log(const char* format, ...) { Logger::Get().Debug(LogCategory::Rendering, String(buffer)); } -// Window procedure LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CLOSE: @@ -113,7 +99,6 @@ LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { return DefWindowProc(hwnd, msg, wParam, lParam); } -// Matrix utilities void IdentityMatrix(float* m) { memset(m, 0, 16 * sizeof(float)); m[0] = m[5] = m[10] = m[15] = 1.0f; @@ -153,12 +138,15 @@ void LookAtMatrix(float* m, const float* eye, const float* target, const float* m[15] = 1.0f; } -void RotationYMatrix(float* m, float angle) { - IdentityMatrix(m); - float c = cosf(angle); - float s = sinf(angle); - m[0] = c; m[2] = s; - m[8] = -s; m[10] = c; +void MultiplyMatrix(float* dst, const float* a, const float* b) { + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + dst[i * 4 + j] = 0; + for (int k = 0; k < 4; k++) { + dst[i * 4 + j] += a[i * 4 + k] * b[k * 4 + j]; + } + } + } } void TransposeMatrix(float* dst, const float* src) { @@ -169,28 +157,15 @@ void TransposeMatrix(float* dst, const float* src) { } } -void InvertMatrix(float* dst, const float* src) { - // Simplified inverse for orthogonal matrices - memcpy(dst, src, 16 * sizeof(float)); - // For rotation matrices, inverse = transpose - float tmp[16]; - TransposeMatrix(tmp, src); - memcpy(dst, tmp, 16 * sizeof(float)); -} - -// Simple sphere generation struct Vertex { - float position[4]; + float pos[4]; float texcoord[4]; - float normal[4]; - float tangent[4]; }; -void GenerateSphere(std::vector& vertices, std::vector& indices, float radius, int segments) { +void GenerateSphere(std::vector& vertices, std::vector& indices, float radius, int segments) { vertices.clear(); indices.clear(); - // Generate vertices for (int lat = 0; lat <= segments; lat++) { float theta = lat * 3.14159f / segments; float sinTheta = sinf(theta); @@ -202,31 +177,20 @@ void GenerateSphere(std::vector& vertices, std::vector& indices, float cosPhi = cosf(phi); Vertex v; - v.position[0] = radius * sinTheta * cosPhi; - v.position[1] = radius * cosTheta; - v.position[2] = radius * sinTheta * sinPhi; - v.position[3] = 1.0f; + v.pos[0] = radius * sinTheta * cosPhi; + v.pos[1] = radius * cosTheta; + v.pos[2] = radius * sinTheta * sinPhi; + v.pos[3] = 1.0f; v.texcoord[0] = (float)lon / segments; v.texcoord[1] = (float)lat / segments; v.texcoord[2] = 0.0f; v.texcoord[3] = 0.0f; - v.normal[0] = sinTheta * cosPhi; - v.normal[1] = cosTheta; - v.normal[2] = sinTheta * sinPhi; - v.normal[3] = 0.0f; - - v.tangent[0] = -sinPhi; - v.tangent[1] = 0.0f; - v.tangent[2] = cosPhi; - v.tangent[3] = 0.0f; - vertices.push_back(v); } } - // Generate indices for (int lat = 0; lat < segments; lat++) { for (int lon = 0; lon < segments; lon++) { int first = lat * (segments + 1) + lon; @@ -243,8 +207,7 @@ void GenerateSphere(std::vector& vertices, std::vector& indices, } } -// Load texture -bool LoadTexture(const char* filename, D3D12Texture& texture, D3D12ShaderResourceView& srv, ID3D12Device* device, D3D12DescriptorHeap& srvHeap) { +bool LoadTexture(const char* filename, D3D12Texture& texture, D3D12ShaderResourceView& srv, ID3D12Device* device, D3D12DescriptorHeap& srvHeap, ID3D12GraphicsCommandList* commandList, D3D12CommandAllocator& allocator, D3D12CommandQueue& queue) { int width, height, channels; stbi_uc* pixels = stbi_load(filename, &width, &height, &channels, STBI_rgb_alpha); if (!pixels) { @@ -252,29 +215,30 @@ bool LoadTexture(const char* filename, D3D12Texture& texture, D3D12ShaderResourc return false; } - Log("[INFO] Loaded texture %s: %dx%d", filename, width, height); + allocator.Reset(); + commandList->Reset(allocator.GetCommandAllocator(), nullptr); - // Create texture using InitializeFromData - if (!texture.InitializeFromData(device, nullptr, pixels, width, height, DXGI_FORMAT_R8G8B8A8_UNORM)) { + if (!texture.InitializeFromData(device, commandList, pixels, width, height, DXGI_FORMAT_R8G8B8A8_UNORM)) { Log("[ERROR] Failed to initialize texture"); stbi_image_free(pixels); return false; } - texture.SetName(filename); - stbi_image_free(pixels); + commandList->Close(); + ID3D12CommandList* lists[] = { commandList }; + queue.ExecuteCommandListsInternal(1, lists); + queue.WaitForIdle(); - // Create SRV - srvHeap.Initialize(device, DescriptorHeapType::CBV_SRV_UAV, 1); + texture.SetName(filename); + + srvHeap.Initialize(device, DescriptorHeapType::CBV_SRV_UAV, 1, true); D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = D3D12ShaderResourceView::CreateDesc(Format::R8G8B8A8_UNorm, D3D12_SRV_DIMENSION_TEXTURE2D); srv.InitializeAt(device, texture.GetResource(), srvHeap.GetCPUDescriptorHandleForHeapStart(), &srvDesc); return true; } -// Initialize D3D12 bool InitD3D12() { - // Create device RHIDeviceDesc deviceDesc; deviceDesc.windowHandle = gHWND; deviceDesc.width = gWidth; @@ -291,238 +255,250 @@ bool InitD3D12() { ID3D12Device* device = gDevice.GetDevice(); IDXGIFactory4* factory = gDevice.GetFactory(); - // Create command queue if (!gCommandQueue.Initialize(device, CommandQueueType::Direct)) { Log("[ERROR] Failed to initialize command queue"); return false; } - // Create swap chain - DXGI_SWAP_CHAIN_DESC swapChainDesc = {}; - swapChainDesc.BufferCount = 2; - swapChainDesc.BufferDesc.Width = gWidth; - swapChainDesc.BufferDesc.Height = gHeight; - swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - swapChainDesc.OutputWindow = gHWND; - swapChainDesc.SampleDesc.Count = 1; - swapChainDesc.Windowed = true; - swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; - - IDXGISwapChain* dxgiSwapChain = nullptr; - HRESULT hr = factory->CreateSwapChain(gCommandQueue.GetCommandQueue(), &swapChainDesc, &dxgiSwapChain); - if (FAILED(hr)) { - Log("[ERROR] Failed to create swap chain"); - return false; - } - - if (!gSwapChain.Initialize(dxgiSwapChain, (uint32_t)gWidth, (uint32_t)gHeight)) { + if (!gSwapChain.Initialize(factory, gCommandQueue.GetCommandQueue(), gHWND, gWidth, gHeight, 2)) { Log("[ERROR] Failed to initialize swap chain"); return false; } - // Initialize depth stencil gDepthStencil.InitializeDepthStencil(device, gWidth, gHeight); - // Create RTV heap gRTVHeap.Initialize(device, DescriptorHeapType::RTV, 2); gRTVDescriptorSize = gDevice.GetDescriptorHandleIncrementSize(DescriptorHeapType::RTV); - // Create DSV heap gDSVHeap.Initialize(device, DescriptorHeapType::DSV, 1); gDSVDescriptorSize = gDevice.GetDescriptorHandleIncrementSize(DescriptorHeapType::DSV); - // Create RTVs for back buffers - D3D12_CPU_DESCRIPTOR_HANDLE rtvHeapStart = gRTVHeap.GetCPUDescriptorHandleForHeapStart(); for (int i = 0; i < 2; i++) { - ID3D12Resource* buffer = nullptr; - gSwapChain.GetSwapChain()->GetBuffer(i, IID_PPV_ARGS(&buffer)); - gColorRTs[i].InitializeFromExisting(buffer); - - D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle; - rtvHandle.ptr = rtvHeapStart.ptr + i * gRTVDescriptorSize; - gRTVs[i].InitializeAt(device, gColorRTs[i].GetResource(), rtvHandle, nullptr); + D3D12Texture& backBuffer = gSwapChain.GetBackBuffer(i); + CPUDescriptorHandle rtvCpuHandle = gRTVHeap.GetCPUDescriptorHandle(i); + D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = { rtvCpuHandle.ptr }; + gRTVs[i].InitializeAt(device, backBuffer.GetResource(), rtvHandle, nullptr); } - // Create DSV D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = D3D12DepthStencilView::CreateDesc(Format::D24_UNorm_S8_UInt); - gDSV.InitializeAt(device, gDepthStencil.GetResource(), gDSVHeap.GetCPUDescriptorHandleForHeapStart(), &dsvDesc); + CPUDescriptorHandle dsvCpuHandle = gDSVHeap.GetCPUDescriptorHandle(0); + D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle = { dsvCpuHandle.ptr }; + gDSV.InitializeAt(device, gDepthStencil.GetResource(), dsvHandle, &dsvDesc); - // Create command allocator and list gCommandAllocator.Initialize(device, CommandQueueType::Direct); gCommandList.Initialize(device, CommandQueueType::Direct, gCommandAllocator.GetCommandAllocator()); - // Create fence - gFence.Initialize(device, 0); + if (!gVertexShader.CompileFromFile(L"Res/Shader/sphere.hlsl", "MainVS", "vs_5_1")) { + Log("[ERROR] Failed to compile vertex shader"); + return false; + } + Log("[INFO] Vertex shader compiled, bytecode size: %zu", gVertexShader.GetBytecodeSize()); - Log("[INFO] D3D12 initialized successfully"); - return true; -} + if (!gPixelShader.CompileFromFile(L"Res/Shader/sphere.hlsl", "MainPS", "ps_5_1")) { + Log("[ERROR] Failed to compile pixel shader"); + return false; + } + Log("[INFO] Pixel shader compiled, bytecode size: %zu", gPixelShader.GetBytecodeSize()); -// Initialize rendering resources -bool InitRendering() { - Log("[INFO] InitRendering: Starting..."); - ID3D12Device* device = gDevice.GetDevice(); - Log("[INFO] InitRendering: Got device"); + D3D12_DESCRIPTOR_RANGE descriptorRange = D3D12RootSignature::CreateDescriptorRange( + D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 1, 0); + + D3D12_ROOT_PARAMETER rootParameters[2]; + rootParameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; + rootParameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX; + rootParameters[0].Descriptor.ShaderRegister = 0; + rootParameters[0].Descriptor.RegisterSpace = 0; + + rootParameters[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + rootParameters[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; + rootParameters[1].DescriptorTable.NumDescriptorRanges = 1; + rootParameters[1].DescriptorTable.pDescriptorRanges = &descriptorRange; + + D3D12_STATIC_SAMPLER_DESC samplerDesc = D3D12RootSignature::CreateStaticSampler( + 0, + D3D12RootSignature::CreateSamplerDesc( + FilterMode::Linear, + TextureAddressMode::Clamp, + D3D12_FLOAT32_MAX + ), + ShaderVisibility::Pixel + ); + + D3D12_ROOT_SIGNATURE_DESC rsDesc = D3D12RootSignature::CreateDesc( + rootParameters, 2, + &samplerDesc, 1, + D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT + ); + + if (!gRootSignature.Initialize(device, rsDesc)) { + Log("[ERROR] Failed to initialize root signature"); + return false; + } + + D3D12_INPUT_ELEMENT_DESC inputElements[] = { + D3D12PipelineState::CreateInputElement("POSITION", 0, Format::R32G32B32A32_Float, 0, 0), + D3D12PipelineState::CreateInputElement("TEXCOORD", 0, Format::R32G32B32A32_Float, 0, 16), + }; + + D3D12_SHADER_BYTECODE emptyGs = {}; + + D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {}; + psoDesc.pRootSignature = gRootSignature.GetRootSignature(); + psoDesc.VS = gVertexShader.GetD3D12Bytecode(); + psoDesc.PS = gPixelShader.GetD3D12Bytecode(); + psoDesc.GS = emptyGs; + psoDesc.InputLayout.NumElements = 2; + psoDesc.InputLayout.pInputElementDescs = inputElements; + psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; + psoDesc.DSVFormat = DXGI_FORMAT_D24_UNORM_S8_UINT; + psoDesc.SampleDesc.Count = 1; + psoDesc.SampleDesc.Quality = 0; + psoDesc.SampleMask = 0xffffffff; + psoDesc.NumRenderTargets = 1; + psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + psoDesc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID; + psoDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE; + psoDesc.RasterizerState.FrontCounterClockwise = FALSE; + psoDesc.RasterizerState.DepthClipEnable = TRUE; + psoDesc.DepthStencilState.DepthEnable = FALSE; + psoDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO; + psoDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS; + psoDesc.BlendState.RenderTarget[0].BlendEnable = FALSE; + psoDesc.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_ONE; + psoDesc.BlendState.RenderTarget[0].DestBlend = D3D12_BLEND_ZERO; + psoDesc.BlendState.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD; + psoDesc.BlendState.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE; + psoDesc.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ZERO; + psoDesc.BlendState.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD; + psoDesc.BlendState.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; + + if (!gPipelineState.Initialize(device, psoDesc)) { + Log("[ERROR] Failed to initialize pipeline state"); + return false; + } - // Generate sphere geometry - Log("[INFO] Generating sphere geometry..."); std::vector vertices; - std::vector indices; + std::vector indices; GenerateSphere(vertices, indices, 1.0f, 32); gIndexCount = (UINT)indices.size(); Log("[INFO] Generated %d vertices, %d indices", vertices.size(), indices.size()); - // Create vertex buffer - gVertexBuffer.Initialize(device, CommandQueueType::Direct, - vertices.data(), (UINT)(sizeof(Vertex) * vertices.size()), - ResourceStates::VertexAndConstantBuffer); - gVertexBuffer.SetName("VertexBuffer"); - - // Create index buffer - gIndexBuffer.Initialize(device, CommandQueueType::Direct, - indices.data(), (UINT)(sizeof(UINT16) * indices.size()), - ResourceStates::IndexBuffer); - gIndexBuffer.SetName("IndexBuffer"); - - // Load texture - Log("[INFO] Loading texture..."); - if (!LoadTexture("Res/Image/earth_d.jpg", gDiffuseTexture, gDiffuseSRV, device, gSRVHeap)) { - Log("[WARN] Failed to load texture, continuing without it"); + if (!gVertexBuffer.InitializeWithData(device, gCommandList.GetCommandList(), vertices.data(), (UINT)(sizeof(Vertex) * vertices.size()), D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER)) { + Log("[ERROR] Failed to initialize vertex buffer"); + return false; } + gVertexBuffer.SetStride(sizeof(Vertex)); + gVertexBuffer.SetBufferType(BufferType::Vertex); - // Skip shader compilation for debug - Log("[INFO] Skipping shader compilation for debug"); - Log("[INFO] Skipping root signature for debug"); - Log("[INFO] Skipping pipeline state for debug"); + if (!gIndexBuffer.InitializeWithData(device, gCommandList.GetCommandList(), indices.data(), (UINT)(sizeof(UINT32) * indices.size()), D3D12_RESOURCE_STATE_INDEX_BUFFER)) { + Log("[ERROR] Failed to initialize index buffer"); + return false; + } + gIndexBuffer.SetBufferType(BufferType::Index); - // Initialize matrices PerspectiveMatrix(gProjectionMatrix, 45.0f * 3.14159f / 180.0f, (float)gWidth / (float)gHeight, 0.1f, 100.0f); - float eye[3] = { 0.0f, 0.0f, 5.0f }; + float eye[3] = { 0.0f, 0.0f, 3.0f }; float target[3] = { 0.0f, 0.0f, 0.0f }; float up[3] = { 0.0f, 1.0f, 0.0f }; LookAtMatrix(gViewMatrix, eye, target, up); IdentityMatrix(gModelMatrix); - InvertMatrix(gIT_ModelMatrix, gModelMatrix); + MultiplyMatrix(gTempMatrix, gViewMatrix, gModelMatrix); + MultiplyMatrix(gMVPMatrix, gProjectionMatrix, gTempMatrix); + TransposeMatrix(gTransposedMatrix, gMVPMatrix); - Log("[INFO] Rendering resources initialized"); + gMVPBuffer.InitializeWithData(device, gCommandList.GetCommandList(), gTransposedMatrix, sizeof(gTransposedMatrix), D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER); + + if (!LoadTexture("Res/Image/earth.png", gDiffuseTexture, gDiffuseSRV, device, gSRVHeap, gCommandList.GetCommandList(), gCommandAllocator, gCommandQueue)) { + Log("[ERROR] Failed to load texture"); + return false; + } + + Log("[INFO] D3D12 initialized successfully"); return true; } -// Wait for GPU void WaitForGPU() { gCommandQueue.WaitForIdle(); } -// Execute command list void ExecuteCommandList() { gCommandList.Close(); void* commandLists[] = { gCommandList.GetCommandList() }; gCommandQueue.ExecuteCommandLists(1, commandLists); - gFenceValue += 1; } -// Begin rendering void BeginRender() { gCurrentRTIndex = gSwapChain.GetCurrentBackBufferIndex(); - // Transition render target - gCommandList.TransitionBarrier(gColorRTs[gCurrentRTIndex].GetResource(), + D3D12Texture& currentBackBuffer = gSwapChain.GetBackBuffer(gCurrentRTIndex); + gCommandList.TransitionBarrier(currentBackBuffer.GetResource(), ResourceStates::Present, ResourceStates::RenderTarget); - // Set render targets - D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle; - rtvHandle.ptr = gRTVHeap.GetCPUDescriptorHandleForHeapStart().ptr + gCurrentRTIndex * gRTVDescriptorSize; - D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle = gDSVHeap.GetCPUDescriptorHandleForHeapStart(); + CPUDescriptorHandle rtvCpuHandle = gRTVHeap.GetCPUDescriptorHandle(gCurrentRTIndex); + CPUDescriptorHandle dsvCpuHandle = gDSVHeap.GetCPUDescriptorHandle(0); + D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = { rtvCpuHandle.ptr }; + D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle = { dsvCpuHandle.ptr }; gCommandList.SetRenderTargetsHandle(1, &rtvHandle, &dsvHandle); - // Set viewport and scissor Viewport viewport = { 0.0f, 0.0f, (float)gWidth, (float)gHeight, 0.0f, 1.0f }; Rect scissorRect = { 0, 0, gWidth, gHeight }; gCommandList.SetViewport(viewport); gCommandList.SetScissorRect(scissorRect); - // Clear - float clearColor[] = { 0.1f, 0.1f, 0.2f, 1.0f }; + float clearColor[] = { 0.0f, 0.0f, 1.0f, 1.0f }; gCommandList.ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr); gCommandList.ClearDepthStencilView(dsvHandle, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr); } -// Render scene -void RenderScene() { - // Simplified rendering - just like minimal test - // (Add actual rendering code later once basic test passes) -} - -// End rendering void EndRender() { - gCommandList.TransitionBarrier(gColorRTs[gCurrentRTIndex].GetResource(), + D3D12Texture& currentBackBuffer = gSwapChain.GetBackBuffer(gCurrentRTIndex); + gCommandList.TransitionBarrier(currentBackBuffer.GetResource(), ResourceStates::RenderTarget, ResourceStates::Present); } -// Take screenshot -void TakeScreenshot() { - ID3D12Resource* backBuffer = gColorRTs[gCurrentRTIndex].GetResource(); - D3D12Screenshot::Capture(gDevice.GetDevice(), &gCommandQueue, backBuffer, "screenshot.ppm", gWidth, gHeight); - Log("[INFO] Screenshot saved to screenshot.ppm"); -} - -// Main entry int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { - // Initialize logger Logger::Get().Initialize(); Logger::Get().AddSink(std::make_unique()); Logger::Get().SetMinimumLevel(LogLevel::Debug); - Log("[INFO] D3D12 Render Model Test Starting"); + Log("[INFO] D3D12 Sphere Test Starting"); - // Register window class WNDCLASSEX wc = {}; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WindowProc; wc.hInstance = hInstance; - wc.lpszClassName = L"D3D12Test"; + wc.lpszClassName = L"D3D12SphereTest"; if (!RegisterClassEx(&wc)) { - MessageBox(NULL, L"Failed to register window class", L"Error", MB_OK); + Log("[ERROR] Failed to register window class"); return -1; } - // Create window RECT rect = { 0, 0, gWidth, gHeight }; AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE); - gHWND = CreateWindowEx(0, L"D3D12Test", L"D3D12 Render Model Test", + gHWND = CreateWindowEx(0, L"D3D12SphereTest", L"D3D12 Sphere Test", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, hInstance, NULL); if (!gHWND) { - MessageBox(NULL, L"Failed to create window", L"Error", MB_OK); + Log("[ERROR] Failed to create window"); return -1; } - // Initialize D3D12 if (!InitD3D12()) { - MessageBox(NULL, L"Failed to initialize D3D12", L"Error", MB_OK); + Log("[ERROR] Failed to initialize D3D12"); return -1; } - // Initialize rendering resources - if (!InitRendering()) { - MessageBox(NULL, L"Failed to initialize rendering", L"Error", MB_OK); - return -1; - } - - // Show window ShowWindow(gHWND, nShowCmd); UpdateWindow(gHWND); - // Main loop MSG msg = {}; int frameCount = 0; const int targetFrameCount = 30; @@ -535,54 +511,62 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine TranslateMessage(&msg); DispatchMessage(&msg); } else { - // Reset command list for this frame + if (frameCount > 0) { + gCommandQueue.WaitForPreviousFrame(); + } + gCommandAllocator.Reset(); gCommandList.Reset(); - // Render BeginRender(); - RenderScene(); - EndRender(); - // Execute - ExecuteCommandList(); + gCommandList.SetRootSignature(gRootSignature.GetRootSignature()); + gCommandList.SetPipelineState(gPipelineState.GetPipelineState()); - // Present - gSwapChain.Present(0, 0); + ID3D12DescriptorHeap* heaps[] = { gSRVHeap.GetDescriptorHeap() }; + gCommandList.SetDescriptorHeaps(1, heaps); + gCommandList.SetGraphicsRootConstantBufferView(0, gMVPBuffer.GetResource()->GetGPUVirtualAddress()); + gCommandList.SetGraphicsRootDescriptorTable(1, gSRVHeap.GetGPUDescriptorHandleForHeapStart()); + + gCommandList.SetPrimitiveTopology(PrimitiveTopology::TriangleList); + gCommandList.SetVertexBuffer(0, gVertexBuffer.GetResource(), 0, gVertexBuffer.GetStride()); + gCommandList.SetIndexBuffer(gIndexBuffer.GetResource(), 0, Format::R32_UInt); + Log("[DEBUG] DrawIndexed with %d indices", gIndexCount); + gCommandList.DrawIndexed(gIndexCount, 1, 0, 0, 0); frameCount++; if (frameCount >= targetFrameCount) { - Log("[INFO] Reached target frame count %d - taking screenshot!", targetFrameCount); - // Wait for GPU and take screenshot + ExecuteCommandList(); WaitForGPU(); - TakeScreenshot(); + + bool screenshotResult = D3D12Screenshot::Capture( + gDevice, + gCommandQueue, + gSwapChain.GetBackBuffer(gCurrentRTIndex), + "sphere.ppm" + ); + if (screenshotResult) { + Log("[INFO] Screenshot saved to sphere.ppm"); + } else { + Log("[ERROR] Screenshot failed"); + } break; } + + EndRender(); + ExecuteCommandList(); + gSwapChain.Present(0, 0); } } - // Wait for GPU to finish - WaitForGPU(); - - // Shutdown (simplified) - // gPipelineState.Shutdown(); - // gRootSignature.Shutdown(); - // gVertexShader.Shutdown(); - // gGeometryShader.Shutdown(); - // gPixelShader.Shutdown(); - // gVertexBuffer.Shutdown(); - // gIndexBuffer.Shutdown(); - // gDiffuseTexture.Shutdown(); - // gSRVHeap.Shutdown(); gCommandList.Shutdown(); gCommandAllocator.Shutdown(); - gFence.Shutdown(); gSwapChain.Shutdown(); gDevice.Shutdown(); Logger::Get().Shutdown(); - Log("[INFO] D3D12 Render Model Test Finished"); + Log("[INFO] D3D12 Sphere Test Finished"); return 0; -} +} \ No newline at end of file