游戏引擎学习第12天

视频参考:https://www.bilibili.com/video/BV1yom9YnEWY
这节没讲什么东西,主要是改了一下音频的代码
后面有介绍一些alloc 和malloc,VirtualAlloc 的东西
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
_alloca 函数(或 alloca)分配的是栈内存,它的特点是:

  1. 生命周期受限于函数调用栈

    • 栈上的内存是函数调用的一部分,分配的内存会在函数返回时自动释放。因此,_alloca 分配的内存只在分配它的函数的生命周期内有效。
    • 一旦函数返回,栈指针会复位,之前分配的内存就会被标记为可用,新的函数调用可能覆盖这些内容。
  2. 使用场景

    • _alloca 通常用于临时数据存储,例如小型缓冲区,能够快速分配和释放。
    • 不适合用于需要跨函数、长期使用的数据存储,因为这种内存无法脱离栈的生命周期存在。
  3. malloc 的对比

    • malloc/free 使用堆内存(heap memory),生命周期由程序员管理,适合长期存储。
    • _alloca 使用栈内存(stack memory),生命周期由函数作用域控制,适合临时、短期需求。
  4. 风险

    • 栈溢出:栈内存是有限的,大量或频繁调用 _alloca 可能导致栈溢出(stack overflow)。
    • 悬挂指针:如果返回指向 _alloca 分配的内存的指针并在外部使用,访问将导致未定义行为。

代码示例

#include <cstdio>
#include <cstdlib>void 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 {// TODO(casey):未来,渲染将特别变成一个三层抽象!!!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)
// 使用的缓冲区(Buffer)

game.cpp

#include "game.h"internal 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;// 设置位图信息头(BITMAPINFOHEADER)Buffer->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 位(即 RGBA)Buffer->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 Code)bool 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,             // 父窗口句柄(此处无父窗口,传0)0,             // 菜单句柄(此处没有菜单,传0)hInst,         // 当前应用程序的实例句柄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 到 32767)int16 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/474119.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

django——创建 Django 项目和 APP

2.创建 Django 项目和 APP 命令&#xff1a; 创建Django项目 django-admin startproject name 创建子应用 python manager.py startapp name 2.1 创建工程 在使用Flask框架时&#xff0c;项目工程目录的组织与创建是需要我们自己手动创建完成的。 在django中&#xff0c;…

OceanBase 分区表详解

1、分区表的定义 在OceanBase数据库中&#xff0c;普通的表数据可以根据预设的规则被分割并存储到不同的数据区块中&#xff0c;同一区块的数据是在一个物理存储上。这样被分区块的表被称为分区表&#xff0c;而其中的每一个独立的数据区块则被称为一个分区。 如下图所示&…

学习大数据DAY61 宽表加工

目录 模型设计 加工宽表 任务调度&#xff1a; 大表 - 把很多数据整合起来 方便后续的明细查询和指标计算 模型设计 设计 建模 设计: excel 文档去编写 建模: 使用建模工具 PowerDesigner Navicat 在线画图工具... 把表结构给绘 制出来 共享\项目课工具\pd 加工宽表 数…

uniapp微信小程序转发跳转指定页面

onShareAppMessage 是微信小程序中的一个重要函数&#xff0c;用于自定义转发内容。当用户点击右上角的菜单按钮&#xff0c;并选择“转发”时&#xff0c;会触发这个函数。开发者可以在这个函数中返回一个对象&#xff0c;用于定义分享卡片的标题、图片、路径等信息。 使用场…

Datawhale组队学习】模型减肥秘籍:模型压缩技术3——模型量化

模型量化的目的是通过将浮点运算转换为定点运算&#xff0c;以减少模型大小、内存和存储需求&#xff0c;同时加快推理速度&#xff0c;降低计算功耗&#xff0c;使得模型在低算力设备上运行更加高效&#xff0c;特别适用于嵌入式设备或移动端场景。 不同数据类型&#xff08;…

arkUI:网格布局(Grid)

arkUI&#xff1a;网格布局&#xff08;Grid&#xff09; 1 主要内容说明2 网格布局的相关内容2.1 基本使用2.1.1 源码1 &#xff08;Grid网格布局&#xff09;2.1.2 源码1运行效果 2.2 设置排列方式2.2.1 核心布局参数2.2.2 网格单元格参数&#xff08;GridItem&#xff09;2.…

Cherno OpenGL(28 ~ 33)

批量渲染-介绍 在这里我们将在一个drawcall打包多个几何体。即 batch geometry。 我们在这里将聚焦于2d渲染&#xff0c;我们如何渲染一堆2d的quads或者说rectangles呢&#xff1f; 一种情况是比如一个2d游戏有很多个tile组成&#xff0c;要去渲染这些tile&#xff1b;另一种…

网络安全之国际主流网络安全架构模型

目前&#xff0c;国际主流的网络安全架构模型主要有&#xff1a; ● 信息技术咨询公司Gartner的ASA&#xff08;Adaptive Security Architecture自适应安全架构&#xff09; ● 美国政府资助的非营利研究机构MITRE的ATT&CK&#xff08;Adversarial Tactics Techniques &…

游戏引擎学习第16天

视频参考:https://www.bilibili.com/video/BV1mEUCY8EiC/ 这些字幕讨论了编译器警告的概念以及如何在编译过程中启用和处理警告。以下是字幕的内容摘要&#xff1a; 警告的定义&#xff1a;警告是编译器用来告诉你某些地方可能存在问题&#xff0c;尽管编译器不强制要求你修复…

【Nginx从入门到精通】03 、安装部署-让虚拟机可以联网

文章目录 总结一、配置联网【Minimal 精简版】1.1、查看网络配置1.2、配置ip地址 : 修改配置文件 <font colororange>ifcfg-ens33Stage 1&#xff1a;输入指令Stage 2&#xff1a;修改参数Stage 3&#xff1a;重启网络Stage 4&#xff1a;测试上网 二、配置联网【Everyth…

【UGUI】背包的交互01(道具信息跟随鼠标+道具信息面板显示)

详细程序逻辑过程 初始化物品栏&#xff1a; 在 Awake 方法中&#xff0c;通过标签找到提示框和信息面板。 循环生成10个背包格子&#xff0c;并为每个格子设置图标和名称。 为每个格子添加 UInterMaager232 脚本&#xff0c;以便处理交互事件。 关闭提示框和信息面板&#…

MySQL45讲 第二十四讲 MySQL是怎么保证主备一致的?——阅读总结

文章目录 MySQL45讲 第二十四讲 MySQL是怎么保证主备一致的&#xff1f;——阅读总结一、MySQL 主备基本原理&#xff08;一&#xff09;主备切换流程&#xff08;二&#xff09;主备数据同步流程 二、binlog 格式及相关问题&#xff08;一&#xff09;binlog 的三种格式&#…

RSA算法原理

1、RSA加密算法介绍 2、RSA算法的基本原理 2.1、RSA密钥生成 2.2、RSA加密和解密 1、RSA加密算法介绍 简单介绍一下&#xff0c;RSA算法是现代密码学的基石之一&#xff0c;广泛应用与安全通信、数据加密和身份验证等领域。 RSA加密是一种非对称加密算法&#xff0c;由罗…

html 图片转svg 并使用svg路径来裁剪html元素

1.png转svg 工具地址: Vectorizer – 免费图像矢量化 打开svg图片,复制其中的path中的d标签的路径 查看生成的svg路径是否正确 在线SVG路径预览工具 - UU在线工具 2.在html中使用svg路径 <svg xmlns"http://www.w3.org/2000/svg" width"318px" height…

哋它亢SEO技术分析:如何提升网站在搜索引擎中的可见性

文章目录 哋它亢SEO技术分析&#xff1a;如何提升网站在搜索引擎中的可见性网站的基本情况SEO优化分析与建议1. 元数据优化2. 关键词优化3. URL结构4. 图像优化5. 移动端优化6. 网站速度7. 结构化数据&#xff08;Schema Markup&#xff09;8. 内链与外链9. 社交分享 哋它亢SEO…

Liunx-Ubuntu22.04.1系统下配置Anaconda+pycharm+pytorch-gpu环境配置

这里写自定义目录标题 Liunx-Ubuntu22.04.1系统下配置Anacondapycharmpytorch-gpu环境配置一、Anaconda3配置1.Anaconda安装2.Anaconda更新3.Anaconda删除 二、pycharm配置1.pycharm安装 三、pytorch配置 Liunx-Ubuntu22.04.1系统下配置Anacondapycharmpytorch-gpu环境配置 一…

视觉SLAM--经典视觉SLAM框架

整个视觉SLAM流程主要包括以下步骤&#xff1a; 1、传感器信息读取&#xff1a;在视觉SLAM中主要为相机图像信息的读取和预处理。 2、前端视觉里程计&#xff1a;估算相邻图像间相机的运动&#xff0c;以及局部地图的样子。 3、后端&#xff08;非线性&#xff09;优化&#…

3、C语言基本数据类型关键字

本章学习目标&#xff1a; 1、认识存储单位&#xff1a;位bit和字节Byte 2、根据不同的应用场景选择符合需求的数据类型关键字,了解关键字的表示数据范围 1、内存单位 一、位 内存中的数据无论是文本、图像、音频还是视频&#xff0c;都是以位的形式进行存储的。 位是二进制…

Mac os 系统上如何开启远程登录的 ssh 22 端口

macOS 允许使用 SSH 连接&#xff0c;但为了安全起见&#xff0c;需要手动启动 SSH 服务&#xff0c;有两种方法可以激活远程登录。 方法一&#xff1a;界面化操作 1. 打开“系统偏好设置”&#xff0c;点击屏幕左上角的 Apple 图标&#xff0c;然后选择 系统偏好设置。 2. …

利用Python爬虫获取淘宝店铺详情

在数字化时代&#xff0c;数据已成为企业最宝贵的资产之一。对于电商平台&#xff0c;尤其是淘宝这样的大型电商平台&#xff0c;店铺详情数据的获取和分析对于商家来说至关重要。它不仅可以帮助商家了解市场趋势&#xff0c;还可以优化营销策略&#xff0c;提升销售业绩。本文…