366 lines
9.5 KiB
C++

#include "DX12LibPCH.h"
#include "Application.h"
#include "CommandQueue.h"
#include "Window.h"
#include "Game.h"
Window::Window(HWND hWnd, const std::wstring& windowName, int clientWidth, int clientHeight, bool vSync)
: m_hWnd(hWnd)
, m_WindowName(windowName)
, m_ClientWidth(clientWidth)
, m_ClientHeight(clientHeight)
, m_VSync(vSync)
, m_Fullscreen(false)
, m_FrameCounter(0)
{
Application& app = Application::Get();
m_IsTearingSupported = app.IsTearingSupported();
m_dxgiSwapChain = CreateSwapChain();
m_d3d12RTVDescriptorHeap = app.CreateDescriptorHeap(BufferCount, D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
m_RTVDescriptorSize = app.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
UpdateRenderTargetViews();
}
Window::~Window()
{
// Window should be destroyed with Application::DestroyWindow before
// the window goes out of scope.
assert(!m_hWnd && "Use Application::DestroyWindow before destruction.");
}
HWND Window::GetWindowHandle() const
{
return m_hWnd;
}
const std::wstring& Window::GetWindowName() const
{
return m_WindowName;
}
void Window::Show()
{
::ShowWindow(m_hWnd, SW_SHOW);
}
/**
* Hide the window.
*/
void Window::Hide()
{
::ShowWindow(m_hWnd, SW_HIDE);
}
void Window::Destroy()
{
if (auto pGame = m_pGame.lock())
{
// Notify the registered game that the window is being destroyed.
pGame->OnWindowDestroy();
}
if (m_hWnd)
{
DestroyWindow(m_hWnd);
m_hWnd = nullptr;
}
}
int Window::GetClientWidth() const
{
return m_ClientWidth;
}
int Window::GetClientHeight() const
{
return m_ClientHeight;
}
bool Window::IsVSync() const
{
return m_VSync;
}
void Window::SetVSync(bool vSync)
{
m_VSync = vSync;
}
void Window::ToggleVSync()
{
SetVSync(!m_VSync);
}
bool Window::IsFullScreen() const
{
return m_Fullscreen;
}
// Set the fullscreen state of the window.
void Window::SetFullscreen(bool fullscreen)
{
if (m_Fullscreen != fullscreen)
{
m_Fullscreen = fullscreen;
if (m_Fullscreen) // Switching to fullscreen.
{
// Store the current window dimensions so they can be restored
// when switching out of fullscreen state.
::GetWindowRect(m_hWnd, &m_WindowRect);
// Set the window style to a borderless window so the client area fills
// the entire screen.
UINT windowStyle = WS_OVERLAPPEDWINDOW & ~(WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
::SetWindowLongW(m_hWnd, GWL_STYLE, windowStyle);
// Query the name of the nearest display device for the window.
// This is required to set the fullscreen dimensions of the window
// when using a multi-monitor setup.
HMONITOR hMonitor = ::MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST);
MONITORINFOEX monitorInfo = {};
monitorInfo.cbSize = sizeof(MONITORINFOEX);
::GetMonitorInfo(hMonitor, &monitorInfo);
::SetWindowPos(m_hWnd, HWND_TOPMOST,
monitorInfo.rcMonitor.left,
monitorInfo.rcMonitor.top,
monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left,
monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top,
SWP_FRAMECHANGED | SWP_NOACTIVATE);
::ShowWindow(m_hWnd, SW_MAXIMIZE);
}
else
{
// Restore all the window decorators.
::SetWindowLong(m_hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
::SetWindowPos(m_hWnd, HWND_NOTOPMOST,
m_WindowRect.left,
m_WindowRect.top,
m_WindowRect.right - m_WindowRect.left,
m_WindowRect.bottom - m_WindowRect.top,
SWP_FRAMECHANGED | SWP_NOACTIVATE);
::ShowWindow(m_hWnd, SW_NORMAL);
}
}
}
void Window::ToggleFullscreen()
{
SetFullscreen(!m_Fullscreen);
}
void Window::RegisterCallbacks(std::shared_ptr<Game> pGame)
{
m_pGame = pGame;
return;
}
void Window::OnUpdate(UpdateEventArgs&)
{
m_UpdateClock.Tick();
if (auto pGame = m_pGame.lock())
{
m_FrameCounter++;
UpdateEventArgs updateEventArgs(m_UpdateClock.GetDeltaSeconds(), m_UpdateClock.GetTotalSeconds());
pGame->OnUpdate(updateEventArgs);
}
}
void Window::OnRender(RenderEventArgs&)
{
m_RenderClock.Tick();
if (auto pGame = m_pGame.lock())
{
RenderEventArgs renderEventArgs(m_RenderClock.GetDeltaSeconds(), m_RenderClock.GetTotalSeconds());
pGame->OnRender(renderEventArgs);
}
}
void Window::OnKeyPressed(KeyEventArgs& e)
{
if (auto pGame = m_pGame.lock())
{
pGame->OnKeyPressed(e);
}
}
void Window::OnKeyReleased(KeyEventArgs& e)
{
if (auto pGame = m_pGame.lock())
{
pGame->OnKeyReleased(e);
}
}
// The mouse was moved
void Window::OnMouseMoved(MouseMotionEventArgs& e)
{
if (auto pGame = m_pGame.lock())
{
pGame->OnMouseMoved(e);
}
}
// A button on the mouse was pressed
void Window::OnMouseButtonPressed(MouseButtonEventArgs& e)
{
if (auto pGame = m_pGame.lock())
{
pGame->OnMouseButtonPressed(e);
}
}
// A button on the mouse was released
void Window::OnMouseButtonReleased(MouseButtonEventArgs& e)
{
if (auto pGame = m_pGame.lock())
{
pGame->OnMouseButtonReleased(e);
}
}
// The mouse wheel was moved.
void Window::OnMouseWheel(MouseWheelEventArgs& e)
{
if (auto pGame = m_pGame.lock())
{
pGame->OnMouseWheel(e);
}
}
void Window::OnResize(ResizeEventArgs& e)
{
// Update the client size.
if (m_ClientWidth != e.Width || m_ClientHeight != e.Height)
{
m_ClientWidth = std::max(1, e.Width);
m_ClientHeight = std::max(1, e.Height);
Application::Get().Flush();
for (int i = 0; i < BufferCount; ++i)
{
m_d3d12BackBuffers[i].Reset();
}
DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
ThrowIfFailed(m_dxgiSwapChain->GetDesc(&swapChainDesc));
ThrowIfFailed(m_dxgiSwapChain->ResizeBuffers(BufferCount, m_ClientWidth,
m_ClientHeight, swapChainDesc.BufferDesc.Format, swapChainDesc.Flags));
m_CurrentBackBufferIndex = m_dxgiSwapChain->GetCurrentBackBufferIndex();
UpdateRenderTargetViews();
}
if (auto pGame = m_pGame.lock())
{
pGame->OnResize(e);
}
}
Microsoft::WRL::ComPtr<IDXGISwapChain4> Window::CreateSwapChain()
{
Application& app = Application::Get();
ComPtr<IDXGISwapChain4> dxgiSwapChain4;
ComPtr<IDXGIFactory4> dxgiFactory4;
UINT createFactoryFlags = 0;
#if defined(_DEBUG)
createFactoryFlags = DXGI_CREATE_FACTORY_DEBUG;
#endif
ThrowIfFailed(CreateDXGIFactory2(createFactoryFlags, IID_PPV_ARGS(&dxgiFactory4)));
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.Width = m_ClientWidth;
swapChainDesc.Height = m_ClientHeight;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.Stereo = FALSE;
swapChainDesc.SampleDesc = { 1, 0 };
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = BufferCount;
swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
// It is recommended to always allow tearing if tearing support is available.
swapChainDesc.Flags = m_IsTearingSupported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;
ID3D12CommandQueue* pCommandQueue = app.GetCommandQueue()->GetD3D12CommandQueue().Get();
ComPtr<IDXGISwapChain1> swapChain1;
ThrowIfFailed(dxgiFactory4->CreateSwapChainForHwnd(
pCommandQueue,
m_hWnd,
&swapChainDesc,
nullptr,
nullptr,
&swapChain1));
// Disable the Alt+Enter fullscreen toggle feature. Switching to fullscreen
// will be handled manually.
ThrowIfFailed(dxgiFactory4->MakeWindowAssociation(m_hWnd, DXGI_MWA_NO_ALT_ENTER));
ThrowIfFailed(swapChain1.As(&dxgiSwapChain4));
m_CurrentBackBufferIndex = dxgiSwapChain4->GetCurrentBackBufferIndex();
return dxgiSwapChain4;
}
// Update the render target views for the swapchain back buffers.
void Window::UpdateRenderTargetViews()
{
auto device = Application::Get().GetDevice();
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_d3d12RTVDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
for (int i = 0; i < BufferCount; ++i)
{
ComPtr<ID3D12Resource> backBuffer;
ThrowIfFailed(m_dxgiSwapChain->GetBuffer(i, IID_PPV_ARGS(&backBuffer)));
device->CreateRenderTargetView(backBuffer.Get(), nullptr, rtvHandle);
m_d3d12BackBuffers[i] = backBuffer;
rtvHandle.Offset(m_RTVDescriptorSize);
}
}
D3D12_CPU_DESCRIPTOR_HANDLE Window::GetCurrentRenderTargetView() const
{
return CD3DX12_CPU_DESCRIPTOR_HANDLE(m_d3d12RTVDescriptorHeap->GetCPUDescriptorHandleForHeapStart(),
m_CurrentBackBufferIndex, m_RTVDescriptorSize);
}
Microsoft::WRL::ComPtr<ID3D12Resource> Window::GetCurrentBackBuffer() const
{
return m_d3d12BackBuffers[m_CurrentBackBufferIndex];
}
UINT Window::GetCurrentBackBufferIndex() const
{
return m_CurrentBackBufferIndex;
}
UINT Window::Present()
{
UINT syncInterval = m_VSync ? 1 : 0;
UINT presentFlags = m_IsTearingSupported && !m_VSync ? DXGI_PRESENT_ALLOW_TEARING : 0;
ThrowIfFailed(m_dxgiSwapChain->Present(syncInterval, presentFlags));
m_CurrentBackBufferIndex = m_dxgiSwapChain->GetCurrentBackBufferIndex();
return m_CurrentBackBufferIndex;
}