ToB企服应用市场:ToB评测及商务社交产业平台

标题: 游戏引擎学习第八天 [打印本页]

作者: 海哥    时间: 2024-11-14 08:39
标题: 游戏引擎学习第八天
视频参考:
https://www.bilibili.com/video/BV1ouUPYAErK/
明白下面的代码


关于虚函数

代码分解

虚函数表(vtable)机制


vtable 查找过程

在内存中,编译器会为包罗虚拟函数的类分配一个虚函数表(vtable)。每个类的对象会有一个指向其虚函数表的指针 vptr,指向这个类的虚拟函数表。虚函数表的内容是类中每个虚拟函数的地点。
this->X 和 vtable 结合


总结




写入声音数据通常是将数据放入 GlobalSecondaryBuffer 中


生成方波数据

要用到的变量


锁定缓冲区

以下是带有注释的 Lock 函数声明息争释:
  1. STDMETHOD(Lock) (
  2.     THIS_
  3.     DWORD dwOffset,                            // 缓冲区偏移量,指定从缓冲区起始位置偏移多少字节开始锁定区域
  4.     DWORD dwBytes,                             // 锁定的字节数,指定要锁定多少字节的数据
  5.     _Outptr_result_bytebuffer_(*pdwAudioBytes1) LPVOID *ppvAudioPtr1,  // 输出参数,返回锁定区域的内存指针,应用程序可以访问此内存区进行读取或写入
  6.     _Out_ LPDWORD pdwAudioBytes1,              // 输出参数,返回第一个锁定区域实际锁定的字节数
  7.     _Outptr_opt_result_bytebuffer_(*pdwAudioBytes2) LPVOID *ppvAudioPtr2,  // 可选的输出参数,返回第二个锁定区域的内存指针(用于双缓冲或环形缓冲区)
  8.     _Out_opt_ LPDWORD pdwAudioBytes2,         // 可选的输出参数,返回第二个锁定区域实际锁定的字节数
  9.     DWORD dwFlags                             // 标志参数,用于指定锁定缓冲区时的行为,如锁定写光标位置、读取光标位置或环形缓冲区等
  10. ) PURE;  // 纯虚函数,需要在实际的派生类中实现
复制代码
在音频缓冲区中,DirectSound 使用一种“环形缓冲区”机制来允许应用程序一连不停地填充音频数据,避免播放的中断。Region1 和 Region2 是 DirectSound 锁定(Lock)时返回的两个数据区域,用于在不同情况下机动填充音频数据。
环形缓冲区与 Region1 和 Region2 的含义

在 DirectSound 中,环形缓冲区是一个逻辑上一连但物理上循环的缓冲区。我们可以把它想象成一个圆形的数据布局,数据在头尾毗连起来,因此称为环形。
当需要写入的区域未跨越缓冲区尾部

当需要写入的区域跨越缓冲区尾部

小结




像返回的Region1 和 Region2 中写入方波数据




播放音频

Play 方法用于启动音频缓冲区的播放。它是 DirectSound 接口中的一个方法,允许开辟者控制音频缓冲区的播放举动。
  1. STDMETHOD(Play) (THIS_ DWORD dwReserved1, DWORD dwPriority, DWORD dwFlags) PURE;
复制代码
参数解释


使用场景

调用 Play 方法后,缓冲区开始播放指定的音频数据,应用场景包罗:

代码示例

假设我们要在一个音频缓冲区中播放配景音乐,可以按如下设置:
  1. GlobalSecondaryBuffer->Play(0, 0, DSBPLAY_LOOPING);
复制代码

修改bug


  1. // game.cpp : Defines the entry point for the application.//#include <cstdint>#include <dsound.h>#include <minwindef.h>#include <processenv.h>#include <stdint.h>#include <windows.h>#include <winerror.h>#include <xinput.h>#define internal static        // 用于定义内翻译单位内部函数#define local_persist static   // 局部静态变量#define global_variable static // 全局变量typedef 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;struct win32_offscreen_buffer {  BITMAPINFO Info;  void *Memory;  // 后备缓冲区的宽度和高度  int Width;  int Height;  int Pitch;  int BytesPerPixel;};// 添加这个去掉重复的冗余代码struct win32_window_dimension {  int Width;  int Height;};// 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.dll    XInputLibrary = 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 接口的指针,并初始化为 NULL    IDirectSound *DirectSound = NULL;    if (DirectSoundCreate && SUCCEEDED(DirectSoundCreate(                                 0,                                 // 传入 0 作为装备 GUID,表示使用默认音频装备                                 &DirectSound,                                 // 将创建的 DirectSound 对象的指针存储到                                 // DirectSound 变量中                                 0                                 // 传入 0 作为外部未知接口指针,通常为 NULL                                 ))) //    {      // clang-format off      WAVEFORMATEX 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 format            OutputDebugString("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;}// 渲染一个奇特的渐变图案internal void RenderWeirdGradient(win32_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; // 移动到下一行  }}// 这个函数用于重新调解 DIB(装备独立位图)大小internal void Win32ResizeDIBSection(win32_offscreen_buffer *Buffer, int width,                                    int height) {  // device independent bitmap(装备独立位图)  // TODO: 进一步优化代码的结实性  // 可能的改进:先不释放,先尝试其他方法,再假如失败再释放。  if (Buffer->Memory) {    VirtualFree(        Buffer->Memory, // 指定要释放的内存块起始地点        0, // 要释放的大小(字节),对部分释放有效,团体释放则设为 0        MEM_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; // 设置颜色平面数,通常为 1  Buffer->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 CALLBACKWin32MainWindowCallback(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; // 返回处置惩罚结果}int CALLBACK WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, //                     PSTR cmdline, int cmdshow) {  Win32LoadXInput();  WNDCLASS WindowClass = {};  // 使用大括号初始化,全部成员都被初始化为零(0)或 nullptr  Win32ResizeDIBSection(&GlobalBackbuffer, 1280, 720);  // 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;      // 音频测试      uint32 RunningSampleIndex = 0; // 样本索引      int SquareWaveCounter = 0;     // 方波数      int16 ToneVolume = 3000;       // 音量      int SamplesPerSecond = 48000;  // 采样率:每秒采样48000次      int ToneHz = 256;              // 方波频率:256 Hz      int SquareWavePeriod = SamplesPerSecond / ToneHz; // 方波周期(样本数)      int HalfSquareWavePeriod = SquareWavePeriod / 2; // 方波半周期(样本数)      int BytesPerSample = sizeof(int16) * 2;          // 一个样本的大小      int SecondaryBufferSize = SamplesPerSecond * BytesPerSample; // 缓冲区大小      bool32 SoundIsPlaying = false;      Win32InitDSound(Window, SamplesPerSecond, SecondaryBufferSize);      GloblaRunning = true;      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);            // std::cout << "AButton " << AButton << " BButton " << BButton            //           << " XButton " << XButton << " YButton " << YButton            //           << std::endl;            // 获取摇杆的 X 和 Y 坐标值(-32768 到 32767)            int16 StickX = Pad->sThumbLX;            int16 StickY = Pad->sThumbLY;            if (AButton) {              yOffset += 2;            }          } else {          }        }        DWORD PlayCursor = 0;  // 播放游标,指示当前播放位置        DWORD WriteCursor = 0; // 写入游标,指示当前写入位置        // 获取音频缓冲区的当前播放和写入位置        if (SUCCEEDED(GlobalSecondaryBuffer->GetCurrentPosition(                &PlayCursor, &WriteCursor))) {          // 计算需要锁定的字节位置,基于当前样本索引和每样本字节数          DWORD BytesToLock =              RunningSampleIndex * BytesPerSample % SecondaryBufferSize;          DWORD BytesToWrite = 0; // 需要写入的字节数          // 判定 BytesToLock 与 PlayCursor 的位置关系以确定写入量          if (BytesToLock == PlayCursor) {            // 假如锁定位置恰好即是播放位置,写入整个缓冲区            if (!SoundIsPlaying) {              BytesToWrite = SecondaryBufferSize;            }          } else if (BytesToLock > PlayCursor) {            // 假如锁定位置在播放位置之后,写入从锁定位置到缓冲区末尾,再加上开头到播放位置的字节数            BytesToWrite = (SecondaryBufferSize - BytesToLock) + PlayCursor;          } else {            // 假如锁定位置在播放位置之前,写入从锁定位置到播放位置之间的字节数            BytesToWrite = PlayCursor - BytesToLock;          }          VOID *Region1; // 第一段区域指针,用于存放锁定后的首部分缓冲区地点          DWORD Region1Size; // 第一段区域的大小(字节数)          VOID *Region2; // 第二段区域指针,用于存放锁定后的剩余部分缓冲区地点          DWORD Region2Size; // 第二段区域的大小(字节数)          if (SUCCEEDED(GlobalSecondaryBuffer->Lock(                  BytesToLock, // 缓冲区偏移量,指定开始锁定的字节位置                  BytesToWrite, // 锁定的字节数,指定要锁定的区域大小                  &Region1, // 输出,返回锁定区域的内存指针(第一个区域)                  &Region1Size, // 输出,返回第一个锁定区域的现实字节数                  &Region2, // 输出,返回第二个锁定区域的内存指针(可选,双缓冲或环形缓冲时使用)                  &Region2Size, // 输出,返回第二个锁定区域的现实字节数                  0 // 标志,控制锁定举动(如从光标位置锁定等)                  ))) {            // int16 int16 int16            // 左 右 左 右 左 右 左 右 左 右            DWORD Region1SampleCount =                Region1Size / BytesPerSample; // 计算第一段区域中的样本数量            int16 *SampleOut = (int16 *)Region1; // 将第一段区域指针转换为 16                                                 // 位整型指针,准备写入样本数据            if (Region2Size > 48000 && BytesToLock != PlayCursor) {              OutputDebugStringA("test");            }            // 循环写入样本到第一段区域            for (DWORD SampleIndex = 0; SampleIndex < Region1SampleCount;                 ++SampleIndex) {              // 计算每个样本的值,使用方波产生音频数据              // RunningSampleIndex++ / HalfSquareWavePeriod) % 2              // 用于生成方波,隔半个周期翻转振幅              int16 SampleValue =                  ((RunningSampleIndex++ / HalfSquareWavePeriod) % 2)                      ? ToneVolume   // 假如为偶数周期,输出正振幅                      : -ToneVolume; // 假如为奇数周期,输出负振幅              *SampleOut++ = SampleValue; // 左声道              *SampleOut++ = SampleValue; // 右声道            }            DWORD Region2SampleCount =                Region2Size / BytesPerSample; // 计算第二段区域中的样本数量            SampleOut = (int16 *)Region2; // 将第二段区域指针转换为 16                                          // 位整型指针,准备写入样本数据            // 循环写入样本到第二段区域            for (DWORD SampleIndex = 0; SampleIndex < Region2SampleCount;                 ++SampleIndex) {              // 使用雷同逻辑生成方波样本数据              int16 SampleValue =                  ((RunningSampleIndex++ / HalfSquareWavePeriod) % 2)                      ? ToneVolume        // 偶数周期,输出正振幅                      : -ToneVolume;      // 奇数周期,输出负振幅              *SampleOut++ = SampleValue; // 左声道              *SampleOut++ = SampleValue; // 右声道            }            // 解锁音频缓冲区,将数据提交给音频装备            GlobalSecondaryBuffer->Unlock(Region1, Region1Size, Region2,                                          Region2Size);          }        }        if (!SoundIsPlaying) {          GlobalSecondaryBuffer->Play(0, 0, DSBPLAY_LOOPING);
  2.           SoundIsPlaying = true;        }        RenderWeirdGradient(GlobalBackbuffer, xOffset, yOffset);        // 这个地方需要渲染一下否则是黑屏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);        }        ++xOffset;      }    } else { // 假如窗口创建失败             // 这里可以处置惩罚窗口创建失败的逻辑             // 比如输堕落误信息,或退出程序等             // TODO:    }  } else { // 假如窗口类注册失败           // 这里可以处置惩罚注册失败的逻辑           // 比如输堕落误信息,或退出程序等           // TODO:  }  return 0;}
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4