Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 105 additions & 58 deletions src/d3d_helpers.cpp
Original file line number Diff line number Diff line change
@@ -1,61 +1,120 @@
#include "d3d_helpers.h"

#include <Windows.h>
#include <dxgi1_4.h>

#include <array>
#include <format>
#include <string>

static void GetMonitorRefreshRate(HWND hwnd, UINT& numerator, UINT& denominator)
namespace
{
numerator = 60;
denominator = 1;
HMONITOR mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
MONITORINFOEXW mi{};
mi.cbSize = sizeof(mi);
if (!GetMonitorInfoW(mon, &mi))
return;
DEVMODEW dm{};
dm.dmSize = sizeof(dm);
if (EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &dm) == 0)
return;
numerator = dm.dmDisplayFrequency;
denominator = 1;
const char* SwapEffectName(DXGI_SWAP_EFFECT effect)
{
switch (effect)
{
case DXGI_SWAP_EFFECT_FLIP_DISCARD: return "FLIP_DISCARD";
case DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL: return "FLIP_SEQUENTIAL";
case DXGI_SWAP_EFFECT_DISCARD: return "DISCARD";
default: return "unknown";
}
}

void LogD3d(const std::string& message)
{
OutputDebugStringA(message.c_str());
}
}

bool D3DContext::Create(const HWND hwnd, const int refreshRatePreferred)
bool D3DContext::Create(const HWND hwnd)
{
UINT num = 60, den = 1;
if (refreshRatePreferred == 0)
GetMonitorRefreshRate(hwnd, num, den);
else
num = static_cast<UINT>(refreshRatePreferred);

// Flip model (Windows 8+) is tried first; legacy DISCARD remains the fallback.
// A non-zero refresh rate makes FLIP_DISCARD and FLIP_SEQUENTIAL creation fail,
// and 0/0 is also the correct windowed value for the DISCARD fallback.
// ALLOW_MODE_SWITCH is omitted because the app never enters exclusive fullscreen
// and ResizeBuffers is called with flags 0.
DXGI_SWAP_CHAIN_DESC sd{};
sd.BufferCount = 2;
sd.BufferDesc.Width = 0;
sd.BufferDesc.Height = 0;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = num;
sd.BufferDesc.RefreshRate.Denominator = den;
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
sd.BufferDesc.RefreshRate.Numerator = 0;
sd.BufferDesc.RefreshRate.Denominator = 0;
sd.Flags = 0;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = hwnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

constexpr std::array effects = {
DXGI_SWAP_EFFECT_FLIP_DISCARD,
DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
DXGI_SWAP_EFFECT_DISCARD,
};
constexpr std::array levels = {D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0};
D3D_FEATURE_LEVEL obtained;
constexpr UINT levelCount = static_cast<UINT>(levels.size());

HRESULT hr = E_FAIL;
DXGI_SWAP_EFFECT chosen = DXGI_SWAP_EFFECT_DISCARD;
for (const DXGI_SWAP_EFFECT effect : effects)
{
sd.SwapEffect = effect;
D3D_FEATURE_LEVEL obtained = D3D_FEATURE_LEVEL_11_0;
hr = D3D11CreateDeviceAndSwapChain(
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0,
levels.data(), levelCount, D3D11_SDK_VERSION,
&sd, swapChain.put(), device.put(), &obtained, deviceCtx.put());
if (hr == DXGI_ERROR_UNSUPPORTED)
{
hr = D3D11CreateDeviceAndSwapChain(
nullptr, D3D_DRIVER_TYPE_WARP, nullptr, 0,
levels.data(), levelCount, D3D11_SDK_VERSION,
&sd, swapChain.put(), device.put(), &obtained, deviceCtx.put());
}
if (hr == S_OK)
{
chosen = effect;
break;
}

// A failed call should already null the outputs; drop anything leftover before the next try.
Cleanup();
}

if (hr != S_OK)
{
LogD3d(std::format(
"D3D11CreateDeviceAndSwapChain failed: {:#010x}\n",
static_cast<unsigned>(hr)));
return false;
}

if (D3D11CreateDeviceAndSwapChain(
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0,
levels.data(), levels.size(), D3D11_SDK_VERSION,
&sd, swapChain.put(), device.put(), &obtained, deviceCtx.put()) != S_OK)
hr = CreateRTV();
if (FAILED(hr))
{
LogD3d(std::format(
"CreateRenderTargetView failed: {:#010x}\n",
static_cast<unsigned>(hr)));
Cleanup();
return false;
}

CreateRTV();
LogD3d(std::format("Created D3D11 swap chain with SwapEffect {}\n", SwapEffectName(chosen)));
return true;
}

void D3DContext::CreateRTV()
HRESULT D3DContext::CreateRTV()
{
rtv.reset();
if (!swapChain || !device)
return E_FAIL;

wil::com_ptr<ID3D11Texture2D> back;
swapChain->GetBuffer(0, IID_PPV_ARGS(back.put()));
device->CreateRenderTargetView(back.get(), nullptr, rtv.put());
const HRESULT bufferHr = swapChain->GetBuffer(0, IID_PPV_ARGS(back.put()));
if (FAILED(bufferHr))
return bufferHr;
return device->CreateRenderTargetView(back.get(), nullptr, rtv.put());
}

void D3DContext::CleanupRTV()
Expand All @@ -71,33 +130,21 @@ void D3DContext::Cleanup()
device.reset();
}

void D3DContext::Resize(LPARAM lParam)
HRESULT D3DContext::Resize(const UINT width, const UINT height)
{
CleanupRTV();
swapChain->ResizeBuffers(0,
LOWORD(lParam),
HIWORD(lParam),
DXGI_FORMAT_UNKNOWN, 0);
CreateRTV();
}
if (!swapChain || width == 0 || height == 0)
return E_INVALIDARG;

void D3DContext::Present(const bool vsync) const
{
swapChain->Present(vsync ? 1 : 0, 0);
CleanupRTV();
const HRESULT hr = swapChain->ResizeBuffers(0, width, height, DXGI_FORMAT_UNKNOWN, 0);
if (FAILED(hr))
return hr;
return CreateRTV();
}

void D3DContext::SetRefreshRate(const int numerator, const int denominator)
HRESULT D3DContext::Present(const bool vsync, const UINT flags) const
{
DXGI_SWAP_CHAIN_DESC desc{};
if (swapChain->GetDesc(&desc) != S_OK)
return;
DXGI_MODE_DESC mode{};
mode.Width = desc.BufferDesc.Width;
mode.Height = desc.BufferDesc.Height;
mode.RefreshRate.Numerator = numerator;
mode.RefreshRate.Denominator = denominator;
mode.Format = desc.BufferDesc.Format;
mode.ScanlineOrdering = desc.BufferDesc.ScanlineOrdering;
mode.Scaling = desc.BufferDesc.Scaling;
swapChain->ResizeTarget(&mode);
if (!swapChain)
return E_FAIL;
return swapChain->Present(vsync ? 1u : 0u, flags);
}
11 changes: 6 additions & 5 deletions src/d3d_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ struct D3DContext
wil::com_ptr<IDXGISwapChain> swapChain;
wil::com_ptr<ID3D11RenderTargetView> rtv;

[[nodiscard]] bool Create(HWND hwnd, int refreshRatePreferred = 60);
void CreateRTV();
// Windowed creation uses refresh 0/0 so flip-model swap chains are accepted.
[[nodiscard]] bool Create(HWND hwnd);
[[nodiscard]] HRESULT CreateRTV();
void CleanupRTV();
void Cleanup();
void Resize(LPARAM lParam);
void Present(bool vsync) const;
void SetRefreshRate(int numerator, int denominator);
[[nodiscard]] HRESULT Resize(UINT width, UINT height);
// flags may be DXGI_PRESENT_TEST; pass vsync false for that test.
[[nodiscard]] HRESULT Present(bool vsync, UINT flags = 0) const;
};
Loading
Loading