mirror of
https://github.com/narzoul/DDrawCompat
synced 2024-12-30 08:55:36 +01:00
522 lines
14 KiB
C++
522 lines
14 KiB
C++
#include <array>
|
|
#include <map>
|
|
#include <sstream>
|
|
|
|
#include <Common/Comparison.h>
|
|
#include <Common/CompatVtable.h>
|
|
#include <Common/Rect.h>
|
|
#include <Config/Settings/Antialiasing.h>
|
|
#include <Config/Settings/DisplayAspectRatio.h>
|
|
#include <Config/Settings/PalettizedTextures.h>
|
|
#include <Config/Settings/RenderColorDepth.h>
|
|
#include <Config/Settings/ResolutionScale.h>
|
|
#include <Config/Settings/SupportedDepthFormats.h>
|
|
#include <D3dDdi/Adapter.h>
|
|
#include <D3dDdi/AdapterFuncs.h>
|
|
#include <D3dDdi/Device.h>
|
|
#include <D3dDdi/DeviceCallbacks.h>
|
|
#include <D3dDdi/DeviceFuncs.h>
|
|
#include <D3dDdi/FormatInfo.h>
|
|
#include <D3dDdi/KernelModeThunks.h>
|
|
#include <D3dDdi/SurfaceRepository.h>
|
|
#include <Win32/DisplayMode.h>
|
|
|
|
namespace
|
|
{
|
|
struct DepthFormat
|
|
{
|
|
DWORD flag;
|
|
D3DDDIFORMAT format;
|
|
};
|
|
|
|
const std::array<DepthFormat, 3> g_depthFormats = { {
|
|
{ DDBD_16, D3DDDIFMT_D16 },
|
|
{ DDBD_24, D3DDDIFMT_X8D24 },
|
|
{ DDBD_32, D3DDDIFMT_D32 }
|
|
} };
|
|
|
|
std::string bitDepthsToString(DWORD bitDepths)
|
|
{
|
|
std::string result;
|
|
if (bitDepths & DDBD_8) { result += ", 8"; }
|
|
if (bitDepths & DDBD_16) { result += ", 16"; }
|
|
if (bitDepths & DDBD_24) { result += ", 24"; }
|
|
if (bitDepths & DDBD_32) { result += ", 32"; }
|
|
|
|
if (result.empty())
|
|
{
|
|
return "none";
|
|
}
|
|
return result.substr(2);
|
|
}
|
|
}
|
|
|
|
namespace D3dDdi
|
|
{
|
|
Adapter::Adapter(const D3DDDIARG_OPENADAPTER& data)
|
|
: m_adapter(data.hAdapter)
|
|
, m_origVtable(CompatVtable<D3DDDI_ADAPTERFUNCS>::s_origVtable)
|
|
, m_runtimeVersion(data.Version)
|
|
, m_driverVersion(data.DriverVersion)
|
|
, m_guid(nullptr)
|
|
, m_guidBuf{}
|
|
, m_luid(KernelModeThunks::getLastOpenAdapterInfo().luid)
|
|
, m_deviceName(KernelModeThunks::getLastOpenAdapterInfo().deviceName)
|
|
, m_repository{}
|
|
, m_info(findInfo())
|
|
{
|
|
}
|
|
|
|
SIZE Adapter::getAspectRatio(SIZE appRes, SIZE displayRes) const
|
|
{
|
|
SIZE ar = Config::displayAspectRatio.get();
|
|
if (Config::Settings::DisplayAspectRatio::APP == ar)
|
|
{
|
|
return 0 != appRes.cx ? appRes : Rect::getSize(Win32::DisplayMode::getMonitorInfo(m_deviceName).rcEmulated);
|
|
}
|
|
else if (Config::Settings::DisplayAspectRatio::DISPLAY == ar)
|
|
{
|
|
return 0 != displayRes.cx ? displayRes : Rect::getSize(Win32::DisplayMode::getMonitorInfo(m_deviceName).rcReal);
|
|
}
|
|
return ar;
|
|
}
|
|
|
|
SIZE Adapter::getAspectRatio() const
|
|
{
|
|
return getAspectRatio({}, {});
|
|
}
|
|
|
|
const Adapter::AdapterInfo& Adapter::findInfo() const
|
|
{
|
|
auto it = s_adapterInfos.find(m_luid);
|
|
if (it != s_adapterInfos.end())
|
|
{
|
|
return it->second;
|
|
}
|
|
|
|
AdapterInfo& info = s_adapterInfos.insert({ m_luid, {} }).first->second;
|
|
getCaps(D3DDDICAPS_GETD3D7CAPS, info.d3dExtendedCaps);
|
|
|
|
LOG_INFO << "Supported resource formats:";
|
|
info.formatOps = getFormatOps();
|
|
|
|
auto d3d9on12 = GetModuleHandle("d3d9on12");
|
|
info.isD3D9On12 = d3d9on12 && d3d9on12 == Compat::getModuleHandleFromAddress(m_origVtable.pfnGetCaps);
|
|
info.isMsaaDepthResolveSupported =
|
|
!info.isD3D9On12 &&
|
|
info.formatOps.find(FOURCC_RESZ) != info.formatOps.end() &&
|
|
info.formatOps.find(FOURCC_INTZ) != info.formatOps.end() &&
|
|
info.formatOps.find(FOURCC_NULL) != info.formatOps.end();
|
|
info.fixedFormatOps = getFixedFormatOps(info);
|
|
info.supportedZBufferBitDepths = getSupportedZBufferBitDepths(info.fixedFormatOps);
|
|
|
|
LOG_INFO << "Supported z-buffer bit depths: " << bitDepthsToString(info.supportedZBufferBitDepths);
|
|
LOG_INFO << "Supported MSAA modes: " << getSupportedMsaaModes(info.formatOps);
|
|
|
|
for (const auto& depthFormat : g_depthFormats)
|
|
{
|
|
if (!Config::supportedDepthFormats.isSupported(depthFormat.format))
|
|
{
|
|
info.supportedZBufferBitDepths &= ~depthFormat.flag;
|
|
}
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
std::map<D3DDDIFORMAT, FORMATOP> Adapter::getFixedFormatOps(const AdapterInfo& info) const
|
|
{
|
|
std::map<D3DDDIFORMAT, FORMATOP> fixedFormatOps;
|
|
|
|
for (auto& formatOp : info.formatOps)
|
|
{
|
|
if (D3DDDIFMT_P8 == formatOp.first)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto fixedFormatOp = formatOp.second;
|
|
if (isEmulatedRenderTargetFormat(formatOp.first, info.formatOps))
|
|
{
|
|
fixedFormatOp.Operations |= FORMATOP_OFFSCREEN_RENDERTARGET;
|
|
}
|
|
|
|
if (D3DDDIFMT_L8 == formatOp.first)
|
|
{
|
|
auto p8FormatOp = formatOp.second;
|
|
p8FormatOp.Format = D3DDDIFMT_P8;
|
|
p8FormatOp.Operations |= FORMATOP_OFFSCREENPLAIN;
|
|
fixedFormatOps[D3DDDIFMT_P8] = p8FormatOp;
|
|
}
|
|
|
|
if (D3DDDIFMT_D24X4S4 == formatOp.first || D3DDDIFMT_X4S4D24 == formatOp.first)
|
|
{
|
|
// If these formats are reported as depth buffers, then EnumZBufferFormats returns only D16
|
|
fixedFormatOp.Operations &= ~(FORMATOP_ZSTENCIL | FORMATOP_ZSTENCIL_WITH_ARBITRARY_COLOR_DEPTH);
|
|
}
|
|
|
|
if (info.isD3D9On12)
|
|
{
|
|
if (D3DDDIFMT_D24X8 == formatOp.first)
|
|
{
|
|
fixedFormatOp.Format = D3DDDIFMT_X8D24;
|
|
}
|
|
else if (D3DDDIFMT_D24S8 == formatOp.first)
|
|
{
|
|
fixedFormatOp.Format = D3DDDIFMT_S8D24;
|
|
}
|
|
}
|
|
|
|
fixedFormatOps[fixedFormatOp.Format] = fixedFormatOp;
|
|
}
|
|
|
|
return fixedFormatOps;
|
|
}
|
|
|
|
template <typename Data>
|
|
HRESULT Adapter::getCaps(D3DDDICAPS_TYPE type, Data& data, UINT size) const
|
|
{
|
|
D3DDDIARG_GETCAPS caps = {};
|
|
caps.Type = type;
|
|
caps.pData = &data;
|
|
caps.DataSize = size;
|
|
return m_origVtable.pfnGetCaps(m_adapter, &caps);
|
|
}
|
|
|
|
std::map<D3DDDIFORMAT, FORMATOP> Adapter::getFormatOps() const
|
|
{
|
|
UINT formatCount = 0;
|
|
getCaps(D3DDDICAPS_GETFORMATCOUNT, formatCount);
|
|
|
|
std::vector<FORMATOP> formatOps(formatCount);
|
|
getCaps(D3DDDICAPS_GETFORMATDATA, formatOps[0], formatCount * sizeof(FORMATOP));
|
|
|
|
std::map<D3DDDIFORMAT, FORMATOP> result;
|
|
for (UINT i = 0; i < formatCount; ++i)
|
|
{
|
|
if (D3DDDIFMT_UNKNOWN != formatOps[i].Format)
|
|
{
|
|
LOG_INFO << " " << formatOps[i];
|
|
auto& formatOp = result[formatOps[i].Format];
|
|
formatOp.Format = formatOps[i].Format;
|
|
formatOp.Operations |= formatOps[i].Operations;
|
|
formatOp.FlipMsTypes |= formatOps[i].FlipMsTypes;
|
|
formatOp.BltMsTypes |= formatOps[i].BltMsTypes;
|
|
if (formatOps[i].PrivateFormatBitCount > formatOp.PrivateFormatBitCount)
|
|
{
|
|
formatOp.PrivateFormatBitCount = formatOps[i].PrivateFormatBitCount;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::pair<D3DDDIMULTISAMPLE_TYPE, UINT> Adapter::getMultisampleConfig(D3DDDIFORMAT format) const
|
|
{
|
|
UINT samples = Config::antialiasing.get();
|
|
if (D3DDDIMULTISAMPLE_NONE == samples)
|
|
{
|
|
return { D3DDDIMULTISAMPLE_NONE, 0 };
|
|
}
|
|
|
|
const auto& info(getInfo());
|
|
auto it = info.formatOps.find(format);
|
|
if (it == info.formatOps.end())
|
|
{
|
|
return { D3DDDIMULTISAMPLE_NONE, 0 };
|
|
}
|
|
|
|
auto msTypes = it->second.BltMsTypes;
|
|
if (0 == msTypes && (FOURCC_DF16 == it->first || FOURCC_INTZ == it->first))
|
|
{
|
|
auto replacement = info.formatOps.find(FOURCC_DF16 == it->first ? D3DDDIFMT_D16 : D3DDDIFMT_S8D24);
|
|
if (replacement != info.formatOps.end())
|
|
{
|
|
msTypes = replacement->second.BltMsTypes;
|
|
}
|
|
}
|
|
|
|
while (samples > 0 && !(msTypes & (1 << (samples - 1))))
|
|
{
|
|
--samples;
|
|
}
|
|
|
|
if (0 == samples)
|
|
{
|
|
return { D3DDDIMULTISAMPLE_NONE, 0 };
|
|
}
|
|
|
|
DDIMULTISAMPLEQUALITYLEVELSDATA levels = {};
|
|
levels.Format = D3DDDIFMT_X8R8G8B8;
|
|
levels.MsType = static_cast<D3DDDIMULTISAMPLE_TYPE>(samples);
|
|
getCaps(D3DDDICAPS_GETMULTISAMPLEQUALITYLEVELS, levels);
|
|
return { levels.MsType, std::min(static_cast<UINT>(Config::antialiasing.getParam()), levels.QualityLevels - 1) };
|
|
}
|
|
|
|
D3DDDIFORMAT Adapter::getRenderColorDepthSrcFormat(D3DDDIFORMAT appFormat) const
|
|
{
|
|
switch (Config::renderColorDepth.get().first)
|
|
{
|
|
case 10:
|
|
if (isSupportedRttFormat(D3DDDIFMT_A2B10G10R10))
|
|
{
|
|
return D3DDDIFMT_A2B10G10R10;
|
|
}
|
|
if (isSupportedRttFormat(D3DDDIFMT_A2R10G10B10))
|
|
{
|
|
return D3DDDIFMT_A2R10G10B10;
|
|
}
|
|
[[fallthrough]];
|
|
|
|
case 8:
|
|
return D3DDDIFMT_X8R8G8B8;
|
|
|
|
case 6:
|
|
return D3DDDIFMT_R5G6B5;
|
|
}
|
|
|
|
return appFormat;
|
|
}
|
|
|
|
D3DDDIFORMAT Adapter::getRenderColorDepthDstFormat() const
|
|
{
|
|
switch (Config::renderColorDepth.get().second)
|
|
{
|
|
case 6: return D3DDDIFMT_R5G6B5;
|
|
case 8: return D3DDDIFMT_X8R8G8B8;
|
|
}
|
|
return 16 == Win32::DisplayMode::getBpp() ? D3DDDIFMT_R5G6B5 : D3DDDIFMT_X8R8G8B8;
|
|
}
|
|
|
|
SIZE Adapter::getScaledSize(Int2 size) const
|
|
{
|
|
const auto scaleFactor = getScaleFactor();
|
|
const int multiplier = Config::resolutionScale.getParam();
|
|
const auto& caps = getInfo().d3dExtendedCaps;
|
|
|
|
Int2 maxSize = { caps.dwMaxTextureWidth, caps.dwMaxTextureHeight };
|
|
if (multiplier < 0)
|
|
{
|
|
maxSize = maxSize / size * size;
|
|
}
|
|
|
|
Int2 scaledSize = Float2(size) * scaleFactor;
|
|
scaledSize = min(scaledSize, maxSize);
|
|
scaledSize = max(scaledSize, size);
|
|
return { scaledSize.x, scaledSize.y };
|
|
}
|
|
|
|
Float2 Adapter::getScaleFactor() const
|
|
{
|
|
const SIZE resolutionScale = Config::resolutionScale.get();
|
|
const int multiplier = Config::resolutionScale.getParam();
|
|
if (0 == multiplier ||
|
|
Config::Settings::ResolutionScale::APP == resolutionScale && 1 == multiplier)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
const auto& mi = Win32::DisplayMode::getMonitorInfo(m_deviceName);
|
|
auto displayRes = Rect::getSize(mi.rcReal);
|
|
auto appRes = Rect::getSize(mi.isEmulated ? mi.rcEmulated : mi.rcReal);
|
|
|
|
Int2 targetResolution = resolutionScale;
|
|
if (Config::Settings::ResolutionScale::APP == resolutionScale)
|
|
{
|
|
targetResolution = appRes;
|
|
}
|
|
else if (Config::Settings::ResolutionScale::DISPLAY == resolutionScale)
|
|
{
|
|
targetResolution = displayRes;
|
|
}
|
|
|
|
targetResolution *= abs(multiplier);
|
|
|
|
const SIZE ar = getAspectRatio(appRes, displayRes);
|
|
if (targetResolution.y * ar.cx / ar.cy <= targetResolution.x)
|
|
{
|
|
targetResolution.x = targetResolution.y * ar.cx / ar.cy;
|
|
}
|
|
else
|
|
{
|
|
targetResolution.y = targetResolution.x * ar.cy / ar.cx;
|
|
}
|
|
|
|
const auto scaleFactor = Float2(targetResolution) / Float2(appRes);
|
|
return multiplier < 0 ? scaleFactor : ceil(scaleFactor);
|
|
}
|
|
|
|
std::string Adapter::getSupportedMsaaModes(const std::map<D3DDDIFORMAT, FORMATOP>& formatOps) const
|
|
{
|
|
auto it = formatOps.find(D3DDDIFMT_X8R8G8B8);
|
|
if (it != formatOps.end() && 0 != it->second.BltMsTypes)
|
|
{
|
|
DDIMULTISAMPLEQUALITYLEVELSDATA levels = {};
|
|
levels.Format = D3DDDIFMT_X8R8G8B8;
|
|
levels.MsType = D3DDDIMULTISAMPLE_NONMASKABLE;
|
|
getCaps(D3DDDICAPS_GETMULTISAMPLEQUALITYLEVELS, levels);
|
|
|
|
std::ostringstream oss;
|
|
oss << "msaa(" << levels.QualityLevels - 1 << ')';
|
|
|
|
for (UINT i = D3DDDIMULTISAMPLE_2_SAMPLES; i <= D3DDDIMULTISAMPLE_16_SAMPLES; ++i)
|
|
{
|
|
if (it->second.BltMsTypes & (1 << (i - 1)))
|
|
{
|
|
levels.MsType = static_cast<D3DDDIMULTISAMPLE_TYPE>(i);
|
|
levels.QualityLevels = 0;
|
|
getCaps(D3DDDICAPS_GETMULTISAMPLEQUALITYLEVELS, levels);
|
|
oss << ", msaa" << i << "x(" << levels.QualityLevels - 1 << ')';
|
|
}
|
|
}
|
|
return oss.str();
|
|
}
|
|
return "none";
|
|
}
|
|
|
|
DWORD Adapter::getSupportedZBufferBitDepths(const std::map<D3DDDIFORMAT, FORMATOP>& formatOps) const
|
|
{
|
|
DWORD supportedZBufferBitDepths = 0;
|
|
for (const auto& depthFormat : g_depthFormats)
|
|
{
|
|
if (formatOps.find(depthFormat.format) != formatOps.end())
|
|
{
|
|
supportedZBufferBitDepths |= depthFormat.flag;
|
|
}
|
|
}
|
|
return supportedZBufferBitDepths;
|
|
}
|
|
|
|
bool Adapter::isEmulatedRenderTargetFormat(D3DDDIFORMAT format) const
|
|
{
|
|
return isEmulatedRenderTargetFormat(format, m_info.formatOps);
|
|
}
|
|
|
|
bool Adapter::isEmulatedRenderTargetFormat(D3DDDIFORMAT format, const std::map<D3DDDIFORMAT, FORMATOP>& formatOps) const
|
|
{
|
|
const auto& fi = getFormatInfo(format);
|
|
if (0 == fi.red.bitCount)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
auto it = formatOps.find(format);
|
|
if (it == formatOps.end() || (it->second.Operations & FORMATOP_OFFSCREEN_RENDERTARGET))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
auto replacementFormat = 0 != fi.alpha.bitCount ? D3DDDIFMT_A8R8G8B8 : D3DDDIFMT_X8R8G8B8;
|
|
it = formatOps.find(replacementFormat);
|
|
return it != formatOps.end() && (it->second.Operations & FORMATOP_OFFSCREEN_RENDERTARGET);
|
|
}
|
|
|
|
bool Adapter::isSupportedRttFormat(D3DDDIFORMAT format) const
|
|
{
|
|
auto it = m_info.formatOps.find(format);
|
|
return it != m_info.formatOps.end() &&
|
|
(it->second.Format & FORMATOP_OFFSCREEN_RENDERTARGET) &&
|
|
(it->second.Format & FORMATOP_TEXTURE);
|
|
}
|
|
|
|
HRESULT Adapter::pfnCloseAdapter()
|
|
{
|
|
auto adapter = m_adapter;
|
|
auto pfnCloseAdapter = m_origVtable.pfnCloseAdapter;
|
|
s_adapters.erase(adapter);
|
|
return pfnCloseAdapter(adapter);
|
|
}
|
|
|
|
HRESULT Adapter::pfnCreateDevice(D3DDDIARG_CREATEDEVICE* pCreateData)
|
|
{
|
|
DeviceCallbacks::hookVtable(*pCreateData->pCallbacks, m_runtimeVersion);
|
|
HRESULT result = m_origVtable.pfnCreateDevice(m_adapter, pCreateData);
|
|
if (SUCCEEDED(result))
|
|
{
|
|
DeviceFuncs::hookVtable(*pCreateData->pDeviceFuncs, m_driverVersion);
|
|
Device::add(*this, pCreateData->hDevice);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
HRESULT Adapter::pfnGetCaps(const D3DDDIARG_GETCAPS* pData)
|
|
{
|
|
HRESULT result = D3DDDICAPS_GETFORMATDATA == pData->Type ? S_OK : m_origVtable.pfnGetCaps(m_adapter, pData);
|
|
if (FAILED(result))
|
|
{
|
|
return result;
|
|
}
|
|
|
|
switch (pData->Type)
|
|
{
|
|
case D3DDDICAPS_DDRAW:
|
|
{
|
|
auto& caps = *static_cast<DDRAW_CAPS*>(pData->pData);
|
|
caps.Caps |= DDRAW_CAPS_COLORKEY | DDRAW_CAPS_BLTDEPTHFILL;
|
|
caps.CKeyCaps = DDRAW_CKEYCAPS_SRCBLT;
|
|
caps.FxCaps = DDRAW_FXCAPS_BLTMIRRORLEFTRIGHT | DDRAW_FXCAPS_BLTMIRRORUPDOWN;
|
|
break;
|
|
}
|
|
|
|
case D3DDDICAPS_GETD3D3CAPS:
|
|
{
|
|
auto& caps = static_cast<D3DNTHAL_GLOBALDRIVERDATA*>(pData->pData)->hwCaps;
|
|
if (caps.dwFlags & D3DDD_DEVICEZBUFFERBITDEPTH)
|
|
{
|
|
caps.dwDeviceZBufferBitDepth = getInfo().supportedZBufferBitDepths;
|
|
}
|
|
if (Config::palettizedTextures.get())
|
|
{
|
|
caps.dpcTriCaps.dwTextureCaps |= D3DPTEXTURECAPS_ALPHAPALETTE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case D3DDDICAPS_GETFORMATCOUNT:
|
|
*static_cast<UINT*>(pData->pData) = m_info.fixedFormatOps.size();
|
|
break;
|
|
|
|
case D3DDDICAPS_GETFORMATDATA:
|
|
{
|
|
UINT count = pData->DataSize / sizeof(FORMATOP);
|
|
if (count > m_info.fixedFormatOps.size())
|
|
{
|
|
count = m_info.fixedFormatOps.size();
|
|
}
|
|
|
|
auto formatOp = static_cast<FORMATOP*>(pData->pData);
|
|
auto it = m_info.fixedFormatOps.begin();
|
|
for (UINT i = 0; i < count; ++i)
|
|
{
|
|
formatOp[i] = it->second;
|
|
++it;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void Adapter::setRepository(LUID luid, GUID* guid, CompatWeakPtr<IDirectDraw7> repository)
|
|
{
|
|
for (auto& adapter : s_adapters)
|
|
{
|
|
if (adapter.second.m_luid == luid)
|
|
{
|
|
if (guid)
|
|
{
|
|
adapter.second.m_guidBuf = *guid;
|
|
adapter.second.m_guid = &adapter.second.m_guidBuf;
|
|
}
|
|
adapter.second.m_repository = repository;
|
|
auto& surfaceRepo = SurfaceRepository::get(adapter.second);
|
|
surfaceRepo.setRepository(repository);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::map<HANDLE, Adapter> Adapter::s_adapters;
|
|
std::map<LUID, Adapter::AdapterInfo> Adapter::s_adapterInfos;
|
|
}
|