Edit : well I forgot to include some related functions that make it difficult for you to identify the error. I'm sorry about this. So I have updated my question again
I'm encountering a peculiar issue with the rotation of a cube in my application. The cube only rotates when I move the mouse or press a key, and I'm looking to have continuous rotation without relying on user input.
Details:
Mouse Movement: The cube rotates correctly when I move the mouse, but I need it to rotate continuously without the need for constant mouse movement. Key Press: Pressing a key rotates the cube momentarily. However, I want the rotation to continue as long as the key is held down. Window Close Button: Clicking the window's close button (X button) doesn't close the application window as expected. Expectations:
Continuous rotation of the cube without requiring constant mouse movement or key presses. Proper functionality of the window close button to close the application window. Current Implementation:
Cube rotation is handled within the Render function, triggered by mouse movement and key presses. The application loop (while loop) processes input events using PeekMessage and GetMessage functions.
Main.cpp
#include "WindowManager.h"
#include "EngineGraphics.h"
ID3D11Device* gp_Device;
ID3D11DeviceContext* gp_DeviceContext;
IDXGIDevice* gp_DXGI_Device;
IDXGIAdapter* gp_DXGI_Adapter;
IDXGIFactory* gp_DXGI_Factory;
IDXGISwapChain* gp_DXGI_SwapChain;
ID3D11VertexShader* gp_VS;
ID3D11PixelShader* gp_PS;
ID3D11InputLayout* gp_InputLayout;
ID3D11Buffer* gp_VBuffer;
ID3D11Buffer* gp_IBuffer;
ID3D11RasterizerState* gp_rs;
ID3D11RenderTargetView* gp_BackBuffer;
float input;
Vertex Cube[] = {
{ XMFLOAT3(-0.5f, -0.5f, -0.5f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
{ XMFLOAT3(-0.5f, 0.5f, -0.5f), XMFLOAT4(1.0f, 0.0f, 1.0f, 1.0f) },
{ XMFLOAT3(0.5f, 0.5f, -0.5f), XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f) },
{ XMFLOAT3(0.5f, -0.5f, -0.5f), XMFLOAT4(1.0f, 1.0f, 0.0f, 1.0f) },
// Back face
{ XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT4(1.0f, 0.0f, 1.0f, 1.0f) },
{ XMFLOAT3(0.5f, -0.5f, 0.5f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
{ XMFLOAT3(0.5f, 0.5f, 0.5f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
{ XMFLOAT3(-0.5f, 0.5f, 0.5f), XMFLOAT4(0.0f, 1.0f, 1.0f, 1.0f) }
};
WORD CubeIndices[] = {
// Front face
0, 1, 2,
0, 2, 3,
// Back face
4, 5, 6,
4, 6, 7,
// Left face
4, 7, 1,
4, 1, 0,
// Right face
3, 2, 6,
3, 6, 5,
// Top face
1, 7, 6,
1, 6, 2,
// Bottom face
4, 0, 3,
4, 3, 5
};
void InitD3D(HWND& hWnd, RECT& wr);
void InitPipeline();
void InitGraphics();
void Render();
void CleanD3D();
using namespace ExtroEngine;
LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HWND hWnd;
UINT style = CS_HREDRAW | CS_VREDRAW;
WindowManager::InitWindowExW(style, WinProc, 0, 0, hInstance, L"Scene", NULL, IDC_ARROW, NULL, NULL, CreateSolidBrush(RGB(0, 0, 0)));
hWnd = CreateWindowExW(NULL, L"Scene", L"Scene", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 600, 600,
NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, nCmdShow);
RECT wr = { 0, 0, SCENE_WIDTH, SCENE_HEIGHT };
AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);
InitD3D(hWnd, wr);
MSG msg = { 0 };
while (TRUE)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
if (msg.message == WM_QUIT)
break;
}
Render();
}
CleanD3D();
return 0;
}
void InitD3D(HWND& hWnd, RECT& wr)
{
HRESULT hr = 0;
GetClientRect(hWnd, &wr);
D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_10_1 };
D3D_DRIVER_TYPE DriveType[] = { D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP, D3D_DRIVER_TYPE_REFERENCE };
DXGI_SWAP_CHAIN_DESC swpDesc;
ZeroMemory(&swpDesc, sizeof(DXGI_SWAP_CHAIN_DESC));
swpDesc.BufferCount = 1;
swpDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swpDesc.BufferDesc.Width = SCENE_WIDTH_MAX_HD;
swpDesc.BufferDesc.Height = SCENE_HEIGHT_MAX_HD;
swpDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swpDesc.OutputWindow = hWnd;
swpDesc.SampleDesc.Count = 1;
swpDesc.SampleDesc.Quality = 0;
swpDesc.Windowed = TRUE;
for (auto DriveSelect : DriveType)
{
hr = D3D11CreateDeviceAndSwapChain(NULL, DriveSelect, NULL, NULL, NULL, NULL,
D3D11_SDK_VERSION, &swpDesc, &gp_DXGI_SwapChain, &gp_Device, featureLevels, &gp_DeviceContext);
if (SUCCEEDED(hr))
{
break;
}
}
if (FAILED(hr)) {
MessageBox(NULL, L"Error Create Device And Swap Chain", L"Error", MB_OK);
CleanD3D();
return;
}
ID3D11Texture2D* pBackBuffer;
gp_DXGI_SwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
gp_Device->CreateRenderTargetView(pBackBuffer, NULL, &gp_BackBuffer);
pBackBuffer->Release();
gp_DeviceContext->OMSetRenderTargets(1, &gp_BackBuffer, NULL);
D3D11_VIEWPORT viewport;
ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = SCENE_WIDTH_MAX_HD;
viewport.Height = SCENE_HEIGHT_MAX_HD;
gp_DeviceContext->RSSetViewports(1, &viewport);
InitPipeline();
InitGraphics();
}
// ------------- KHỞI TẠO BỘ BIÊN DỊCH ĐỒ HOẠ
void InitPipeline()
{
HRESULT hr;
ID3DBlob* VS, * PS;
hr = D3DCompileFromFile(L"ColorVPS.hlsl", NULL, D3D_COMPILE_STANDARD_FILE_INCLUDE,
"VShader", "vs_5_0", 0, 0, &VS, NULL);
if (FAILED(hr)) {
MessageBox(NULL, L"Compile From File {ColorVPS.hlsl} Error With {VShader}",
L"Error", MB_OK);
CleanD3D();
return;
}
hr = D3DCompileFromFile(L"ColorVPS.hlsl", NULL, D3D_COMPILE_STANDARD_FILE_INCLUDE,
"PShader", "ps_5_0", 0, 0, &PS, NULL);
if (FAILED(hr)) {
MessageBox(NULL, L"Compile From File {ColorVPS.hlsl} Error With {PShader}",
L"Error", MB_OK);
CleanD3D();
return;
}
hr = gp_Device->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), NULL, &gp_VS);
if (FAILED(hr)) {
MessageBox(NULL, L"Create Vertex Shader Error With {VS}",
L"Error", MB_OK);
CleanD3D();
return;
}
hr = gp_Device->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), NULL, &gp_PS);
if (FAILED(hr)) {
MessageBox(NULL, L"Create Pixel Shader Error With {PS}",
L"Error", MB_OK);
CleanD3D();
return;
}
gp_DeviceContext->VSSetShader(gp_VS, NULL, 0);
gp_DeviceContext->PSSetShader(gp_PS, NULL, 0);
D3D11_INPUT_ELEMENT_DESC InputShader[] =
{
{"POSITION",0,DXGI_FORMAT_R32G32B32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0},
{"COLOR",0,DXGI_FORMAT_R32G32B32_FLOAT,0,12,D3D11_INPUT_PER_VERTEX_DATA,0}
};
gp_Device->CreateInputLayout(InputShader, 2, VS->GetBufferPointer(), VS->GetBufferSize(), &gp_InputLayout);
gp_DeviceContext->IASetInputLayout(gp_InputLayout);
}
// -------------- KHỞI TẠO ĐỒ HOẠ
void InitGraphics() {
HRESULT hr;
// Tạo và gán giá trị cho Vertex Buffer (gp_VBuffer)
D3D11_BUFFER_DESC bd;
ZeroMemory(&bd, sizeof(bd));
bd.Usage = D3D11_USAGE_DYNAMIC;
bd.ByteWidth = sizeof(Vertex) * ARRAYSIZE(Cube);
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory(&InitData, sizeof(InitData));
InitData.pSysMem = Cube;
hr = gp_Device->CreateBuffer(&bd, &InitData, &gp_VBuffer);
if (!SUCCEEDED(hr)) {
MessageBox(NULL, L"Create Vertex Buffer Error",
L"Error", MB_OK);
CleanD3D();
return;
}
// Tạo và gán giá trị cho Index Buffer (gp_IBuffer)
D3D11_BUFFER_DESC ib = {};
ib.Usage = D3D11_USAGE_IMMUTABLE;
ib.ByteWidth = sizeof(WORD) * ARRAYSIZE(CubeIndices);
ib.BindFlags = D3D11_BIND_INDEX_BUFFER;
D3D11_SUBRESOURCE_DATA initData = {};
initData.pSysMem = CubeIndices;
hr = gp_Device->CreateBuffer(&ib, &initData, &gp_IBuffer);
if (FAILED(hr)) {
MessageBox(NULL, L"Create Index Buffer Error",
L"Error", MB_OK);
CleanD3D();
return;
}
// Tạo và thiết lập Rasterizer State (gp_rs)
D3D11_RASTERIZER_DESC rasterizerDesc = {};
rasterizerDesc.FillMode = D3D11_FILL_SOLID;
rasterizerDesc.CullMode = D3D11_CULL_BACK; // or whatever cull mode you need
hr = gp_Device->CreateRasterizerState(&rasterizerDesc, &gp_rs);
if (FAILED(hr)) {
MessageBox(NULL, L"Create Rasterizer State Error",
L"Error", MB_OK);
CleanD3D();
return;
}
}
//-------------------VẼ CỬA SỔ-------------------//
void Render()
{
float clearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
gp_DeviceContext->ClearRenderTargetView(gp_BackBuffer, clearColor);
gp_DeviceContext->VSSetShader(gp_VS, NULL, 0);
gp_DeviceContext->PSSetShader(gp_PS, NULL, 0);
gp_DeviceContext->IASetInputLayout(gp_InputLayout);
gp_DeviceContext->OMSetRenderTargets(1, &gp_BackBuffer, NULL);
///////////////////// THIS ///////////////////////////////
input = Input::GetKeyRaw(K);
for (int i = 0; i < ARRAYSIZE(Cube); i++)
{
Rotation(0.05f, input, 0.05f, Cube[i].ScreenCoordinates);
}
////////////////////////////////////////////////////////////
D3D11_MAPPED_SUBRESOURCE map;
gp_DeviceContext->Map(gp_VBuffer, NULL, D3D11_MAP_WRITE_DISCARD, NULL, &map);
memcpy(map.pData, Cube, sizeof(Cube));
gp_DeviceContext->Unmap(gp_VBuffer, NULL);
UINT stride = sizeof(Vertex);
UINT offset = 0;
gp_DeviceContext->IASetIndexBuffer(gp_IBuffer, DXGI_FORMAT_R16_UINT, 0);
gp_DeviceContext->RSSetState(gp_rs);
gp_DeviceContext->IASetVertexBuffers(0, 1, &gp_VBuffer, &stride, &offset);
gp_DeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
gp_DeviceContext->DrawIndexed(ARRAYSIZE(CubeIndices), 0, 0);
gp_DXGI_SwapChain->Present(0, 0);
}
//-------------------DỌN DẸP CỬA SỔ -------------//
void CleanD3D()
{
SafeRelease(gp_Device);
SafeRelease(gp_DeviceContext);
SafeRelease(gp_DXGI_Device);
SafeRelease(gp_DXGI_Adapter);
SafeRelease(gp_DXGI_Factory);
SafeRelease(gp_DXGI_SwapChain);
SafeRelease(gp_VS);
SafeRelease(gp_PS);
SafeRelease(gp_InputLayout);
SafeRelease(gp_IBuffer);
SafeRelease(gp_rs);
SafeRelease(gp_BackBuffer);
}
LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_PAINT:
Render();
ValidateRect(hWnd, NULL);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
WindowManager.h
#pragma once
#include <Windows.h>
struct WindowManager
{
static void InitWindowExW(UINT style, WNDPROC lpfnWndProc, int cbClsExtra, int cbWndExtra, HINSTANCE hInstance, LPCWSTR lpszClassName, LPCWSTR lpszMenuName, LPCWSTR hCursor, LPCWSTR hIcon, LPCWSTR hIconSm, HBRUSH hbrBackground) {
WNDCLASSEXW wndClass = {};
wndClass.cbSize = sizeof(WNDCLASSEXW);
wndClass.style = style;
wndClass.lpfnWndProc = lpfnWndProc;
wndClass.cbClsExtra = cbClsExtra;
wndClass.hInstance = hInstance;
wndClass.lpszClassName = lpszClassName;
wndClass.lpszMenuName = lpszMenuName;
wndClass.hCursor = LoadCursorW(hInstance, hCursor);
wndClass.hIcon = LoadIconW(hInstance, hIcon);
wndClass.hIconSm = LoadIconW(hInstance, hIconSm);
wndClass.hbrBackground = hbrBackground;
if (!RegisterClassExW(&wndClass)) {
}
}
};
EngineGraphics.h
#pragma once
#include <d3d11.h>
#include <dxgi1_2.h>
#include <d3dcompiler.h>
#include "ExtroEngine.h"
#include "EngineUtility.h"
#define SCENE_HEIGHT 600
#define SCENE_HEIGHT_MAX_HD 3300
#define SCENE_WIDTH_MAX_HD 2240
#define SCENE_WIDTH 600
using namespace DirectX;
struct Vertex
{
XMFLOAT3 ScreenCoordinates;
XMFLOAT4 Color;
};
template<typename T>
inline void SafeRelease(T& ptr)
{
if (ptr != NULL)
{
ptr->Release();
ptr = NULL;
}
}
ExtroEngine.cpp
#include "ExtroEngine.h"
namespace ExtroEngine
{
float Input::GetKeyRaw(Key key)
{
MSG msg = { 0 };
if (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
switch (msg.message)
{
case WM_KEYDOWN:
if (msg.wParam == key)
{
return 1.0f;
}
break;
}
}
return 0.0f;
}
void Rotation(float AngleX, float AngleY, float AngleZ, XMFLOAT3& point)
{
float rX = AngleX * (XM_PI / 180.0f);
float rY = AngleY * (XM_PI / 180.0f);
float rZ = AngleZ * (XM_PI / 180.0f);
XMMATRIX rotationMatrixX = XMMatrixRotationX(rX);
XMMATRIX rotationMatrixY = XMMatrixRotationY(rY);
XMMATRIX rotationMatrixZ = XMMatrixRotationZ(rZ);
XMMATRIX finalRotationMatrix = rotationMatrixX * rotationMatrixY * rotationMatrixZ;
XMVECTOR pointVector = XMLoadFloat3(&point);
pointVector = XMVector3TransformCoord(pointVector, finalRotationMatrix);
XMStoreFloat3(&point, pointVector);
}
void Translation(float x, float y, float z, XMFLOAT3& point)
{
XMVECTOR pV = XMLoadFloat3(&point);
XMMATRIX translationMatrix = XMMatrixTranslation(x, y, z);
XMVECTOR transformedPoint = XMVector3TransformCoord(pV, translationMatrix);
XMStoreFloat3(&point, transformedPoint);
}
float Time::DeltaTime = 0.0f;
}
ExtroEngine.h
#pragma once
#include "Key.h"
#include <DirectXMath.h>
using namespace DirectX;
namespace ExtroEngine
{
class Transform
{
public:
XMFLOAT3 Position;
XMFLOAT3 Rotation;
XMFLOAT3 Scale;
Transform()
{
Position = { 0.0f,0.0f,0.0f };
Rotation = { 0.0f,0.0f,0.0f };
Scale = { 0.0f,0.0f,0.0f };
}
};
struct Input
{
static float GetKeyRaw(Key key);
};
struct Time
{
static float DeltaTime;
};
void Rotation(float AngleX, float AngleY, float AngleZ, XMFLOAT3& point);
void Translation(float x, float y, float z, XMFLOAT3& point);
}
and Key.h
#pragma once
#include <Windows.h>
typedef enum Key {
A = 'A', B = 'B', C = 'C', D = 'D', E = 'E', F = 'F', G = 'G', H = 'H', I = 'I', J = 'J', K = 'K',
L = 'L', M = 'M', N = 'N', O = 'O', P = 'P', Q = 'Q', R = 'R', S = 'S', T = 'T', U = 'U', V = 'V',
W = 'W', X = 'X', Y = 'Y', Z = 'Z',
NUM_0 = '0', NUM_1 = '1', NUM_2 = '2', NUM_3 = '3', NUM_4 = '4', NUM_5 = '5', NUM_6 = '6',
NUM_7 = '7', NUM_8 = '8', NUM_9 = '9',
ESC = VK_ESCAPE
} Key;
add ColorVPS.hlsl
struct VOut
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
VOut VShader(float4 position : POSITION, float4 color : COLOR)
{
VOut output;
output.position = position;
output.color = color;
return output;
}
float4 PShader(float4 position : SV_POSITION, float4 color : COLOR) : SV_TARGET
{
return color;
}
void main(in float4 position : POSITION, in float4 color : COLOR, out float4 oPosition : SV_POSITION, out float4 oColor : COLOR)
{
VOut output = VShader(position, color);
oPosition = output.position;
oColor = output.color;
}
Notes: I'm using DirectX 11 for rendering and handling input events.
The application runs without any errors or crashes, but the described issues with cube rotation and window close button persist.
I want to know where my mistake is in the code
There are multiple ways to fix this.
First, you could use the official message loop from Using Messages and Message Queues, so replace your while(TRUE) ...
by this:
BOOL bRet;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
break;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
// don't put Render() here
}
}
And then do your render loop in WM_PAINT
. This is what you wanted to do but one important detail is as doc says:
Return value An application returns zero if it processes this message.
So replace your WM_PAINT
handling by this:
case WM_PAINT:
Render();
return 0; // we handle it
What it means to Windows is in fact you never validate your update region so Windows keeps sending the WM_PAINT
message.
Another solution (more common in DirectX gaming loops) is to keep Render
in your current message loop and handle WM_PAINT
just validating the region, like this, so Windows won't send it forever:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
}
return 0;
And change your current Input::GetKeyRaw(Key key)
implementation because it runs another message loop which causes weird things. Move WM_KEYDOWN
handling in the main loop, and simply store the keyboard state somewhere so you can use it in Render()
.