梧州市网站建设_网站建设公司_腾讯云_seo优化
2025/12/21 21:40:57 网站建设 项目流程

什么是 Direct3D 12?

DirectX 12 引入了 Direct3D 的下一个版本,即 DirectX 的核心 3D 图形 API。

此版本的 Direct3D 比任何以前的版本更快、更高效。

Direct3D 12 可实现更丰富的场景、更多的对象、更复杂的效果,以及充分利用现代 GPU 硬件。

若要为 Windows 10 和 Windows 10 移动版编写 3D 游戏和应用,必须了解 Direct3D 12 技术的基础知识,以及如何准备在游戏和应用中使用它。

D3DApp初始化

Windows函数调用过程

调用者(caller) → 参数准备 → call指令 → 被调用者(callee)执行 → 返回 → 栈清理

栈的基本原理

想象栈就像一个临时工作台:

调用函数时:把参数"放"到工作台上

函数执行时:从工作台"拿"参数使用

函数结束后:需要把工作台"清理干净"

规则:

调用Windows API时:不用管清理,Windows会处理

写回调函数时:必须用__stdcall,并在返回时清理栈

调用C运行时函数时:编译器会自动帮"我"清理栈

写C++成员函数时:编译器自动处理,不用操心

"我"调用Windows → Windows清理

Windows调用"我" → "我"清理

"我"调用C运行时 → "我"清理(编译器帮忙)

"我"的C++方法 → "我"清理(编译器自动处理)

如果不清理会怎样?

void FunctionA(int x, int y) {

// 使用x,y...

}

void FunctionB(int a, int b, int c) {

// 使用a,b,c...

}

int main() {

FunctionA(1, 2); // 栈上放了 [1, 2]

// 如果不清理,栈上还有 [1, 2]

FunctionB(3, 4, 5); // 栈变成 [3, 4, 5, 1, 2] ← 混乱!

}

清理栈就是调整栈指针(ESP),让栈回到函数调用前的状态:

调用前:ESP指向位置X

调用时:push参数 → ESP移动到位置Y (Y < X)

清理后:ESP回到位置X

// ✅ 正确:调用Windows API(不用管清理)

MessageBox(NULL, "Text", "Title", MB_OK);

// ✅ 正确:写回调函数(用CALLBACK宏)

LRESULT CALLBACK MyCallback(HWND, UINT, WPARAM, LPARAM);

// ✅ 正确:调用可变参数函数(编译器自动清理)

printf("Values: %d %d", x, y);

// ❌ 错误:回调函数不用__stdcall

LRESULT MyBadCallback(HWND, UINT, WPARAM, LPARAM); // 会崩溃!

机制举例

__cdecl - "我请客,我收拾"

// "我"调用printf(Windows的C运行时库)

printf("Count: %d %d", 10, 20);

; "我"调用printf后

push offset text ; 参数3

push value ; 参数2

push num ; 参数1

push offset format ; 参数0

call printf

add esp, 16 ; ⭐"我"清理栈:4个参数×4字节

分工:

"我":放参数 + 清理栈

Windows/CRT:只用参数,不清理

__stdcall - "Windows服务,Windows收拾"

// "我"调用Windows API

CreateWindow(className, title, style, x, y, width, height, ...);

; "我"调用CreateWindow后

push 0 ; 参数11

push hInstance ; 参数10

; ... 更多参数 ...

push className ; 参数1

call CreateWindowEx

; ⭐没有add esp! Windows会自己清理

分工:

"我":放参数

Windows:用参数 + 清理栈

__stdcall回调 - "Windows调用,我收拾"

// Windows调用"我"的回调

LRESULT CALLBACK MyWindowProc(HWND, UINT, WPARAM, LPARAM);

; Windows调用"我"的WndProc

_WndProc@16:

; "我"的处理逻辑...

ret 16 ; ⭐"我"清理16字节参数

分工:

Windows:放参数

"我":用参数 + 清理栈

__fastcall - "快速服务,Windows收拾"

// 假设是Windows的某个性能API

int __fastcall FastAPI(int a, int b, int c);

分工:

"我":前两个参数放寄存器,其余放栈

Windows:用参数 + 清理栈上的参数

性能优化:寄存器比内存快

Windows负责清理,保持API一致性

__thiscall - "对象方法,我收拾"

// "我"的C++类

class MyClass {

public:

void Method(int param); // 自动__thiscall

};

分工:

"我":this放寄存器,参数放栈

"我":用参数 + 清理栈

C++对象模型,"我"完全控制自己的类

编译器自动处理,对"我"透明

创建一个Windows窗口

image

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

// Forward hwnd on because we can get messages (e.g., WM_CREATE)

// before CreateWindow returns, and thus before mhMainWnd is valid.

//转发消息给 D3DApp::GetApp()->MsgProc()

return D3DApp::GetApp()->MsgProc(hwnd, msg, wParam, lParam);

}

bool D3DApp::InitMainWindow()

{

// 设置窗口类属性

WNDCLASS wc;

// 窗口尺寸变化时重绘 ,CS_HREDRAW 宽度改变时重绘 ,CS_VREDRAW 高度改变时重绘

wc.style = CS_HREDRAW | CS_VREDRAW;

// 建立消息处理回调机制,所有发送到此窗口的消息都由该函数处理 , 在代码中,MainWndProc 转发消息给 D3DApp::GetApp()->MsgProc()

wc.lpfnWndProc = MainWndProc;

// 应用程序实例句柄

wc.hInstance = mhAppInst;

// 额外的类内存 ,用于存储自定义数据,这里设为0表示不需要

wc.cbClsExtra = 0;

// 额外的窗口内存字节数 ,用于存储自定义数据,这里设为0表示不需要

wc.cbWndExtra = 0;

// 加载系统预定义的应用程序图标

wc.hIcon = LoadIcon(0, IDI_APPLICATION);

// 加载系统预定义的箭头光标

wc.hCursor = LoadCursor(0, IDC_ARROW);

// 窗口背景画刷 NULL_BRUSH 表示不绘制背景,适合DirectX应用(因为DirectX会完全覆盖客户区).

避免GDI与DirectX绘制冲突

wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);

// 设为0表示此窗口类没有菜单

wc.lpszMenuName = 0;

// 窗口类名称,用于在系统中唯一标识此窗口类

wc.lpszClassName = L"MainWnd";

//将定义好的窗口类注册到操作系统中. Windows机制:系统内部维护一个窗口类表;注册成功后,该类可用于创建多个窗口实例;失败通常是因为类名重复或参数无效.

if( !RegisterClass(&wc) )

{

MessageBox(0, L"RegisterClass Failed.", 0, 0);

return false;

}

// Compute window rectangle dimensions based on requested client area dimensions.

/*

窗口尺寸计算:

客户区 (Client Area):应用程序实际可绘制内容的区域(mClientWidth × mClientHeight)

窗口矩形 (Window Rect):包含标题栏、边框、菜单等的完整窗口

AdjustWindowRect 根据窗口样式自动计算转换关系 .

┌─────────────────────────┐

│ 标题栏 (非客户区) │

├────────────┬────────────┤

│ │ │

│ │ │

│ 客户区 │ 滚动条 │

│ │ (非客户区) │

│ │ │

└────────────┴────────────┘

原始客户区: (0,0) 到 (800,600)

AdjustWindowRect 调整后: 可能变成 (-8,-31) 到 (808,631)

最终窗口尺寸: 816 × 662 像素

*/

RECT R = { 0, 0, mClientWidth, mClientHeight };

AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);

int width = R.right - R.left;

int height = R.bottom - R.top;

/* 窗口创建:

L"MainWnd":窗口类名,必须与注册的类名一致 | WS_OVERLAPPEDWINDOW:窗口样式,包含标题栏、系统菜单、最小化/最大化按钮、可调整边框

CW_USEDEFAULT, CW_USEDEFAULT:窗口初始位置,让系统自动选择

Windows创建机制:系统分配内部窗口数据结构 | 发送 WM_CREATE 等初始化消息 | 返回唯一的窗口句柄 mhMainWnd

*/

mhMainWnd = CreateWindow(L"MainWnd", mMainWndCaption.c_str(),

WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, 0, 0, mhAppInst, 0);

if( !mhMainWnd )

{

MessageBox(0, L"CreateWindow Failed.", 0, 0);

return false;

}

//改变窗口可视状态为显示

ShowWindow(mhMainWnd, SW_SHOW);

//强制发送 WM_PAINT 消息,立即重绘窗口

UpdateWindow(mhMainWnd);

return true;

}

image

MainWndProc 相关API

https://learn.microsoft.com/zh-cn/windows/win32/winmsg/window-notifications

https://learn.microsoft.com/zh-cn/windows/win32/api/_winmsg/

PeekMessage: 检查线程消息队列中是否有消息,如果有消息 将消息复制到提供的msg结构中,PM_REMOVE标志表示从队列中移除该消息

LRESULT D3DApp::MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

switch(msg)

{

if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))

{

//...

}

// 各种消息处理 case

// ...

}

return DefWindowProc(hwnd, msg, wParam, lParam);

}

窗口消息

WM_ACTIVATE- 窗口激活状态变化

case WM_ACTIVATE:

//LOWORD(wParam):低16位表示激活状态

//WA_INACTIVE:窗口变为非活动状态

//失活时暂停,激活时恢复,智能暂停机制提升系统整体性能

if( LOWORD(wParam) == WA_INACTIVE )

{

mAppPaused = true;

mTimer.Stop();

}

else

{

mAppPaused = false;

mTimer.Start();

}

return 0;

WM_SIZE- 窗口尺寸变化

LOWORD 和 HIWORD 是 Windows API 中的宏定义,用于从一个 32 位值中提取低 16 位和高 16 位部分。

case WM_SIZE:

mClientWidth = LOWORD(lParam); // 新宽度

mClientHeight = HIWORD(lParam); // 新高度

• 低 16 位 (LOWORD) 存储新宽度(以像素为单位)

• 高 16 位 (HIWORD) 存储新高度(以像素为单位)

SIZE_MINIMIZED最小化情况,完全暂停应用程序,不进行任何渲染

SIZE_MAXIMIZED最大化情况,立即调整D3D资源适应新尺寸

SIZE_RESTORED恢复情况(最复杂)

else if( wParam == SIZE_RESTORED )

{

// 从最小化恢复

if( mMinimized )

{

mAppPaused = false;

mMinimized = false;

OnResize();

}

// 从最大化恢复

else if( mMaximized )

{

mAppPaused = false;

mMaximized = false;

OnResize();

}

// 用户正在拖拽调整大小

//拖拽时不立即调整,避免频繁资源重建

else if( mResizing )

{

// 故意不调用OnResize() - 性能优化

}

// 程序化尺寸改变

else

{

OnResize();

}

}

WM_ENTERSIZEMOVE / WM_EXITSIZEMOVE - 调整大小过程管理

WM_ENTERSIZEMOVE:开始拖拽,设置标志位暂停调整

WM_EXITSIZEMOVE:结束拖拽,清除标志位并执行最终调整

case WM_ENTERSIZEMOVE:

mAppPaused = true;

mResizing = true;

mTimer.Stop();

return 0;

case WM_EXITSIZEMOVE:

mAppPaused = false;

mResizing = false;

mTimer.Start();

OnResize(); // 拖拽结束后一次性调整

return 0;

WM_DESTROY - 窗口销毁

发送 WM_QUIT 到消息队列,导致 Run() 中的主循环退出

PostQuitMessage(0) → 系统消息队列 → 线程消息队列 → PeekMessage() → msg变量

Windows消息系统架构

系统消息队列 (全局)

线程消息队列 (每个线程独立)

PeekMessage/GetMessage (应用程序检索)

case WM_DESTROY:

PostQuitMessage(0);

return 0;

int D3DApp::Run()

{

MSG msg = {0};

mTimer.Reset();

while(msg.message != WM_QUIT)

{

if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))

{

TranslateMessage( &msg );

DispatchMessage( &msg );

}

return (int)msg.wParam;

}

WM_MENUCHAR- 菜单字符处理

处理 Alt+Enter 等组合键,避免系统蜂鸣声

MNC_CLOSE表示关闭菜单而不发出蜂鸣

case WM_MENUCHAR:

return MAKELRESULT(0, MNC_CLOSE);

WM_GETMINMAXINFO- 窗口尺寸限制

设置窗口最小尺寸为 200×200,防止窗口过小导致渲染问题

case WM_GETMINMAXINFO:

((MINMAXINFO*)lParam)->ptMinTrackSize.x = 200;

((MINMAXINFO*)lParam)->ptMinTrackSize.y = 200;

return 0;

鼠标消息处理

GET_X_LPARAM(lParam):从 lParam 提取 X 坐标

GET_Y_LPARAM(lParam):从 lParam 提取 Y 坐标

wParam:按键状态(Ctrl、Shift 等)

设计模式:使用虚函数提供扩展点,派生类可重写鼠标处理

case WM_LBUTTONDOWN:

case WM_MBUTTONDOWN:

case WM_RBUTTONDOWN:

OnMouseDown(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));

return 0;

// 类似的鼠标抬起和移动处理

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询