云南科技公司网站,公众号开发者密码怎么启用,网页设计与制作总结报告,微享网络网站建设视频参考:https://www.bilibili.com/video/BV1yom9YnEWY 这节没讲什么东西#xff0c;主要是改了一下音频的代码 后面有介绍一些alloc 和malloc,VirtualAlloc 的东西 _alloca 函数#xff08;或 alloca#xff09;分配的是栈内存#xff0c;它的特点是#xff1a; 生命周…视频参考:https://www.bilibili.com/video/BV1yom9YnEWY 这节没讲什么东西主要是改了一下音频的代码 后面有介绍一些alloc 和malloc,VirtualAlloc 的东西 _alloca 函数或 alloca分配的是栈内存它的特点是 生命周期受限于函数调用栈 栈上的内存是函数调用的一部分分配的内存会在函数返回时自动释放。因此_alloca 分配的内存只在分配它的函数的生命周期内有效。一旦函数返回栈指针会复位之前分配的内存就会被标记为可用新的函数调用可能覆盖这些内容。 使用场景 _alloca 通常用于临时数据存储例如小型缓冲区能够快速分配和释放。不适合用于需要跨函数、长期使用的数据存储因为这种内存无法脱离栈的生命周期存在。 与 malloc 的对比 malloc/free 使用堆内存heap memory生命周期由程序员管理适合长期存储。_alloca 使用栈内存stack memory生命周期由函数作用域控制适合临时、短期需求。 风险 栈溢出栈内存是有限的大量或频繁调用 _alloca 可能导致栈溢出stack overflow。悬挂指针如果返回指向 _alloca 分配的内存的指针并在外部使用访问将导致未定义行为。 代码示例
#include cstdio
#include cstdlibvoid test_alloca() {char* buffer (char*)_alloca(128); // 在栈上分配 128 字节snprintf(buffer, 128, This is temporary storage);printf(%s\n, buffer); // 输出正常// 函数返回后buffer 指向的内存无效
}int main() {char* permanent (char*)malloc(128); // 在堆上分配 128 字节snprintf(permanent, 128, This is permanent storage);test_alloca();printf(%s\n, permanent); // 输出仍正常堆内存仍有效free(permanent); // 手动释放堆内存return 0;
}总结
_alloca 分配的内存是临时的受限于栈的生命周期。如果需要长期使用或在多个函数间共享数据应使用堆内存例如 malloc。理解栈和堆的区别有助于避免常见的内存管理问题如悬挂指针和栈溢出。
game.h
#pragma once
#include cmath
#include cstdint
#include malloc.h#define internal static // 用于定义内翻译单元内部函数
#define local_persist static // 局部静态变量
#define global_variable static // 全局变量
#define Pi32 3.14159265359typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef uint64_t uint64;typedef int8_t int8;
typedef int16_t int16;
typedef int32_t int32;
typedef int64_t int64;
typedef int32 bool32;typedef float real32;
typedef double real64;// NOTE: 平台层为游戏提供的服务
// NOTE: 游戏为平台玩家提供的服务
// 这个部分未来可能扩展——例如声音处理可能在单独的线程中// 四个主要功能 - 时间管理控制器/键盘输入位图缓冲区声音缓冲区struct game_offscreen_buffer {// TODOcasey未来渲染将特别变成一个三层抽象void *Memory;// 后备缓冲区的宽度和高度int Width;int Height;int Pitch;int BytesPerPixel;
};struct game_sound_output_buffer {int SamplesPerSecond; // 采样率每秒采样48000次int SampleCount;int16 *Samples;
};// 游戏更新和渲染的主函数
internal void GameUpdateAndRender(game_offscreen_buffer *Buffer, int BlueOffset,int GreenOffset);// 三个主要功能
// 1. 时间管理Timing
// 2. 控制器/键盘输入Controller/Keyboard Input
// 3. 位图输出Bitmap Output和声音Sound
// 使用的缓冲区Buffergame.cpp
#include game.hinternal void GameOutputSound(game_sound_output_buffer *SoundBuffer,int ToneHz) {local_persist real32 tSine;int16 ToneVolume 3000;int16 *SampleOut SoundBuffer-Samples;int WavePeriod SoundBuffer-SamplesPerSecond / ToneHz;// 循环写入样本到第一段区域for (int SampleIndex 0; SampleIndex SoundBuffer-SampleCount;SampleIndex) {real32 SineValue sinf(tSine);int16 SampleValue (int16)(SineValue * ToneVolume);*SampleOut SampleValue; // 左声道*SampleOut SampleValue; // 右声道tSine 2.0f * (real32)Pi32 * 1.0f / (real32)WavePeriod;}
}// 渲染一个奇异的渐变图案
internal void
RenderWeirdGradient(game_offscreen_buffer *Buffer, int BlueOffset,int GreenOffset) { // TODO:让我们看看优化器是怎么做的uint8 *Row (uint8 *)Buffer-Memory; // 指向位图数据的起始位置for (int Y 0; Y Buffer-Height; Y) { // 遍历每一行uint32 *Pixel (uint32 *)Row; // 指向每一行的起始像素for (int X 0; X Buffer-Width; X) { // 遍历每一列uint8 Blue (X BlueOffset); // 计算蓝色分量uint8 Green (Y GreenOffset); // 计算绿色分量*Pixel ((Green 8) | Blue); // 设置当前像素的颜色}Row Buffer-Pitch; // 移动到下一行}
}internal void GameUpdateAndRender(game_offscreen_buffer *Buffer, int BlueOffset,int GreenOffset,game_sound_output_buffer *SoundBuffer,int ToneHz) {GameOutputSound(SoundBuffer, ToneHz);RenderWeirdGradient(Buffer, BlueOffset, GreenOffset);
}win32_game.cpp
// game.cpp : Defines the entry point for the application.
///**
T这不是最终版本的平台层
1. 存档位置
2. 获取自己可执行文件的句柄
3. 资源加载路径
4. 线程启动线程
5. 原始输入支持多个键盘
6. Sleep/TimeBeginPeriod
7. ClipCursor()多显示器支持
8. 全屏支持
9. WM_SETCURSOR控制光标可见性
10. QueryCancelAutoplay
11. WM_ACTIVATEAPP当我们不是活动应用程序时
12. Blit速度优化BitBlt
13. 硬件加速OpenGL或Direct3D或两者
14. GetKeyboardLayout支持法语键盘、国际化WASD键支持
只是一个部分清单
*/#include cstdint
#include dsound.h
#include memoryapi.h
#include windows.h
#include winnt.h
#include xinput.h#include game.cpp
#include game.h// 添加这个去掉重复的冗余代码
struct win32_window_dimension {int Width;int Height;
};struct win32_offscreen_buffer {BITMAPINFO Info;void *Memory;// 后备缓冲区的宽度和高度int Width;int Height;int Pitch;int BytesPerPixel;
};struct win32_sound_output {// 音频测试uint32 RunningSampleIndex; // 样本索引int16 ToneVolume; // 音量int SamplesPerSecond; // 采样率每秒采样48000次int ToneHz; // 波频率256 Hzint WavePeriod; // 波周期(样本数)int HalfWavePeriod; // 波半周期(样本数)int BytesPerSample; // 一个样本的大小int SecondaryBufferSize; // 缓冲区大小real32 tSine; // 保存当前的相位int LatencySampleCount;
};// TODO: 全局变量
// 用于控制程序运行的全局布尔变量通常用于循环条件
global_variable bool GloblaRunning;
// 用于存储屏幕缓冲区的全局变量
global_variable win32_offscreen_buffer GlobalBackbuffer;
global_variable LPDIRECTSOUNDBUFFER GlobalSecondaryBuffer;/*** param dwUserIndex // 与设备关联的玩家索引* param pState // 接收当前状态的结构体*/
#define X_INPUT_GET_STATE(name) \DWORD WINAPI name(DWORD dwUserIndex, \XINPUT_STATE *pState) // 定义一个宏将指定名称设置为// XInputGetState 函数的类型定义/*** param dwUserIndex // 与设备关联的玩家索引* param pVibration // 要发送到控制器的震动信息*/
#define X_INPUT_SET_STATE(name) \DWORD WINAPI name( \DWORD dwUserIndex, \XINPUT_VIBRATION *pVibration) // 定义一个宏将指定名称设置为// XInputSetState 函数的类型定义typedef X_INPUT_GET_STATE(x_input_get_state); // 定义了 x_input_get_state 类型为 XInputGetState// 函数的类型
typedef X_INPUT_SET_STATE(x_input_set_state); // 定义了 x_input_set_state 类型为 XInputSetState// 函数的类型// 定义一个 XInputGetState 的打桩函数返回值为
// ERROR_DEVICE_NOT_CONNECTED表示设备未连接
X_INPUT_GET_STATE(XInputGetStateStub) { //return (ERROR_DEVICE_NOT_CONNECTED);
}// 定义一个 XInputSetState 的打桩函数返回值为
// ERROR_DEVICE_NOT_CONNECTED表示设备未连接
X_INPUT_SET_STATE(XInputSetStateStub) { //return (ERROR_DEVICE_NOT_CONNECTED);
}// 设置全局变量 XInputGetState_ 和 XInputSetState_ 的初始值为打桩函数
global_variable x_input_get_state *XInputGetState_ XInputGetStateStub;
global_variable x_input_set_state *XInputSetState_ XInputSetStateStub;// 定义宏将 XInputGetState 和 XInputSetState 重新指向 XInputGetState_ 和
// XInputSetState_
#define XInputGetState XInputGetState_
#define XInputSetState XInputSetState_// 加载 XInput DLL 并获取函数地址
internal void Win32LoadXInput(void) { //HMODULE XInputLibrary LoadLibrary(xinput1_4.dll);if (!XInputLibrary) {// 如果无法加载 xinput1_4.dll则回退到 xinput1_3.dllXInputLibrary LoadLibrary(xinput1_3.dll);} else {// TODO:Diagnostic}if (XInputLibrary) { // 检查库是否加载成功XInputGetState (x_input_get_state *)GetProcAddress(XInputLibrary, XInputGetState); // 获取 XInputGetState 函数地址if (!XInputGetState) { // 如果获取失败使用打桩函数XInputGetState XInputGetStateStub;}XInputSetState (x_input_set_state *)GetProcAddress(XInputLibrary, XInputSetState); // 获取 XInputSetState 函数地址if (!XInputSetState) { // 如果获取失败使用打桩函数XInputSetState XInputSetStateStub;}} else {// TODO:Diagnostic}
}#define DIRECT_SOUND_CREATE(name) \HRESULT WINAPI name(LPCGUID pcGuidDevice, LPDIRECTSOUND *ppDS, \LPUNKNOWN pUnkOuter);
// 定义一个宏用于声明 DirectSound 创建函数的原型typedef DIRECT_SOUND_CREATE(direct_sound_create);
// 定义一个类型别名 direct_sound_create代表
// DirectSound 创建函数internal void Win32InitDSound(HWND window, int32 SamplesPerSecond,int32 BufferSize) {// 注意: 加载 dsound.dll 动态链接库HMODULE DSoundLibrary LoadLibraryA(dsound.dll);if (DSoundLibrary) {// 注意: 获取 DirectSound 创建函数的地址// 通过 GetProcAddress 函数查找 DirectSoundCreate 函数在 dsound.dll// 中的地址并将其转换为 direct_sound_create 类型的函数指针direct_sound_create *DirectSoundCreate (direct_sound_create *)GetProcAddress(DSoundLibrary,DirectSoundCreate);// 定义一个指向 IDirectSound 接口的指针并初始化为 NULLIDirectSound *DirectSound NULL;if (DirectSoundCreate SUCCEEDED(DirectSoundCreate(0,// 传入 0 作为设备 GUID表示使用默认音频设备DirectSound,// 将创建的 DirectSound 对象的指针存储到// DirectSound 变量中0// 传入 0 作为外部未知接口指针通常为 NULL))) //{// clang-format offWAVEFORMATEX WaveFormat {};WaveFormat.wFormatTag WAVE_FORMAT_PCM; // 设置格式标签为 WAVE_FORMAT_PCM表示使用未压缩的 PCM 格式WaveFormat.nChannels 2; // 设置声道数为 2表示立体声两个声道左声道和右声道WaveFormat.nSamplesPerSec SamplesPerSecond; // 采样率 表示每秒钟的样本数常见值为 44100 或 48000 等WaveFormat.wBitsPerSample 16; // 16位音频 设置每个样本的位深为 16 位WaveFormat.nBlockAlign (WaveFormat.nChannels * WaveFormat.wBitsPerSample) / 8;// 计算数据块对齐大小公式为nBlockAlign nChannels * (wBitsPerSample / 8)// 这里除以 8 是因为每个样本的大小是按字节来计算的nChannels 是声道数// wBitsPerSample 是每个样本的位数除以 8 转换为字节WaveFormat.nAvgBytesPerSec WaveFormat.nSamplesPerSec * WaveFormat.nBlockAlign;// 计算每秒的平均字节数公式为nAvgBytesPerSec nSamplesPerSec * nBlockAlign// 这表示每秒音频数据流的字节数它帮助估算缓冲区大小// clang-format on// 函数用于设置 DirectSound 的协作等级if (SUCCEEDED(DirectSound-SetCooperativeLevel(window, DSSCL_PRIORITY))) {// 注意: 创建一个主缓冲区// 使用 DirectSoundCreate 函数创建一个 DirectSound// 对象并初始化主缓冲区 具体的实现步骤可以根据实际需求补充DSBUFFERDESC BufferDescription {};BufferDescription.dwSize sizeof(BufferDescription); // 结构的大小// dwFlags设置为// DSBCAPS_PRIMARYBUFFER指定我们要创建的是主缓冲区而不是次缓冲区。BufferDescription.dwFlags DSBCAPS_PRIMARYBUFFER;LPDIRECTSOUNDBUFFER PrimaryBuffer NULL;if (SUCCEEDED(DirectSound-CreateSoundBuffer(BufferDescription, // 指向缓冲区描述结构体的指针PrimaryBuffer, // 指向创建的缓冲区对象的指针NULL // 外部未知接口通常传入 NULL))) {if (SUCCEEDED(PrimaryBuffer-SetFormat(WaveFormat))) {// NOTE:we have finally set the formatOutputDebugString(SetFormat 成功);} else {// NOTE:OutputDebugString(SetFormat 失败);}} else {}} else {}// 注意: 创建第二个缓冲区// 创建次缓冲区来承载音频数据并在播放时使用// 对象并初始化主缓冲区 具体的实现步骤可以根据实际需求补充DSBUFFERDESC BufferDescription {};BufferDescription.dwSize sizeof(BufferDescription); // 结构的大小// dwFlags设置为// DSBCAPS_GETCURRENTPOSITION2 |// DSBCAPS_GLOBALFOCUS两个标志会使次缓冲区在播放时更加精确同时在应用失去焦点时保持音频输出BufferDescription.dwFlags DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;BufferDescription.dwBufferBytes BufferSize; // 缓冲区大小BufferDescription.lpwfxFormat WaveFormat; // 指向音频格式的指针if (SUCCEEDED(DirectSound-CreateSoundBuffer(BufferDescription, // 指向缓冲区描述结构体的指针GlobalSecondaryBuffer, // 指向创建的缓冲区对象的指针NULL // 外部未知接口通常传入 NULL))) {OutputDebugString(SetFormat 成功);} else {OutputDebugString(SetFormat 失败);}// 注意: 开始播放!// 调用相应的 DirectSound API 开始播放音频} else {}} else {}
}internal win32_window_dimension Win32GetWindowDimension(HWND Window) {win32_window_dimension Result;RECT ClientRect;GetClientRect(Window, ClientRect);// 计算绘制区域的宽度和高度Result.Height ClientRect.bottom - ClientRect.top;Result.Width ClientRect.right - ClientRect.left;return Result;
}// 这个函数用于重新调整 DIB设备独立位图大小
internal void Win32ResizeDIBSection(win32_offscreen_buffer *Buffer, int width,int height) {// device independent bitmap设备独立位图// TODO: 进一步优化代码的健壮性// 可能的改进先不释放先尝试其他方法再如果失败再释放。if (Buffer-Memory) {VirtualFree(Buffer-Memory, // 指定要释放的内存块起始地址0, // 要释放的大小字节对部分释放有效整体释放则设为 0MEM_RELEASE); // MEM_RELEASE释放整个内存块将内存和地址空间都归还给操作系统}// 赋值后备缓冲的宽度和高度Buffer-Width width;Buffer-Height height;Buffer-BytesPerPixel 4;// 设置位图信息头BITMAPINFOHEADERBuffer-Info.bmiHeader.biSize sizeof(BITMAPINFOHEADER); // 位图头大小Buffer-Info.bmiHeader.biWidth Buffer-Width; // 设置位图的宽度Buffer-Info.bmiHeader.biHeight -Buffer-Height; // 设置位图的高度负号表示自上而下的方向Buffer-Info.bmiHeader.biPlanes 1; // 设置颜色平面数通常为 1Buffer-Info.bmiHeader.biBitCount 32; // 每像素的位数这里为 32 位即 RGBABuffer-Info.bmiHeader.biCompression BI_RGB; // 无压缩直接使用 RGB 颜色模式// 创建 DIBSection设备独立位图并返回句柄// TODO我们可以自己分配int BitmapMemorySize (Buffer-Width * Buffer-Height) * Buffer-BytesPerPixel;Buffer-Memory VirtualAlloc(0, // lpAddress指定内存块的起始地址。// 通常设为 NULL由系统自动选择一个合适的地址。BitmapMemorySize, // 要分配的内存大小单位是字节。MEM_COMMIT, // 分配物理内存并映射到虚拟地址。已提交的内存可以被进程实际访问和操作。PAGE_READWRITE // 内存可读写);Buffer-Pitch width * Buffer-BytesPerPixel; // 每一行的字节数// TODO:可能会把它清除成黑色
}// 这个函数用于将 DIBSection 绘制到窗口设备上下文
internal void Win32DisplayBufferInWindow(HDC DeviceContext, int WindowWidth,int WindowHeight,win32_offscreen_buffer Buffer, int X,int Y, int Width, int Height) {// 使用 StretchDIBits 将 DIBSection 绘制到设备上下文中StretchDIBits(DeviceContext, // 目标设备上下文窗口或屏幕的设备上下文/*X, Y, Width, Height, // 目标区域的 x, y 坐标及宽高X, Y, Width, Height,*/0, 0, WindowWidth, WindowHeight, //0, 0, Buffer.Width, Buffer.Height, //// 源区域的 x, y 坐标及宽高此处源区域与目标区域相同Buffer.Memory, // 位图内存指针指向 DIBSection 数据Buffer.Info, // 位图信息包含位图的大小、颜色等信息DIB_RGB_COLORS, // 颜色类型使用 RGB 颜色SRCCOPY); // 使用 SRCCOPY 操作符进行拷贝即源图像直接拷贝到目标区域
}LRESULT CALLBACK
Win32MainWindowCallback(HWND hwnd, // 窗口句柄表示消息来源的窗口UINT Message, // 消息标识符表示当前接收到的消息类型WPARAM wParam, // 与消息相关的附加信息取决于消息类型LPARAM LParam) { // 与消息相关的附加信息取决于消息类型LRESULT Result 0; // 定义一个变量来存储消息处理的结果switch (Message) { // 根据消息类型进行不同的处理case WM_CREATE: {OutputDebugStringA(WM_CREATE\n);};case WM_SIZE: { // 窗口大小发生变化时的消息} break;case WM_DESTROY: { // 窗口销毁时的消息// TODO: 处理错误用重建窗口GloblaRunning false;} break;case WM_SYSKEYDOWN: // 系统按键按下消息例如 Alt 键组合。case WM_SYSKEYUP: // 系统按键释放消息。case WM_KEYDOWN: // 普通按键按下消息。case WM_KEYUP: { // 普通按键释放消息。uint64 VKCode wParam; // wParam 包含按键的虚拟键码Virtual-Key Codebool WasDown ((LParam (1 30)) ! 0);bool IsDown ((LParam (1 30)) 0);bool32 AltKeyWasDown (LParam (1 29)); // 检查Alt键是否被按下// bool AltKeyWasDown ((LParam (1 29)) ! 0); //// 检查Alt键是否被按下if (IsDown ! WasDown) {if (VKCode W) { // 检查是否按下了 W 键} else if (VKCode A) {} else if (VKCode S) {} else if (VKCode D) {} else if (VKCode Q) {} else if (VKCode E) {} else if (VKCode VK_UP) {} else if (VKCode VK_DOWN) {} else if (VKCode VK_LEFT) {} else if (VKCode VK_RIGHT) {} else if (VKCode VK_ESCAPE) {OutputDebugStringA(ESCAPE: );if (IsDown) {OutputDebugString( IsDown );}if (WasDown) {OutputDebugString( WasDown );}} else if (VKCode VK_SPACE) {}}if ((VKCode VK_F4) AltKeyWasDown) {GloblaRunning false;}} break;case WM_CLOSE: { // 窗口关闭时的消息// TODO: 像用户发送消息进行处理GloblaRunning false;} break;case WM_ACTIVATEAPP: { // 应用程序激活或失去焦点时的消息OutputDebugStringA(WM_ACTIVATEAPP\n); // 输出调试信息表示应用程序激活或失去焦点} break;case WM_PAINT: { // 处理 WM_PAINT 消息通常在窗口需要重新绘制时触发PAINTSTRUCT Paint; // 定义一个 PAINTSTRUCT 结构体保存绘制的信息// 调用 BeginPaint 开始绘制并获取设备上下文 (HDC)同时填充 Paint 结构体HDC DeviceContext BeginPaint(hwnd, Paint);// 获取当前绘制区域的左上角坐标int X Paint.rcPaint.left;int Y Paint.rcPaint.top;// 计算绘制区域的宽度和高度int Height Paint.rcPaint.bottom - Paint.rcPaint.top;int Width Paint.rcPaint.right - Paint.rcPaint.left;win32_window_dimension Dimension Win32GetWindowDimension(hwnd);Win32DisplayBufferInWindow(DeviceContext, Dimension.Width, Dimension.Height,GlobalBackbuffer, X, Y, Width, Height);// 调用 EndPaint 结束绘制并释放设备上下文EndPaint(hwnd, Paint);} break;default: { // 对于不处理的消息调用默认的窗口过程Result DefWindowProc(hwnd, Message, wParam, LParam);// 调用默认窗口过程处理消息} break;}return Result; // 返回处理结果
}
internal void Win32ClearBuffer(win32_sound_output *SoundOutput) {VOID *Region1; // 第一段区域指针用于存放锁定后的首部分缓冲区地址DWORD Region1Size; // 第一段区域的大小字节数VOID *Region2; // 第二段区域指针用于存放锁定后的剩余部分缓冲区地址DWORD Region2Size; // 第二段区域的大小字节数if (SUCCEEDED(GlobalSecondaryBuffer-Lock(0, // 缓冲区偏移量指定开始锁定的字节位置SoundOutput-SecondaryBufferSize, // 锁定的字节数指定要锁定的区域大小Region1, // 输出返回锁定区域的内存指针第一个区域Region1Size, // 输出返回第一个锁定区域的实际字节数Region2, // 输出返回第二个锁定区域的内存指针可选双缓冲或环形缓冲时使用Region2Size, // 输出返回第二个锁定区域的实际字节数0 // 标志控制锁定行为如从光标位置锁定等))) {int8 *DestSample (int8 *)Region1; // 将第一段区域指针转换为 16// 位整型指针准备写入样本数据// 循环写入样本到第一段区域for (DWORD ByteIndex 0; ByteIndex Region1Size; ByteIndex) {*DestSample 0;}for (DWORD ByteIndex 0; ByteIndex Region2Size; ByteIndex) {*DestSample 0;}GlobalSecondaryBuffer-Unlock(Region1, Region1Size, //Region2, Region2Size);}
}
internal void Win32FillSoundBuffer(win32_sound_output *SoundOutput,DWORD ByteToLock, DWORD BytesToWrite,game_sound_output_buffer *SourceBuffer) {VOID *Region1; // 第一段区域指针用于存放锁定后的首部分缓冲区地址DWORD Region1Size; // 第一段区域的大小字节数VOID *Region2; // 第二段区域指针用于存放锁定后的剩余部分缓冲区地址DWORD Region2Size; // 第二段区域的大小字节数if (SUCCEEDED(GlobalSecondaryBuffer-Lock(ByteToLock, // 缓冲区偏移量指定开始锁定的字节位置BytesToWrite, // 锁定的字节数指定要锁定的区域大小Region1, // 输出返回锁定区域的内存指针第一个区域Region1Size, // 输出返回第一个锁定区域的实际字节数Region2, // 输出返回第二个锁定区域的内存指针可选双缓冲或环形缓冲时使用Region2Size, // 输出返回第二个锁定区域的实际字节数0 // 标志控制锁定行为如从光标位置锁定等))) {// int16 int16 int16// 左 右 左 右 左 右 左 右 左 右DWORD Region1SampleCount Region1Size / SoundOutput-BytesPerSample; // 计算第一段区域中的样本数量int16 *DestSample (int16 *)Region1; // 将第一段区域指针转换为 16// 位整型指针准备写入样本数据int16 *SourceSample SourceBuffer-Samples;// 循环写入样本到第一段区域for (DWORD SampleIndex 0; SampleIndex Region1SampleCount;SampleIndex) {*DestSample *SourceSample; // 左声道*DestSample *SourceSample; // 右声道SoundOutput-RunningSampleIndex;}DWORD Region2SampleCount Region2Size / SoundOutput-BytesPerSample; // 计算第二段区域中的样本数量DestSample (int16 *)Region2; // 将第二段区域指针转换为 16// 位整型指针准备写入样本数据// 循环写入样本到第二段区域for (DWORD SampleIndex 0; SampleIndex Region2SampleCount;SampleIndex) {// 使用相同逻辑生成方波样本数据*DestSample *SourceSample; // 左声道*DestSample *SourceSample; // 右声道SoundOutput-RunningSampleIndex;}// 解锁音频缓冲区将数据提交给音频设备GlobalSecondaryBuffer-Unlock(Region1, Region1Size, Region2, Region2Size);}
}int CALLBACK WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, //PSTR cmdline, int cmdshow) {LARGE_INTEGER PerfCountFrequencyResult;QueryPerformanceFrequency(PerfCountFrequencyResult);int64 PerfCountFrequency PerfCountFrequencyResult.QuadPart;Win32LoadXInput(); // 加载 XInput 库用于处理 Xbox 控制器输入WNDCLASS WindowClass {}; // 初始化窗口类结构默认值为零// 使用大括号初始化所有成员都被初始化为零0或 nullptrWin32ResizeDIBSection(GlobalBackbuffer, 1280,720); // 调整 DIB设备独立位图大小// WindowClass.style表示窗口类的样式。通常设置为一些 Windows// 窗口样式标志例如 CS_HREDRAW, CS_VREDRAW。WindowClass.style CS_OWNDC | CS_HREDRAW | CS_VREDRAW;// CS_HREDRAW 当窗口的宽度发生变化时窗口会被重绘。// CS_VREDRAW 当窗口的高度发生变化时窗口会被重绘// WindowClass.lpfnWndProc指向窗口过程函数的指针窗口过程用于处理与窗口相关的消息。WindowClass.lpfnWndProc Win32MainWindowCallback;// WindowClass.hInstance指定当前应用程序的实例句柄Windows// 应用程序必须有一个实例句柄。WindowClass.hInstance hInst;// WindowClass.lpszClassName指定窗口类的名称通常用于创建窗口时注册该类。WindowClass.lpszClassName gameWindowClass; // 类名if (RegisterClass(WindowClass)) { // 如果窗口类注册成功HWND Window CreateWindowEx(0, // 创建窗口使用扩展窗口风格WindowClass.lpszClassName, // 窗口类的名称指向已注册的窗口类game, // 窗口标题窗口的名称WS_OVERLAPPEDWINDOW |WS_VISIBLE, // 窗口样式重叠窗口带有菜单、边框等并且可见CW_USEDEFAULT, // 窗口的初始位置使用默认位置X坐标CW_USEDEFAULT, // 窗口的初始位置使用默认位置Y坐标CW_USEDEFAULT, // 窗口的初始宽度使用默认宽度CW_USEDEFAULT, // 窗口的初始高度使用默认高度0, // 父窗口句柄此处无父窗口传00, // 菜单句柄此处没有菜单传0hInst, // 当前应用程序的实例句柄0 // 额外的创建参数此处没有传递额外参数);// 如果窗口创建成功Window 将保存窗口的句柄if (Window) { // 检查窗口句柄是否有效若有效则进入消息循环// 图像测试int xOffset 0;int yOffset 0;win32_sound_output SoundOutput {}; // 初始化声音输出结构体// 音频测试SoundOutput.RunningSampleIndex 0; // 样本索引SoundOutput.ToneVolume 3000; // 音量SoundOutput.SamplesPerSecond 48000; // 采样率每秒采样48000次SoundOutput.ToneHz 256; // 波频率256 HzSoundOutput.WavePeriod SoundOutput.SamplesPerSecond / SoundOutput.ToneHz; // 波周期(样本数)SoundOutput.HalfWavePeriod SoundOutput.WavePeriod / 2; // 波半周期(样本数)SoundOutput.BytesPerSample sizeof(int16) * 2; // 一个样本的大小SoundOutput.SecondaryBufferSize SoundOutput.SamplesPerSecond *SoundOutput.BytesPerSample; // 缓冲区大小SoundOutput.LatencySampleCount SoundOutput.SamplesPerSecond / 15;int16 *Samples (int16 *)VirtualAlloc(0, 48000 * 2 * sizeof(int16),MEM_RESERVE | MEM_COMMIT,PAGE_READWRITE); //[48000 * 2];Win32InitDSound(Window, SoundOutput.SamplesPerSecond,SoundOutput.SecondaryBufferSize); // 初始化 DirectSoundWin32ClearBuffer(SoundOutput);bool32 SoundIsPlaying false;GloblaRunning true;LARGE_INTEGER LastCounter; // 保留上次计数器的值QueryPerformanceCounter(LastCounter);int64 LastCycleCount __rdtsc();while (GloblaRunning) { // 启动一个无限循环等待和处理消息MSG Message; // 声明一个 MSG 结构体用于接收消息while (PeekMessage(Message,// 指向一个 MSG 结构的指针。PeekMessage// 将在 lpMsg 中填入符合条件的消息内容。0,// hWnd 为NULL则检查当前线程中所有窗口的消息// 如果设置为特定的窗口句柄则只检查该窗口的消息。0, //0, // 用于设定消息类型的范围PM_REMOVE // 将消息从消息队列中移除类似于 GetMessage 的行为。)) {if (Message.message WM_QUIT) {GloblaRunning false;}TranslateMessage(Message); // 翻译消息如果是键盘消息需要翻译DispatchMessage(Message); // 分派消息调用窗口过程处理消息}// TODO: 我们应该频繁的轮询吗for (DWORD ControllerIndex 0; ControllerIndex XUSER_INDEX_ANY;ControllerIndex) {// 定义一个 XINPUT_STATE 结构体用来存储控制器的状态XINPUT_STATE ControllerState;// 调用 XInputGetState 获取控制器的状态if (XInputGetState(ControllerIndex, ControllerState) ERROR_SUCCESS) {// 如果获取控制器状态成功提取 Gamepad 的数据// NOTE:// 获取方向键的按键状态XINPUT_GAMEPAD *Pad ControllerState.Gamepad;bool Up (Pad-wButtons XINPUT_GAMEPAD_DPAD_UP);bool Down (Pad-wButtons XINPUT_GAMEPAD_DPAD_DOWN);bool Left (Pad-wButtons XINPUT_GAMEPAD_DPAD_LEFT);bool Right (Pad-wButtons XINPUT_GAMEPAD_DPAD_RIGHT);// 获取肩部按钮的按键状态bool LeftShoulder (Pad-wButtons XINPUT_GAMEPAD_LEFT_SHOULDER);bool RightShoulder (Pad-wButtons XINPUT_GAMEPAD_RIGHT_SHOULDER);// 获取功能按钮的按键状态bool Start (Pad-wButtons XINPUT_GAMEPAD_START);bool Back (Pad-wButtons XINPUT_GAMEPAD_BACK);bool AButton (Pad-wButtons XINPUT_GAMEPAD_A);bool BButton (Pad-wButtons XINPUT_GAMEPAD_B);bool XButton (Pad-wButtons XINPUT_GAMEPAD_X);bool YButton (Pad-wButtons XINPUT_GAMEPAD_Y);// 获取摇杆的 X 和 Y 坐标值-32768 到 32767int16 StickX Pad-sThumbLX;int16 StickY Pad-sThumbLY;// 根据摇杆的 Y 坐标值调整音调和声音xOffset StickX 12;yOffset StickY 12;// 更新音调频率 (ToneHz)通过摇杆的 Y 值来调节// 这里是将 StickY 映射到频率范围内使得频率与摇杆的上下运动相关。// 512 是基准频率StickY 值影响音频频率的变化范围。SoundOutput.ToneHz 512 (int)(256.0f * ((real32)StickY / 30000.0f));// 计算波周期基于频率决定波形的周期SoundOutput.WavePeriod SoundOutput.SamplesPerSecond / SoundOutput.ToneHz;}}DWORD ByteToLock;DWORD PlayCursor 0; // 播放游标指示当前播放位置DWORD WriteCursor 0; // 写入游标指示当前写入位置DWORD TargetCursor 0;bool32 SoundIsValid false;DWORD BytesToWrite 0; // 需要写入的字节数// 获取音频缓冲区的当前播放和写入位置if (SUCCEEDED(GlobalSecondaryBuffer-GetCurrentPosition(PlayCursor, WriteCursor))) {ByteToLock ((SoundOutput.RunningSampleIndex * SoundOutput.BytesPerSample) %SoundOutput.SecondaryBufferSize);TargetCursor (PlayCursor (SoundOutput.LatencySampleCount *SoundOutput.BytesPerSample)) %SoundOutput.SecondaryBufferSize;// 判断 ByteToLock 与 TargetCursor 的位置关系以确定写入量if (ByteToLock TargetCursor) {// 如果锁定位置正好等于播放位置写入整个缓冲区BytesToWrite 0;} else if (ByteToLock TargetCursor) {// 如果锁定位置在播放位置之后写入从锁定位置到缓冲区末尾再加上开头到播放位置的字节数BytesToWrite (SoundOutput.SecondaryBufferSize - ByteToLock) TargetCursor;} else {// 如果锁定位置在播放位置之前写入从锁定位置到播放位置之间的字节数BytesToWrite TargetCursor - ByteToLock;}SoundIsValid true;}if (!SoundIsPlaying) {GlobalSecondaryBuffer-Play(0, 0, DSBPLAY_LOOPING);SoundIsPlaying true;}game_sound_output_buffer SoundBuffer {};SoundBuffer.SamplesPerSecond SoundOutput.SamplesPerSecond;SoundBuffer.SampleCount BytesToWrite / SoundOutput.BytesPerSample;SoundBuffer.Samples Samples;game_offscreen_buffer Buffer {};Buffer.Memory GlobalBackbuffer.Memory;Buffer.Width GlobalBackbuffer.Width;Buffer.Height GlobalBackbuffer.Height;Buffer.Pitch GlobalBackbuffer.Pitch;GameUpdateAndRender(Buffer, xOffset, yOffset, SoundBuffer,SoundOutput.ToneHz);if (SoundIsValid) {Win32FillSoundBuffer(SoundOutput, ByteToLock, BytesToWrite,SoundBuffer);// 计算需要锁定的字节位置基于当前样本索引和每样本字节数}// 这个地方需要渲染一下不然是黑屏a{HDC DeviceContext GetDC(Window);win32_window_dimension Dimension Win32GetWindowDimension(Window);RECT WindowRect;GetClientRect(Window, WindowRect);int WindowWidth WindowRect.right - WindowRect.left;int WindowHeigh WindowRect.bottom - WindowRect.top;Win32DisplayBufferInWindow(DeviceContext, Dimension.Width,Dimension.Height, GlobalBackbuffer, 0, 0,WindowWidth, WindowHeigh);ReleaseDC(Window, DeviceContext);}int64 EndCycleCount __rdtsc();LARGE_INTEGER EndCounter;QueryPerformanceCounter(EndCounter);// TODO: 显示结果int64 CyclesElapsed EndCycleCount - LastCycleCount;int64 CounterElapsed EndCounter.QuadPart - LastCounter.QuadPart;real32 MillisecondPerFrame (real32)((1000.f * (real32)CounterElapsed) /(real32)PerfCountFrequency);real32 FPS (real32)PerfCountFrequency / (real32)CounterElapsed;real32 MCPF (real32)CyclesElapsed / (1000.0f * 1000.0f);
#if 0char Buffer[256];sprintf_s(Buffer, %fms/f, %ff/s, %fmc/f\n, MillisecondPerFrame, FPS,MCPF);OutputDebugString(Buffer);
#endifLastCounter EndCounter;LastCycleCount EndCycleCount;}} else { // 如果窗口创建失败// 这里可以处理窗口创建失败的逻辑// 比如输出错误信息或退出程序等// TODO:}} else { // 如果窗口类注册失败// 这里可以处理注册失败的逻辑// 比如输出错误信息或退出程序等// TODO:}return 0;
}