feat: 添加独立的输入系统和平台抽象层
- 新增 Platform 模块:PlatformTypes.h, Window.h, WindowsWindow - 新增 Input 模块:InputTypes, InputEvent, InputAxis, InputModule, InputManager - 新增 WindowsInputModule 处理 Win32 消息转换 - 将 RHI 集成测试从 render_model 迁移到 sphere - 更新 CMakeLists.txt 添加 Platform 和 Input 模块
This commit is contained in:
546
docs/design/XCEngine输入系统设计.md
Normal file
546
docs/design/XCEngine输入系统设计.md
Normal file
@@ -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<const KeyEvent&>& OnKeyEvent() { return m_onKeyEvent; }
|
||||||
|
Core::Event<const MouseButtonEvent&>& OnMouseButton() { return m_onMouseButton; }
|
||||||
|
Core::Event<const MouseMoveEvent&>& OnMouseMove() { return m_onMouseMove; }
|
||||||
|
Core::Event<const MouseWheelEvent&>& OnMouseWheel() { return m_onMouseWheel; }
|
||||||
|
Core::Event<const TextInputEvent&>& 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<bool> m_keyDownThisFrame;
|
||||||
|
std::vector<bool> m_keyDownLastFrame;
|
||||||
|
std::vector<bool> m_keyDown;
|
||||||
|
|
||||||
|
// 鼠标状态
|
||||||
|
Math::Vector2 m_mousePosition;
|
||||||
|
Math::Vector2 m_mouseDelta;
|
||||||
|
float m_mouseScrollDelta = 0.0f;
|
||||||
|
std::vector<bool> m_mouseButtonDownThisFrame;
|
||||||
|
std::vector<bool> m_mouseButtonDownLastFrame;
|
||||||
|
std::vector<bool> m_mouseButtonDown;
|
||||||
|
|
||||||
|
// 事件
|
||||||
|
Core::Event<const KeyEvent&> m_onKeyEvent;
|
||||||
|
Core::Event<const MouseButtonEvent&> m_onMouseButton;
|
||||||
|
Core::Event<const MouseMoveEvent&> m_onMouseMove;
|
||||||
|
Core::Event<const MouseWheelEvent&> m_onMouseWheel;
|
||||||
|
Core::Event<const TextInputEvent&> 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 <Windows.h>
|
||||||
|
|
||||||
|
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 现有的组件模式和事件系统。
|
||||||
922
docs/plan/输入模块的设计与实现.md
Normal file
922
docs/plan/输入模块的设计与实现.md
Normal file
@@ -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<const KeyEvent&>& OnKeyEvent() { return m_onKeyEvent; }
|
||||||
|
Core::Event<const MouseButtonEvent&>& OnMouseButton() { return m_onMouseButton; }
|
||||||
|
Core::Event<const MouseMoveEvent&>& OnMouseMove() { return m_onMouseMove; }
|
||||||
|
Core::Event<const MouseWheelEvent&>& OnMouseWheel() { return m_onMouseWheel; }
|
||||||
|
Core::Event<const TextInputEvent&>& 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<bool> m_keyDownThisFrame;
|
||||||
|
std::vector<bool> m_keyDownLastFrame;
|
||||||
|
std::vector<bool> m_keyDown;
|
||||||
|
|
||||||
|
// 鼠标状态
|
||||||
|
Math::Vector2 m_mousePosition;
|
||||||
|
Math::Vector2 m_mouseDelta;
|
||||||
|
float m_mouseScrollDelta = 0.0f;
|
||||||
|
std::vector<bool> m_mouseButtonDownThisFrame;
|
||||||
|
std::vector<bool> m_mouseButtonDownLastFrame;
|
||||||
|
std::vector<bool> m_mouseButtonDown;
|
||||||
|
|
||||||
|
// 触摸状态
|
||||||
|
std::vector<TouchState> m_touches;
|
||||||
|
|
||||||
|
// 轴映射
|
||||||
|
std::unordered_map<Containers::String, InputAxis> m_axes;
|
||||||
|
std::unordered_map<Containers::String, KeyCode> m_buttons;
|
||||||
|
std::vector<bool> m_buttonDownThisFrame;
|
||||||
|
std::vector<bool> m_buttonDownLastFrame;
|
||||||
|
|
||||||
|
// 事件
|
||||||
|
Core::Event<const KeyEvent&> m_onKeyEvent;
|
||||||
|
Core::Event<const MouseButtonEvent&> m_onMouseButton;
|
||||||
|
Core::Event<const MouseMoveEvent&> m_onMouseMove;
|
||||||
|
Core::Event<const MouseWheelEvent&> m_onMouseWheel;
|
||||||
|
Core::Event<const TextInputEvent&> 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 <Windows.h>
|
||||||
|
|
||||||
|
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 提供完整的事件支持
|
||||||
@@ -223,6 +223,22 @@ add_library(XCEngine STATIC
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Scene/Scene.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/Scene/Scene.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Scene/SceneManager.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
|
# Audio
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Audio/AudioTypes.h
|
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Audio/AudioTypes.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Audio/AudioConfig.h
|
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Audio/AudioConfig.h
|
||||||
|
|||||||
34
engine/include/XCEngine/Input/InputAxis.h
Normal file
34
engine/include/XCEngine/Input/InputAxis.h
Normal file
@@ -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
|
||||||
49
engine/include/XCEngine/Input/InputEvent.h
Normal file
49
engine/include/XCEngine/Input/InputEvent.h
Normal file
@@ -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
|
||||||
101
engine/include/XCEngine/Input/InputManager.h
Normal file
101
engine/include/XCEngine/Input/InputManager.h
Normal file
@@ -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<const KeyEvent&>& OnKeyEvent() { return m_onKeyEvent; }
|
||||||
|
Core::Event<const MouseButtonEvent&>& OnMouseButton() { return m_onMouseButton; }
|
||||||
|
Core::Event<const MouseMoveEvent&>& OnMouseMove() { return m_onMouseMove; }
|
||||||
|
Core::Event<const MouseWheelEvent&>& OnMouseWheel() { return m_onMouseWheel; }
|
||||||
|
Core::Event<const TextInputEvent&>& 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<bool> m_keyDownThisFrame;
|
||||||
|
std::vector<bool> m_keyDownLastFrame;
|
||||||
|
std::vector<bool> m_keyDown;
|
||||||
|
|
||||||
|
Math::Vector2 m_mousePosition;
|
||||||
|
Math::Vector2 m_mouseDelta;
|
||||||
|
float m_mouseScrollDelta = 0.0f;
|
||||||
|
std::vector<bool> m_mouseButtonDownThisFrame;
|
||||||
|
std::vector<bool> m_mouseButtonDownLastFrame;
|
||||||
|
std::vector<bool> m_mouseButtonDown;
|
||||||
|
|
||||||
|
std::vector<TouchState> m_touches;
|
||||||
|
|
||||||
|
std::unordered_map<Containers::String, InputAxis> m_axes;
|
||||||
|
std::unordered_map<Containers::String, KeyCode> m_buttons;
|
||||||
|
std::vector<bool> m_buttonDownThisFrame;
|
||||||
|
std::vector<bool> m_buttonDownLastFrame;
|
||||||
|
|
||||||
|
Core::Event<const KeyEvent&> m_onKeyEvent;
|
||||||
|
Core::Event<const MouseButtonEvent&> m_onMouseButton;
|
||||||
|
Core::Event<const MouseMoveEvent&> m_onMouseMove;
|
||||||
|
Core::Event<const MouseWheelEvent&> m_onMouseWheel;
|
||||||
|
Core::Event<const TextInputEvent&> m_onTextInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Input
|
||||||
|
} // namespace XCEngine
|
||||||
19
engine/include/XCEngine/Input/InputModule.h
Normal file
19
engine/include/XCEngine/Input/InputModule.h
Normal file
@@ -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
|
||||||
54
engine/include/XCEngine/Input/InputTypes.h
Normal file
54
engine/include/XCEngine/Input/InputTypes.h
Normal file
@@ -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
|
||||||
40
engine/include/XCEngine/Input/Platform/WindowsInputModule.h
Normal file
40
engine/include/XCEngine/Input/Platform/WindowsInputModule.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Input/InputModule.h"
|
||||||
|
#include "Input/InputTypes.h"
|
||||||
|
#include "Math/Vector2.h"
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
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
|
||||||
36
engine/include/XCEngine/Platform/PlatformTypes.h
Normal file
36
engine/include/XCEngine/Platform/PlatformTypes.h
Normal file
@@ -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
|
||||||
29
engine/include/XCEngine/Platform/Window.h
Normal file
29
engine/include/XCEngine/Platform/Window.h
Normal file
@@ -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
|
||||||
48
engine/include/XCEngine/Platform/Windows/WindowsWindow.h
Normal file
48
engine/include/XCEngine/Platform/Windows/WindowsWindow.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Platform/Window.h"
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
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<void(HWND, UINT, WPARAM, LPARAM)> 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<void(HWND, UINT, WPARAM, LPARAM)> m_messageCallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Platform
|
||||||
|
} // namespace XCEngine
|
||||||
304
engine/src/Input/InputManager.cpp
Normal file
304
engine/src/Input/InputManager.cpp
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
#include "Input/InputManager.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
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<size_t>(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t InputManager::GetMouseButtonIndex(MouseButton button) const {
|
||||||
|
return static_cast<size_t>(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<int>(m_touches.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
TouchState InputManager::GetTouch(int index) const {
|
||||||
|
if (index >= 0 && index < static_cast<int>(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<float>(x);
|
||||||
|
m_mousePosition.y = static_cast<float>(y);
|
||||||
|
m_mouseDelta.x = static_cast<float>(deltaX);
|
||||||
|
m_mouseDelta.y = static_cast<float>(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<float>(x);
|
||||||
|
event.position.y = static_cast<float>(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<float>(x);
|
||||||
|
event.position.y = static_cast<float>(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
|
||||||
240
engine/src/Input/Windows/WindowsInputModule.cpp
Normal file
240
engine/src/Input/Windows/WindowsInputModule.cpp
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
#include "Input/Platform/WindowsInputModule.h"
|
||||||
|
#include "Input/InputManager.h"
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
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<HWND>(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<int>(wParam));
|
||||||
|
|
||||||
|
if (keyCode != KeyCode::None) {
|
||||||
|
InputManager::Get().ProcessKeyDown(keyCode, repeat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsInputModule::ProcessKeyUp(WPARAM wParam) {
|
||||||
|
KeyCode keyCode = VKCodeToKeyCode(static_cast<int>(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<int>(m_lastMousePosition.x);
|
||||||
|
int deltaY = y - static_cast<int>(m_lastMousePosition.y);
|
||||||
|
|
||||||
|
m_lastMousePosition.x = static_cast<float>(x);
|
||||||
|
m_lastMousePosition.y = static_cast<float>(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<short>(HIWORD(wParam));
|
||||||
|
|
||||||
|
InputManager::Get().ProcessMouseWheel(static_cast<float>(delta) / 120.0f, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsInputModule::ProcessCharInput(WPARAM wParam) {
|
||||||
|
char c = static_cast<char>(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
|
||||||
191
engine/src/Platform/Windows/WindowsWindow.cpp
Normal file
191
engine/src/Platform/Windows/WindowsWindow.cpp
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
#include "Platform/Windows/WindowsWindow.h"
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
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<LONG>(desc.width), static_cast<LONG>(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<void(HWND, UINT, WPARAM, LPARAM)> 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<CREATESTRUCTA*>(lParam);
|
||||||
|
window = reinterpret_cast<WindowsWindow*>(cs->lpCreateParams);
|
||||||
|
SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(window));
|
||||||
|
} else {
|
||||||
|
window = reinterpret_cast<WindowsWindow*>(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
|
||||||
@@ -9,4 +9,4 @@ enable_testing()
|
|||||||
add_subdirectory(minimal)
|
add_subdirectory(minimal)
|
||||||
add_subdirectory(triangle)
|
add_subdirectory(triangle)
|
||||||
add_subdirectory(quad)
|
add_subdirectory(quad)
|
||||||
add_subdirectory(render_model)
|
add_subdirectory(sphere)
|
||||||
|
|||||||
Binary file not shown.
@@ -2,27 +2,27 @@ cmake_minimum_required(VERSION 3.15)
|
|||||||
|
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
project(D3D12_RenderModel)
|
project(D3D12_Sphere)
|
||||||
|
|
||||||
set(ENGINE_ROOT_DIR ${CMAKE_SOURCE_DIR}/engine)
|
set(ENGINE_ROOT_DIR ${CMAKE_SOURCE_DIR}/engine)
|
||||||
|
|
||||||
add_executable(D3D12_RenderModel
|
add_executable(D3D12_Sphere
|
||||||
WIN32
|
WIN32
|
||||||
main.cpp
|
main.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(D3D12_RenderModel PRIVATE
|
target_include_directories(D3D12_Sphere PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
${ENGINE_ROOT_DIR}/include
|
${ENGINE_ROOT_DIR}/include
|
||||||
${ENGINE_ROOT_DIR}
|
${ENGINE_ROOT_DIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_definitions(D3D12_RenderModel PRIVATE
|
target_compile_definitions(D3D12_Sphere PRIVATE
|
||||||
UNICODE
|
UNICODE
|
||||||
_UNICODE
|
_UNICODE
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(D3D12_RenderModel PRIVATE
|
target_link_libraries(D3D12_Sphere PRIVATE
|
||||||
d3d12
|
d3d12
|
||||||
dxgi
|
dxgi
|
||||||
d3dcompiler
|
d3dcompiler
|
||||||
@@ -30,26 +30,26 @@ target_link_libraries(D3D12_RenderModel PRIVATE
|
|||||||
XCEngine
|
XCEngine
|
||||||
)
|
)
|
||||||
|
|
||||||
add_custom_command(TARGET D3D12_RenderModel POST_BUILD
|
add_custom_command(TARGET D3D12_Sphere POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/Res
|
${CMAKE_CURRENT_SOURCE_DIR}/Res
|
||||||
$<TARGET_FILE_DIR:D3D12_RenderModel>/Res
|
$<TARGET_FILE_DIR:D3D12_Sphere>/Res
|
||||||
)
|
)
|
||||||
|
|
||||||
add_custom_command(TARGET D3D12_RenderModel POST_BUILD
|
add_custom_command(TARGET D3D12_Sphere POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
${CMAKE_SOURCE_DIR}/tests/RHI/D3D12/integration/compare_ppm.py
|
${CMAKE_SOURCE_DIR}/tests/RHI/D3D12/integration/compare_ppm.py
|
||||||
$<TARGET_FILE_DIR:D3D12_RenderModel>/
|
$<TARGET_FILE_DIR:D3D12_Sphere>/
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
${CMAKE_SOURCE_DIR}/tests/RHI/D3D12/integration/run_integration_test.py
|
${CMAKE_SOURCE_DIR}/tests/RHI/D3D12/integration/run_integration_test.py
|
||||||
$<TARGET_FILE_DIR:D3D12_RenderModel>/
|
$<TARGET_FILE_DIR:D3D12_Sphere>/
|
||||||
)
|
)
|
||||||
|
|
||||||
add_test(NAME D3D12_RenderModel_Integration
|
add_test(NAME D3D12_Sphere_Integration
|
||||||
COMMAND ${Python3_EXECUTABLE} $<TARGET_FILE_DIR:D3D12_RenderModel>/run_integration_test.py
|
COMMAND ${Python3_EXECUTABLE} $<TARGET_FILE_DIR:D3D12_Sphere>/run_integration_test.py
|
||||||
$<TARGET_FILE:D3D12_RenderModel>
|
$<TARGET_FILE:D3D12_Sphere>
|
||||||
screenshot.ppm
|
screenshot.ppm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/GT.ppm
|
${CMAKE_CURRENT_SOURCE_DIR}/GT.ppm
|
||||||
5
|
5
|
||||||
WORKING_DIRECTORY $<TARGET_FILE_DIR:D3D12_RenderModel>
|
WORKING_DIRECTORY $<TARGET_FILE_DIR:D3D12_Sphere>
|
||||||
)
|
)
|
||||||
BIN
tests/RHI/D3D12/integration/sphere/GT.ppm
Normal file
BIN
tests/RHI/D3D12/integration/sphere/GT.ppm
Normal file
Binary file not shown.
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 189 KiB |
BIN
tests/RHI/D3D12/integration/sphere/Image/earth_d.jpg
Normal file
BIN
tests/RHI/D3D12/integration/sphere/Image/earth_d.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
BIN
tests/RHI/D3D12/integration/sphere/Res/Image/earth.png
Normal file
BIN
tests/RHI/D3D12/integration/sphere/Res/Image/earth.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 189 KiB |
24
tests/RHI/D3D12/integration/sphere/Res/Shader/sphere.hlsl
Normal file
24
tests/RHI/D3D12/integration/sphere/Res/Shader/sphere.hlsl
Normal file
@@ -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);
|
||||||
|
}
|
||||||
23
tests/RHI/D3D12/integration/sphere/Shader/sphere.hlsl
Normal file
23
tests/RHI/D3D12/integration/sphere/Shader/sphere.hlsl
Normal file
@@ -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);
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <fstream>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "XCEngine/RHI/RHIEnums.h"
|
#include "XCEngine/RHI/RHIEnums.h"
|
||||||
@@ -23,8 +22,8 @@
|
|||||||
#include "XCEngine/RHI/D3D12/D3D12RenderTargetView.h"
|
#include "XCEngine/RHI/D3D12/D3D12RenderTargetView.h"
|
||||||
#include "XCEngine/RHI/D3D12/D3D12DepthStencilView.h"
|
#include "XCEngine/RHI/D3D12/D3D12DepthStencilView.h"
|
||||||
#include "XCEngine/RHI/D3D12/D3D12Shader.h"
|
#include "XCEngine/RHI/D3D12/D3D12Shader.h"
|
||||||
#include "XCEngine/RHI/D3D12/D3D12PipelineState.h"
|
|
||||||
#include "XCEngine/RHI/D3D12/D3D12RootSignature.h"
|
#include "XCEngine/RHI/D3D12/D3D12RootSignature.h"
|
||||||
|
#include "XCEngine/RHI/D3D12/D3D12PipelineState.h"
|
||||||
#include "XCEngine/RHI/D3D12/D3D12ShaderResourceView.h"
|
#include "XCEngine/RHI/D3D12/D3D12ShaderResourceView.h"
|
||||||
#include "XCEngine/RHI/D3D12/D3D12Screenshot.h"
|
#include "XCEngine/RHI/D3D12/D3D12Screenshot.h"
|
||||||
#include "XCEngine/Debug/Logger.h"
|
#include "XCEngine/Debug/Logger.h"
|
||||||
@@ -43,57 +42,45 @@ using namespace XCEngine::Containers;
|
|||||||
#pragma comment(lib,"d3dcompiler.lib")
|
#pragma comment(lib,"d3dcompiler.lib")
|
||||||
#pragma comment(lib,"winmm.lib")
|
#pragma comment(lib,"winmm.lib")
|
||||||
|
|
||||||
// Global D3D12 objects
|
|
||||||
D3D12Device gDevice;
|
D3D12Device gDevice;
|
||||||
D3D12CommandQueue gCommandQueue;
|
D3D12CommandQueue gCommandQueue;
|
||||||
D3D12SwapChain gSwapChain;
|
D3D12SwapChain gSwapChain;
|
||||||
D3D12CommandAllocator gCommandAllocator;
|
D3D12CommandAllocator gCommandAllocator;
|
||||||
D3D12CommandList gCommandList;
|
D3D12CommandList gCommandList;
|
||||||
D3D12Fence gFence;
|
|
||||||
|
|
||||||
// Render targets
|
|
||||||
D3D12Texture gColorRTs[2];
|
|
||||||
D3D12Texture gDepthStencil;
|
D3D12Texture gDepthStencil;
|
||||||
D3D12DescriptorHeap gRTVHeap;
|
D3D12DescriptorHeap gRTVHeap;
|
||||||
D3D12DescriptorHeap gDSVHeap;
|
D3D12DescriptorHeap gDSVHeap;
|
||||||
|
D3D12DescriptorHeap gSRVHeap;
|
||||||
D3D12RenderTargetView gRTVs[2];
|
D3D12RenderTargetView gRTVs[2];
|
||||||
D3D12DepthStencilView gDSV;
|
D3D12DepthStencilView gDSV;
|
||||||
|
|
||||||
// Pipeline objects
|
|
||||||
D3D12Shader gVertexShader;
|
D3D12Shader gVertexShader;
|
||||||
D3D12Shader gGeometryShader;
|
|
||||||
D3D12Shader gPixelShader;
|
D3D12Shader gPixelShader;
|
||||||
D3D12RootSignature gRootSignature;
|
D3D12RootSignature gRootSignature;
|
||||||
D3D12PipelineState gPipelineState;
|
D3D12PipelineState gPipelineState;
|
||||||
|
|
||||||
// Model data
|
|
||||||
D3D12Buffer gVertexBuffer;
|
D3D12Buffer gVertexBuffer;
|
||||||
D3D12Buffer gIndexBuffer;
|
D3D12Buffer gIndexBuffer;
|
||||||
UINT gIndexCount = 0;
|
D3D12Buffer gMVPBuffer;
|
||||||
|
|
||||||
// Texture
|
|
||||||
D3D12Texture gDiffuseTexture;
|
D3D12Texture gDiffuseTexture;
|
||||||
D3D12DescriptorHeap gSRVHeap;
|
|
||||||
D3D12ShaderResourceView gDiffuseSRV;
|
D3D12ShaderResourceView gDiffuseSRV;
|
||||||
|
|
||||||
// Matrices
|
|
||||||
float gProjectionMatrix[16];
|
|
||||||
float gViewMatrix[16];
|
|
||||||
float gModelMatrix[16];
|
|
||||||
float gIT_ModelMatrix[16];
|
|
||||||
|
|
||||||
// Descriptor sizes
|
|
||||||
UINT gRTVDescriptorSize = 0;
|
UINT gRTVDescriptorSize = 0;
|
||||||
UINT gDSVDescriptorSize = 0;
|
UINT gDSVDescriptorSize = 0;
|
||||||
int gCurrentRTIndex = 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;
|
HWND gHWND = nullptr;
|
||||||
int gWidth = 1280;
|
int gWidth = 1280;
|
||||||
int gHeight = 720;
|
int gHeight = 720;
|
||||||
|
|
||||||
// Log helper
|
|
||||||
void Log(const char* format, ...) {
|
void Log(const char* format, ...) {
|
||||||
char buffer[1024];
|
char buffer[1024];
|
||||||
va_list args;
|
va_list args;
|
||||||
@@ -103,7 +90,6 @@ void Log(const char* format, ...) {
|
|||||||
Logger::Get().Debug(LogCategory::Rendering, String(buffer));
|
Logger::Get().Debug(LogCategory::Rendering, String(buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Window procedure
|
|
||||||
LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||||
switch (msg) {
|
switch (msg) {
|
||||||
case WM_CLOSE:
|
case WM_CLOSE:
|
||||||
@@ -113,7 +99,6 @@ LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
|||||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matrix utilities
|
|
||||||
void IdentityMatrix(float* m) {
|
void IdentityMatrix(float* m) {
|
||||||
memset(m, 0, 16 * sizeof(float));
|
memset(m, 0, 16 * sizeof(float));
|
||||||
m[0] = m[5] = m[10] = m[15] = 1.0f;
|
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;
|
m[15] = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RotationYMatrix(float* m, float angle) {
|
void MultiplyMatrix(float* dst, const float* a, const float* b) {
|
||||||
IdentityMatrix(m);
|
for (int i = 0; i < 4; i++) {
|
||||||
float c = cosf(angle);
|
for (int j = 0; j < 4; j++) {
|
||||||
float s = sinf(angle);
|
dst[i * 4 + j] = 0;
|
||||||
m[0] = c; m[2] = s;
|
for (int k = 0; k < 4; k++) {
|
||||||
m[8] = -s; m[10] = c;
|
dst[i * 4 + j] += a[i * 4 + k] * b[k * 4 + j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TransposeMatrix(float* dst, const float* src) {
|
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 {
|
struct Vertex {
|
||||||
float position[4];
|
float pos[4];
|
||||||
float texcoord[4];
|
float texcoord[4];
|
||||||
float normal[4];
|
|
||||||
float tangent[4];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void GenerateSphere(std::vector<Vertex>& vertices, std::vector<UINT16>& indices, float radius, int segments) {
|
void GenerateSphere(std::vector<Vertex>& vertices, std::vector<UINT32>& indices, float radius, int segments) {
|
||||||
vertices.clear();
|
vertices.clear();
|
||||||
indices.clear();
|
indices.clear();
|
||||||
|
|
||||||
// Generate vertices
|
|
||||||
for (int lat = 0; lat <= segments; lat++) {
|
for (int lat = 0; lat <= segments; lat++) {
|
||||||
float theta = lat * 3.14159f / segments;
|
float theta = lat * 3.14159f / segments;
|
||||||
float sinTheta = sinf(theta);
|
float sinTheta = sinf(theta);
|
||||||
@@ -202,31 +177,20 @@ void GenerateSphere(std::vector<Vertex>& vertices, std::vector<UINT16>& indices,
|
|||||||
float cosPhi = cosf(phi);
|
float cosPhi = cosf(phi);
|
||||||
|
|
||||||
Vertex v;
|
Vertex v;
|
||||||
v.position[0] = radius * sinTheta * cosPhi;
|
v.pos[0] = radius * sinTheta * cosPhi;
|
||||||
v.position[1] = radius * cosTheta;
|
v.pos[1] = radius * cosTheta;
|
||||||
v.position[2] = radius * sinTheta * sinPhi;
|
v.pos[2] = radius * sinTheta * sinPhi;
|
||||||
v.position[3] = 1.0f;
|
v.pos[3] = 1.0f;
|
||||||
|
|
||||||
v.texcoord[0] = (float)lon / segments;
|
v.texcoord[0] = (float)lon / segments;
|
||||||
v.texcoord[1] = (float)lat / segments;
|
v.texcoord[1] = (float)lat / segments;
|
||||||
v.texcoord[2] = 0.0f;
|
v.texcoord[2] = 0.0f;
|
||||||
v.texcoord[3] = 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);
|
vertices.push_back(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate indices
|
|
||||||
for (int lat = 0; lat < segments; lat++) {
|
for (int lat = 0; lat < segments; lat++) {
|
||||||
for (int lon = 0; lon < segments; lon++) {
|
for (int lon = 0; lon < segments; lon++) {
|
||||||
int first = lat * (segments + 1) + lon;
|
int first = lat * (segments + 1) + lon;
|
||||||
@@ -243,8 +207,7 @@ void GenerateSphere(std::vector<Vertex>& vertices, std::vector<UINT16>& indices,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load texture
|
bool LoadTexture(const char* filename, D3D12Texture& texture, D3D12ShaderResourceView& srv, ID3D12Device* device, D3D12DescriptorHeap& srvHeap, ID3D12GraphicsCommandList* commandList, D3D12CommandAllocator& allocator, D3D12CommandQueue& queue) {
|
||||||
bool LoadTexture(const char* filename, D3D12Texture& texture, D3D12ShaderResourceView& srv, ID3D12Device* device, D3D12DescriptorHeap& srvHeap) {
|
|
||||||
int width, height, channels;
|
int width, height, channels;
|
||||||
stbi_uc* pixels = stbi_load(filename, &width, &height, &channels, STBI_rgb_alpha);
|
stbi_uc* pixels = stbi_load(filename, &width, &height, &channels, STBI_rgb_alpha);
|
||||||
if (!pixels) {
|
if (!pixels) {
|
||||||
@@ -252,29 +215,30 @@ bool LoadTexture(const char* filename, D3D12Texture& texture, D3D12ShaderResourc
|
|||||||
return false;
|
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, commandList, pixels, width, height, DXGI_FORMAT_R8G8B8A8_UNORM)) {
|
||||||
if (!texture.InitializeFromData(device, nullptr, pixels, width, height, DXGI_FORMAT_R8G8B8A8_UNORM)) {
|
|
||||||
Log("[ERROR] Failed to initialize texture");
|
Log("[ERROR] Failed to initialize texture");
|
||||||
stbi_image_free(pixels);
|
stbi_image_free(pixels);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
texture.SetName(filename);
|
commandList->Close();
|
||||||
stbi_image_free(pixels);
|
ID3D12CommandList* lists[] = { commandList };
|
||||||
|
queue.ExecuteCommandListsInternal(1, lists);
|
||||||
|
queue.WaitForIdle();
|
||||||
|
|
||||||
// Create SRV
|
texture.SetName(filename);
|
||||||
srvHeap.Initialize(device, DescriptorHeapType::CBV_SRV_UAV, 1);
|
|
||||||
|
srvHeap.Initialize(device, DescriptorHeapType::CBV_SRV_UAV, 1, true);
|
||||||
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = D3D12ShaderResourceView::CreateDesc(Format::R8G8B8A8_UNorm, D3D12_SRV_DIMENSION_TEXTURE2D);
|
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = D3D12ShaderResourceView::CreateDesc(Format::R8G8B8A8_UNorm, D3D12_SRV_DIMENSION_TEXTURE2D);
|
||||||
srv.InitializeAt(device, texture.GetResource(), srvHeap.GetCPUDescriptorHandleForHeapStart(), &srvDesc);
|
srv.InitializeAt(device, texture.GetResource(), srvHeap.GetCPUDescriptorHandleForHeapStart(), &srvDesc);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize D3D12
|
|
||||||
bool InitD3D12() {
|
bool InitD3D12() {
|
||||||
// Create device
|
|
||||||
RHIDeviceDesc deviceDesc;
|
RHIDeviceDesc deviceDesc;
|
||||||
deviceDesc.windowHandle = gHWND;
|
deviceDesc.windowHandle = gHWND;
|
||||||
deviceDesc.width = gWidth;
|
deviceDesc.width = gWidth;
|
||||||
@@ -291,238 +255,250 @@ bool InitD3D12() {
|
|||||||
ID3D12Device* device = gDevice.GetDevice();
|
ID3D12Device* device = gDevice.GetDevice();
|
||||||
IDXGIFactory4* factory = gDevice.GetFactory();
|
IDXGIFactory4* factory = gDevice.GetFactory();
|
||||||
|
|
||||||
// Create command queue
|
|
||||||
if (!gCommandQueue.Initialize(device, CommandQueueType::Direct)) {
|
if (!gCommandQueue.Initialize(device, CommandQueueType::Direct)) {
|
||||||
Log("[ERROR] Failed to initialize command queue");
|
Log("[ERROR] Failed to initialize command queue");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create swap chain
|
if (!gSwapChain.Initialize(factory, gCommandQueue.GetCommandQueue(), gHWND, gWidth, gHeight, 2)) {
|
||||||
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)) {
|
|
||||||
Log("[ERROR] Failed to initialize swap chain");
|
Log("[ERROR] Failed to initialize swap chain");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize depth stencil
|
|
||||||
gDepthStencil.InitializeDepthStencil(device, gWidth, gHeight);
|
gDepthStencil.InitializeDepthStencil(device, gWidth, gHeight);
|
||||||
|
|
||||||
// Create RTV heap
|
|
||||||
gRTVHeap.Initialize(device, DescriptorHeapType::RTV, 2);
|
gRTVHeap.Initialize(device, DescriptorHeapType::RTV, 2);
|
||||||
gRTVDescriptorSize = gDevice.GetDescriptorHandleIncrementSize(DescriptorHeapType::RTV);
|
gRTVDescriptorSize = gDevice.GetDescriptorHandleIncrementSize(DescriptorHeapType::RTV);
|
||||||
|
|
||||||
// Create DSV heap
|
|
||||||
gDSVHeap.Initialize(device, DescriptorHeapType::DSV, 1);
|
gDSVHeap.Initialize(device, DescriptorHeapType::DSV, 1);
|
||||||
gDSVDescriptorSize = gDevice.GetDescriptorHandleIncrementSize(DescriptorHeapType::DSV);
|
gDSVDescriptorSize = gDevice.GetDescriptorHandleIncrementSize(DescriptorHeapType::DSV);
|
||||||
|
|
||||||
// Create RTVs for back buffers
|
|
||||||
D3D12_CPU_DESCRIPTOR_HANDLE rtvHeapStart = gRTVHeap.GetCPUDescriptorHandleForHeapStart();
|
|
||||||
for (int i = 0; i < 2; i++) {
|
for (int i = 0; i < 2; i++) {
|
||||||
ID3D12Resource* buffer = nullptr;
|
D3D12Texture& backBuffer = gSwapChain.GetBackBuffer(i);
|
||||||
gSwapChain.GetSwapChain()->GetBuffer(i, IID_PPV_ARGS(&buffer));
|
CPUDescriptorHandle rtvCpuHandle = gRTVHeap.GetCPUDescriptorHandle(i);
|
||||||
gColorRTs[i].InitializeFromExisting(buffer);
|
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = { rtvCpuHandle.ptr };
|
||||||
|
gRTVs[i].InitializeAt(device, backBuffer.GetResource(), rtvHandle, nullptr);
|
||||||
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle;
|
|
||||||
rtvHandle.ptr = rtvHeapStart.ptr + i * gRTVDescriptorSize;
|
|
||||||
gRTVs[i].InitializeAt(device, gColorRTs[i].GetResource(), rtvHandle, nullptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create DSV
|
|
||||||
D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = D3D12DepthStencilView::CreateDesc(Format::D24_UNorm_S8_UInt);
|
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);
|
gCommandAllocator.Initialize(device, CommandQueueType::Direct);
|
||||||
gCommandList.Initialize(device, CommandQueueType::Direct, gCommandAllocator.GetCommandAllocator());
|
gCommandList.Initialize(device, CommandQueueType::Direct, gCommandAllocator.GetCommandAllocator());
|
||||||
|
|
||||||
// Create fence
|
if (!gVertexShader.CompileFromFile(L"Res/Shader/sphere.hlsl", "MainVS", "vs_5_1")) {
|
||||||
gFence.Initialize(device, 0);
|
Log("[ERROR] Failed to compile vertex shader");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log("[INFO] Vertex shader compiled, bytecode size: %zu", gVertexShader.GetBytecodeSize());
|
||||||
|
|
||||||
Log("[INFO] D3D12 initialized successfully");
|
if (!gPixelShader.CompileFromFile(L"Res/Shader/sphere.hlsl", "MainPS", "ps_5_1")) {
|
||||||
return true;
|
Log("[ERROR] Failed to compile pixel shader");
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
Log("[INFO] Pixel shader compiled, bytecode size: %zu", gPixelShader.GetBytecodeSize());
|
||||||
|
|
||||||
// Initialize rendering resources
|
D3D12_DESCRIPTOR_RANGE descriptorRange = D3D12RootSignature::CreateDescriptorRange(
|
||||||
bool InitRendering() {
|
D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 1, 0);
|
||||||
Log("[INFO] InitRendering: Starting...");
|
|
||||||
ID3D12Device* device = gDevice.GetDevice();
|
D3D12_ROOT_PARAMETER rootParameters[2];
|
||||||
Log("[INFO] InitRendering: Got device");
|
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<Vertex> vertices;
|
std::vector<Vertex> vertices;
|
||||||
std::vector<UINT16> indices;
|
std::vector<UINT32> indices;
|
||||||
GenerateSphere(vertices, indices, 1.0f, 32);
|
GenerateSphere(vertices, indices, 1.0f, 32);
|
||||||
gIndexCount = (UINT)indices.size();
|
gIndexCount = (UINT)indices.size();
|
||||||
Log("[INFO] Generated %d vertices, %d indices", vertices.size(), indices.size());
|
Log("[INFO] Generated %d vertices, %d indices", vertices.size(), indices.size());
|
||||||
|
|
||||||
// Create vertex buffer
|
if (!gVertexBuffer.InitializeWithData(device, gCommandList.GetCommandList(), vertices.data(), (UINT)(sizeof(Vertex) * vertices.size()), D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER)) {
|
||||||
gVertexBuffer.Initialize(device, CommandQueueType::Direct,
|
Log("[ERROR] Failed to initialize vertex buffer");
|
||||||
vertices.data(), (UINT)(sizeof(Vertex) * vertices.size()),
|
return false;
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
gVertexBuffer.SetStride(sizeof(Vertex));
|
||||||
|
gVertexBuffer.SetBufferType(BufferType::Vertex);
|
||||||
|
|
||||||
// Skip shader compilation for debug
|
if (!gIndexBuffer.InitializeWithData(device, gCommandList.GetCommandList(), indices.data(), (UINT)(sizeof(UINT32) * indices.size()), D3D12_RESOURCE_STATE_INDEX_BUFFER)) {
|
||||||
Log("[INFO] Skipping shader compilation for debug");
|
Log("[ERROR] Failed to initialize index buffer");
|
||||||
Log("[INFO] Skipping root signature for debug");
|
return false;
|
||||||
Log("[INFO] Skipping pipeline state for debug");
|
}
|
||||||
|
gIndexBuffer.SetBufferType(BufferType::Index);
|
||||||
|
|
||||||
// Initialize matrices
|
|
||||||
PerspectiveMatrix(gProjectionMatrix, 45.0f * 3.14159f / 180.0f, (float)gWidth / (float)gHeight, 0.1f, 100.0f);
|
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 target[3] = { 0.0f, 0.0f, 0.0f };
|
||||||
float up[3] = { 0.0f, 1.0f, 0.0f };
|
float up[3] = { 0.0f, 1.0f, 0.0f };
|
||||||
LookAtMatrix(gViewMatrix, eye, target, up);
|
LookAtMatrix(gViewMatrix, eye, target, up);
|
||||||
|
|
||||||
IdentityMatrix(gModelMatrix);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for GPU
|
|
||||||
void WaitForGPU() {
|
void WaitForGPU() {
|
||||||
gCommandQueue.WaitForIdle();
|
gCommandQueue.WaitForIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute command list
|
|
||||||
void ExecuteCommandList() {
|
void ExecuteCommandList() {
|
||||||
gCommandList.Close();
|
gCommandList.Close();
|
||||||
void* commandLists[] = { gCommandList.GetCommandList() };
|
void* commandLists[] = { gCommandList.GetCommandList() };
|
||||||
gCommandQueue.ExecuteCommandLists(1, commandLists);
|
gCommandQueue.ExecuteCommandLists(1, commandLists);
|
||||||
gFenceValue += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin rendering
|
|
||||||
void BeginRender() {
|
void BeginRender() {
|
||||||
gCurrentRTIndex = gSwapChain.GetCurrentBackBufferIndex();
|
gCurrentRTIndex = gSwapChain.GetCurrentBackBufferIndex();
|
||||||
|
|
||||||
// Transition render target
|
D3D12Texture& currentBackBuffer = gSwapChain.GetBackBuffer(gCurrentRTIndex);
|
||||||
gCommandList.TransitionBarrier(gColorRTs[gCurrentRTIndex].GetResource(),
|
gCommandList.TransitionBarrier(currentBackBuffer.GetResource(),
|
||||||
ResourceStates::Present, ResourceStates::RenderTarget);
|
ResourceStates::Present, ResourceStates::RenderTarget);
|
||||||
|
|
||||||
// Set render targets
|
CPUDescriptorHandle rtvCpuHandle = gRTVHeap.GetCPUDescriptorHandle(gCurrentRTIndex);
|
||||||
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle;
|
CPUDescriptorHandle dsvCpuHandle = gDSVHeap.GetCPUDescriptorHandle(0);
|
||||||
rtvHandle.ptr = gRTVHeap.GetCPUDescriptorHandleForHeapStart().ptr + gCurrentRTIndex * gRTVDescriptorSize;
|
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = { rtvCpuHandle.ptr };
|
||||||
D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle = gDSVHeap.GetCPUDescriptorHandleForHeapStart();
|
D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle = { dsvCpuHandle.ptr };
|
||||||
|
|
||||||
gCommandList.SetRenderTargetsHandle(1, &rtvHandle, &dsvHandle);
|
gCommandList.SetRenderTargetsHandle(1, &rtvHandle, &dsvHandle);
|
||||||
|
|
||||||
// Set viewport and scissor
|
|
||||||
Viewport viewport = { 0.0f, 0.0f, (float)gWidth, (float)gHeight, 0.0f, 1.0f };
|
Viewport viewport = { 0.0f, 0.0f, (float)gWidth, (float)gHeight, 0.0f, 1.0f };
|
||||||
Rect scissorRect = { 0, 0, gWidth, gHeight };
|
Rect scissorRect = { 0, 0, gWidth, gHeight };
|
||||||
gCommandList.SetViewport(viewport);
|
gCommandList.SetViewport(viewport);
|
||||||
gCommandList.SetScissorRect(scissorRect);
|
gCommandList.SetScissorRect(scissorRect);
|
||||||
|
|
||||||
// Clear
|
float clearColor[] = { 0.0f, 0.0f, 1.0f, 1.0f };
|
||||||
float clearColor[] = { 0.1f, 0.1f, 0.2f, 1.0f };
|
|
||||||
gCommandList.ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
|
gCommandList.ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
|
||||||
gCommandList.ClearDepthStencilView(dsvHandle, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 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() {
|
void EndRender() {
|
||||||
gCommandList.TransitionBarrier(gColorRTs[gCurrentRTIndex].GetResource(),
|
D3D12Texture& currentBackBuffer = gSwapChain.GetBackBuffer(gCurrentRTIndex);
|
||||||
|
gCommandList.TransitionBarrier(currentBackBuffer.GetResource(),
|
||||||
ResourceStates::RenderTarget, ResourceStates::Present);
|
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) {
|
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
|
||||||
// Initialize logger
|
|
||||||
Logger::Get().Initialize();
|
Logger::Get().Initialize();
|
||||||
Logger::Get().AddSink(std::make_unique<ConsoleLogSink>());
|
Logger::Get().AddSink(std::make_unique<ConsoleLogSink>());
|
||||||
Logger::Get().SetMinimumLevel(LogLevel::Debug);
|
Logger::Get().SetMinimumLevel(LogLevel::Debug);
|
||||||
|
|
||||||
Log("[INFO] D3D12 Render Model Test Starting");
|
Log("[INFO] D3D12 Sphere Test Starting");
|
||||||
|
|
||||||
// Register window class
|
|
||||||
WNDCLASSEX wc = {};
|
WNDCLASSEX wc = {};
|
||||||
wc.cbSize = sizeof(WNDCLASSEX);
|
wc.cbSize = sizeof(WNDCLASSEX);
|
||||||
wc.style = CS_HREDRAW | CS_VREDRAW;
|
wc.style = CS_HREDRAW | CS_VREDRAW;
|
||||||
wc.lpfnWndProc = WindowProc;
|
wc.lpfnWndProc = WindowProc;
|
||||||
wc.hInstance = hInstance;
|
wc.hInstance = hInstance;
|
||||||
wc.lpszClassName = L"D3D12Test";
|
wc.lpszClassName = L"D3D12SphereTest";
|
||||||
|
|
||||||
if (!RegisterClassEx(&wc)) {
|
if (!RegisterClassEx(&wc)) {
|
||||||
MessageBox(NULL, L"Failed to register window class", L"Error", MB_OK);
|
Log("[ERROR] Failed to register window class");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create window
|
|
||||||
RECT rect = { 0, 0, gWidth, gHeight };
|
RECT rect = { 0, 0, gWidth, gHeight };
|
||||||
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);
|
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,
|
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
|
||||||
rect.right - rect.left, rect.bottom - rect.top,
|
rect.right - rect.left, rect.bottom - rect.top,
|
||||||
NULL, NULL, hInstance, NULL);
|
NULL, NULL, hInstance, NULL);
|
||||||
|
|
||||||
if (!gHWND) {
|
if (!gHWND) {
|
||||||
MessageBox(NULL, L"Failed to create window", L"Error", MB_OK);
|
Log("[ERROR] Failed to create window");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize D3D12
|
|
||||||
if (!InitD3D12()) {
|
if (!InitD3D12()) {
|
||||||
MessageBox(NULL, L"Failed to initialize D3D12", L"Error", MB_OK);
|
Log("[ERROR] Failed to initialize D3D12");
|
||||||
return -1;
|
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);
|
ShowWindow(gHWND, nShowCmd);
|
||||||
UpdateWindow(gHWND);
|
UpdateWindow(gHWND);
|
||||||
|
|
||||||
// Main loop
|
|
||||||
MSG msg = {};
|
MSG msg = {};
|
||||||
int frameCount = 0;
|
int frameCount = 0;
|
||||||
const int targetFrameCount = 30;
|
const int targetFrameCount = 30;
|
||||||
@@ -535,54 +511,62 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
|||||||
TranslateMessage(&msg);
|
TranslateMessage(&msg);
|
||||||
DispatchMessage(&msg);
|
DispatchMessage(&msg);
|
||||||
} else {
|
} else {
|
||||||
// Reset command list for this frame
|
if (frameCount > 0) {
|
||||||
|
gCommandQueue.WaitForPreviousFrame();
|
||||||
|
}
|
||||||
|
|
||||||
gCommandAllocator.Reset();
|
gCommandAllocator.Reset();
|
||||||
gCommandList.Reset();
|
gCommandList.Reset();
|
||||||
|
|
||||||
// Render
|
|
||||||
BeginRender();
|
BeginRender();
|
||||||
RenderScene();
|
|
||||||
EndRender();
|
|
||||||
|
|
||||||
// Execute
|
gCommandList.SetRootSignature(gRootSignature.GetRootSignature());
|
||||||
ExecuteCommandList();
|
gCommandList.SetPipelineState(gPipelineState.GetPipelineState());
|
||||||
|
|
||||||
// Present
|
ID3D12DescriptorHeap* heaps[] = { gSRVHeap.GetDescriptorHeap() };
|
||||||
gSwapChain.Present(0, 0);
|
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++;
|
frameCount++;
|
||||||
|
|
||||||
if (frameCount >= targetFrameCount) {
|
if (frameCount >= targetFrameCount) {
|
||||||
Log("[INFO] Reached target frame count %d - taking screenshot!", targetFrameCount);
|
ExecuteCommandList();
|
||||||
// Wait for GPU and take screenshot
|
|
||||||
WaitForGPU();
|
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;
|
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();
|
gCommandList.Shutdown();
|
||||||
gCommandAllocator.Shutdown();
|
gCommandAllocator.Shutdown();
|
||||||
gFence.Shutdown();
|
|
||||||
gSwapChain.Shutdown();
|
gSwapChain.Shutdown();
|
||||||
gDevice.Shutdown();
|
gDevice.Shutdown();
|
||||||
|
|
||||||
Logger::Get().Shutdown();
|
Logger::Get().Shutdown();
|
||||||
|
|
||||||
Log("[INFO] D3D12 Render Model Test Finished");
|
Log("[INFO] D3D12 Sphere Test Finished");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user