news 2026/6/10 15:51:29

DX12-1-DirectX3D初始化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DX12-1-DirectX3D初始化

什么是 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;

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 14:04:33

企业AI落地真相:从“降本增效“到骨感现实的深度剖析

文章揭示了企业AI落地面临的现实挑战&#xff0c;指出多数企业对AI"降本增效"的期望与实际效果存在巨大差距。AI价值被过度神化&#xff0c;成为部分人博取名声的工具&#xff0c;而忽视了数据治理、质量等基础要素。企业领导认知滞后&#xff0c;仍用传统思维推动AI…

作者头像 李华
网站建设 2026/6/10 10:51:45

【课程设计/毕业设计】基于SpringBoot框架的乡村政务信息管理系统基于springboot的村务管理系统的设计与实现【附源码、数据库、万字文档】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/6/10 10:50:00

重庆三峡学院图书资料管理系统设计与实现(源码+论文+部署+安装)

感兴趣的可以先收藏起来&#xff0c;还有在毕设选题&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;我会一一回复&#xff0c;希望可以帮到大家。一、程序背景在信息化高速发展的当下&#xff0c;数字化、网络化成为现代图书馆发展的核心方向。重庆三峡…

作者头像 李华
网站建设 2026/6/10 10:46:51

我的一个oier朋友

第一部我没有意识到到我们的故事开始了。一个下午&#xff08;或是早上&#xff0c;我忘了&#xff0c;只记得阳光透过窗帘照进&#xff0c;鹅黄的色调&#xff09;&#xff0c;电脑室A&#xff0c;js。来了一个女孩&#xff0c;在我身边坐下&#xff0c;我很是开心&#xff0c…

作者头像 李华
网站建设 2026/6/10 10:53:45

Flutter官方拒绝适配鸿蒙的真相:不是技术问题,而是...

有人评论说应该是Flutter官方适配鸿蒙&#xff0c;而不是鸿蒙适配Flutter。其实这么说也是有一点道理的&#xff08;虽然不多&#xff09;&#xff0c;今天老刘就展开分析以下到底应该是谁来适配谁&#xff1f;从技术角度看&#xff1a;Flutter确实应该主动适配鸿蒙Flutter作为…

作者头像 李华
网站建设 2026/6/10 10:51:53

【模板】动态 dp 学习笔记(树剖版)

歉&#xff1a;作者是在打代码之前就完成了文字部分&#xff0c;转移方程的锅代码中修了&#xff0c;文字部分没修&#xff0c;在此致歉。【模板】动态 DP 加强版 题解该篇为题解。总文章&#xff08;动态 dp 学习笔记&#xff09;同步发表于 cnblogs。总文章&#xff08;动态 …

作者头像 李华